// picogkffi voxels: Signed-distance / level-set volume operations.

///|
pub struct Voxels {
  h : Int64
}

///|
pub fn new_voxels() -> Voxels {
  Voxels::{ h: voxels_h_create(get_instance()) }
}

///|
pub fn new_sphere(center : Vec3, radius : Double) -> Voxels {
  Voxels::{
    h: voxels_h_create_sphere(
      get_instance(),
      pack_vec3(center),
      Float::from_double(radius),
    ),
  }
}

///|
pub fn new_capsule(
  start : Vec3,
  end : Vec3,
  radius1 : Double,
  radius2 : Double,
) -> Voxels {
  Voxels::{
    h: voxels_h_create_capsule(
      get_instance(),
      pack_vec3(start),
      pack_vec3(end),
      Float::from_double(radius1),
      Float::from_double(radius2),
    ),
  }
}

///|
pub fn new_mesh_shell(mesh : Mesh, radius : Double) -> Voxels {
  Voxels::{
    h: voxels_h_create_mesh_shell(
      get_instance(),
      mesh.h,
      Float::from_double(radius),
    ),
  }
}

///|
pub fn from_mesh(mesh : Mesh) -> Voxels {
  let v = new_voxels()
  voxels_render_mesh(get_instance(), v.h, mesh.h)
  v
}

///|
pub fn from_lattice(lat : Lattice) -> Voxels {
  let v = new_voxels()
  voxels_render_lattice(get_instance(), v.h, lat.h)
  v
}

///|
pub fn Voxels::copy(self : Voxels) -> Voxels {
  Voxels::{ h: voxels_h_create_copy(get_instance(), self.h) }
}

///|
pub fn Voxels::destroy(self : Voxels) -> Unit {
  voxels_destroy(get_instance(), self.h)
}

///|
pub fn Voxels::is_valid(self : Voxels) -> Bool {
  voxels_b_is_valid(get_instance(), self.h)
}

///|
pub fn Voxels::is_empty(self : Voxels) -> Bool {
  voxels_b_is_empty(get_instance(), self.h)
}

///|
pub fn Voxels::mem_usage(self : Voxels) -> Int64 {
  voxels_n_mem_usage(get_instance(), self.h)
}

///|
pub fn Voxels::voxel_size(self : Voxels) -> Double {
  voxels_f_voxel_size(get_instance(), self.h).to_double()
}

///|
pub fn Voxels::diagnose(self : Voxels) -> String {
  let buf = FixedArray::make(256, 0L.to_byte())
  ignore(voxels_b_diagnose(get_instance(), self.h, buf))
  bytes_to_string(buf)
}

///|
pub fn Voxels::bool_add(self : Voxels, other : Voxels) -> Unit {
  voxels_bool_add(get_instance(), self.h, other.h)
}

///|
pub fn Voxels::bool_subtract(self : Voxels, other : Voxels) -> Unit {
  voxels_bool_subtract(get_instance(), self.h, other.h)
}

///|
pub fn Voxels::bool_intersect(self : Voxels, other : Voxels) -> Unit {
  voxels_bool_intersect(get_instance(), self.h, other.h)
}

///|
pub fn Voxels::add(self : Voxels, other : Voxels) -> Voxels {
  let r = self.copy()
  r.bool_add(other)
  r
}

///|
pub fn Voxels::sub(self : Voxels, other : Voxels) -> Voxels {
  let r = self.copy()
  r.bool_subtract(other)
  r
}

///|
pub fn Voxels::intersect(self : Voxels, other : Voxels) -> Voxels {
  let r = self.copy()
  r.bool_intersect(other)
  r
}

///|
pub fn Voxels::offset(self : Voxels, dist : Double) -> Unit {
  voxels_offset(get_instance(), self.h, Float::from_double(dist))
}

///|
pub fn Voxels::double_offset(self : Voxels, d1 : Double, d2 : Double) -> Unit {
  voxels_double_offset(
    get_instance(),
    self.h,
    Float::from_double(d1),
    Float::from_double(d2),
  )
}

///|
pub fn Voxels::triple_offset(self : Voxels, dist : Double) -> Unit {
  voxels_triple_offset(get_instance(), self.h, Float::from_double(dist))
}

///|
pub fn Voxels::shell(self : Voxels, thickness : Double) -> Unit {
  let c = self.copy()
  c.offset(-thickness)
  self.bool_subtract(c)
  c.destroy()
}

///|
pub fn Voxels::render_mesh(self : Voxels, mesh : Mesh) -> Unit {
  voxels_render_mesh(get_instance(), self.h, mesh.h)
}

///|
pub fn Voxels::render_lattice(self : Voxels, lat : Lattice) -> Unit {
  voxels_render_lattice(get_instance(), self.h, lat.h)
}

///|
pub fn Voxels::project_z_slice(
  self : Voxels,
  start_z : Double,
  end_z : Double,
) -> Unit {
  voxels_project_z_slice(
    get_instance(),
    self.h,
    Float::from_double(start_z),
    Float::from_double(end_z),
  )
}

///|
pub fn Voxels::is_inside(self : Voxels, pt : Vec3) -> Bool {
  voxels_b_is_inside(get_instance(), self.h, pack_vec3(pt))
}

///|
pub fn Voxels::is_equal(self : Voxels, other : Voxels) -> Bool {
  voxels_b_is_equal(get_instance(), self.h, other.h)
}

///|
pub fn Voxels::volume(self : Voxels) -> Double {
  voxels_f_calculate_volume(get_instance(), self.h).to_double()
}

