polyhedron-ops 0.2.4

Conway/Hart Polyhedron Operations
Documentation
#![allow(clippy::many_single_char_names)]
//! # Conway-Hart Polyhedron Operations
//!
//! This crate implements the [Conway Polyhedron
//! Operators](http://en.wikipedia.org/wiki/Conway_polyhedron_notation)
//! and their extensions by [George W. Hart](http://www.georgehart.com/) and others.
//!
//! The internal representation uses *n*-gon mesh buffers.  These need
//! preprocessing before they can be sent to a GPU but are almost fine to send
//! to an offline renderer, as-is.
//!
//! See the `playground` example for code on how to do either.
//!
//! ## Example
//! ```
//! use polyhedron_ops::Polyhedron;
//! use std::path::Path;
//!
//! // Conway notation: gapcD
//! let polyhedron = Polyhedron::dodecahedron()
//!     .chamfer(None, true)
//!     .propellor(None, true)
//!     .ambo(None, true)
//!     .gyro(None, None, true)
//!     .finalize();
//!
//! // Export as ./polyhedron-gapcD.obj
//! # #[cfg(feature = "obj")]
//! # {
//! polyhedron.write_to_obj(&Path::new("."), false);
//! # }
//! ```
//! The above code starts from a [dodecahedron](https://en.wikipedia.org/wiki/Dodecahedron)
//! and iteratively applies four operators.
//!
//! The resulting shape is shown below.
//!
//! ![](https://raw.githubusercontent.com/virtualritz/polyhedron-operators/HEAD/gapcD.jpg)
//!
//! ## Cargo Features
//!
//! ```toml
//! [dependencies]
//! polyhedron-ops = { version = "0.3", features = [ "bevy", "nsi", "obj" ] }
//! ```
//!
//! * `bevy` – A polyhedro can be converted into a [`bevy`](https://bevyengine.org/)
//!   [`Mesh`](https://docs.rs/bevy/latest/bevy/render/mesh/struct.Mesh.html).
//!   See the `bevy` example. ```ignore Mesh::from(polyhedron) ```
//!
//! * `nsi` – Add supports for sending data to renderers implementing the [ɴsɪ](https://crates.io/crates/nsi/)
//!   API. The function is called [`to_nsi()`](Polyhedron::to_nsi()).
//!
//! * `obj` – Add support for output to [Wavefront OBJ](https://en.wikipedia.org/wiki/Wavefront_.obj_file)
//!   via the [`write_to_obj()`](Polyhedron::write_to_obj()) function.
use itertools::Itertools;
use num_traits::FloatConst;
use rayon::prelude::*;
#[cfg(feature = "obj")]
use std::{
    error::Error,
    fs::File,
    io::Write as IoWrite,
    path::{Path, PathBuf},
};
use std::{
    fmt::{Display, Write},
    iter::Iterator,
};
use ultraviolet as uv;

mod helpers;
use helpers::*;

#[cfg(test)]
mod tests;

pub mod prelude {
    //! Re-exports commonly used types and traits.
    //!
    //! Importing the contents of this module is recommended.
    pub use crate::*;
}

static EPSILON: f32 = 0.00000001;

pub type Float = f32;
pub type VertexKey = u32;
pub type FaceKey = u32;
pub type Face = Vec<VertexKey>;
pub(crate) type FaceSlice = [VertexKey];
pub type Faces = Vec<Face>;
pub(crate) type FacesSlice = [Face];
pub type FaceSet = Vec<VertexKey>;
pub type Edge = [VertexKey; 2];
pub type Edges = Vec<Edge>;
pub type EdgesSlice = [Edge];
pub(crate) type _EdgeSlice = [Edge];
pub type Point = uv::vec::Vec3;
pub type Vector = uv::vec::Vec3;
pub type Normal = Vector;
#[allow(dead_code)]
pub type Normals = Vec<Normal>;
pub type Points = Vec<Point>;
pub(crate) type PointsSlice = [Point];
pub(crate) type PointsRefSlice<'a> = [&'a Point];

#[derive(Clone, Debug)]
pub struct Polyhedron {
    face_index: Faces,
    positions: Points,
    name: String,
    // This stores a FaceSet for each
    // set of faces belonging to the
    // same operations.
    face_set_index: Vec<FaceSet>,
}

impl Default for Polyhedron {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(feature = "tilings")]
use tilings::RegularTiling;
#[cfg(feature = "tilings")]
impl From<RegularTiling> for Polyhedron {
    fn from(rt: RegularTiling) -> Polyhedron {
        Polyhedron {
            positions: rt
                .positions()
                .iter()
                .map(|p| Point::new(p.x, 0.0, p.y))
                .collect(),
            face_index: rt.faces().clone(),
            face_set_index: Vec::new(),
            name: rt.name().to_string(),
        }
    }
}

#[cfg(feature = "tilings")]
use tilings::SemiRegularTiling;
#[cfg(feature = "tilings")]
impl From<SemiRegularTiling> for Polyhedron {
    fn from(rt: SemiRegularTiling) -> Polyhedron {
        Polyhedron {
            positions: rt
                .positions()
                .iter()
                .map(|p| Point::new(p.x, 0.0, p.y))
                .collect(),
            face_index: rt.faces().clone(),
            face_set_index: Vec::new(),
            name: rt.name().to_string(),
        }
    }
}

impl Polyhedron {
    #[inline]
    pub fn new() -> Self {
        Self {
            positions: Vec::new(),
            face_index: Vec::new(),
            face_set_index: Vec::new(),
            name: String::new(),
        }
    }

    #[inline]
    pub fn from(
        name: &str,
        positions: Points,
        face_index: Faces,
        face_set_index: Option<Vec<FaceSet>>,
    ) -> Self {
        Self {
            positions,
            face_index,
            face_set_index: face_set_index.unwrap_or_default(),
            name: name.to_string(),
        }
    }

    /// Returns the axis-aligned bounding box of the polyhedron in the format
    /// `[x_min, y_min, z_min, x_max, y_max, z_max]`.
    #[inline]
    pub fn bounding_box(&self) -> [f64; 6] {
        let mut bounds = [0.0f64; 6];
        self.positions.iter().for_each(|point| {
            if bounds[0] > point.x as _ {
                bounds[0] = point.x as _;
            } else if bounds[3] < point.x as _ {
                bounds[3] = point.x as _;
            }

            if bounds[1] > point.y as _ {
                bounds[1] = point.y as _;
            } else if bounds[4] < point.y as _ {
                bounds[4] = point.y as _;
            }

            if bounds[2] > point.z as _ {
                bounds[2] = point.z as _;
            } else if bounds[5] < point.z as _ {
                bounds[5] = point.z as _;
            }
        });
        bounds
    }

    /// Appends indices for newly added faces as a new FaceSet to the
    /// FaceSetIndex.
    #[inline]
    fn append_new_face_set(&mut self, size: usize) {
        self.face_set_index
            .append(&mut vec![((self.face_index.len() as VertexKey)
                ..((self.face_index.len() + size) as VertexKey))
                .collect()]);
    }

    /// Creates vertices with valence (aka degree) four.

    /// It is also called [rectification](https://en.wikipedia.org/wiki/Rectification_(geometry)),
    /// or the  [medial graph](https://en.wikipedia.org/wiki/Medial_graph) in graph theory.
    #[inline]
    pub fn ambo(
        &mut self,
        ratio: Option<Float>,
        change_name: bool,
    ) -> &mut Self {
        let ratio_ = match ratio {
            Some(r) => r.clamp(0.0, 1.0),
            None => 1. / 2.,
        };

        let edges = self.to_edges();

        let positions: Vec<(&Edge, Point)> = edges
            .par_iter()
            .map(|edge| {
                let edge_positions = index_as_positions(edge, &self.positions);
                (
                    edge,
                    ratio_ * *edge_positions[0]
                        + (1.0 - ratio_) * *edge_positions[1],
                )
            })
            .collect();

        let new_ids = vertex_ids_edge_ref_ref(&positions, 0);

        let face_index: Faces = self
            .face_index
            .par_iter()
            .map(|face| {
                let edges = distinct_face_edges(face);
                let result = edges
                    .iter()
                    .filter_map(|edge| vertex_edge(edge, &new_ids))
                    .collect::<Vec<_>>();
                result
            })
            .chain(
                self.positions
                    // Each old vertex creates a new face ...
                    .par_iter()
                    .enumerate()
                    .map(|(polygon_vertex, _)| {
                        let vertex_number = polygon_vertex as VertexKey;
                        ordered_vertex_edges(
                            vertex_number,
                            &vertex_faces(vertex_number, &self.face_index),
                        )
                        .iter()
                        .map(|ve| {
                            vertex_edge(&distinct_edge(ve), &new_ids).unwrap()
                        })
                        .collect::<Vec<_>>()
                    }),
            )
            .collect();

        self.append_new_face_set(face_index.len());

        self.face_index = face_index;
        self.positions = vertex_values(&positions);

        if change_name {
            let mut params = String::new();
            if let Some(ratio) = ratio {
                write!(&mut params, "{:.2}", ratio).unwrap();
            }
            self.name = format!("a{}{}", params, self.name);
        }

        self
    }

