use decorum::{Real, R64};
use num::traits::FloatConst;
use num::{NumCast, One};
use std::cmp;
use crate::geometry::Triplet;
use crate::primitive::generate::{
PolygonGenerator, PositionGenerator, PositionIndexGenerator, PositionPolygonGenerator,
PositionVertexGenerator,
};
use crate::primitive::topology::{Polygon, Quad, Triangle};
#[derive(Clone, Copy)]
pub struct Bounds {
radius: R64,
}
impl Bounds {
pub fn with_radius(radius: R64) -> Self {
Bounds { radius }
}
pub fn with_width(width: R64) -> Self {
Self::with_radius(width / 2.0)
}
pub fn unit_radius() -> Self {
Self::with_radius(One::one())
}
pub fn unit_width() -> Self {
Self::with_width(One::one())
}
}
impl Default for Bounds {
fn default() -> Self {
Self::unit_radius()
}
}
#[derive(Clone, Copy)]
pub struct UvSphere {
nu: usize, nv: usize, }
impl UvSphere {
pub fn new(nu: usize, nv: usize) -> Self {
UvSphere {
nu: cmp::max(3, nu),
nv: cmp::max(2, nv),
}
}
fn vertex_with_position_from(
&self,
state: &<Self as PositionGenerator>::State,
u: usize,
v: usize,
) -> <Self as PositionVertexGenerator>::Output {
let u = (<R64 as NumCast>::from(u).unwrap() / <R64 as NumCast>::from(self.nu).unwrap())
* R64::PI()
* 2.0;
let v = (<R64 as NumCast>::from(v).unwrap() / <R64 as NumCast>::from(self.nv).unwrap())
* R64::PI();
Triplet(
state.radius * u.cos() * v.sin(),
state.radius * u.sin() * v.sin(),
state.radius * v.cos(),
)
}
fn index_for_position(&self, u: usize, v: usize) -> usize {
if v == 0 {
0
}
else if v == self.nv {
((self.nv - 1) * self.nu) + 1
}
else {
((v - 1) * self.nu) + (u % self.nu) + 1
}
}
fn map_polygon_index(&self, index: usize) -> (usize, usize) {
(index % self.nu, index / self.nu)
}
}
impl Default for UvSphere {
fn default() -> Self {
UvSphere::new(16, 16)
}
}
impl PolygonGenerator for UvSphere {
fn polygon_count(&self) -> usize {
self.nu * self.nv
}
}
impl PositionGenerator for UvSphere {
type State = Bounds;
}
impl PositionVertexGenerator for UvSphere {
type Output = Triplet<R64>;
fn vertex_with_position_from(&self, state: &Self::State, index: usize) -> Self::Output {
if index == 0 {
self.vertex_with_position_from(state, 0, 0)
}
else if index == self.vertex_with_position_count() - 1 {
self.vertex_with_position_from(state, 0, self.nv)
}
else {
let index = index - 1;
self.vertex_with_position_from(state, index % self.nu, (index / self.nu) + 1)
}
}
fn vertex_with_position_count(&self) -> usize {
(self.nv - 1) * self.nu + 2
}
}
impl PositionPolygonGenerator for UvSphere {
type Output = Polygon<Triplet<R64>>;
fn polygon_with_position_from(&self, state: &Self::State, index: usize) -> Self::Output {
let (u, v) = self.map_polygon_index(index);
let (p, q) = ((u + 1) % self.nu, v + 1);
let lower = self.vertex_with_position_from(state, u, v);
if v == 0 {
Polygon::Triangle(Triangle::new(
lower,
self.vertex_with_position_from(state, u, q),
self.vertex_with_position_from(state, p, q),
))
}
else if v == self.nv - 1 {
Polygon::Triangle(Triangle::new(
self.vertex_with_position_from(state, 0, self.nv),
self.vertex_with_position_from(state, p, v),
lower,
))
}
else {
Polygon::Quad(Quad::new(
lower,
self.vertex_with_position_from(state, u, q),
self.vertex_with_position_from(state, p, q),
self.vertex_with_position_from(state, p, v),
))
}
}
}
impl PositionIndexGenerator for UvSphere {
type Output = Polygon<usize>;
fn index_for_position(&self, index: usize) -> <Self as PositionIndexGenerator>::Output {
let (u, v) = self.map_polygon_index(index);
let (p, q) = (u + 1, v + 1);
let low = self.index_for_position(u, v);
let high = self.index_for_position(p, q);
if v == 0 {
Polygon::Triangle(Triangle::new(low, self.index_for_position(u, q), high))
}
else if v == self.nv - 1 {
Polygon::Triangle(Triangle::new(high, self.index_for_position(p, v), low))
}
else {
Polygon::Quad(Quad::new(
low,
self.index_for_position(u, q),
high,
self.index_for_position(p, v),
))
}
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use std::iter::FromIterator;
use crate::primitive::decompose::*;
use crate::primitive::generate::*;
use crate::primitive::sphere::UvSphere;
#[test]
fn vertex_count() {
assert_eq!(
5,
UvSphere::new(3, 2)
.vertices_with_position() .count()
);
}
#[test]
fn polygon_vertex_count() {
assert_eq!(
18,
UvSphere::new(3, 2)
.polygons_with_position() .vertices()
.count()
);
}
#[test]
fn position_index_to_vertex_mapping() {
assert_eq!(
5,
BTreeSet::from_iter(
UvSphere::new(3, 2)
.indices_for_position() .vertices()
)
.len()
)
}
}