extern crate cgmath;
extern crate glium;
use errors::ShapeCreationError;
use self::cgmath::*;
use std::f32;
use vertex::Vertex;
pub struct Sphere {
vertices: glium::vertex::VertexBufferAny,
}
impl<'a> glium::vertex::IntoVerticesSource<'a> for &'a Sphere {
fn into_vertices_source(self) -> glium::vertex::VerticesSource<'a> {
return self.vertices.into_vertices_source();
}
}
impl<'a> Into<glium::index::IndicesSource<'a>> for &'a Sphere {
fn into(self) -> glium::index::IndicesSource<'a> {
return glium::index::IndicesSource::NoIndices {
primitives: glium::index::PrimitiveType::TrianglesList,
};
}
}
pub struct SphereBuilder {
matrix: cgmath::Matrix4<f32>,
u_divisions: usize,
v_divisions: usize,
}
impl Default for SphereBuilder {
fn default() -> Self {
SphereBuilder {
matrix: cgmath::Matrix4::<f32>::identity(),
u_divisions: 24,
v_divisions: 12,
}
}
}
impl SphereBuilder {
pub fn new() -> SphereBuilder {
Default::default()
}
pub fn with_divisions(mut self, u: usize, v: usize) -> Self {
self.u_divisions = u;
self.v_divisions = v;
return self;
}
pub fn scale(mut self, x: f32, y: f32, z: f32) -> Self {
self.matrix = cgmath::Matrix4::from_nonuniform_scale(x, y, z) * self.matrix;
return self;
}
pub fn translate(mut self, x: f32, y: f32, z: f32) -> Self {
self.matrix = cgmath::Matrix4::from_translation([x, y, z].into()) * self.matrix;
return self;
}
pub fn rotate_x(mut self, radians: f32) -> Self {
self.matrix = cgmath::Matrix4::<f32>::from(
cgmath::Matrix3::<f32>::from_angle_x(
cgmath::Rad::<f32>(radians)
)
) * self.matrix;
return self;
}
pub fn rotate_y(mut self, radians: f32) -> Self {
self.matrix = cgmath::Matrix4::<f32>::from(
cgmath::Matrix3::<f32>::from_angle_y(
cgmath::Rad::<f32>(radians)
)
) * self.matrix;
return self;
}
pub fn rotate_z(mut self, radians: f32) -> Self {
self.matrix = cgmath::Matrix4::<f32>::from(
cgmath::Matrix3::<f32>::from_angle_z(
cgmath::Rad::<f32>(radians)
)
) * self.matrix;
return self;
}
pub fn build<F>(self, display: &F) -> Result<Sphere, ShapeCreationError>
where F: glium::backend::Facade
{
let vertices =
try!(glium::vertex::VertexBuffer::<Vertex>::new(display, &try!(self.build_vertices())));
Ok(Sphere { vertices: glium::vertex::VertexBufferAny::from(vertices) })
}
pub fn build_vertices(&self) -> Result<Vec<Vertex>, ShapeCreationError> {
if self.u_divisions < 3 {
return Err(ShapeCreationError::NotEnoughDivisionsInU);
}
if self.v_divisions < 2 {
return Err(ShapeCreationError::NotEnoughDivisionsInV);
}
let u_angle = 2.0 * f32::consts::PI / self.u_divisions as f32;
let v_angle = f32::consts::PI / self.v_divisions as f32;
fn sin_cos(val: f32) -> [f32; 2] {
[val.sin(), val.cos()]
}
let u_tab = (0..(self.u_divisions + 1))
.map(|x| sin_cos(((x % self.u_divisions) as f32) * u_angle))
.collect::<Vec<[f32; 2]>>();
let v_tab = (0..(self.v_divisions + 1))
.map(|x| sin_cos((x as f32) * v_angle))
.collect::<Vec<[f32; 2]>>();
let indices = [0, 1, 2, 2, 1, 3];
let normal_matrix = Matrix3::<f32>::from_cols(self.matrix.x.truncate(),
self.matrix.y.truncate(),
self.matrix.z.truncate())
.invert()
.unwrap_or(Matrix3::<f32>::identity())
.transpose();
let total_num_verts = self.num_vertices();
let mut vertices = Vec::<Vertex>::with_capacity(total_num_verts);
for v in 0..self.v_divisions {
for u in 0..self.u_divisions {
let verts = [Vector3::<f32>::new(u_tab[u + 1][1] * v_tab[v][0],
v_tab[v][1],
u_tab[u + 1][0] * v_tab[v][0]),
Vector3::<f32>::new(u_tab[u + 1][1] * v_tab[v + 1][0],
v_tab[v + 1][1],
u_tab[u + 1][0] * v_tab[v + 1][0]),
Vector3::<f32>::new(u_tab[u][1] * v_tab[v][0],
v_tab[v][1],
u_tab[u][0] * v_tab[v][0]),
Vector3::<f32>::new(u_tab[u][1] * v_tab[v + 1][0],
v_tab[v + 1][1],
u_tab[u][0] * v_tab[v + 1][0])];
let lut_coords = [(u + 1, v), (u + 1, v + 1), (u, v), (u, v + 1)];
let (offset, count) = if v == 0 {
(3, 3)
} else if v == self.v_divisions - 1 {
(0, 3)
} else {
(0, 6)
};
let v0 = &verts[indices[offset + 0]];
let v1 = &verts[indices[offset + 1]];
let v2 = &verts[indices[offset + 2]];
let normal = (v1 - v0).cross(v2 - v0).normalize();
for index in offset..offset + count {
let vpos = &verts[indices[index]];
let pos = self.matrix * vpos.extend(1.0);
let (u, v) = lut_coords[indices[index]];
vertices.push(Vertex {
position: Point3::<f32>::from_homogeneous(pos).into(),
normal: (normal_matrix * normal).normalize().into(),
texcoord: [u as f32 / self.u_divisions as f32,
v as f32 / self.v_divisions as f32],
});
}
}
}
assert!(vertices.len() == total_num_verts);
return Ok(vertices);
}
pub fn num_caps(&self) -> usize {
2
}
pub fn num_vertices_per_cap_face(&self) -> usize {
3
}
pub fn num_vertices_per_cap(&self) -> usize {
self.num_vertices_per_cap_face() * self.u_divisions
}
pub fn num_slices(&self) -> usize {
self.v_divisions - self.num_caps()
}
pub fn num_vertices_per_slice_face(&self) -> usize {
6
}
pub fn num_vertices_per_slice(&self) -> usize {
self.num_vertices_per_slice_face() * self.u_divisions
}
pub fn num_vertices(&self) -> usize {
(self.num_vertices_per_slice() * self.num_slices()) +
(self.num_vertices_per_cap() * self.num_caps())
}
}
#[test]
pub fn ensure_default_sphere_is_unit_sphere() {
let vertices = SphereBuilder::new()
.build_vertices()
.expect("Failed to build vertices");
for ref vertex in vertices {
assert_ulps_eq!(Vector3::<f32>::from(vertex.position).magnitude(), 1.0);
}
}
#[test]
pub fn ensure_default_sphere_has_centroid_at_origin() {
let vertices = SphereBuilder::new()
.build_vertices()
.expect("Failed to build vertices");
let mut sum = Vector3::<f32>::zero();
for ref vertex in vertices {
sum = sum + Vector3::<f32>::from(vertex.position);
}
assert_ulps_eq!(sum, Vector3::<f32>::zero(), epsilon = 0.0001);
}
#[test]
pub fn ensure_default_sphere_has_outward_facing_normals() {
let vertices = SphereBuilder::new()
.scale(2.0, 2.0, 2.0)
.build_vertices()
.expect("Failed to build vertices");
for ref vertex in vertices {
let position = Vector3::<f32>::from(vertex.position);
let normal = Vector3::<f32>::from(vertex.normal);
let outside = position + normal;
assert!(outside.x.abs() >= position.x.abs());
assert!(outside.y.abs() >= position.y.abs());
assert!(outside.z.abs() >= position.z.abs());
}
}
#[test]
pub fn ensure_default_sphere_has_uvs_in_unit_range() {
let vertices = SphereBuilder::new()
.with_divisions(4, 4)
.build_vertices()
.expect("Failed to build vertices");
for ref vertex in vertices {
assert!(vertex.texcoord[0] >= 0.0);
assert!(vertex.texcoord[1] >= 0.0);
assert!(vertex.texcoord[0] <= 1.0);
assert!(vertex.texcoord[1] <= 1.0);
}
}
#[test]
pub fn ensure_default_sphere_has_ccw_triangles() {
let vertices = SphereBuilder::new()
.build_vertices()
.expect("Failed to build vertices");
for chunk in vertices.chunks(3) {
let v0 = Vector3::<f32>::from(chunk[0].position);
let v1 = Vector3::<f32>::from(chunk[1].position);
let v2 = Vector3::<f32>::from(chunk[2].position);
let eyepos = v0 + Vector3::<f32>::from(chunk[0].normal);
let e0 = v1 - v0;
let e1 = v2 - v0;
let n = e0.cross(e1);
assert!(n.dot(v0 - eyepos) <= 0.0);
assert!(n.dot(v1 - eyepos) <= 0.0);
assert!(n.dot(v2 - eyepos) <= 0.0);
}
}
#[test]
pub fn ensure_default_sphere_has_faceted_normals() {
let vertices = SphereBuilder::new()
.build_vertices()
.expect("Failed to build vertices");
for chunk in vertices.chunks(3) {
let v0 = Vector3::<f32>::from(chunk[0].position);
let v1 = Vector3::<f32>::from(chunk[1].position);
let v2 = Vector3::<f32>::from(chunk[2].position);
let n0 = Vector3::<f32>::from(chunk[0].normal);
let n1 = Vector3::<f32>::from(chunk[1].normal);
let n2 = Vector3::<f32>::from(chunk[2].normal);
let e0 = v1 - v0;
let e1 = v2 - v0;
let n = e0.cross(e1).normalize();
assert_ulps_eq!(n, n0, epsilon = 0.0001);
assert_ulps_eq!(n, n1, epsilon = 0.0001);
assert_ulps_eq!(n, n2, epsilon = 0.0001);
}
}
#[test]
pub fn ensure_default_sphere_has_planar_quads() {
let builder = SphereBuilder::new();
let vertices = builder.build_vertices()
.expect("Failed to build vertices");
let mut index = builder.num_vertices_per_cap();
for _ in 0..builder.num_slices() {
for _ in 0..builder.num_vertices_per_slice() / 6 {
let tri0 = [Vector3::<f32>::from(vertices[index + 0].position),
Vector3::<f32>::from(vertices[index + 1].position),
Vector3::<f32>::from(vertices[index + 2].position)];
let tri1 = [Vector3::<f32>::from(vertices[index + 3].position),
Vector3::<f32>::from(vertices[index + 4].position),
Vector3::<f32>::from(vertices[index + 5].position)];
index += 6;
let n0 = (tri0[1] - tri0[0]).cross(tri0[2] - tri0[0]).normalize();
let n1 = (tri1[1] - tri1[0]).cross(tri1[2] - tri1[0]).normalize();
assert_ulps_eq!(n0, n1, epsilon = 0.0001);
}
}
}