    pub fn bevel(
        &mut self,
        ratio: Option<Float>,
        height: Option<Float>,
        vertex_valence: Option<&[usize]>,
        regular_faces_only: Option<bool>,
        change_name: bool,
    ) -> &mut Self {
        self.truncate(height, vertex_valence, regular_faces_only, false);
        self.ambo(ratio, false);

        if change_name {
            let mut params = String::new();
            if let Some(ratio) = ratio {
                write!(&mut params, "{:.2}", ratio).unwrap();
            }
            if let Some(height) = height {
                write!(&mut params, ",{:.2}", height).unwrap();
            } else {
                write!(&mut params, ",").unwrap();
            }
            if let Some(vertex_valence) = vertex_valence {
                write!(&mut params, ",{}", format_slice(vertex_valence))
                    .unwrap();
            } else {
                write!(&mut params, ",").unwrap();
            }
            if let Some(regular_faces_only) = regular_faces_only {
                if regular_faces_only {
                    params.push_str(",{t}");
                }
            } else {
                write!(&mut params, ",").unwrap();
            }
            params = params.trim_end_matches(',').to_string();
            self.name = format!("b{}{}", params, self.name);
        }

        self
    }

    /// Apply proper canoicalization. Typical number of `iterarations` are
    /// `200`+.
    /// FIXME: this is b0rked atm.
    #[inline]
    pub fn _canonicalize(
        &mut self,
        iterations: Option<usize>,
        change_name: bool,
    ) {
        let mut dual = self.clone().dual(false).finalize();

        for _ in 0..iterations.unwrap_or(200) {
            // Reciprocate faces.
            dual.positions =
                reciprocate_faces(&self.face_index, &self.positions);
            self.positions =
                reciprocate_faces(&dual.face_index, &dual.positions);
        }

        if change_name {
            let mut params = String::new();
            if let Some(iterations) = iterations {
                write!(&mut params, "{}", iterations).unwrap();
            }
            self.name = format!("N{}{}", params, self.name);
        }
    }

    /// Performs Catmull-Clark subdivision.
    ///
    /// Each face is replaced with *n* quadralaterals based on edge midpositions
    /// vertices and centroid edge midpositions are average of edge endpositions
    /// and adjacent centroids original vertices replaced by weighted
    /// average of original vertex, face centroids and edge midpositions.
    pub fn catmull_clark_subdivide(&mut self, change_name: bool) -> &mut Self {
        let new_face_vertices = self
            .face_index
            .par_iter()
            .map(|face| {
                let face_positions = index_as_positions(face, &self.positions);
                (face.as_slice(), centroid_ref(&face_positions))
            })
            .collect::<Vec<_>>();

        let edges = self.to_edges();

        let new_edge_vertices = edges
            .par_iter()
            .map(|edge| {
                let ep = index_as_positions(edge, &self.positions);
                let af1 = face_with_edge(edge, &self.face_index);
                let af2 = face_with_edge(&[edge[1], edge[0]], &self.face_index);
                let fc1 =
                    vertex_point(&af1, new_face_vertices.as_slice()).unwrap();
                let fc2 =
                    vertex_point(&af2, new_face_vertices.as_slice()).unwrap();
                (edge, (*ep[0] + *ep[1] + *fc1 + *fc2) * 0.25)
            })
            .collect::<Vec<_>>();

        let new_face_vertex_ids = vertex_ids_ref_ref(
            new_face_vertices.as_slice(),
            self.positions.len() as _,
        );
        let new_edge_vertex_ids = vertex_ids_edge_ref_ref(
            new_edge_vertices.as_slice(),
            (self.positions.len() + new_face_vertices.len()) as _,
        );

        let new_face_index = self
            .face_index
            .par_iter()
            .flat_map(|face| {
                let centroid = vertex(face, &new_face_vertex_ids).unwrap();

                face.iter()
                    .circular_tuple_windows::<(_, _, _)>()
                    .map(|triplet| {
                        let mid1 = vertex_edge(
                            &distinct_edge(&[*triplet.0, *triplet.1]),
                            new_edge_vertex_ids.as_slice(),
                        )
                        .unwrap();
                        let mid2 = vertex_edge(
                            &distinct_edge(&[*triplet.1, *triplet.2]),
                            new_edge_vertex_ids.as_slice(),
                        )
                        .unwrap();
                        vec![centroid, mid1, *triplet.1, mid2]
                    })
                    .collect::<Vec<_>>()
            })
            .collect::<Vec<_>>();

        let new_positions = self
            .positions
            .par_iter()
            .enumerate()
            .map(|point| {
                let i = point.0 as u32;
                let v = point.1;
                let vertex_faces = vertex_faces(i, &self.face_index)
                    .iter()
                    .map(|face| {
                        vertex_point(face, new_face_vertices.as_slice())
                            .unwrap()
                    })
                    .collect::<Vec<_>>();
                let n = vertex_faces.len() as Float;
                let f = centroid_ref(&vertex_faces);
                let r = centroid_ref(
                    &vertex_edges(i, &edges)
                        .iter()
                        .map(|edge| {
                            vertex_edge_point(
                                edge,
                                new_edge_vertices.as_slice(),
                            )
                            .unwrap()
                        })
                        .collect::<Vec<_>>(),
                );
                (f + 2.0 * r + (n - 3.0) * *v) / n
            })
            .chain(vertex_values(new_face_vertices.as_slice()))
            .chain(vertex_values(new_edge_vertices.as_slice()))
            .collect::<Points>();

        self.positions = new_positions;
        self.face_index = new_face_index;

        if change_name {
            self.name = format!("v{}", self.name);
        }

        self
    }

    pub fn chamfer(
        &mut self,
        ratio: Option<Float>,
        change_name: bool,
    ) -> &mut Self {
        let ratio_ = match ratio {
            Some(r) => r.clamp(0.0, 1.0),
            None => 1. / 2.,
        };

        let new_positions: Vec<(Face, Point)> = self
            .face_index
            .par_iter()
            .flat_map(|face| {
                let face_positions = index_as_positions(face, &self.positions);
                let centroid = centroid_ref(&face_positions);
                // println!("{:?}", ep);
                let mut result = Vec::new();
                face.iter().enumerate().for_each(|face_point| {
                    let j = face_point.0;
                    let mut new_face = face.clone();
                    new_face.push(face[j]);
                    result.push((
                        new_face,
                        *face_positions[j]
                            + ratio_ * (centroid - *face_positions[j]),
                    ))
                });
                result
            })
            .collect();

        let new_ids =
            vertex_ids_ref(&new_positions, self.positions_len() as VertexKey);

        let face_index: Faces = self
            .face_index
            .par_iter()
            .map(|face| {
                // FIXME: use iterators with double collect
                let mut new_face = Vec::with_capacity(face.len());
                face.iter().for_each(|vertex_key| {
                    let mut face_key = face.clone();
                    face_key.push(*vertex_key);
                    new_face.push(vertex(&face_key, &new_ids).unwrap());
                });
                new_face
            })
            .chain(self.face_index.par_iter().flat_map(|face| {
                face.iter()
                    .circular_tuple_windows::<(_, _, _)>()
                    .filter_map(|v| {
                        if v.0 < v.1 {
                            let a: VertexKey = *v.0;
                            let b: VertexKey = *v.1;
                            let opposite_face = face_with_edge(&[b, a], &self.face_index);

                            /*once(a)
                            .chain(once(vertex(&extend![..opposite_face, a], &new_ids).unwrap()))
                            .chain(once(vertex(&extend![..opposite_face, b], &new_ids).unwrap()))
                            .chain(once(b))
                            .chain(once(vertex(&extend![..face, b], &new_ids).unwrap()))
                            .chain(once(vertex(&extend![..face, a], &new_ids).unwrap()))*/

                            Some(vec![
                                a,
                                vertex(&extend![..opposite_face, a], &new_ids).unwrap(),
                                vertex(&extend![..opposite_face, b], &new_ids).unwrap(),
                                b,
                                vertex(&extend![..face, b], &new_ids).unwrap(),
                                vertex(&extend![..face, a], &new_ids).unwrap(),
                            ])
                        } else {
                            None
                        }
                    })
                    .collect::<Faces>()
            }))
            .collect::<Faces>();

        self.append_new_face_set(face_index.len());

        self.face_index = face_index;
        self.positions.par_iter_mut().for_each(|point| {
            *point = (1.5 * ratio_) * *point;
        });
        self.positions.extend(vertex_values(&new_positions));

        if change_name {
            let mut params = String::new();
            if let Some(ratio) = ratio {
                write!(&mut params, "{:.2}", ratio).unwrap();
            }
            self.name = format!("c{}{}", params, self.name);
        }

        self
    }