///|
pub fn Voxels::surface_normal(self : Voxels, pt : Vec3) -> Vec3 {
  let out = FixedArray::make(12, 0L.to_byte())
  voxels_get_surface_normal(get_instance(), self.h, pack_vec3(pt), out)
  unpack_vec3(out)
}

///|
pub fn Voxels::closest_point(self : Voxels, pt : Vec3) -> Vec3? {
  let out = FixedArray::make(12, 0L.to_byte())
  if voxels_b_closest_point_on_surface(
      get_instance(),
      self.h,
      pack_vec3(pt),
      out,
    ) {
    Some(unpack_vec3(out))
  } else {
    None
  }
}

///|
pub fn Voxels::ray_cast(self : Voxels, origin : Vec3, dir : Vec3) -> Vec3? {
  let out = FixedArray::make(12, 0L.to_byte())
  if voxels_b_ray_cast_to_surface(
      get_instance(),
      self.h,
      pack_vec3(origin),
      pack_vec3(dir),
      out,
    ) {
    Some(unpack_vec3(out))
  } else {
    None
  }
}

///|
pub fn Voxels::voxel_dimensions(
  self : Voxels,
) -> (Int, Int, Int, Int, Int, Int) {
  let ox = FixedArray::make(4, 0L.to_byte())
  let oy = FixedArray::make(4, 0L.to_byte())
  let oz = FixedArray::make(4, 0L.to_byte())
  let sx = FixedArray::make(4, 0L.to_byte())
  let sy = FixedArray::make(4, 0L.to_byte())
  let sz = FixedArray::make(4, 0L.to_byte())
  voxels_get_voxel_dimensions(get_instance(), self.h, ox, oy, oz, sx, sy, sz)
  (
    read_int32_le(ox, 0),
    read_int32_le(oy, 0),
    read_int32_le(oz, 0),
    read_int32_le(sx, 0),
    read_int32_le(sy, 0),
    read_int32_le(sz, 0),
  )
}

///|
pub fn Voxels::bounding_box(self : Voxels) -> BBox3 {
  let (_, _, _, sx, sy, sz) = self.voxel_dimensions()
  let vox_min = FixedArray::make(12, 0L.to_byte())
  let vox_max = FixedArray::make(12, 0L.to_byte())
  write_float_le(vox_min, 0, 0.0)
  write_float_le(vox_min, 4, 0.0)
  write_float_le(vox_min, 8, 0.0)
  write_float_le(vox_max, 0, Float::from_int(sx))
  write_float_le(vox_max, 4, Float::from_int(sy))
  write_float_le(vox_max, 8, Float::from_int(sz))
  let mm_min = FixedArray::make(12, 0L.to_byte())
  let mm_max = FixedArray::make(12, 0L.to_byte())
  library_voxels_to_mm(get_instance(), vox_min, mm_min)
  library_voxels_to_mm(get_instance(), vox_max, mm_max)
  BBox3::{
    min: Vec3::{
      x: read_float_le(mm_min, 0).to_double(),
      y: read_float_le(mm_min, 4).to_double(),
      z: read_float_le(mm_min, 8).to_double(),
    },
    max: Vec3::{
      x: read_float_le(mm_max, 0).to_double(),
      y: read_float_le(mm_max, 4).to_double(),
      z: read_float_le(mm_max, 8).to_double(),
    },
  }
}

///|
pub fn Voxels::to_mesh(self : Voxels) -> Mesh {
  Mesh::{ h: mesh_h_create_from_voxels(get_instance(), self.h) }
}

///|
pub fn Voxels::get_x_slice(self : Voxels, x : Int) -> Array[Double] {
  let (_, _, _, _, sy, sz) = self.voxel_dimensions()
  let n = sy * sz
  if n <= 0 {
    return []
  }
  let out = FixedArray::make(n * 4, 0L.to_byte())
  voxels_get_x_slice(
    get_instance(),
    self.h,
    x,
    out,
    FixedArray::make(0, 0L.to_byte()),
  )
  let arr = Array::make(n, 0.0)
  for i in 0.. Array[Double] {
  let (_, _, _, sx, _, sz) = self.voxel_dimensions()
  let n = sx * sz
  if n <= 0 {
    return []
  }
  let out = FixedArray::make(n * 4, 0L.to_byte())
  voxels_get_y_slice(
    get_instance(),
    self.h,
    y,
    out,
    FixedArray::make(0, 0L.to_byte()),
  )
  let arr = Array::make(n, 0.0)
  for i in 0.. Array[Double] {
  let (_, _, _, sx, sy, _) = self.voxel_dimensions()
  let n = sx * sy
  if n <= 0 {
    return []
  }
  let out = FixedArray::make(n * 4, 0L.to_byte())
  voxels_get_z_slice(
    get_instance(),
    self.h,
    z,
    out,
    FixedArray::make(0, 0L.to_byte()),
  )
  let arr = Array::make(n, 0.0)
  for i in 0.. Array[Double] {
  let (_, _, _, sx, sy, _) = self.voxel_dimensions()
  let n = sx * sy
  if n <= 0 {
    return []
  }
  let out = FixedArray::make(n * 4, 0L.to_byte())
  voxels_get_interpolated_z_slice(
    get_instance(),
    self.h,
    Float::from_double(z),
    out,
    FixedArray::make(0, 0L.to_byte()),
  )
  let arr = Array::make(n, 0.0)
  for i in 0..