///|
/// Compute unique undirected edges from a triangle index buffer.
pub fn unique_triangle_edges(
  triangles : ArrayView[(Int, Int, Int)],
) -> Array[(Int, Int)] {
  let result : Array[(Int, Int)] = []
  for triangle in triangles {
    let (a, b, c) = triangle
    let e0 = if a < b { (a, b) } else { (b, a) }
    let e1 = if b < c { (b, c) } else { (c, b) }
    let e2 = if c < a { (c, a) } else { (a, c) }
    if !result.contains(e0) {
      result.push(e0)
    }
    if !result.contains(e1) {
      result.push(e1)
    }
    if !result.contains(e2) {
      result.push(e2)
    }
  }
  result
}

///|
pub fn triangle_area3(a : Point3, b : Point3, c : Point3) -> Double {
  b.minus(a).cross(c.minus(a)).norm() / 2.0
}

///|
pub fn triangle_centroid3(a : Point3, b : Point3, c : Point3) -> Point3 {
  Point3::new(
    x=(a.x + b.x + c.x) / 3.0,
    y=(a.y + b.y + c.y) / 3.0,
    z=(a.z + b.z + c.z) / 3.0,
  )
}

///|
pub fn mesh_surface_area(
  vertices : ArrayView[Point3],
  triangles : ArrayView[(Int, Int, Int)],
) -> Double {
  let mut area = 0.0
  for triangle in triangles {
    let (a, b, c) = triangle
    if a >= 0 &&
      b >= 0 &&
      c >= 0 &&
      a < vertices.length() &&
      b < vertices.length() &&
      c < vertices.length() {
      area += triangle_area3(vertices[a], vertices[b], vertices[c])
    }
  }
  area
}

///|
pub fn mesh_centroid(
  vertices : ArrayView[Point3],
) -> Point3 raise GeometryError {
  centroid3(vertices)
}

///|
pub fn translate_mesh(
  vertices : ArrayView[Point3],
  translation : Vec3,
) -> Array[Point3] {
  let result : Array[Point3] = []
  for vertex in vertices {
    result.push(vertex.translate(translation))
  }
  result
}

///|
pub fn scale_mesh(
  vertices : ArrayView[Point3],
  scale : Double,
) -> Array[Point3] {
  let result : Array[Point3] = []
  for vertex in vertices {
    result.push(
      Point3::new(x=vertex.x * scale, y=vertex.y * scale, z=vertex.z * scale),
    )
  }
  result
}

///|
pub fn mesh_diagonal(
  vertices : ArrayView[Point3],
) -> Double raise GeometryError {
  let box = PointCloud::from_points(vertices).bounds()
  box.max.minus(box.min).norm()
}