    /// Replaces each face with a vertex, and each vertex with a face.
    pub fn dual(&mut self, change_name: bool) -> &mut Self {
        let new_positions = face_centers(&self.face_index, &self.positions);
        self.face_index = positions_to_faces(&self.positions, &self.face_index);
        self.positions = new_positions;
        // FIXME: FaceSetIndex

        if change_name {
            self.name = format!("d{}", self.name);
        }

        self
    }

    pub fn expand(
        &mut self,
        ratio: Option<Float>,
        change_name: bool,
    ) -> &mut Self {
        self.ambo(ratio, false);
        self.ambo(ratio, false);

        if change_name {
            let mut params = String::new();
            if let Some(ratio) = ratio {
                write!(&mut params, "{:.2}", ratio).unwrap();
            }
            self.name = format!("e{}{}", params, self.name);
        }

        self
    }

    /// Splits each edge and connects new edges at the split point to the face
    /// centroid. Existing positions are retained.
    /// ![Gyro](https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Conway_gC.png/200px-Conway_gC.png)
    /// # Arguments
    /// * `ratio` – The ratio at which the adjacent edges get split.
    /// * `height` – An offset to add to the face centroid point along the face
    ///   normal.
    /// * `regular_faces_only` – Only faces whose edges are 90% the same length,
    ///   within the same face, are affected.
    pub fn gyro(
        &mut self,
        ratio: Option<f32>,
        height: Option<f32>,
        change_name: bool,
    ) -> &mut Self {
        let ratio_ = match ratio {
            Some(r) => r.clamp(0.0, 1.0),
            None => 1. / 3.,
        };
        let height_ = height.unwrap_or(0.);

        let edges = self.to_edges();
        let reversed_edges: Edges =
            edges.par_iter().map(|edge| [edge[1], edge[0]]).collect();

        // Retain original positions, add face centroids and directed
        // edge positions each N-face becomes N pentagons.
        let new_positions: Vec<(&FaceSlice, Point)> = self
            .face_index
            .par_iter()
            .map(|face| {
                let fp = index_as_positions(face, &self.positions);
                (
                    face.as_slice(),
                    centroid_ref(&fp).normalized()
                        + average_normal_ref(&fp).unwrap() * height_,
                )
            })
            .chain(edges.par_iter().enumerate().flat_map(|edge| {
                let edge_positions =
                    index_as_positions(edge.1, &self.positions);
                vec![
                    (
                        &edge.1[..],
                        *edge_positions[0]
                            + ratio_
                                * (*edge_positions[1] - *edge_positions[0]),
                    ),
                    (
                        &reversed_edges[edge.0][..],
                        *edge_positions[1]
                            + ratio_
                                * (*edge_positions[0] - *edge_positions[1]),
                    ),
                ]
            }))
            .collect();

        let new_ids = vertex_ids_ref_ref(
            &new_positions,
            self.positions_len() as VertexKey,
        );

        self.positions.extend(vertex_values_as_ref(&new_positions));

        self.face_index = self
            .face_index
            .par_iter()
            .flat_map(|face| {
                face.iter()
                    .cycle()
                    .skip(face.len() - 1)
                    .tuple_windows::<(_, _, _)>()
                    .take(face.len())
                    .map(|v| {
                        let a = *v.1;
                        let b = *v.2;
                        let z = *v.0;
                        let eab = vertex(&[a, b], &new_ids).unwrap();
                        let eza = vertex(&[z, a], &new_ids).unwrap();
                        let eaz = vertex(&[a, z], &new_ids).unwrap();
                        let centroid = vertex(face, &new_ids).unwrap();
                        vec![a, eab, centroid, eza, eaz]
                    })
                    .collect::<Faces>()
            })
            .collect();

        if change_name {
            let mut params = String::new();
            if let Some(ratio) = ratio {
                write!(&mut params, "{:.2}", ratio).unwrap();
            }
            if let Some(height) = height {
                write!(&mut params, ",{:.2}", height).unwrap();
            }
            self.name = format!("g{}{}", params, self.name);
        }

        self
    }

    /// Creates quadrilateral faces around each original edge. Original
    /// edges are discarded.
    /// # Arguments
    /// * `ratio` – The ratio at which the adjacent edges get split. Will be
    ///   clamped to `[0, 1]`. Default value is `0.5`.
    pub fn join(
        &mut self,
        ratio: Option<Float>,
        change_name: bool,
    ) -> &mut Self {
        self.dual(false);
        self.ambo(ratio, false);
        self.dual(false);

        if change_name {
            let mut params = String::new();
            if let Some(ratio) = ratio {
                write!(&mut params, "{:.2}", ratio).unwrap();
            }
            self.name = format!("j{}{}", params, self.name);
        }

        self
    }

    /// Splits each face into triangles, one for each edge, which
    /// extend to the face centroid. Existing positions are retained.
    /// # Arguments
    /// * `height` - An offset to add to the face centroid point along the face
    ///   normal.
    /// * `face_arity` - Only faces matching the given arities will be affected.
    /// * `regular_faces_only` - Only faces whose edges are 90% the same length,
    ///   within the same face, are affected.
    pub fn kis(
        &mut self,
        height: Option<Float>,
        face_arity_mask: Option<&[usize]>,
        face_index_mask: Option<&[FaceKey]>,
        regular_faces_only: Option<bool>,
        change_name: bool,
    ) -> &mut Self {
        let new_positions: Vec<(&FaceSlice, Point)> = self
            .face_index
            .par_iter()
            .enumerate()
            .filter_map(|(index, face)| {
                if is_face_selected(
                    face,
                    index,
                    &self.positions,
                    face_arity_mask,
                    face_index_mask,
                    regular_faces_only,
                ) {
                    let face_positions =
                        index_as_positions(face, &self.positions);
                    Some((
                        face.as_slice(),
                        centroid_ref(&face_positions)
                            + average_normal_ref(&face_positions).unwrap()
                                * height.unwrap_or(0.),
                    ))
                } else {
                    None
                }
            })
            .collect();

        let new_ids = vertex_ids_ref_ref(
            &new_positions,
            self.positions.len() as VertexKey,
        );

        self.positions.extend(vertex_values_as_ref(&new_positions));

        self.face_index = self
            .face_index
            .par_iter()
            .flat_map(|face: &Face| match vertex(face, &new_ids) {
                Some(centroid) => face
                    .iter()
                    .cycle()
                    .tuple_windows::<(&VertexKey, _)>()
                    .take(face.len())
                    .map(|v| vec![*v.0, *v.1, centroid as VertexKey])
                    .collect(),
                None => vec![face.clone()],
            })
            .collect();

        if change_name {
            let mut params = String::new();
            if let Some(height) = height {
                write!(&mut params, "{:.2}", height).unwrap();
            }
            if let Some(face_arity_mask) = face_arity_mask {
                write!(&mut params, ",{}", format_slice(face_arity_mask))
                    .unwrap();
            } else {
                write!(&mut params, ",").unwrap();
            }
            if let Some(face_index_mask) = face_index_mask {
                write!(&mut params, ",{}", format_slice(face_index_mask))
                    .unwrap();
            } else {
                write!(&mut params, ",").unwrap();
            }
            if let Some(regular_faces_only) = regular_faces_only {
                if regular_faces_only {
                    params.push_str(",{t}");
                }
            } else {
                write!(&mut params, ",").unwrap();
            }
            params = params.trim_end_matches(',').to_string();
            self.name = format!("k{}{}", params, self.name);
        }

        self
    }

    // Inset faces by `offset` from the original edges.
    pub fn inset(
        &mut self,
        offset: Option<Float>,
        face_arity: Option<&[usize]>,
        change_name: bool,
    ) -> &mut Self {
        if change_name {
            let mut params = String::new();
            if let Some(offset) = offset {
                write!(&mut params, "{:.2}", offset).unwrap();
            }
            if let Some(face_arity) = &face_arity {
                write!(&mut params, ",{}", format_slice(face_arity)).unwrap();
            }
            self.name = format!("i{}{}", params, self.name);
        }

        self.extrude(Some(0.0), Some(offset.unwrap_or(0.3)), face_arity, false);

        self
    }

    // Extrudes faces by `height` and shrinks the extruded faces by `distance`
    // from the original edges.
    pub fn extrude(
        &mut self,
        height: Option<Float>,
        offset: Option<Float>,
        face_arity: Option<&[usize]>,
        change_name: bool,
    ) -> &mut Self {
        let new_positions = self
            .face_index
            .par_iter()
            .filter(|face| face_arity_matches(face, face_arity))
            .flat_map(|face| {
                let face_positions = index_as_positions(face, &self.positions);
                let centroid = centroid_ref(&face_positions);
                face.iter()
                    .zip(&face_positions)
                    .map(|face_vertex_point| {
                        (
                            extend![..face, *face_vertex_point.0],
                            **face_vertex_point.1
                                + offset.unwrap_or(0.0)
                                    * (centroid - **face_vertex_point.1)
                                + average_normal_ref(&face_positions).unwrap()
                                    * height.unwrap_or(0.3),
                        )
                    })
                    .collect::<Vec<_>>()
            })
            .collect::<Vec<_>>();

        let new_ids =
            vertex_ids_ref(&new_positions, self.positions_len() as VertexKey);

        self.face_index = self
            .face_index
            .par_iter()
            .flat_map(|face| {
                if face_arity_matches(face, face_arity) {
                    face.iter()
                        .enumerate()
                        .flat_map(|index_vertex| {
                            let a = *index_vertex.1;
                            let inset_a =
                                vertex(&extend![..face, a], &new_ids).unwrap();
                            let b = face[(index_vertex.0 + 1) % face.len()];
                            let inset_b =
                                vertex(&extend![..face, b], &new_ids).unwrap();
                            if height.unwrap_or(0.3).is_sign_positive() {
                                vec![vec![a, b, inset_b, inset_a]]
                            } else {
                                vec![vec![inset_a, inset_b, b, a]]
                            }
                        })
                        .chain(vec![face
                            .iter()
                            .map(|v| {
                                vertex(&extend![..face, *v], &new_ids).unwrap()
                            })
                            .collect::<Vec<_>>()])
                        .collect::<Vec<_>>()
                } else {
                    vec![face.clone()]
                }
            })
            .collect();

        self.positions.extend(vertex_values_as_ref(&new_positions));

        if change_name {
            let mut params = String::new();
            if let Some(height) = height {
                write!(&mut params, "{:.2}", height).unwrap();
            }
            if let Some(offset) = offset {
                write!(&mut params, ",{:.2}", offset).unwrap();
            } else {
                write!(&mut params, ",").unwrap();
            }
            if let Some(face_arity) = face_arity {
                write!(&mut params, ",{}", format_slice(face_arity)).unwrap();
            } else {
                write!(&mut params, ",").unwrap();
            }
            params = params.trim_end_matches(',').to_string();
            self.name = format!("x{}{}", params, self.name);
        }

        self
    }

    pub fn medial(
        &mut self,
        ratio: Option<Float>,
        height: Option<Float>,
        vertex_valence: Option<&[usize]>,
        regular_faces_only: Option<bool>,
        change_name: bool,
    ) -> &mut Self {
        self.dual(false);
        self.truncate(height, vertex_valence, regular_faces_only, false);
        self.ambo(ratio, false);

        if change_name {
            let mut params = String::new();
            if let Some(ratio) = ratio {
                write!(&mut params, "{:.2}", ratio).unwrap();
            }
            if let Some(height) = height {
                write!(&mut params, ",{:.2}", height).unwrap();
            } else {
                write!(&mut params, ",").unwrap();
            }
            if let Some(vertex_valence) = vertex_valence {
                write!(&mut params, ",{}", format_slice(vertex_valence))
                    .unwrap();
            } else {
                write!(&mut params, ",").unwrap();
            }
            if let Some(regular_faces_only) = regular_faces_only {
                if regular_faces_only {
                    params.push_str(",{t}");
                }
            } else {
                write!(&mut params, ",").unwrap();
            }
            params = params.trim_end_matches(',').to_string();
            self.name = format!("M{}{}", params, self.name);
        }

        self
    }

    pub fn meta(
        &mut self,
        ratio: Option<Float>,
        height: Option<Float>,
        vertex_valence: Option<&[usize]>,
        regular_faces_only: Option<bool>,
        change_name: bool,
    ) -> &mut Self {
        self.kis(
            height,
            match vertex_valence {
                // By default meta works on vertices of valence three.
                None => Some(&[3]),
                _ => vertex_valence,
            },
            None,
            regular_faces_only,
            false,
        );
        self.join(ratio, false);

        if change_name {
            let mut params = String::new();
            if let Some(ratio) = ratio {
                write!(&mut params, "{:.2}", ratio).unwrap();
            }
            if let Some(height) = height {
                write!(&mut params, ",{:.2}", height).unwrap();
            } else {
                write!(&mut params, ",").unwrap();
            }
            if let Some(vertex_valence) = vertex_valence {
                write!(&mut params, ",{}", format_slice(vertex_valence))
                    .unwrap();
            } else {
                write!(&mut params, ",").unwrap();
            }
            if let Some(regular_faces_only) = regular_faces_only {
                if regular_faces_only {
                    params.push_str(",{t}");
                }
            } else {
                write!(&mut params, ",").unwrap();
            }
            params = params.trim_end_matches(',').to_string();
            self.name = format!("m{}{}", params, self.name);
        }

        self
    }

    pub fn needle(
        &mut self,
        height: Option<Float>,
        vertex_valence: Option<&[usize]>,
        regular_faces_only: Option<bool>,
        change_name: bool,
    ) -> &mut Self {
        self.dual(false);
        self.truncate(height, vertex_valence, regular_faces_only, false);

        if change_name {
            let mut params = String::new();
            if let Some(height) = height {
                write!(&mut params, "{:.2}", height).unwrap();
            }
            if let Some(vertex_valence) = vertex_valence {
                write!(&mut params, ",{}", format_slice(vertex_valence))
                    .unwrap();
            } else {
                write!(&mut params, ",").unwrap();
            }
            if let Some(regular_faces_only) = regular_faces_only {
                if regular_faces_only {
                    params.push_str(",{t}");
                }
            } else {
                write!(&mut params, ",").unwrap();
            }
            params = params.trim_end_matches(',').to_string();
            self.name = format!("n{}{}", params, self.name);
        }

        self
    }

    pub fn ortho(
        &mut self,
        ratio: Option<Float>,
        change_name: bool,
    ) -> &mut Self {
        self.join(ratio, false);
        self.join(ratio, false);

        if change_name {
            let mut params = String::new();
            if let Some(ratio) = ratio {
                write!(&mut params, "{:.2}", ratio).unwrap();
            }
            self.name = format!("o{}{}", params, self.name);
        }

        self
    }

    /// Apply quick and dirty canonicalization. Typical number of `iterations
    /// are `100`+.
    #[inline]
    pub fn planarize(&mut self, iterations: Option<usize>, change_name: bool) {
        let mut dual = self.clone().dual(false).finalize();

        for _ in 0..iterations.unwrap_or(100) {
            // Reciprocate face centers.
            dual.positions =
                reciprocate_face_centers(&self.face_index, &self.positions);
            self.positions =
                reciprocate_face_centers(&dual.face_index, &dual.positions);
        }

        if change_name {
            let mut params = String::new();
            if let Some(iterations) = iterations {
                write!(&mut params, "{}", iterations).unwrap();
            }
            self.name = format!("K{}{}", params, self.name);
        }
    }

    pub fn propeller(
        &mut self,
        ratio: Option<Float>,
        change_name: bool,
    ) -> &mut Self {
        let ratio_ = match ratio {
            Some(r) => r.clamp(0.0, 1.0),
            None => 1. / 3.,
        };

        let edges = self.to_edges();
        let reversed_edges: Edges =
            edges.iter().map(|edge| [edge[1], edge[0]]).collect();

        let new_positions = edges
            .iter()
            .zip(reversed_edges.iter())
            .flat_map(|(edge, reversed_edge)| {
                let edge_positions = index_as_positions(edge, &self.positions);
                vec![
                    (
                        edge,
                        *edge_positions[0]
                            + ratio_
                                * (*edge_positions[1] - *edge_positions[0]),
                    ),
                    (
                        reversed_edge,
                        *edge_positions[1]
                            + ratio_
                                * (*edge_positions[0] - *edge_positions[1]),
                    ),
                ]
            })
            .collect::<Vec<_>>();

        let new_ids = vertex_ids_edge_ref_ref(
            &new_positions,
            self.positions_len() as VertexKey,
        );

        self.face_index = self
            .face_index
            .par_iter()
            .map(|face| {
                face.iter()
                    .circular_tuple_windows::<(_, _)>()
                    .map(|f| vertex_edge(&[*f.0, *f.1], &new_ids).unwrap())
                    .collect()

                /*(0..face.len())
                .map(|j| vertex_edge(&[face[j], face[(j + 1) % face.len()]], &new_ids).unwrap())
                .collect()*/
            })
            .chain(self.face_index.par_iter().flat_map(|face| {
                (0..face.len())
                    .map(|j| {
                        let a = face[j];
                        let b = face[(j + 1) % face.len()];
                        let z = face[(j + face.len() - 1) % face.len()];
                        let eab = vertex_edge(&[a, b], &new_ids).unwrap();
                        let eba = vertex_edge(&[b, a], &new_ids).unwrap();
                        let eza = vertex_edge(&[z, a], &new_ids).unwrap();
                        vec![eba, eab, eza, a]
                        //vec![eza, eab, eba, a]
                    })
                    .collect::<Faces>()
            }))
            .collect::<Faces>();

        self.positions.extend(vertex_values_as_ref(&new_positions));

        if change_name {
            let mut params = String::new();
            if let Some(ratio) = ratio {
                write!(&mut params, "{:.2}", ratio).unwrap();
            }
            self.name = format!("p{}{}", params, self.name);
        }

        self
    }

    pub fn quinto(
        &mut self,
        height: Option<Float>,
        change_name: bool,
    ) -> &mut Self {
        let height_ = match height {
            Some(h) => {
                if h < 0.0 {
                    0.0
                } else {
                    h
                }
            }
            None => 0.5,
        };

        let mut new_positions: Vec<(Face, Point)> = self
            .to_edges()
            .par_iter()
            .map(|edge| {
                let edge_positions = index_as_positions(edge, &self.positions);
                (
                    edge.to_vec(),
                    height_ * (*edge_positions[0] + *edge_positions[1]),
                )
            })
            .collect();

        new_positions.extend(
            self.face_index
                .par_iter()
                .flat_map(|face| {
                    let edge_positions =
                        index_as_positions(face, &self.positions);
                    let centroid = centroid_ref(&edge_positions);
                    (0..face.len())
                        .map(|i| {
                            (
                                extend![..face, i as VertexKey],
                                (*edge_positions[i]
                                    + *edge_positions[(i + 1) % face.len()]
                                    + centroid)
                                    / 3.,
                            )
                        })
                        .collect::<Vec<(Face, Point)>>()
                })
                .collect::<Vec<(Face, Point)>>(),
        );

        let new_ids =
            vertex_ids_ref(&new_positions, self.positions_len() as VertexKey);

        self.positions.extend(vertex_values_as_ref(&new_positions));

        self.face_index = self
            .face_index
            .par_iter()
            .map(|face| {
                (0..face.len())
                    .map(|face_vertex| {
                        vertex(
                            &extend![..face, face_vertex as VertexKey],
                            &new_ids,
                        )
                        .unwrap()
                    })
                    .collect()
            })
            .chain(self.face_index.par_iter().flat_map(|face| {
                (0..face.len())
                    .map(|i| {
                        let v = face[i];
                        let e0 =
                            [face[(i + face.len() - 1) % face.len()], face[i]];
                        let e1 = [face[i], face[(i + 1) % face.len()]];
                        let e0p =
                            vertex(&distinct_edge(&e0), &new_ids).unwrap();
                        let e1p =
                            vertex(&distinct_edge(&e1), &new_ids).unwrap();
                        let iv0 = vertex(
                            &extend![
                                ..face,
                                ((i + face.len() - 1) % face.len())
                                    as VertexKey
                            ],
                            &new_ids,
                        )
                        .unwrap();
                        let iv1 =
                            vertex(&extend![..face, i as VertexKey], &new_ids)
                                .unwrap();
                        vec![v, e1p, iv1, iv0, e0p]
                    })
                    .collect::<Faces>()
            }))
            .collect::<Faces>();

        if change_name {
            let mut params = String::new();
            if let Some(h) = height {
                write!(&mut params, "{:.2}", h).unwrap();
            }
            self.name = format!("q{}{}", params, self.name);
        }

        self
    }

    pub fn reflect(&mut self, change_name: bool) -> &mut Self {
        self.positions = self
            .positions
            .par_iter()
            .map(|v| Point::new(v.x, -v.y, v.z))
            .collect();
        self.reverse();

        if change_name {
            self.name = format!("r{}", self.name);
        }

        self
    }

    pub fn snub(
        &mut self,
        ratio: Option<Float>,
        height: Option<Float>,
        change_name: bool,
    ) -> &mut Self {
        self.dual(false);
        self.gyro(ratio, height, false);
        self.dual(false);

        if change_name {
            let mut params = String::new();
            if let Some(ratio) = ratio {
                write!(&mut params, "{:.2}", ratio).unwrap();
            }
            if let Some(height) = height {
                write!(&mut params, ",{:.2}", height).unwrap();
            }
            self.name = format!("s{}{}", params, self.name);
        }

        self
    }

    /// Projects all positions on the unit sphere (at `strength` `1.0`).
    ///
    /// If `strength` is zero this is a no-op and will neither change the
    /// geometry nor the name. Even if `change_name` is `true`.
    pub fn spherize(
        &mut self,
        strength: Option<Float>,
        change_name: bool,
    ) -> &mut Self {
        let strength_ = strength.unwrap_or(1.0);

        if 0.0 != strength_ {
            self.positions.par_iter_mut().for_each(|point| {
                *point =
                    (1.0 - strength_) * *point + strength_ * point.normalized();
            });

            if change_name {
                let mut params = String::new();
                if let Some(strength) = strength {
                    write!(&mut params, "{:.2}", strength).unwrap();
                }
                self.name = format!("S{}{}", params, self.name);
            }
        }

        self
    }

    pub fn truncate(
        &mut self,
        height: Option<Float>,
        vertex_valence: Option<&[usize]>,
        regular_faces_only: Option<bool>,
        change_name: bool,
    ) -> &mut Self {
        self.dual(false);
        self.kis(height, vertex_valence, None, regular_faces_only, false);
        self.dual(false);

        if change_name {
            let mut params = String::new();
            if let Some(height) = height {
                write!(&mut params, "{:.2}", height).unwrap();
            }
            if let Some(vertex_valence) = vertex_valence {
                write!(&mut params, ",{}", format_slice(vertex_valence))
                    .unwrap();
            } else {
                write!(&mut params, ",").unwrap();
            }
            if let Some(regular_faces_only) = regular_faces_only {
                if regular_faces_only {
                    params.push_str(",{t}");
                }
            } else {
                write!(&mut params, ",").unwrap();
            }
            params = params.trim_end_matches(',').to_string();
            self.name = format!("t{}{}", params, self.name);
        }

        self
    }

    pub fn whirl(
        &mut self,
        ratio: Option<Float>,
        height: Option<Float>,
        change_name: bool,
    ) -> &mut Self {
        let ratio_ = match ratio {
            Some(r) => r.clamp(0.0, 1.0),
            None => 1. / 3.,
        };
        let height_ = height.unwrap_or(0.);

        let new_positions: Vec<(Face, Point)> = self
            .face_index
            .par_iter()
            .flat_map(|face| {
                let face_positions = index_as_positions(face, &self.positions);
                let center = centroid_ref(&face_positions)
                    + average_normal_ref(&face_positions).unwrap() * height_;
                face.iter()
                    .enumerate()
                    .map(|v| {
                        let edge_positions = [
                            face_positions[v.0],
                            face_positions[(v.0 + 1) % face.len()],
                        ];
                        let middle: Point = *edge_positions[0]
                            + ratio_
                                * (*edge_positions[1] - *edge_positions[0]);
                        (
                            extend![..face, *v.1],
                            middle + ratio_ * (center - middle),
                        )
                    })
                    .collect::<Vec<_>>()
            })
            .chain(self.to_edges().par_iter().flat_map(|edge| {
                let edge_positions = index_as_positions(edge, &self.positions);
                vec![
                    (
                        edge.to_vec(),
                        *edge_positions[0]
                            + ratio_
                                * (*edge_positions[1] - *edge_positions[0]),
                    ),
                    (
                        vec![edge[1], edge[0]],
                        *edge_positions[1]
                            + ratio_
                                * (*edge_positions[0] - *edge_positions[1]),
                    ),
                ]
            }))
            .collect();

        let new_ids =
            vertex_ids_ref(&new_positions, self.positions_len() as VertexKey);

        self.positions.extend(vertex_values(&new_positions));

        let old_face_index_len = self.face_index.len();

        self.face_index = self
            .face_index
            .par_iter()
            .flat_map(|face| {
                face.iter()
                    .circular_tuple_windows::<(_, _, _)>()
                    .map(|v| {
                        let edeg_ab = vertex(&[*v.0, *v.1], &new_ids).unwrap();
                        let edeg_ba = vertex(&[*v.1, *v.0], &new_ids).unwrap();
                        let edeg_bc = vertex(&[*v.1, *v.2], &new_ids).unwrap();
                        let mut mid = face.clone();
                        mid.push(*v.0);
                        let mid_a = vertex(&mid, &new_ids).unwrap();
                        mid.pop();
                        mid.push(*v.1);
                        let mid_b = vertex(&mid, &new_ids).unwrap();
                        vec![edeg_ab, edeg_ba, *v.1, edeg_bc, mid_b, mid_a]
                    })
                    .collect::<Faces>()
            })
            .chain(self.face_index.par_iter().map(|face| {
                let mut new_face = face.clone();
                face.iter()
                    .map(|a| {
                        new_face.push(*a);
                        let result = vertex(&new_face, &new_ids).unwrap();
                        new_face.pop();
                        result
                    })
                    .collect()
            }))
            .collect::<Faces>();

        self.append_new_face_set(self.face_index.len() - old_face_index_len);

        if change_name {
            let mut params = String::new();
            if let Some(ratio) = ratio {
                write!(&mut params, "{:.2}", ratio).unwrap();
            }
            if let Some(height) = height {
                write!(&mut params, ",{:.2}", height).unwrap();
            }
            self.name = format!("w{}{}", params, self.name);
        }

        self
    }

    pub fn zip(
        &mut self,
        height: Option<Float>,
        vertex_valence: Option<&[usize]>,
        regular_faces_only: Option<bool>,
        change_name: bool,
    ) -> &mut Self {
        self.dual(false);
        self.kis(height, vertex_valence, None, regular_faces_only, false);

        if change_name {
            let mut params = String::new();
            if let Some(height) = height {
                write!(&mut params, "{:.2}", height).unwrap();
            }
            if let Some(vertex_valence) = vertex_valence {
                write!(&mut params, ",{}", format_slice(vertex_valence))
                    .unwrap();
            } else {
                write!(&mut params, ",").unwrap();
            }
            if let Some(regular_faces_only) = regular_faces_only {
                if regular_faces_only {
                    params.push_str(",{t}");
                }
            } else {
                write!(&mut params, ",").unwrap();
            }
            params = params.trim_end_matches(',').to_string();
            self.name = format!("z{}{}", params, self.name);
        }

        self
    }

    /// Reverses the winding order of faces.
    ///
    /// Clockwise(default) becomes counter-clockwise and vice versa.
    pub fn reverse(&mut self) -> &mut Self {
        self.face_index
            .par_iter_mut()
            .for_each(|face| face.reverse());

        self
    }

    /// Returns the name of this polyhedron. This can be used to reconstruct the
    /// polyhedron using `Polyhedron::from<&str>()`.
    #[inline]
    pub fn name(&self) -> &String {
        &self.name
    }

    #[inline]
    pub fn positions_len(&self) -> usize {
        self.positions.len()
    }

    #[inline]
    pub fn positions(&self) -> &Points {
        &self.positions
    }

    pub fn faces(&self) -> &Faces {
        &self.face_index
    }

    // Resizes the polyhedron to fit inside a unit sphere.
    #[inline]
    pub fn normalize(&mut self) -> &mut Self {
        max_resize(&mut self.positions, 1.);
        self
    }

    /// Compute the edges of the polyhedron.
    #[inline]
    pub fn to_edges(&self) -> Edges {
        let edges = self
            .face_index
            .par_iter()
            .map(|face| {
                face.iter()
                    // Grab two index entries.
                    .circular_tuple_windows::<(_, _)>()
                    .filter(|t| t.0 < t.1)
                    // Create an edge from them.
                    .map(|t| [*t.0, *t.1])
                    .collect::<Vec<_>>()
            })
            .flatten()
            .collect::<Vec<_>>();

        edges.into_iter().unique().collect()
    }

    /// Returns a flat [`u32`] triangle index buffer and two matching point and
    /// normal buffers.
    ///
    /// All the faces are disconnected. I.e. positions & normals are duplicated
    /// for each shared vertex.
    pub fn to_triangle_mesh_buffers(&self) -> (Vec<u32>, Points, Normals) {
        let (positions, normals): (Vec<_>, Vec<_>) = self
            .face_index
            .par_iter()
            .flat_map(|face| {
                face.iter()
                    // Cycle forever.
                    .cycle()
                    // Start at 3-tuple belonging to the
                    // face's last vertex.
                    .skip(face.len() - 1)
                    // Grab the next three vertex index
                    // entries.
                    .tuple_windows::<(_, _, _)>()
                    .take(face.len())
                    .map(|t| {
                        // The middle point of out tuple
                        let point = self.positions[*t.1 as usize];
                        // Create a normal from that
                        let normal = -orthogonal(
                            &self.positions[*t.0 as usize],
                            &point,
                            &self.positions[*t.2 as usize],
                        );
                        let mag_sq = normal.mag_sq();

                        (
                            point,
                            // Check for collinearity:
                            if mag_sq < EPSILON as _ {
                                average_normal_ref(&index_as_positions(
                                    face,
                                    self.positions(),
                                ))
                                .unwrap()
                            } else {
                                normal / mag_sq.sqrt()
                            },
                        )
                    })
                    // For each vertex of the face.
                    .collect::<Vec<_>>()
            })
            .unzip();

        // Build a new face index. Same topology as the old one, only with new
        // keys.
        let triangle_face_index = self
            .face_index
            .iter()
            // Build a new index where each face has the original arity and the
            // new numbering.
            .scan(0.., |counter, face| {
                Some(counter.take(face.len()).collect::<Vec<u32>>())
            })
            // Now split each of these faces into triangles.
            .flat_map(|face| match face.len() {
                // Filter out degenerate faces.
                1 | 2 => vec![],
                // Bitriangulate quadrilateral faces use shortest diagonal so
                // triangles are most nearly equilateral.
                4 => {
                    let p = index_as_positions(&face, &positions);

                    if (*p[0] - *p[2]).mag_sq() < (*p[1] - *p[3]).mag_sq() {
                        vec![
                            face[0], face[1], face[2], face[0], face[2],
                            face[3],
                        ]
                    } else {
                        vec![
                            face[1], face[2], face[3], face[1], face[3],
                            face[0],
                        ]
                    }
                }
                5 => vec![
                    face[0], face[1], face[4], face[1], face[2], face[4],
                    face[4], face[2], face[3],
                ],
                // FIXME: a nicer way to triangulate n-gons.
                _ => {
                    let a = face[0];
                    let mut bb = face[1];
                    face.iter()
                        .skip(2)
                        .flat_map(|c| {
                            let b = bb;
                            bb = *c;
                            vec![a, b, *c]
                        })
                        .collect()
                }
            })
            .collect();

        (triangle_face_index, positions, normals)
    }

    #[inline]
    pub fn triangulate(&mut self, shortest: Option<bool>) -> &mut Self {
        self.face_index = self
            .face_index
            .par_iter()
            .flat_map(|face| match face.len() {
                // Bitriangulate quadrilateral faces use shortest diagonal so
                // triangles are most nearly equilateral.
                4 => {
                    let p = index_as_positions(face, &self.positions);

                    if shortest.unwrap_or(true)
                        == ((*p[0] - *p[2]).mag_sq() < (*p[1] - *p[3]).mag_sq())
                    {
                        vec![
                            vec![face[0], face[1], face[2]],
                            vec![face[0], face[2], face[3]],
                        ]
                    } else {
                        vec![
                            vec![face[1], face[2], face[3]],
                            vec![face[1], face[3], face[0]],
                        ]
                    }
                }
                5 => vec![
                    vec![face[0], face[1], face[4]],
                    vec![face[1], face[2], face[4]],
                    vec![face[4], face[2], face[3]],
                ],
                // FIXME: a nicer way to triangulate n-gons.
                _ => {
                    let a = face[0];
                    let mut bb = face[1];
                    face.iter()
                        .skip(2)
                        .map(|c| {
                            let b = bb;
                            bb = *c;
                            vec![a, b, *c]
                        })
                        .collect()
                }
            })
            .collect();

        self
    }

    #[allow(clippy::too_many_arguments)]
    pub fn _open_face(
        &self,
        outer_inset_ratio: Option<Float>,
        outer_inset: Option<Float>,
        inner_inset_ratio: Option<Float>,
        inner_inset: Option<Float>,
        depth: Option<Float>,
        face_arity: Option<&[usize]>,
        min_edge_length: Option<Float>,
        _no_cut: Option<bool>,
    ) {
        // upper and lower inset can be specified by ratio or absolute distance
        //  let(inner_inset_ratio= inner_inset_ratio == undef ?
        // outer_inset_ratio : inner_inset_ratio,

        //pf=p_faces(obj),
        //pv=p_vertices(obj))

        // Corresponding positions on inner surface.
        let inverse_positions = self
            .positions
            .iter()
            .enumerate()
            .map(|point| {
                let vertex_faces = vertex_faces(point.0 as _, &self.face_index);
                // Calculate average normal at vertex.
                let average_normal_ref = vertex_faces
                    .iter()
                    .map(|face| {
                        average_normal_ref(&index_as_positions(
                            face,
                            &self.positions,
                        ))
                        .unwrap()
                    })
                    .fold(Normal::zero(), |accumulate, normal| {
                        accumulate + normal
                    })
                    / vertex_faces.len() as Float;

                *point.1 + depth.unwrap_or(0.2) * average_normal_ref
            })
            .collect::<Vec<_>>();

        let _new_vertices = self
            .face_index
            .iter()
            // Filter out faces that have an unwanted arity or are too small.
            .filter(|face| {
                face_arity_matches(face, face_arity)
                    && minimal_edge_length(face, &self.positions)
                        > min_edge_length.unwrap_or(0.01)
            })
            .flat_map(|face| {
                let face_positions = index_as_positions(face, &self.positions);
                let ofp = index_as_positions(face, &inverse_positions);
                let c = centroid_ref(&face_positions);
                let oc = centroid_ref(&ofp);

                face.iter()
                    .enumerate()
                    .flat_map(|f| {
                        let _v = *f.1;
                        let p = face_positions[f.0];
                        let p1 = face_positions[(f.0 + 1) % face.len()];
                        let p0 =
                            face_positions[(f.0 + face.len() - 1) % face.len()];

                        let sa = angle_between(&(*p0 - *p), &(*p1 - *p), None);
                        let bv = 0.5
                            * ((*p1 - *p).normalized()
                                + (*p0 - *p).normalized());
                        let op = ofp[f.0];

                        let _ip = match outer_inset {
                            None => {
                                *p + (c - *p) * outer_inset_ratio.unwrap_or(0.2)
                            }
                            Some(outer_inset) => {
                                *p + outer_inset / sa.sin() * bv
                            }
                        };
                        let _oip = match inner_inset {
                            None => {
                                *op + (oc - *op)
                                    * inner_inset_ratio.unwrap_or(0.2)
                            }
                            Some(inner_inset) => {
                                *op + inner_inset / sa.sin() * bv
                            }
                        };
                        //vec![[[face, v], ip], [[face, -v - 1], oip]]
                        vec![]
                    })
                    .collect::<Vec<_>>()
                //vec![]
            })
            .collect::<Vec<Point>>();
        /*
        // the inset positions on outer and inner surfaces
        // outer inset positions keyed by face, v, inner positions by face,-v-1
                flatten(
                  [ for (face = pf)
                    if(face_arity_matches(face,fn)
                       && min_edge_length(face,pv) > min_edge_length)
                        let(fp=as_positions(face,pv),
                            ofp=as_positions(face,inv),
                            c=centroid(fp),
                            oc=centroid(ofp))

                        flatten(
                           [for (i=[0:len(face)-1])
                            let(v=face[i],
                                p = fp[i],
                                p1= fp[(i+1)%len(face)],
                                p0=fp[(i-1 + len(face))%len(face)],
                                sa = angle_between(p0-p,p1-p),
                                bv = (unitv(p1-p)+unitv(p0-p))/2,
                                op= ofp[i],
                                ip = outer_inset ==  undef
                                    ? p + (c-p)*outer_inset_ratio
                                    : p + outer_inset/sin(sa) * bv ,
                                oip = inner_inset == undef
                                    ? op + (oc-op)*inner_inset_ratio
                                    : op + inner_inset/sin(sa) * bv)
                            [ [[face,v],ip],[[face,-v-1],oip]]
                           ])
                    ])
                  )
          let(newids=vertex_ids(newv,2*len(pv)))
          let(newf =
                flatten(
                 [ for (i = [0:len(pf)-1])
                   let(face = pf[i])
                   flatten(
                     face_arity_matches(face,fn)
                       && min_edge_length(face,pv) > min_edge_length
                       && i  >= nocut

                       ? [for (j=[0:len(face)-1])   //  replace N-face with 3*N quads
                         let (a=face[j],
                              inseta = vertex([face,a],newids),
                              oinseta= vertex([face,-a-1],newids),
                              b=face[(j+1)%len(face)],
                              insetb= vertex([face,b],newids),
                              oinsetb=vertex([face,-b-1],newids),
                              oa=len(pv) + a,
                              ob=len(pv) + b)

                            [
                              [a,b,insetb,inseta]  // outer face
                             ,[inseta,insetb,oinsetb,oinseta]  //wall
                             ,[oa,oinseta,oinsetb,ob]  // inner face
                            ]
                          ]
                       :  [[face],  //outer face
                           [reverse([  //inner face
                                  for (j=[0:len(face)-1])
                                  len(pv) +face[j]
                                ])
                           ]
                          ]
                      )
                ] ))

          poly(name=str("L",p_name(obj)),
              vertices=  concat(pv, inv, vertex_values(newv)) ,
              faces= newf,
              debug=newv
              )
           ; // end openface
           */
    }

    /// Turns the builder into a final object.
    pub fn finalize(&self) -> Self {
        self.clone()
    }

    /// Sends the polyhedron to the specified
    /// [ɴsɪ](https:://crates.io/crates/nsi) context.
    /// # Arguments
    /// * `handle` – Handle of the node being created. If omitted, the name of
    ///   the polyhedron will be used as a handle.
    ///
    /// * `crease_hardness` - The hardness of edges (default: 10).
    ///
    /// * `corner_hardness` - The hardness of vertices (default: 0).
    ///
    /// * `smooth_corners` - Whether to keep corners smooth, where more than two
    ///   edges meet. When set to `false` these automatically form a hard corner
    ///   with the same hardness as `crease_hardness`.
    #[cfg(feature = "nsi")]
    pub fn to_nsi(
        &self,
        ctx: &nsi::Context,
        handle: Option<&str>,
        crease_hardness: Option<f32>,
        corner_hardness: Option<f32>,
        smooth_corners: Option<bool>,
    ) -> String {
        let handle = handle.unwrap_or_else(|| self.name.as_str()).to_string();
        // Create a new mesh node.
        ctx.create(handle.clone(), nsi::NodeType::Mesh, &[]);

        // Flatten point vector.
        // Fast, unsafe version. May exploce on some platforms.
        // If so, use commented out code below instead.
        let positions = unsafe {
            std::slice::from_raw_parts(
                self.positions.as_ptr().cast::<Float>(),
                3 * self.positions_len(),
            )
        };

        /*
        let positions: Vec<f32> = self
            .positions
            .into_par_iter()
            .flat_map(|p3| once(p3.x as _).chain(once(p3.y as _)).chain(once(p3.z as _)))
            .collect();
        */

        ctx.set_attribute(
            handle.clone(),
            &[
                // Positions.
                nsi::points!("P", positions),
                // VertexKey into the position array.
                nsi::integers!(
                    "P.indices",
                    bytemuck::cast_slice(
                        &self
                            .face_index
                            .par_iter()
                            .flat_map(|face| face.clone())
                            .collect::<Vec<_>>()
                    )
                ),
                // Arity of each face.
                nsi::integers!(
                    "nvertices",
                    &self
                        .face_index
                        .par_iter()
                        .map(|face| face.len() as i32)
                        .collect::<Vec<_>>()
                ),
                // Render this as a C-C subdivison surface.
                nsi::string!("subdivision.scheme", "catmull-clark"),
                // This saves us from having to reverse the mesh ourselves.
                nsi::integer!("clockwisewinding", true as _),
            ],
        );

        // Default: semi sharp creases.
        let crease_hardness = crease_hardness.unwrap_or(10.);

        // Crease each of our edges a bit?
        if 0.0 != crease_hardness {
            let edges = self
                .to_edges()
                .into_iter()
                .flat_map(|edge| edge.to_vec())
                .collect::<Vec<_>>();
            ctx.set_attribute(
                handle.clone(),
                &[
                    nsi::integers!(
                        "subdivision.creasevertices",
                        bytemuck::cast_slice(&edges)
                    ),
                    nsi::floats!(
                        "subdivision.creasesharpness",
                        &vec![crease_hardness; edges.len() / 2]
                    ),
                ],
            );
        }

        match corner_hardness {
            Some(hardness) => {
                if 0.0 < hardness {
                    let corners = self
                        .positions
                        .par_iter()
                        .enumerate()
                        .map(|(i, _)| i as u32)
                        .collect::<Vec<_>>();
                    ctx.set_attribute(
                        handle.clone(),
                        &[
                            nsi::integers!(
                                "subdivision.cornervertices",
                                bytemuck::cast_slice(&corners)
                            ),
                            nsi::floats!(
                                "subdivision.cornersharpness",
                                &vec![hardness; corners.len()]
                            ),
                        ],
                    );
                }
            }

            // Have the renderer semi create sharp corners automagically.
            None => ctx.set_attribute(
                handle.clone(),
                &[
                    // Disabling below flag activates the specific
                    // deRose extensions for the C-C creasing
                    // algorithm that causes any vertex with where
                    // more then three creased edges meet to forma a
                    // corner.
                    // See fig. 8c/d in this paper:
                    // http://graphics.pixar.com/people/derose/publications/Geri/paper.pdf
                    nsi::integer!(
                        "subdivision.smoothcreasecorners",
                        smooth_corners.unwrap_or(false) as _
                    ),
                ],
            ),
        };

        handle
    }

    #[cfg(feature = "obj")]
    pub fn read_from_obj(&self, source: &Path, reverse_winding: bool) {
        //Result<Self, Box<dyn Error>> {

        let obj = tobj::load_obj(source, &tobj::LoadOptions::default());
    }

    /// Write the polyhedron to a
    /// [Wavefront OBJ](https://en.wikipedia.org/wiki/Wavefront_.obj_file)
    /// file.
    ///
    /// The [`name`](Polyhedron::name()) of the polyhedron is appended
    /// to the given `destination` and postfixed with the extension
    /// `.obj`.
    ///
    /// Depending on the target coordinate system (left- or right
    /// handed) the mesh’s winding order can be reversed with the
    /// `reverse_face_winding` flag.
    ///
    /// The return value, on success, is the final, complete path of
    /// the OBJ file.
    #[cfg(feature = "obj")]
    pub fn write_to_obj(
        &self,
        destination: &Path,
        reverse_winding: bool,
    ) -> Result<PathBuf, Box<dyn Error>> {
        let path = destination.join(format!("polyhedron-{}.obj", self.name));
        let mut file = File::create(path.clone())?;

        writeln!(file, "o {}", self.name)?;

        for vertex in &self.positions {
            writeln!(file, "v {} {} {}", vertex.x, vertex.y, vertex.z)?;
        }

        match reverse_winding {
            true => {
                for face in &self.face_index {
                    write!(file, "f")?;
                    for vertex_index in face.iter().rev() {
                        write!(file, " {}", vertex_index + 1)?;
                    }
                    writeln!(file)?;
                }
            }
            false => {
                for face in &self.face_index {
                    write!(file, "f")?;
                    for vertex_index in face {
                        write!(file, " {}", vertex_index + 1)?;
                    }
                    writeln!(file)?;
                }
            }
        };

        file.flush()?;

        Ok(path)
    }

    pub fn tetrahedron() -> Self {
        let c0 = 1.0;

        Self {
            positions: vec![
                Point::new(c0, c0, c0),
                Point::new(c0, -c0, -c0),
                Point::new(-c0, c0, -c0),
                Point::new(-c0, -c0, c0),
            ],
            face_index: vec![
                vec![2, 1, 0],
                vec![3, 2, 0],
                vec![1, 3, 0],
                vec![2, 3, 1],
            ],
            face_set_index: vec![(0..4).collect()],
            name: String::from("T"),
        }
    }

    #[inline]
    pub fn cube() -> Self {
        Self::hexahedron()
    }

    pub fn hexahedron() -> Self {
        let c0 = 1.0;

        Self {
            positions: vec![
                Point::new(c0, c0, c0),
                Point::new(c0, c0, -c0),
                Point::new(c0, -c0, c0),
                Point::new(c0, -c0, -c0),
                Point::new(-c0, c0, c0),
                Point::new(-c0, c0, -c0),
                Point::new(-c0, -c0, c0),
                Point::new(-c0, -c0, -c0),
            ],
            face_index: vec![
                vec![4, 5, 1, 0],
                vec![2, 6, 4, 0],
                vec![1, 3, 2, 0],
                vec![6, 2, 3, 7],
                vec![5, 4, 6, 7],
                vec![3, 1, 5, 7],
            ],
            face_set_index: vec![(0..6).collect()],
            name: String::from("C"),
        }
    }

    pub fn octahedron() -> Self {
        let c0 = 0.707_106_77;

        Self {
            positions: vec![
                Point::new(0.0, 0.0, c0),
                Point::new(0.0, 0.0, -c0),
                Point::new(c0, 0.0, 0.0),
                Point::new(-c0, 0.0, 0.0),
                Point::new(0.0, c0, 0.0),
                Point::new(0.0, -c0, 0.0),
            ],
            face_index: vec![
                vec![4, 2, 0],
                vec![3, 4, 0],
                vec![5, 3, 0],
                vec![2, 5, 0],
                vec![5, 2, 1],
                vec![3, 5, 1],
                vec![4, 3, 1],
                vec![2, 4, 1],
            ],
            face_set_index: vec![(0..8).collect()],
            name: String::from("O"),
        }
    }

    pub fn dodecahedron() -> Self {
        let c0 = 0.809_017;
        let c1 = 1.309_017;

        Self {
            positions: vec![
                Point::new(0.0, 0.5, c1),
                Point::new(0.0, 0.5, -c1),
                Point::new(0.0, -0.5, c1),
                Point::new(0.0, -0.5, -c1),
                Point::new(c1, 0.0, 0.5),
                Point::new(c1, 0.0, -0.5),
                Point::new(-c1, 0.0, 0.5),
                Point::new(-c1, 0.0, -0.5),
                Point::new(0.5, c1, 0.0),
                Point::new(0.5, -c1, 0.0),
                Point::new(-0.5, c1, 0.0),
                Point::new(-0.5, -c1, 0.0),
                Point::new(c0, c0, c0),
                Point::new(c0, c0, -c0),
                Point::new(c0, -c0, c0),
                Point::new(c0, -c0, -c0),
                Point::new(-c0, c0, c0),
                Point::new(-c0, c0, -c0),
                Point::new(-c0, -c0, c0),
                Point::new(-c0, -c0, -c0),
            ],
            face_index: vec![
                vec![12, 4, 14, 2, 0],
                vec![16, 10, 8, 12, 0],
                vec![2, 18, 6, 16, 0],
                vec![17, 10, 16, 6, 7],
                vec![19, 3, 1, 17, 7],
                vec![6, 18, 11, 19, 7],
                vec![15, 3, 19, 11, 9],
                vec![14, 4, 5, 15, 9],
                vec![11, 18, 2, 14, 9],
                vec![8, 10, 17, 1, 13],
                vec![5, 4, 12, 8, 13],
                vec![1, 3, 15, 5, 13],
            ],
            face_set_index: vec![(0..12).collect()],
            name: String::from("D"),
        }
    }

    pub fn icosahedron() -> Self {
        let c0 = 0.809_017;

        Self {
            positions: vec![
                Point::new(0.5, 0.0, c0),
                Point::new(0.5, 0.0, -c0),
                Point::new(-0.5, 0.0, c0),
                Point::new(-0.5, 0.0, -c0),
                Point::new(c0, 0.5, 0.0),
                Point::new(c0, -0.5, 0.0),
                Point::new(-c0, 0.5, 0.0),
                Point::new(-c0, -0.5, 0.0),
                Point::new(0.0, c0, 0.5),
                Point::new(0.0, c0, -0.5),
                Point::new(0.0, -c0, 0.5),
                Point::new(0.0, -c0, -0.5),
            ],
            face_index: vec![
                vec![10, 2, 0],
                vec![5, 10, 0],
                vec![4, 5, 0],
                vec![8, 4, 0],
                vec![2, 8, 0],
                vec![6, 8, 2],
                vec![7, 6, 2],
                vec![10, 7, 2],
                vec![11, 7, 10],
                vec![5, 11, 10],
                vec![1, 11, 5],
                vec![4, 1, 5],
                vec![9, 1, 4],
                vec![8, 9, 4],
                vec![6, 9, 8],
                vec![3, 9, 6],
                vec![7, 3, 6],
                vec![11, 3, 7],
                vec![1, 3, 11],
                vec![9, 3, 1],
            ],
            face_set_index: vec![(0..20).collect()],
            name: String::from("I"),
        }
    }

    pub fn prism(n: usize) -> Self {
        let n = if n < 3 { 3 } else { n };

        // Angle.
        let theta = f32::TAU() / n as f32;
        // Half-edge.
        let h = (theta * 0.5).sin();

        let mut face_index = vec![
            (0..n).map(|i| i as VertexKey).collect::<Vec<_>>(),
            (n..2 * n).rev().map(|i| i as VertexKey).collect::<Vec<_>>(),
        ];

        // Sides.
        face_index.extend((0..n).map(|i| {
            vec![
                i as VertexKey,
                (i + n) as VertexKey,
                ((i + 1) % n + n) as VertexKey,
                ((i + 1) % n) as VertexKey,
            ]
        }));

        Self {
            name: format!("P{}", n),
            positions: (0..n)
                .map(move |i| {
                    Point::new(
                        (i as f32 * theta).cos() as _,
                        h,
                        (i as f32 * theta).sin() as _,
                    )
                })
                .chain((0..n).map(move |i| {
                    Point::new(
                        (i as f32 * theta).cos() as _,
                        -h,
                        (i as f32 * theta).sin() as _,
                    )
                }))
                .collect(),

            face_index,
            face_set_index: Vec::new(),
        }
    }
}

#[cfg(feature = "bevy")]
use bevy::render::mesh::{
    Indices, Mesh, PrimitiveTopology, VertexAttributeValues,
};

#[cfg(feature = "bevy")]
impl From<Polyhedron> for Mesh {
    fn from(mut polyhedron: Polyhedron) -> Self {
        polyhedron.reverse();

        let (index, positions, normals) = polyhedron.to_triangle_mesh_buffers();

        let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
        mesh.set_indices(Some(Indices::U32(index)));

        mesh.insert_attribute(
            Mesh::ATTRIBUTE_POSITION,
            VertexAttributeValues::Float32x3(
                positions
                    .par_iter()
                    .map(|p| [p.x, p.y, p.z])
                    .collect::<Vec<_>>(),
            ),
        );

        mesh.insert_attribute(
            Mesh::ATTRIBUTE_NORMAL,
            VertexAttributeValues::Float32x3(
                normals
                    .par_iter()
                    .map(|n| [-n.x, -n.y, -n.z])
                    .collect::<Vec<_>>(),
            ),
        );

        mesh
    }
}