From f3440e2bc3a3ad6b4eeadbc4d395076af7f27d70 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 19 Dec 2020 00:53:56 +0000 Subject: [PATCH 01/37] Began refactor --- .gitignore | 3 + Cargo.toml | 13 +- examples/spinning_cube.rs | 148 ++++++++++---------- examples/teapot.rs | 84 ++++++------ examples/triangle.rs | 47 ++++--- src/buffer.rs | 94 +++++++++++++ src/buffer/mod.rs | 68 ---------- src/interpolate.rs | 119 ---------------- src/lib.rs | 121 ++--------------- src/math.rs | 64 +++++++++ src/pipeline.rs | 237 ++++++++++++++++++++++++++++++++ src/rasterizer/lines.rs | 132 ------------------ src/rasterizer/mod.rs | 78 ++++------- src/rasterizer/triangles.rs | 262 +++++++++++++++--------------------- src/sampler/mod.rs | 82 ++++++++++- src/texture.rs | 144 ++++++++++++++------ 16 files changed, 873 insertions(+), 823 deletions(-) create mode 100644 src/buffer.rs delete mode 100644 src/buffer/mod.rs delete mode 100644 src/interpolate.rs create mode 100644 src/math.rs create mode 100644 src/pipeline.rs delete mode 100644 src/rasterizer/lines.rs diff --git a/.gitignore b/.gitignore index 6936990..bbfd2ba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ /target **/*.rs.bk Cargo.lock + +perf.* +framegraph.svg diff --git a/Cargo.toml b/Cargo.toml index b9a0a5a..9a2daf5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,19 +14,22 @@ exclude = [ ] [dependencies] -num-traits = { version = "0.2.11", default-features = false, optional = true } -vek = { version = "0.12.1", default-features = false, features = ["rgb", "rgba"] } +num-traits = { version = "0.2", default-features = false, optional = true } +vek = { version = "0.12", default-features = false, features = [] } [features] -default = ["std"] +default = ["std", "simd"] std = ["vek/std"] libm = ["vek/libm", "num-traits"] +nightly = [] +simd = ["vek/repr_simd"] [dev-dependencies] -minifb = "0.19.1" +mini_gl_fb = "0.7.0" tobj = "2.0.2" -criterion = "0.3" +criterion = "0.3.3" image = "0.23.12" +vek = "0.12.1" [lib] bench = false diff --git a/examples/spinning_cube.rs b/examples/spinning_cube.rs index 9691d2f..67e5282 100644 --- a/examples/spinning_cube.rs +++ b/examples/spinning_cube.rs @@ -1,23 +1,28 @@ -use euc::{buffer::Buffer2d, rasterizer, Pipeline, Target}; use vek::*; +use euc::{ + buffer2::Buffer2d, + pipeline2::Pipeline, + texture::{Empty, Target}, + rasterizer2, + DepthStrategy, +}; -struct Cube<'a> { +struct Cube { mvp: Mat4, - positions: &'a [Vec4], } -impl<'a> Pipeline for Cube<'a> { - type Vertex = (usize, Rgba); - type VsOut = Rgba; - type Pixel = u32; +impl Pipeline for Cube { + type Vertex = (usize, Vec4); + type VsOut = Vec4; + type Fragment = u32; #[inline(always)] - fn vert(&self, (v_index, v_color): &Self::Vertex) -> ([f32; 4], Self::VsOut) { - ((self.mvp * self.positions[*v_index]).into_array(), *v_color) + fn vertex_shader(&self, (v_index, v_color): &Self::Vertex) -> ([f32; 4], Self::VsOut) { + ((self.mvp * VERTICES[*v_index]).into_array(), *v_color) } #[inline(always)] - fn frag(&self, v_color: &Self::VsOut) -> Self::Pixel { + fn fragment_shader(&self, v_color: Self::VsOut) -> Self::Fragment { let bytes = v_color.map(|e| (e * 255.0) as u8).into_array(); (bytes[2] as u32) << 0 | (bytes[1] as u32) << 8 @@ -29,16 +34,53 @@ impl<'a> Pipeline for Cube<'a> { const W: usize = 640; const H: usize = 480; +const VERTICES: &[Vec4] = &[ + Vec4::new(-1.0, -1.0, -1.0, 1.0), + Vec4::new(-1.0, -1.0, 1.0, 1.0), + Vec4::new(-1.0, 1.0, -1.0, 1.0), + Vec4::new(-1.0, 1.0, 1.0, 1.0), + Vec4::new( 1.0, -1.0, -1.0, 1.0), + Vec4::new( 1.0, -1.0, 1.0, 1.0), + Vec4::new( 1.0, 1.0, -1.0, 1.0), + Vec4::new( 1.0, 1.0, 1.0, 1.0), +]; + +const RED: Vec4 = Vec4::new(1.0, 0.0, 0.0, 1.0); +const GREEN: Vec4 = Vec4::new(0.0, 1.0, 0.0, 1.0); +const BLUE: Vec4 = Vec4::new(0.0, 0.0, 1.0, 1.0); + +const INDICES: &[(usize, Vec4)] = &[ + // -x + (0, GREEN), (3, BLUE ), (2, RED ), + (0, GREEN), (1, RED ), (3, BLUE ), + // +x + (7, BLUE ), (4, GREEN), (6, RED ), + (5, RED ), (4, GREEN), (7, BLUE ), + // -y + (5, BLUE ), (0, RED ), (4, GREEN), + (1, GREEN), (0, RED ), (5, BLUE ), + // +y + (2, RED ), (7, BLUE ), (6, GREEN), + (2, RED ), (3, GREEN), (7, BLUE ), + // -z + (0, RED ), (6, GREEN), (4, BLUE ), + (0, RED ), (2, BLUE ), (6, GREEN), + // +z + (7, GREEN), (1, RED ), (5, BLUE ), + (3, BLUE ), (1, RED ), (7, GREEN), +]; + fn main() { - let mut color = Buffer2d::new([W, H], 0); - let mut depth = Buffer2d::new([W, H], 1.0); + let mut color = Buffer2d::fill([W, H], 0); + let mut depth = Buffer2d::fill([W, H], 1.0); - let mut win = minifb::Window::new("Cube", W, H, minifb::WindowOptions::default()).unwrap(); + let mut win = mini_gl_fb::gotta_go_fast("Cube", W as f64, H as f64); - for i in 0.. { + let mut i = 0; + win.glutin_handle_basic_input(|win, input| { let mvp = Mat4::perspective_fov_rh_no(1.3, W as f32, H as f32, 0.01, 100.0) * Mat4::translation_3d(Vec3::new(0.0, 0.0, -2.0)) - * Mat4::::scaling_3d(0.4) + * Mat4::::scaling_3d(0.6) * Mat4::rotation_x((i as f32 * 0.002).sin() * 8.0) * Mat4::rotation_y((i as f32 * 0.004).cos() * 4.0) * Mat4::rotation_z((i as f32 * 0.008).sin() * 2.0); @@ -46,72 +88,18 @@ fn main() { color.clear(0); depth.clear(1.0); - Cube { - mvp, - positions: &[ - Vec4::new(-1.0, -1.0, -1.0, 1.0), // 0 - Vec4::new(-1.0, -1.0, 1.0, 1.0), // 1 - Vec4::new(-1.0, 1.0, -1.0, 1.0), // 2 - Vec4::new(-1.0, 1.0, 1.0, 1.0), // 3 - Vec4::new(1.0, -1.0, -1.0, 1.0), // 4 - Vec4::new(1.0, -1.0, 1.0, 1.0), // 5 - Vec4::new(1.0, 1.0, -1.0, 1.0), // 6 - Vec4::new(1.0, 1.0, 1.0, 1.0), // 7 - ], - } - .draw::, _>( - &[ - // -x - (0, Rgba::green()), - (3, Rgba::blue()), - (2, Rgba::red()), - (0, Rgba::green()), - (1, Rgba::red()), - (3, Rgba::blue()), - // +x - (7, Rgba::blue()), - (4, Rgba::green()), - (6, Rgba::red()), - (5, Rgba::red()), - (4, Rgba::green()), - (7, Rgba::blue()), - // -y - (5, Rgba::blue()), - (0, Rgba::red()), - (4, Rgba::green()), - (1, Rgba::green()), - (0, Rgba::red()), - (5, Rgba::blue()), - // +y - (2, Rgba::red()), - (7, Rgba::blue()), - (6, Rgba::green()), - (2, Rgba::red()), - (3, Rgba::green()), - (7, Rgba::blue()), - // -z - (0, Rgba::red()), - (6, Rgba::green()), - (4, Rgba::blue()), - (0, Rgba::red()), - (2, Rgba::blue()), - (6, Rgba::green()), - // +z - (7, Rgba::green()), - (1, Rgba::red()), - (5, Rgba::blue()), - (3, Rgba::blue()), - (1, Rgba::red()), - (7, Rgba::green()), - ], + Cube { mvp }.render( + rasterizer2::Triangles, + INDICES, &mut color, - Some(&mut depth), + &mut depth, ); - if win.is_open() { - win.update_with_buffer(color.as_ref(), W, H).unwrap(); - } else { - break; - } - } + win.update_buffer(color.raw()); + win.redraw(); + + i += 1; + + true + }); } diff --git a/examples/teapot.rs b/examples/teapot.rs index 9fc3360..98099a1 100644 --- a/examples/teapot.rs +++ b/examples/teapot.rs @@ -1,6 +1,6 @@ -use euc::{buffer::Buffer2d, rasterizer, Pipeline, Target}; use std::path::Path; use vek::*; +use euc::{Pipeline, Buffer2d, Target, DepthMode, Triangles}; struct Teapot<'a> { mvp: Mat4, @@ -10,40 +10,37 @@ struct Teapot<'a> { } impl<'a> Pipeline for Teapot<'a> { - type Vertex = u32; // Vertex index - type VsOut = Vec3; // Normal - type Pixel = u32; // BGRA + type Vertex = usize; // Vertex index + type VsOut = Rgba; // Color + type Fragment = u32; // BGRA - #[inline(always)] - fn vert(&self, v_index: &Self::Vertex) -> ([f32; 4], Self::VsOut) { - let v_index = *v_index as usize; - // Find vertex position - let v_pos = self.positions[v_index] + Vec3::new(0.0, -0.5, 0.0); // Offset to center the teapot - ( - // Calculate vertex position in camera space - (self.mvp * Vec4::from_point(v_pos)).into_array(), - // Find vertex normal - self.normals[v_index], - ) - } + fn depth_mode(&self) -> DepthMode { DepthMode::LESS_WRITE } #[inline(always)] - fn frag(&self, norm: &Self::VsOut) -> Self::Pixel { + fn vertex_shader(&self, index: &Self::Vertex) -> ([f32; 4], Self::VsOut) { + let pos = self.mvp * Vec4::from_point(self.positions[*index]); + let norm = self.normals[*index]; + let ambient = 0.2; let diffuse = norm.dot(self.light_dir).max(0.0) * 0.5; let specular = self .light_dir - .reflected(Vec3::from(self.mvp * Vec4::from(*norm)).normalized()) + .reflected(Vec3::from(self.mvp * Vec4::from(norm)).normalized()) .dot(-Vec3::unit_z()) .powf(20.0); - let light = ambient + diffuse + specular; + let color = (Rgba::new(1.0, 0.7, 0.1, 1.0) * light).clamped(Rgba::zero(), Rgba::one()); + (pos.into_array(), color) + } + + #[inline(always)] + fn fragment_shader(&self, color: Self::VsOut) -> Self::Fragment { let bytes = (color * 255.0).map(|e| e as u8).into_array(); - (bytes[2] as u32) << 0 + (bytes[0] as u32) << 0 | (bytes[1] as u32) << 8 - | (bytes[0] as u32) << 16 + | (bytes[2] as u32) << 16 | (bytes[3] as u32) << 24 } } @@ -52,18 +49,21 @@ const W: usize = 800; const H: usize = 600; fn main() { - let mut color = Buffer2d::new([W, H], 0); - let mut depth = Buffer2d::new([W, H], 1.0); - - let mut win = minifb::Window::new("Teapot", W, H, minifb::WindowOptions::default()).unwrap(); + let mut color = Buffer2d::fill([W, H], 0); + let mut depth = Buffer2d::fill([W, H], 1.0); let obj = tobj::load_obj(&Path::new("examples/data/teapot.obj"), false).unwrap(); - let indices = &obj.0[0].mesh.indices; + let indices = obj.0[0] + .mesh + .indices + .iter() + .map(|i| *i as usize) + .collect::>(); let positions = obj.0[0] .mesh .positions .chunks(3) - .map(|sl| Vec3::from_slice(sl)) + .map(|sl| Vec3::from_slice(sl) - Vec3::unit_y() * 0.5) // Center model .collect::>(); let normals = obj.0[0] .mesh @@ -72,13 +72,16 @@ fn main() { .map(|sl| Vec3::from_slice(sl)) .collect::>(); - for i in 0.. { + let mut win = mini_gl_fb::gotta_go_fast("Teapot", W as f64, H as f64); + + let mut i = 0; + win.glutin_handle_basic_input(|win, input| { let mvp = Mat4::perspective_fov_rh_no(1.3, W as f32, H as f32, 0.01, 100.0) * Mat4::translation_3d(Vec3::new(0.0, 0.0, -1.5)) * Mat4::::scaling_3d(0.8) - * Mat4::rotation_x((i as f32 * 0.002).sin() * 8.0) - * Mat4::rotation_y((i as f32 * 0.004).cos() * 4.0) - * Mat4::rotation_z((i as f32 * 0.008).sin() * 2.0); + * Mat4::rotation_x((i as f32 * 0.002) * 8.0) + * Mat4::rotation_y((i as f32 * 0.004) * 4.0) + * Mat4::rotation_z((i as f32 * 0.008) * 2.0); color.clear(0); depth.clear(1.0); @@ -89,12 +92,17 @@ fn main() { normals: &normals, light_dir: Vec3::new(1.0, 1.0, 1.0).normalized(), } - .draw::, _>(indices, &mut color, Some(&mut depth)); + .render( + Triangles, + indices.as_slice(), + &mut color, + &mut depth, + ); - if win.is_open() { - win.update_with_buffer(color.as_ref(), W, H).unwrap(); - } else { - break; - } - } + win.update_buffer(color.raw()); + win.redraw(); + + i += 1; + true + }); } diff --git a/examples/triangle.rs b/examples/triangle.rs index 63d827d..3d53f8a 100644 --- a/examples/triangle.rs +++ b/examples/triangle.rs @@ -1,34 +1,37 @@ -use euc::{buffer::Buffer2d, rasterizer, DepthStrategy, Pipeline}; +use euc::{ + buffer2::Buffer2d, + pipeline2::{Pipeline, CullMode, CoordinateMode}, + texture::Empty, + rasterizer2, + DepthStrategy, +}; +use vek::*; struct Triangle; impl Pipeline for Triangle { type Vertex = [f32; 4]; - type VsOut = (); - type Pixel = u32; + type VsOut = Vec2; + type Fragment = u32; + + fn cull_mode(&self) -> CullMode { CullMode::None } // Vertex shader // - Returns the 3D vertex location, and the VsOut value to be passed to the fragment shader #[inline(always)] - fn vert(&self, pos: &[f32; 4]) -> ([f32; 4], Self::VsOut) { - (*pos, ()) - } - - // Specify the depth buffer strategy used for each draw call - #[inline(always)] - fn get_depth_strategy(&self) -> DepthStrategy { - DepthStrategy::None + fn vertex_shader(&self, pos: &[f32; 4]) -> ([f32; 4], Self::VsOut) { + (*pos, Vec2::new(pos[0], pos[1])) } // Fragment shader // - Returns (in this case) a u32 #[inline(always)] - fn frag(&self, _: &Self::VsOut) -> Self::Pixel { - let bytes = [255, 0, 0, 255]; // Red + fn fragment_shader(&self, xy: Self::VsOut) -> Self::Fragment { + let bytes = [(xy.x * 255.0) as u8, (xy.y * 255.0) as u8, 0, 255]; // Red - (bytes[2] as u32) << 0 + (bytes[0] as u32) << 0 | (bytes[1] as u32) << 8 - | (bytes[0] as u32) << 16 + | (bytes[2] as u32) << 16 | (bytes[3] as u32) << 24 } } @@ -37,20 +40,20 @@ const W: usize = 640; const H: usize = 480; fn main() { - let mut color = Buffer2d::new([W, H], 0); + let mut color = Buffer2d::fill([W, H], 0); - Triangle.draw::, _>( + Triangle.render( + rasterizer2::Triangles, &[ [-1.0, -1.0, 0.0, 1.0], [1.0, -1.0, 0.0, 1.0], [0.0, 1.0, 0.0, 1.0], ], &mut color, - None, + Empty::default(), ); - let mut win = minifb::Window::new("Triangle", W, H, minifb::WindowOptions::default()).unwrap(); - while win.is_open() { - win.update_with_buffer(color.as_ref(), W, H).unwrap(); - } + let mut win = mini_gl_fb::gotta_go_fast("Triangle", W as f64, H as f64); + win.update_buffer(color.raw()); + win.persist(); } diff --git a/src/buffer.rs b/src/buffer.rs new file mode 100644 index 0000000..d9ff139 --- /dev/null +++ b/src/buffer.rs @@ -0,0 +1,94 @@ +use crate::texture::{Texture, Target}; +use alloc::vec::Vec; + +/// A generic 1-dimensional buffer that may be used as a texture. +pub type Buffer1d = Buffer; + +/// A generic 2-dimensional buffer that may be used both as a texture and as a render target. +pub type Buffer2d = Buffer; + +/// A generic 3-dimensional buffer that may be used as a texture. +pub type Buffer3d = Buffer; + +/// A generic 4-dimensional buffer that may be used as a texture. +pub type Buffer4d = Buffer; + +/// A generic N-dimensional buffer that may be used both as a texture and as a render target. +pub struct Buffer { + size: [usize; N], + items: Vec, +} + +impl Buffer { + /// Create a new buffer with the given size, filled with duplicates of the given element. + pub fn fill(size: [usize; N], item: T) -> Self where T: Clone { + Self::fill_with(size, || item.clone()) + } + + /// Create a new buffer with the given size, filled by calling the function for each element. + /// + /// If your type implements [`Clone`], use [`Buffer::fill`] instead. + pub fn fill_with T>(size: [usize; N], mut f: F) -> Self { + let mut len = 1usize; + (0..N).for_each(|i| len = len.checked_mul(size[i]).unwrap()); + Self { + size, + items: (0..len).map(|_| f()).collect(), + } + } + + /// Convert the given index into a linear index that can be used to index into the raw data of this buffer. + #[inline(always)] + pub fn linear_index(&self, index: [usize; N]) -> usize { + let mut idx = 0; + let mut factor = 1; + (0..N).for_each(|i| { + idx += index[i] * factor; + factor *= self.size[i]; + }); + idx + } + + /// View this buffer as a linear slice of elements. + pub fn raw(&self) -> &[T] { &self.items } + + /// View this buffer as a linear mutable slice of elements. + pub fn raw_mut(&mut self) -> &mut [T] { &mut self.items } +} + +impl Texture for Buffer { + type Index = usize; + + type Texel = T; + + #[inline(always)] + fn size(&self) -> [Self::Index; N] { self.size } + + #[inline(always)] + fn read(&self, index: [Self::Index; N]) -> Self::Texel { + self.items[self.linear_index(index)].clone() + } + + #[inline(always)] + unsafe fn read_unchecked(&self, index: [Self::Index; N]) -> Self::Texel { + self.items.get_unchecked(self.linear_index(index)).clone() + } +} + +impl Target for Buffer { + #[inline(always)] + fn write(&mut self, index: [usize; 2], texel: Self::Texel) { + let idx = self.linear_index(index); + self.items[idx] = texel; + } + + #[inline(always)] + unsafe fn write_unchecked(&mut self, index: [usize; 2], texel: Self::Texel) { + let idx = self.linear_index(index); + *self.items.get_unchecked_mut(idx) = texel; + } + + fn clear(&mut self, texel: Self::Texel) { + self.items.iter_mut().for_each(|item| *item = texel.clone()); + } +} diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs deleted file mode 100644 index 86f9d2f..0000000 --- a/src/buffer/mod.rs +++ /dev/null @@ -1,68 +0,0 @@ -use core::fmt; - -use alloc::vec::Vec; - -use crate::Target; - -/// A 2-dimensional buffer. -/// -/// This type may be used to contain colour data, depth data, or arbitrary pixel data. -#[derive(Clone)] -pub struct Buffer2d { - items: Vec, - size: [usize; 2], -} - -impl Buffer2d { - pub fn new([width, height]: [usize; 2], fill: T) -> Self { - Self { - items: vec![fill; width * height], - size: [width, height], - } - } -} - -impl Target for Buffer2d { - type Item = T; - - #[inline(always)] - fn size(&self) -> [usize; 2] { - self.size - } - - #[inline(always)] - unsafe fn set(&mut self, [x, y]: [usize; 2], item: Self::Item) { - let [width, _] = self.size; - *self.items.get_unchecked_mut(y * width + x) = item; - } - - #[inline(always)] - unsafe fn get(&self, [x, y]: [usize; 2]) -> Self::Item { - let [width, _] = self.size; - self.items.get_unchecked(y * width + x).clone() - } - - fn clear(&mut self, fill: Self::Item) { - for item in &mut self.items { - *item = fill.clone(); - } - } -} - -impl AsRef<[T]> for Buffer2d { - fn as_ref(&self) -> &[T] { - &self.items - } -} - -impl AsMut<[T]> for Buffer2d { - fn as_mut(&mut self) -> &mut [T] { - &mut self.items - } -} - -impl fmt::Debug for Buffer2d { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Buffer2d(dimensions: {:?})", self.size) - } -} diff --git a/src/interpolate.rs b/src/interpolate.rs deleted file mode 100644 index c95f33b..0000000 --- a/src/interpolate.rs +++ /dev/null @@ -1,119 +0,0 @@ -#[cfg(not(feature = "std"))] -use num_traits::Float; - -/// A trait used to enable types to be interpolated throughout the rasterization process -pub trait Interpolate { - /// Linearly scale two items of this type and sum them - fn lerp2(a: Self, b: Self, x: f32, y: f32) -> Self; - - /// Linearly scale three items of this type and sum them - fn lerp3(a: Self, b: Self, c: Self, x: f32, y: f32, z: f32) -> Self; -} - -// Default impls for certain types -macro_rules! impl_interpolate_for_primitive { - ($t:ty) => { - impl Interpolate for $t { - #[inline(always)] - fn lerp2(a: Self, b: Self, x: f32, y: f32) -> Self { - a.mul_add(x, b * y) - } - #[inline(always)] - fn lerp3(a: Self, b: Self, c: Self, x: f32, y: f32, z: f32) -> Self { - a.mul_add(x, b.mul_add(y, c * z)) - } - } - }; -} -macro_rules! impl_interpolate_for_complex { - ($t:ty) => { - impl Interpolate for $t { - #[inline(always)] - fn lerp2(a: Self, b: Self, x: f32, y: f32) -> Self { - //a * x + b * y - a.map2(b, |a, b| a.mul_add(x, b * y)) - } - #[inline(always)] - fn lerp3(a: Self, b: Self, c: Self, x: f32, y: f32, z: f32) -> Self { - //a * x + b * y + c * z - a.map2(b.map2(c, |b, c| b.mul_add(y, c * z)), |a, bc| { - a.mul_add(x, bc) - }) - } - } - }; -} -impl_interpolate_for_primitive!(f32); -impl_interpolate_for_complex!(vek::Vec2); -impl_interpolate_for_complex!(vek::Vec3); -impl_interpolate_for_complex!(vek::Vec4); -impl_interpolate_for_complex!(vek::Rgb); -impl_interpolate_for_complex!(vek::Rgba); - -impl Interpolate for (T, U) { - #[inline(always)] - fn lerp2(a: Self, b: Self, x: f32, y: f32) -> Self { - (T::lerp2(a.0, b.0, x, y), U::lerp2(a.1, b.1, x, y)) - } - - #[inline(always)] - fn lerp3(a: Self, b: Self, c: Self, x: f32, y: f32, z: f32) -> Self { - ( - T::lerp3(a.0, b.0, c.0, x, y, z), - U::lerp3(a.1, b.1, c.1, x, y, z), - ) - } -} - -impl Interpolate for (T, U, V) { - #[inline(always)] - fn lerp2(a: Self, b: Self, x: f32, y: f32) -> Self { - ( - T::lerp2(a.0, b.0, x, y), - U::lerp2(a.1, b.1, x, y), - V::lerp2(a.2, b.2, x, y), - ) - } - - #[inline(always)] - fn lerp3(a: Self, b: Self, c: Self, x: f32, y: f32, z: f32) -> Self { - ( - T::lerp3(a.0, b.0, c.0, x, y, z), - U::lerp3(a.1, b.1, c.1, x, y, z), - V::lerp3(a.2, b.2, c.2, x, y, z), - ) - } -} - -impl Interpolate for (T, U, V, W) { - #[inline(always)] - fn lerp2(a: Self, b: Self, x: f32, y: f32) -> Self { - ( - T::lerp2(a.0, b.0, x, y), - U::lerp2(a.1, b.1, x, y), - V::lerp2(a.2, b.2, x, y), - W::lerp2(a.3, b.3, x, y), - ) - } - - #[inline(always)] - fn lerp3(a: Self, b: Self, c: Self, x: f32, y: f32, z: f32) -> Self { - ( - T::lerp3(a.0, b.0, c.0, x, y, z), - U::lerp3(a.1, b.1, c.1, x, y, z), - V::lerp3(a.2, b.2, c.2, x, y, z), - W::lerp3(a.3, b.3, c.3, x, y, z), - ) - } -} - -impl Interpolate for () { - #[inline(always)] - fn lerp2(_: Self, _: Self, _: f32, _: f32) -> Self { - () - } - #[inline(always)] - fn lerp3(_: Self, _: Self, _: Self, _: f32, _: f32, _: f32) -> Self { - () - } -} diff --git a/src/lib.rs b/src/lib.rs index 546db94..55c5ad8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,120 +40,21 @@ #![no_std] -#[macro_use] +#![feature(min_const_generics, alloc_prelude, array_map)] + extern crate alloc; pub mod buffer; -pub mod interpolate; +pub mod math; +pub mod pipeline; +pub mod texture; pub mod rasterizer; +pub mod sampler; // Reexports -pub use self::{ - interpolate::Interpolate, - rasterizer::{DepthStrategy, Rasterizer}, +pub use crate::{ + buffer::{Buffer, Buffer1d, Buffer2d, Buffer3d, Buffer4d}, + pipeline::{Pipeline, DepthMode, CullMode, CoordinateMode}, + texture::{Texture, Target, Empty}, + rasterizer::Triangles, }; - -/// Represents the high-level structure of a rendering pipeline. -/// -/// Conventionally, uniform data is stores as state within the type itself. -/// -/// This governs the following things: -/// -/// - Vertex position and data calculation (computed by the vertex shader) -/// - Determining whether each polygon is 'backfacing', and optionally skipping it -/// - Rasterization (performed internally by `euc`) -/// - Comparing the fragment depth against the depth buffer to determine whether it is occluded, -/// and optionally skipping it -/// - Fragment output calculation (computed by the fragment shader) -/// -/// In the future, `euc` may extend its capabilities to include compute, geometry, and tesselation -/// shaders. -pub trait Pipeline -where - Self: Sized, -{ - /// The type of the vertex shader input data. - /// - /// This usually consists of the vertex's position, normal, colour, texture coordinates, and - /// other such per-vertex information. When vertex indexing is used, this tends to consist of - /// the vertex index. - type Vertex; - - /// The type of the data that gets passed on from the vertex shader to the fragment shader. - /// - /// This usually consists of the fragment's normal, colour, texture coordinates and other such - /// per-fragment information. - type VsOut: Clone + Interpolate; - - /// The type of emitted pixels. - /// - /// This type is emitted by the fragment shader and usually corresponds to the colour of the - /// pixel. - type Pixel: Clone; - - /// The vertex shader - fn vert(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VsOut); - - /// The fragment shader - fn frag(&self, vs_out: &Self::VsOut) -> Self::Pixel; - - /// A method used to determine what depth buffer strategy should be used when determining - /// fragment occlusion. - /// - /// This method will be called at minimum only once per draw call, but may be called an - /// arbitrary number of times. - #[inline(always)] - fn get_depth_strategy(&self) -> DepthStrategy { - DepthStrategy::IfLessWrite - } - - /// Perform a draw call with the given uniform data, vertex array, output target and supplement - /// type. - /// - /// The supplement type is commonly used to represent additional surfaces required by the - /// rasterizer, such as a depth buffer target. - fn draw>( - &self, - vertices: &[Self::Vertex], - target: &mut T, - supplement: ::Supplement, - ) { - R::draw::(self, vertices, target, supplement) - } -} - -/// Represents a 2-dimensional rendering target that can have pixel data read and written to it. -pub trait Target { - /// The type of items contained within this target. - type Item: Clone; - - /// Get the dimensions of the target. - fn size(&self) -> [usize; 2]; - - /// Set the item at the specified location in the target to the given item. The validity of the - /// location is not checked, and as such this method is marked `unsafe`. - unsafe fn set(&mut self, pos: [usize; 2], item: Self::Item); - - /// Get a copy of the item at the specified location in the target. The validity of the - /// location is not checked, and as such this method is marked `unsafe`. - unsafe fn get(&self, pos: [usize; 2]) -> Self::Item; - - /// Clear the target with copies of the specified item. - fn clear(&mut self, fill: Self::Item); -} - -impl Target for (T,) { - type Item = T; - - fn size(&self) -> [usize; 2] { - [1; 2] - } - - unsafe fn set(&mut self, _pos: [usize; 2], _item: Self::Item) {} - - unsafe fn get(&self, _pos: [usize; 2]) -> Self::Item { - Self::Item::default() - } - - fn clear(&mut self, _fill: Self::Item) {} -} diff --git a/src/math.rs b/src/math.rs new file mode 100644 index 0000000..01ab2db --- /dev/null +++ b/src/math.rs @@ -0,0 +1,64 @@ +pub trait Lerp { + fn lerp_unchecked(a: &Self, b: &Self, factor: &F) -> Self; +} + +impl Lerp for f32 { + #[inline(always)] + fn lerp_unchecked(a: &Self, b: &Self, factor: &f32) -> Self { factor.mul_add(*b - *a, *a) } +} +impl Lerp for f64 { + #[inline(always)] + fn lerp_unchecked(a: &Self, b: &Self, factor: &f64) -> Self { factor.mul_add(*b - *a, *a) } +} + +impl Lerp for [T; N] + where T: Lerp + Copy +{ + #[inline(always)] + fn lerp_unchecked(a: &Self, b: &Self, factor: &F) -> Self { + let mut out = *a; + (0..N).for_each(|i| out[i] = Lerp::lerp_unchecked(&out[i], &b[i], factor)); + out + } +} + +pub trait Clamp { + fn clamp(&self, min: &Self, max: &Self) -> Self; +} + +impl Clamp for f32 { + #[inline(always)] + fn clamp(&self, min: &f32, max: &f32) -> Self { self.max(*min).min(*max) } +} + +impl Clamp for f64 { + #[inline(always)] + fn clamp(&self, min: &f64, max: &f64) -> Self { self.max(*min).min(*max) } +} + +impl Clamp for [T; N] + where T: Clamp + Copy +{ + #[inline(always)] + fn clamp(&self, min: &Self, max: &Self) -> Self { + let mut out = *self; + (0..N).for_each(|i| out[i] = out[i].clamp(&min[i], &max[i])); + out + } +} + +/// Truncation of positive values to integers +pub trait Truncate { + fn truncate(self) -> T; + fn detruncate(x: T) -> Self; +} + +impl Truncate for f32 { fn truncate(self) -> u16 { self as u16 } fn detruncate(x: u16) -> Self { x as f32 } } +impl Truncate for f32 { fn truncate(self) -> u32 { self as u32 } fn detruncate(x: u32) -> Self { x as f32 } } +impl Truncate for f32 { fn truncate(self) -> u64 { self as u64 } fn detruncate(x: u64) -> Self { x as f32 } } +impl Truncate for f32 { fn truncate(self) -> usize { self as usize } fn detruncate(x: usize) -> Self { x as f32 } } + +impl Truncate for f64 { fn truncate(self) -> u16 { self as u16 } fn detruncate(x: u16) -> Self { x as f64 } } +impl Truncate for f64 { fn truncate(self) -> u32 { self as u32 } fn detruncate(x: u32) -> Self { x as f64 } } +impl Truncate for f64 { fn truncate(self) -> u64 { self as u64 } fn detruncate(x: u64) -> Self { x as f64 } } +impl Truncate for f64 { fn truncate(self) -> usize { self as usize } fn detruncate(x: usize) -> Self { x as f64 } } diff --git a/src/pipeline.rs b/src/pipeline.rs new file mode 100644 index 0000000..d14a2a4 --- /dev/null +++ b/src/pipeline.rs @@ -0,0 +1,237 @@ +use crate::{ + math::*, + texture::Target, + rasterizer::Rasterizer, +}; +use alloc::collections::VecDeque; +use core::{ + cmp::Ordering, + ops::{Add, Mul}, +}; + +/// Defines how a [`Pipeline`] will interact with the depth target. +pub struct DepthMode { + /// The test, if any, that occurs when comparing the depth of the new fragment with that of the current depth. + pub test: Option, + /// Whether the fragment's depth should be written to the depth target if the test was passed. + pub write: bool, +} + +impl DepthMode { + pub const NONE: Self = Self { + test: None, + write: false, + }; + + pub const LESS_WRITE: Self = Self { + test: Some(Ordering::Less), + write: true, + }; + + pub const GREATER_WRITE: Self = Self { + test: Some(Ordering::Greater), + write: true, + }; + + pub const LESS_PASS: Self = Self { + test: Some(Ordering::Less), + write: false, + }; + + pub const GREATER_PASS: Self = Self { + test: Some(Ordering::Greater), + write: false, + }; +} + +impl Default for DepthMode { + fn default() -> Self { + Self::LESS_WRITE + } +} + +impl DepthMode { + /// Determine whether the depth mode needs to interact with the depth target at all. + pub fn uses_depth(&self) -> bool { + self.test.is_some() || self.write + } +} + +/// The face culling strategy used during rendering. +pub enum CullMode { + /// Do not cull triangles regardless of their winding order + None, + /// Cull clockwise triangles + Back, + /// Cull counter-clockwise triangles + Front, +} + +impl Default for CullMode { + fn default() -> Self { + CullMode::Back + } +} + +/// The coordinate space used during rasterization. +pub enum CoordinateMode { + /// right = +x, up = +y, out = -z (used by OpenGL and DirectX), default + Right, + /// right = +x, up = -y, out = -z (used by Vulkan) + Left, +} + +impl Default for CoordinateMode { + fn default() -> Self { + CoordinateMode::Right + } +} + +/// A type that may be used to produce a stream of vertices. +pub trait VertexStream<'a> { + type Vertex: 'a; + type Iter: Iterator; + + fn vertices(self) -> Self::Iter; +} + +impl<'a, V> VertexStream<'a> for core::slice::Iter<'a, V> { + type Vertex = V; + type Iter = Self; + + fn vertices(self) -> Self::Iter { self } +} + +impl<'a, V> VertexStream<'a> for &'a [V] { + type Vertex = V; + type Iter = core::slice::Iter<'a, V>; + + fn vertices(self) -> Self::Iter { self.iter() } +} + +impl<'a, V, const N: usize> VertexStream<'a> for &'a [V; N] { + type Vertex = V; + type Iter = core::slice::Iter<'a, V>; + + fn vertices(self) -> Self::Iter { self.iter() } +} + +/// Represents the high-level structure of a rendering pipeline. +/// +/// Conventionally, uniform data is stores as state within the pipeline itself. +/// +/// Additional methods such as [`Pipeline::depth_mode`], [Pipeline::`cull_mode`], etc. may be implemented to customize +/// the behaviour of the pipeline even further. +pub trait Pipeline: Sized { + type Vertex; + type VsOut: Clone + Mul + Add; + type Fragment; + + /// Returns the [`DepthMode`] of this pipeline. + fn depth_mode(&self) -> DepthMode { DepthMode::NONE } + + /// Returns the [`CullMode`] of this pipeline. + fn cull_mode(&self) -> CullMode { CullMode::default() } + + /// Returns the [`CoordinateMode`] of this pipeline. + fn coordinate_mode(&self) -> CoordinateMode { CoordinateMode::default() } + + /// Transforms a [`Pipeline::Vertex`] into homogeneous NDCs (Normalised Device Coordinates) for the vertex and a + /// [`Pipeline::VsOut`] to be interpolated and passed to the fragment shader. + fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VsOut); + + /// Intercepts the [Pipeline::vertex_shader] and emit additional vertices into the pipeline on the fly. + /// + /// This function will be repeatedly called until there are no more vertex inputs to receive. For this reason, it + /// should never fail to pull at least one vertex from the `input` iterator. + fn geometry_shader(&self, input: I, output: O) + where + I: Iterator, + O: FnMut(([f32; 4], Self::VsOut)), + { + input.for_each(output); + } + + /// Transforms a [`Pipeline::VsOut`] into a fragment to be rendered to a pixel target. + fn fragment_shader(&self, vs_out: Self::VsOut) -> Self::Fragment; + + /// Blend an old fragment with a new fragment. + /// + /// The default implementation simply returns the new fragment and ignores the old one. However, this may be used + /// to implement techniques such as alpha blending. + fn blend_shader(&self, a: Self::Fragment, b: Self::Fragment) -> Self::Fragment { b } + + /// Render a stream of vertices to given provided pixel target and depth target using the rasterizer. + /// + /// **Do not implement this method** + fn render<'a, R, V, T, D>( + &'a self, + rasterizer: R, + vertex_stream: V, + mut pixels: T, + mut depth: D, + ) + where + R: Rasterizer + 'a, + V: VertexStream<'a, Vertex = Self::Vertex>, + T: Target + 'a, + D: Target + 'a, + { + let depth_mode = self.depth_mode(); + let target_size = pixels.size(); + let principal_x = depth.principal_axis() == 0; + + // Ensure that the pixel target and depth target are compatible (but only if we need to actually use the depth + // target). + if depth_mode.uses_depth() { + assert_eq!(target_size, depth.size(), "Depth target size is compatible with the size of other target(s)"); + } + + let mut vertices = vertex_stream.vertices().map(|v| self.vertex_shader(v)).peekable(); + let mut vert_queue = VecDeque::new(); + let fetch_vertex = move || { + loop { + match vert_queue.pop_front() { + Some(v) => break Some(v), + None if vertices.peek().is_none() => break None, + None => self.geometry_shader(&mut vertices, |v| vert_queue.push_back(v)), + } + } + }; + + let emit_fragment = move |pos, w: &[f32], vs_out: &[Self::VsOut], z: f32| { + // Should we attempt to render the fragment at all? + let render_fragment = if let Some(test) = depth_mode.test { + let old_z = unsafe { depth.read_unchecked(pos) }; + z.partial_cmp(&old_z) == Some(test) + } else { + true + }; + + if render_fragment { + let vs_out_lerped = w[1..] + .iter() + .zip(vs_out[1..].iter()) + .fold(vs_out[0].clone() * w[0], |acc, (w, vs_out)| acc + vs_out.clone() * *w); + let frag = self.fragment_shader(vs_out_lerped); + let old_px = unsafe { pixels.read_unchecked(pos) }; + let blended_px = self.blend_shader(old_px, frag); + unsafe { pixels.write_unchecked(pos, blended_px); } + + if depth_mode.write { + unsafe { depth.write_unchecked(pos, z); } + } + } + }; + + unsafe { + rasterizer.rasterize( + self, + core::iter::from_fn(fetch_vertex), + target_size, + principal_x, + emit_fragment, + ); + } + } +} diff --git a/src/rasterizer/lines.rs b/src/rasterizer/lines.rs deleted file mode 100644 index 54e8a1f..0000000 --- a/src/rasterizer/lines.rs +++ /dev/null @@ -1,132 +0,0 @@ -use super::*; -use crate::{Interpolate, Pipeline, Target}; -use core::marker::PhantomData; -use vek::*; - -/// A rasterizer that produces straight lines from groups of 2 consecutive vertices. -pub struct Lines<'a, D> { - phantom: PhantomData<&'a D>, -} - -impl<'a, D: Target> Rasterizer for Lines<'a, D> { - type Input = [f32; 3]; // Vertex coordinates - type Supplement = Option<&'a mut D>; // Depth buffer - - fn draw>( - pipeline: &P, - vertices: &[P::Vertex], - target: &mut T, - mut depth: Self::Supplement, - ) { - if let Some(depth) = depth.as_ref() { - assert_eq!( - target.size(), - depth.size(), - "Target and depth buffers are not similarly sized!" - ); - } - - let size = Vec2::from(target.size()); - let half_scr = size.map(|e: usize| e as f32 * 0.5); - const MIRROR: Vec2 = Vec2 { x: 1.0, y: -1.0 }; - - vertices.chunks_exact(2).for_each(|verts| { - // Compute vertex shader outputs - let (a_hom, a_vs_out) = pipeline.vert(&verts[0]); - let (b_hom, b_vs_out) = pipeline.vert(&verts[1]); - - // Convert homogenous to euclidean coordinates - let a = Vec3::new(a_hom[0], a_hom[1], a_hom[2]) / a_hom[3]; - let b = Vec3::new(b_hom[0], b_hom[1], b_hom[2]) / b_hom[3]; - - // Convert to framebuffer coordinates - let a_scr = half_scr * (Vec2::from(a) * MIRROR + 1.0); - let b_scr = half_scr * (Vec2::from(b) * MIRROR + 1.0); - - let pa: Vec4 = Vec4::from(a_hom); - let pb: Vec4 = Vec4::from(b_hom); - let ab: Vec4 = pb - pa; - - let a_px = a_scr.map(|e| e as i32); - let b_px = b_scr.map(|e| e as i32); - - let min = Vec2::::min(a_px, b_px); - let max = Vec2::::max(a_px, b_px); - - if (max.x - min.x) > (max.y - min.y) { - let (l_scr, r_scr) = if a_scr.x < b_scr.x { - (a_scr, b_scr) - } else { - (b_scr, a_scr) - }; - let factor = 1.0 / (r_scr.x - l_scr.x); - let m = Mat2::new(pa.w, -pa.x, -ab.w, ab.x) / (ab.x * pa.w - pa.x * ab.w); - - for x in l_scr.x as i32..r_scr.x as i32 { - let y = l_scr.y + (x as f32 - l_scr.x) * factor * (r_scr.y - l_scr.y); - - // TODO: This is really bad bounds test code, fix this - if x < 0 || (x as usize) >= size.x || y < 0.0 || (y as usize) >= size.y { - continue; - } - - // Calculate the interpolated depth of this fragment - let s_hom = m * Vec2::new(2.0 * x as f32 / size.x as f32 - 1.0, 1.0); - let s = s_hom.x / s_hom.y; - let z_lerped = (pa.z + s * ab.z) * s_hom.y; - - let (x, y) = (x as usize, y as usize); - - // Depth test - if depth.as_ref().map(|depth| z_lerped <= unsafe { depth.get([x, y]) }).unwrap_or(true) { - // Calculate the interpolated vertex attributes of this fragment - let vs_out_lerped = - P::VsOut::lerp2(a_vs_out.clone(), b_vs_out.clone(), 1.0 - s, s); - - unsafe { - depth.as_mut().map(|depth| depth.set([x, y], z_lerped)); - target.set([x, y], pipeline.frag(&vs_out_lerped)); - } - } - } - } else { - let (l_scr, r_scr) = if a_scr.y < b_scr.y { - (a_scr, b_scr) - } else { - (b_scr, a_scr) - }; - - let factor = 1.0 / (r_scr.y - l_scr.y); - let m = Mat2::new(pa.w, -pa.y, -ab.w, ab.y) / (ab.y * pa.w - pa.y * ab.w); - - for y in l_scr.y as i32..r_scr.y as i32 { - let x = l_scr.x + (y as f32 - l_scr.y) * factor * (r_scr.x - l_scr.x); - - // TODO: This is really bad bounds test code, fix this - if x < 0.0 || (x as usize) >= size.x || y < 0 || (y as usize) >= size.y { - continue; - } - - // Calculate the interpolated depth of this fragment - let s_hom = m * Vec2::new(-2.0 * y as f32 / size.y as f32 + 1.0, 1.0); - let s = s_hom.x / s_hom.y; - let z_lerped = (pa.z + s * ab.z) * s_hom.y; - - let (x, y) = (x as usize, y as usize); - - // Depth test - if depth.as_ref().map(|depth| z_lerped < unsafe { depth.get([x, y]) }).unwrap_or(true) { - // Calculate the interpolated vertex attributes of this fragment - let vs_out_lerped = - P::VsOut::lerp2(a_vs_out.clone(), b_vs_out.clone(), 1.0 - s, s); - - unsafe { - depth.as_mut().map(|depth| depth.set([x, y], z_lerped)); - target.set([x, y], P::frag(pipeline, &vs_out_lerped)); - } - } - } - } - }); - } -} diff --git a/src/rasterizer/mod.rs b/src/rasterizer/mod.rs index cd97327..c73a561 100644 --- a/src/rasterizer/mod.rs +++ b/src/rasterizer/mod.rs @@ -1,59 +1,37 @@ -mod lines; -mod triangles; +pub mod triangles; -// Reexports -pub use self::lines::Lines; pub use self::triangles::Triangles; -use crate::{Pipeline, Target}; +use crate::Pipeline; -#[derive(Copy, Clone, Debug)] -pub enum DepthStrategy { - IfLessWrite, - IfMoreWrite, - IfLessNoWrite, - IfMoreNoWrite, - None, -} - -/// This trait is for internal use only. -pub trait BackfaceMode { - const ENABLED: bool; -} - -/// Implies that reversed polygons should not be culled from the rendering pipeline. -pub struct BackfaceCullingDisabled; - -impl BackfaceMode for BackfaceCullingDisabled { - const ENABLED: bool = false; -} - -/// Implies that reversed polygons should be culled from the rendering pipeline. -pub struct BackfaceCullingEnabled; - -impl BackfaceMode for BackfaceCullingEnabled { - const ENABLED: bool = true; -} - -/// Represents a rasterization algorithm. +/// A trait that represents types that turn vertex streams into fragment coordinates. +/// +/// Rasterizers take an iterator of vertices and emit fragment positions. They do not, by themselves, perform shader +/// execution, depth testing, etc. pub trait Rasterizer { - /// The type of input required during rasterization. + /// Rasterize the given vertices into fragments. /// - /// For most rasterization algorithms, this is the information that corresponds to a vertex - /// position. - type Input; - - /// The type of any supplementary data required by the rasterization algorithm. + /// - `target_size`: The size of the render target(s) in pixels + /// - `principal_x`: Whether the rasterizer should prefer the x axis as the principal iteration access (see + /// [`Texture::principle_axes`]) + /// - `emit_fragment`: The function that should be called with the target coordinate (in pixels), weights for each + /// vertex as a contribution to the final interpolated vertex output, the vertex outputs, and the depth of each + /// rasterized fragment. /// - /// Examples of supplementary data include depth buffers, stencil buffers, etc. - type Supplement; - - /// Rasterize the provided vertex data and write the resulting fragment information to the - /// target. - fn draw>( + /// # Safety + /// + /// `emit_fragment` must only be called with fragment positions that are valid for the `target_size` parameter + /// provided. Undefined behaviour can be assumed to occur if this is not upheld. + unsafe fn rasterize( + &self, pipeline: &P, - vertices: &[P::Vertex], - target: &mut T, - supplement: Self::Supplement, - ); + vertices: I, + target_size: [usize; 2], + principal_x: bool, + emit_fragment: F, + ) + where + P: Pipeline, + I: Iterator, + F: FnMut([usize; 2], &[f32], &[P::VsOut], f32); } diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index cd012f3..dffebd1 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -1,184 +1,132 @@ use super::*; -use crate::{Interpolate, Pipeline, Target}; -use core::marker::PhantomData; -#[cfg(not(feature = "std"))] -use num_traits::Float; -use vek::{Mat3, Vec2, Vec3}; - -/// A rasterizer that produces filled triangles from groups of 3 consecutive vertices. -/// -/// Use the BackfaceCullingEnabled type parameter to enable backface culling. -/// Use the BackfaceCullingDisabled type parameter to disable backface culling. -pub struct Triangles<'a, D, B: BackfaceMode = BackfaceCullingEnabled> { - phantom: PhantomData<(&'a D, B)>, -} +use crate::{CullMode, CoordinateMode}; +use vek::*; -impl<'a, D: Target, B: BackfaceMode> Rasterizer for Triangles<'a, D, B> { - type Input = [f32; 3]; // Vertex coordinates - type Supplement = Option<&'a mut D>; // Depth buffer +/// A rasterizer that produces filled triangles. +pub struct Triangles; - fn draw>( +impl Rasterizer for Triangles { + unsafe fn rasterize( + &self, pipeline: &P, - vertices: &[P::Vertex], - target: &mut T, - mut depth: Self::Supplement, - ) { - if let Some(depth) = depth.as_ref() { - assert_eq!( - target.size(), - depth.size(), - "Target and depth buffers are not similarly sized!" - ); - } + mut vertices: I, + target_size: [usize; 2], + principal_x: bool, + mut emit_fragment: F, + ) + where + P: Pipeline, + I: Iterator, + F: FnMut([usize; 2], &[f32], &[P::VsOut], f32), + { + let cull_dir = match pipeline.cull_mode() { + CullMode::None => None, + CullMode::Back => Some(1.0), + CullMode::Front => Some(-1.0), + }; + + let flip = match pipeline.coordinate_mode() { + CoordinateMode::Left => Vec2::new(1.0, 1.0), + CoordinateMode::Right => Vec2::new(1.0, -1.0), + }; - let size = Vec2::from(target.size()); - let half_scr = size.map(|e: usize| e as f32 * 0.5); + let size = Vec2::from(target_size).map(|e: usize| e as f32); let to_ndc = Mat3::from_row_arrays([ - [2.0 / size.x as f32, 0.0, -1.0], - [0.0, -2.0 / size.y as f32, 1.0], + [2.0 / size.x, 0.0, -1.0], + [0.0, -2.0 / size.y, 1.0], [0.0, 0.0, 1.0], ]); - let (depth_test, depth_less, depth_write) = match pipeline.get_depth_strategy() { - DepthStrategy::IfLessWrite => (true, true, true), - DepthStrategy::IfLessNoWrite => (true, true, false), - DepthStrategy::IfMoreWrite => (true, false, true), - DepthStrategy::IfMoreNoWrite => (true, false, false), - DepthStrategy::None => (false, false, false), - }; + loop { + let verts_hom_out = Vec3::new( + if let Some(v) = vertices.next() { v } else { break }, + if let Some(v) = vertices.next() { v } else { break }, + if let Some(v) = vertices.next() { v } else { break }, + ); + + // Calculate vertex shader outputs and vertex homogeneous coordinates + let verts_hom = Vec3::new(verts_hom_out.x.0, verts_hom_out.y.0, verts_hom_out.z.0).map(Vec4::::from); + let verts_out = Vec3::new(verts_hom_out.x.1, verts_hom_out.y.1, verts_hom_out.z.1); - vertices.chunks_exact(3).for_each(|verts| { - // Compute vertex shader outputs - let (a_hom, a_vs_out) = pipeline.vert(&verts[0]); - let (b_hom, b_vs_out) = pipeline.vert(&verts[1]); - let (c_hom, c_vs_out) = pipeline.vert(&verts[2]); + let verts_hom = verts_hom.map(|v| v * Vec4::new(flip.x, flip.y, 1.0, 1.0)); // Convert homogenous to euclidean coordinates - let a = Vec3::new(a_hom[0], a_hom[1], a_hom[2]) / a_hom[3]; - let b = Vec3::new(b_hom[0], b_hom[1], b_hom[2]) / b_hom[3]; - let c = Vec3::new(c_hom[0], c_hom[1], c_hom[2]) / c_hom[3]; - - // Backface culling - let ((a, a_hom, a_vs_out), (c, c_hom, c_vs_out)) = - // Back face? - if (b - a).cross(c - a).z < 0.0 { - // If backface culling is enabled, just return: we're done with this tri. - if B::ENABLED { - return; - } else { - // Reverse the vertex order - ((c, c_hom, c_vs_out), (a, a_hom, a_vs_out)) - } - } else { - // Maintain vertex order - ((a, a_hom, a_vs_out), (c, c_hom, c_vs_out)) - }; - - let fb_to_weights = { - let c = Vec3::new(c_hom[0], c_hom[1], c_hom[3]); - let ca = Vec3::new(a_hom[0], a_hom[1], a_hom[3]) - c; - let cb = Vec3::new(b_hom[0], b_hom[1], b_hom[3]) - c; + let verts_euc = verts_hom.map(|v_hom| v_hom.xyz() / v_hom.w); + + // Calculate winding direction to determine culling behaviour + let winding = (verts_euc.y - verts_euc.x).cross(verts_euc.z - verts_euc.x).z; + + // Culling and correcting for winding + let (verts_hom, verts_euc, verts_out) = if cull_dir + .map(|cull_dir| winding * cull_dir > 0.0) + .unwrap_or(false) + { + continue; // Cull the triangle + } else if winding < 0.0 { + // Reverse vertex order + (verts_hom.zyx(), verts_euc.zyx(), verts_out.zyx()) + } else { + (verts_hom, verts_euc, verts_out) + }; + + // Create a matrix that allows conversion between screen coordinates and interpolation weights + let coords_to_weights = { + let c = Vec3::new(verts_hom.z.x, verts_hom.z.y, verts_hom.z.w); + let ca = Vec3::new(verts_hom.x.x, verts_hom.x.y, verts_hom.x.w) - c; + let cb = Vec3::new(verts_hom.y.x, verts_hom.y.y, verts_hom.y.w) - c; let n = ca.cross(cb); let rec_det = if n.magnitude_squared() > 0.0 { 1.0 / n.dot(c) } else { 1.0 }; - // Compute matrix inverse - Mat3::from_row_arrays([ - cb.cross(c).into_array(), - c.cross(ca).into_array(), - n.into_array(), - ]) * rec_det - * to_ndc + + Mat3::from_row_arrays([cb.cross(c), c.cross(ca), n].map(|v| v.into_array())) * rec_det * to_ndc }; - debug_assert!(fb_to_weights.into_row_array().iter().all(|e| e.is_finite())); + // Ensure we didn't accidentally end up with infinities or NaNs + debug_assert!(coords_to_weights.into_row_array().iter().all(|e| e.is_finite())); - // Convert to framebuffer coordinates - let a_scr = half_scr - * Vec2 { - x: a.x + 1.0, - y: a.y.mul_add(-1.0, 1.0), - }; - let b_scr = half_scr - * Vec2 { - x: b.x + 1.0, - y: b.y.mul_add(-1.0, 1.0), - }; - let c_scr = half_scr - * Vec2 { - x: c.x + 1.0, - y: c.y.mul_add(-1.0, 1.0), - }; + // Convert vertex coordinates to screen space + let verts_screen = verts_euc.map(|euc| size * (euc.xy() * Vec2::new(0.5, -0.5) + 0.5)); + + // Calculate the triangle bounds as a bounding box + let tri_bounds = Aabr:: { + min: Vec2::max(verts_screen.reduce(|a, b| Vec2::partial_min(a, b)).as_(), Vec2::zero()), + max: Vec2::min(verts_screen.reduce(|a, b| Vec2::partial_max(a, b)).as_() + 1, Vec2::from(target_size) - 1), + }; - let a_px = a_scr.map(|e| e as i32); - let b_px = b_scr.map(|e| e as i32); - let c_px = c_scr.map(|e| e as i32); - - let min = a_px - .map2(b_px, |e, b| e.min(b)) - .map2(c_px, |e, c| e.min(c)) - .map(|e| e.max(0)) - .map2(size, |e, sz| (e).min(sz as i32) as usize); - let max = a_px - .map2(b_px, |e, b| e.max(b)) - .map2(c_px, |e, c| e.max(c)) - .map(|e| e.max(0)) - .map2(size, |e, sz| (e + 1).min(sz as i32) as usize); - - for y in min.y..max.y { - for x in min.x..max.x { - // Where is the centre of the fragment? - let p = Vec3::new(x as f32 + 0.5, y as f32 + 0.5, 1.0); - - // Calculate vertex weights - let weights_hom = fb_to_weights * p; - let wa = weights_hom.x / weights_hom.z; - let wb = weights_hom.y / weights_hom.z; - let wc = 1.0 - wa - wb; - - if (wa - 0.5).abs() > 0.5 || (wb - 0.5).abs() > 0.5 || (wc - 0.5).abs() > 0.5 { - continue; - } - - // Calculate the interpolated depth of this fragment - let z_lerped = - f32::lerp3(a_hom[2], b_hom[2], c_hom[2], wa, wb, wc) * weights_hom.z; - - let should_draw = if depth_test { - if depth_less { - depth.as_ref().map(|depth| z_lerped <= unsafe { depth.get([x, y]) }).unwrap_or(true) - } else { - depth.as_ref().map(|depth| z_lerped >= unsafe { depth.get([x, y]) }).unwrap_or(true) - } - } else { - true - }; - - if should_draw { - // Calculate the interpolated vertex attributes of this fragment - let vs_out_lerped = P::VsOut::lerp3( - a_vs_out.clone(), - b_vs_out.clone(), - c_vs_out.clone(), - wa, - wb, - wc, - ); - - unsafe { - // Write depth - if depth_write { - depth.as_mut().map(|depth| depth.set([x, y], z_lerped)); - } - - target.set([x, y], pipeline.frag(&vs_out_lerped)); - } - } + // Choose an iteration order based on the principal axis + let (xs, ys) = ( + (tri_bounds.min.x, tri_bounds.max.x), + (tri_bounds.min.y, tri_bounds.max.y), + ); + let coords = (if principal_x { ys.0..ys.1 } else { xs.0..xs.1 }) + .map(|j| (if principal_x { xs.0..xs.1 } else { ys.0..ys.1 }) + .map(move |i| if principal_x { (i, j) } else { (j, i) })) + .flatten(); + + // Iterate over fragment candidates within the triangle's bounding box + for (x, y) in coords { + // Calculate fragment center + let p = Vec3::new(x as f32 + 0.5, y as f32 + 0.5, 1.0); + + // Calculate vertex weights to determine vs_out lerping and intersection + let w_hom = coords_to_weights * p; + let w = Vec2::new(w_hom.x / w_hom.z, w_hom.y / w_hom.z); + let w = Vec3::new(w.x, w.y, 1.0 - w.x - w.y); + + // Test the weights to determine whether the fragment is outside the triangle + if w.map(|e| e < 0.0).reduce_or() { + continue; } + + // Calculate the interpolated z coordinate for the depth target + let z: f32 = verts_hom.map2(w, |v, w| v.z * w).sum() * w_hom.z; + + emit_fragment([x, y], w.as_slice(), verts_out.as_slice(), z); } - }); + } } } diff --git a/src/sampler/mod.rs b/src/sampler/mod.rs index 70b786d..f457a45 100644 --- a/src/sampler/mod.rs +++ b/src/sampler/mod.rs @@ -1 +1,81 @@ -// TODO +use crate::{ + texture::Texture, + math::*, +}; +use core::{ + ops::{Div, Deref}, + marker::PhantomData, +}; + +/// A trait implemented by texture samplers. +pub trait Sampler +where + Self::Index: Truncate<>::Index>, +{ + /// The type used to perform sampling. + type Index: Clone; + + // The type the sampler emits when sampled. + type Sample: Clone; + + /// The underlying texture accessed by this sampler. + type Texture: Texture + ?Sized; + + /// Access the underlying texture accessed by this sampler. + fn raw_texture(&self) -> &Self::Texture; + + /// Sample the texture at the given index. + /// + /// # Panics + /// + /// The behaviour of this function is *unspecified* (but not *undefined*) when the index is out of bounds. The + /// implementation is free to panic, or return any proper value. + fn sample(&self, index: [Self::Index; N]) -> Self::Sample; + + /// Sample the texture at the given assumed-valid index. + /// + /// # Safety + /// + /// If the index is invalid, undefined behaviour can be assumed to occur. Ensure that the index is valid before + /// use. + unsafe fn sample_unchecked(&self, index: [Self::Index; N]) -> Self::Sample { + self.sample(index) + } +} + +/// A sampler that uses nearest-neighbor sampling. +pub struct Nearest(T, PhantomData); + +impl Nearest { + /// Create a new + pub fn new(texture: T) -> Self { + Self(texture, PhantomData) + } +} + +impl<'a, T: Deref, I, const N: usize> Sampler for Nearest +where + T::Target: Texture, + I: Clone + Div + Truncate<>::Index>, +{ + type Index = I; + + type Sample = >::Texel; + + type Texture = T::Target; + + #[inline(always)] + fn raw_texture(&self) -> &Self::Texture { &self.0 } + + #[inline(always)] + fn sample(&self, mut index: [Self::Index; N]) -> Self::Sample { + let size = self.raw_texture().size(); + (0..N).for_each(|i| index[i] = index[i].clone() / I::detruncate(size[i].clone())); + self.raw_texture().read(index.map(|x| x.truncate())) + } + + #[inline(always)] + unsafe fn sample_unchecked(&self, index: [Self::Index; N]) -> Self::Sample { + self.raw_texture().read_unchecked(index.map(|x| x.truncate())) + } +} diff --git a/src/texture.rs b/src/texture.rs index 2eb4ea3..65db3b0 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -1,55 +1,117 @@ -use alloc::prelude::*; - -use crate::{ - Target, - Sample, - Interpolate, -}; - -/// A 2-dimensional texture. -/// -/// This type may be used to contain colour data, depth data, or arbitrary pixel data. -pub struct Texture2d { - items: Vec, - size: [usize; 2], -} - -impl Texture2d { - pub fn new(size: [usize; 2], fill: T) -> Self { - Self { - items: vec![fill; size[0] * size[1]], - size, - } +/// A trait implemented by types that may be treated as textures. +pub trait Texture { + /// The type used to index into the texture. + type Index: Clone; + + /// The type of texture elements. + type Texel: Clone; + + /// Get the size of the texture in texels. + /// + /// # Safety + /// + /// The function should report a correct size (i.e: a size for which all bounded indices are valid). While this is + /// not by itself a requirement for safe use, failure to do so may result in invalid texture indices being accessed + /// by users of the texture. + fn size(&self) -> [Self::Index; N]; + + /// Get the texture axis with highest contiguous access times. + /// + /// The ordering of textures in memory can have a very significant impact on the cost of accessing them. It is + /// typical for textures to be ordered first in rows (i.e: a principal x axis) and then columns but this is not + /// always the case. This function allows the texture to signal to users what access patterns are most performant. + /// + /// The default implementation is a principal axis of x, which corresponds to the most common in-memory texture layouts. + fn principal_axis(&self) -> usize { 0 } + + /// Read a texel at the given index. + /// + /// # Panics + /// + /// The behaviour of this function is *unspecified* (but not *undefined*) when the index is out of bounds. The + /// implementation is free to panic, return an entirely different texel, or return texel data not in the texture at + /// all. + fn read(&self, index: [Self::Index; N]) -> Self::Texel; + + /// Read a texel at the given assumed-valid index. + /// + /// # Safety + /// + /// If the index is invalid, undefined behaviour can be assumed to occur. Ensure that the index is valid before + /// use. + unsafe fn read_unchecked(&self, index: [Self::Index; N]) -> Self::Texel { + self.read(index) } } -impl Target for Texture2d { - type Item = T; +impl<'a, T: Texture, const N: usize> Texture for &'a T { + type Index = T::Index; + type Texel = T::Texel; + fn size(&self) -> [Self::Index; N] { (**self).size() } + fn read(&self, index: [Self::Index; N]) -> Self::Texel { (**self).read(index) } + unsafe fn read_unchecked(&self, index: [Self::Index; N]) -> Self::Texel { (**self).read_unchecked(index) } +} - #[inline(always)] - fn size(&self) -> [usize; 2] { - self.size - } +impl<'a, T: Texture, const N: usize> Texture for &'a mut T { + type Index = T::Index; + type Texel = T::Texel; + fn size(&self) -> [Self::Index; N] { (**self).size() } + fn read(&self, index: [Self::Index; N]) -> Self::Texel { (**self).read(index) } + unsafe fn read_unchecked(&self, index: [Self::Index; N]) -> Self::Texel { (**self).read_unchecked(index) } +} - #[inline(always)] - unsafe fn set(&mut self, pos: [usize; 2], item: Self::Item) { - *self.items.get_unchecked_mut(pos[1] * self.size[0] + pos[0]) = item; - } +/// A trait implemented by 2-dimensional textures that may be treated as render targets. +pub trait Target: Texture<2, Index = usize> { + /// Write a texel at the given index. + /// + /// # Panics + /// + /// The behaviour of this function is *unspecified* (but not *undefined*) when the index is out of bounds. The + /// implementation is free to panic, write to an entirely different texel, or do nothing. + fn write(&mut self, index: [usize; 2], texel: Self::Texel); - #[inline(always)] - unsafe fn get(&self, pos: [usize; 2]) -> Self::Item { - self.items.get_unchecked(pos[1] * self.size[0] + pos[0]).clone() + /// Write a texel at the given assumed-valid index. + /// + /// # Safety + /// + /// If the index is invalid, undefined behaviour can be assumed to occur. Ensure that the index is valid before + /// use. + unsafe fn write_unchecked(&mut self, index: [usize; 2], texel: Self::Texel) { + self.write(index, texel); } - fn clear(&mut self, fill: Self::Item) { - self.items = vec![fill; self.size[0] * self.size[1]]; + /// Clears the entire target with the given texel. + fn clear(&mut self, texel: Self::Texel) { + for y in 0..self.size()[1] { + for x in 0..self.size()[0] { + unsafe { self.write_unchecked([x, y], texel.clone()); } + } + } } } -impl Sample +impl<'a, T: Target> Target for &'a mut T { + fn write(&mut self, index: [usize; 2], texel: Self::Texel) { (**self).write(index, texel); } + unsafe fn write_unchecked(&mut self, index: [usize; 2], texel: Self::Texel) { (**self).write_unchecked(index, texel); } + fn clear(&mut self, texel: Self::Texel) { (**self).clear(texel); } +} + +/// An always-empty texture. Useful as a placeholder for an unused target. +pub struct Empty(core::marker::PhantomData); -impl AsRef<[T]> for Texture2d { - fn as_ref(&self) -> &[T] { - &self.items +impl Default for Empty { + fn default() -> Self { + Self(core::marker::PhantomData) } } + +impl Texture for Empty { + type Index = usize; + type Texel = J; + fn size(&self) -> [Self::Index; N] { [0; N] } + fn read(&self, _: [Self::Index; N]) -> Self::Texel { panic!("Cannot read from an empty texture"); } +} + +impl Target for Empty { + fn write(&mut self, _: [usize; 2], _: Self::Texel) { panic!("Cannot write to an empty texture"); } +} From 758cd112fdbb6a47f3f6b9d3ce2bc93fcc6a6994 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 19 Dec 2020 01:17:12 +0000 Subject: [PATCH 02/37] Removed VertexStream, moved CullMode, more docs --- examples/teapot.rs | 4 +-- src/buffer.rs | 1 + src/lib.rs | 10 +++++-- src/pipeline.rs | 59 +++++-------------------------------- src/rasterizer/mod.rs | 17 +++++++++++ src/rasterizer/triangles.rs | 7 +++-- 6 files changed, 39 insertions(+), 59 deletions(-) diff --git a/examples/teapot.rs b/examples/teapot.rs index 98099a1..261c58f 100644 --- a/examples/teapot.rs +++ b/examples/teapot.rs @@ -1,6 +1,6 @@ use std::path::Path; use vek::*; -use euc::{Pipeline, Buffer2d, Target, DepthMode, Triangles}; +use euc::{Pipeline, Buffer2d, Target, DepthMode, Triangles, CullMode}; struct Teapot<'a> { mvp: Mat4, @@ -93,7 +93,7 @@ fn main() { light_dir: Vec3::new(1.0, 1.0, 1.0).normalized(), } .render( - Triangles, + Triangles(CullMode::Back), indices.as_slice(), &mut color, &mut depth, diff --git a/src/buffer.rs b/src/buffer.rs index d9ff139..5592f76 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -14,6 +14,7 @@ pub type Buffer3d = Buffer; pub type Buffer4d = Buffer; /// A generic N-dimensional buffer that may be used both as a texture and as a render target. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Buffer { size: [usize; N], items: Vec, diff --git a/src/lib.rs b/src/lib.rs index 55c5ad8..2af7370 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,17 +44,23 @@ extern crate alloc; +/// N-dimensional buffers that may be used as textures and render targets. pub mod buffer; +/// Math-related functionality. pub mod math; +/// Pipeline definitions. pub mod pipeline; +/// Texture and target definitions. pub mod texture; +/// Rasterization algorithms. pub mod rasterizer; +/// Texture samplers. pub mod sampler; // Reexports pub use crate::{ buffer::{Buffer, Buffer1d, Buffer2d, Buffer3d, Buffer4d}, - pipeline::{Pipeline, DepthMode, CullMode, CoordinateMode}, + pipeline::{Pipeline, DepthMode, CoordinateMode}, texture::{Texture, Target, Empty}, - rasterizer::Triangles, + rasterizer::{Triangles, CullMode}, }; diff --git a/src/pipeline.rs b/src/pipeline.rs index d14a2a4..f996813 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -10,6 +10,7 @@ use core::{ }; /// Defines how a [`Pipeline`] will interact with the depth target. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct DepthMode { /// The test, if any, that occurs when comparing the depth of the new fragment with that of the current depth. pub test: Option, @@ -57,23 +58,8 @@ impl DepthMode { } } -/// The face culling strategy used during rendering. -pub enum CullMode { - /// Do not cull triangles regardless of their winding order - None, - /// Cull clockwise triangles - Back, - /// Cull counter-clockwise triangles - Front, -} - -impl Default for CullMode { - fn default() -> Self { - CullMode::Back - } -} - /// The coordinate space used during rasterization. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum CoordinateMode { /// right = +x, up = +y, out = -z (used by OpenGL and DirectX), default Right, @@ -87,35 +73,6 @@ impl Default for CoordinateMode { } } -/// A type that may be used to produce a stream of vertices. -pub trait VertexStream<'a> { - type Vertex: 'a; - type Iter: Iterator; - - fn vertices(self) -> Self::Iter; -} - -impl<'a, V> VertexStream<'a> for core::slice::Iter<'a, V> { - type Vertex = V; - type Iter = Self; - - fn vertices(self) -> Self::Iter { self } -} - -impl<'a, V> VertexStream<'a> for &'a [V] { - type Vertex = V; - type Iter = core::slice::Iter<'a, V>; - - fn vertices(self) -> Self::Iter { self.iter() } -} - -impl<'a, V, const N: usize> VertexStream<'a> for &'a [V; N] { - type Vertex = V; - type Iter = core::slice::Iter<'a, V>; - - fn vertices(self) -> Self::Iter { self.iter() } -} - /// Represents the high-level structure of a rendering pipeline. /// /// Conventionally, uniform data is stores as state within the pipeline itself. @@ -130,9 +87,6 @@ pub trait Pipeline: Sized { /// Returns the [`DepthMode`] of this pipeline. fn depth_mode(&self) -> DepthMode { DepthMode::NONE } - /// Returns the [`CullMode`] of this pipeline. - fn cull_mode(&self) -> CullMode { CullMode::default() } - /// Returns the [`CoordinateMode`] of this pipeline. fn coordinate_mode(&self) -> CoordinateMode { CoordinateMode::default() } @@ -173,7 +127,7 @@ pub trait Pipeline: Sized { ) where R: Rasterizer + 'a, - V: VertexStream<'a, Vertex = Self::Vertex>, + V: IntoIterator, T: Target + 'a, D: Target + 'a, { @@ -187,7 +141,7 @@ pub trait Pipeline: Sized { assert_eq!(target_size, depth.size(), "Depth target size is compatible with the size of other target(s)"); } - let mut vertices = vertex_stream.vertices().map(|v| self.vertex_shader(v)).peekable(); + let mut vertices = vertex_stream.into_iter().map(|v| self.vertex_shader(v)).peekable(); let mut vert_queue = VecDeque::new(); let fetch_vertex = move || { loop { @@ -201,18 +155,19 @@ pub trait Pipeline: Sized { let emit_fragment = move |pos, w: &[f32], vs_out: &[Self::VsOut], z: f32| { // Should we attempt to render the fragment at all? - let render_fragment = if let Some(test) = depth_mode.test { + let should_render = if let Some(test) = depth_mode.test { let old_z = unsafe { depth.read_unchecked(pos) }; z.partial_cmp(&old_z) == Some(test) } else { true }; - if render_fragment { + if should_render { let vs_out_lerped = w[1..] .iter() .zip(vs_out[1..].iter()) .fold(vs_out[0].clone() * w[0], |acc, (w, vs_out)| acc + vs_out.clone() * *w); + let frag = self.fragment_shader(vs_out_lerped); let old_px = unsafe { pixels.read_unchecked(pos) }; let blended_px = self.blend_shader(old_px, frag); diff --git a/src/rasterizer/mod.rs b/src/rasterizer/mod.rs index c73a561..2f21ff1 100644 --- a/src/rasterizer/mod.rs +++ b/src/rasterizer/mod.rs @@ -4,6 +4,23 @@ pub use self::triangles::Triangles; use crate::Pipeline; +/// The face culling strategy used during rendering. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum CullMode { + /// Do not cull triangles regardless of their winding order + None, + /// Cull clockwise triangles + Back, + /// Cull counter-clockwise triangles + Front, +} + +impl Default for CullMode { + fn default() -> Self { + CullMode::Back + } +} + /// A trait that represents types that turn vertex streams into fragment coordinates. /// /// Rasterizers take an iterator of vertices and emit fragment positions. They do not, by themselves, perform shader diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index dffebd1..eaf2c6e 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -1,9 +1,10 @@ use super::*; -use crate::{CullMode, CoordinateMode}; +use crate::CoordinateMode; use vek::*; /// A rasterizer that produces filled triangles. -pub struct Triangles; +#[derive(Copy, Clone, Debug, Default)] +pub struct Triangles(pub CullMode); impl Rasterizer for Triangles { unsafe fn rasterize( @@ -19,7 +20,7 @@ impl Rasterizer for Triangles { I: Iterator, F: FnMut([usize; 2], &[f32], &[P::VsOut], f32), { - let cull_dir = match pipeline.cull_mode() { + let cull_dir = match self.0 { CullMode::None => None, CullMode::Back => Some(1.0), CullMode::Front => Some(-1.0), From ab1798ad82106a53733768151f65d65bd3a03aac Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 19 Dec 2020 20:48:38 +0000 Subject: [PATCH 03/37] Primitive kinds, proper geometry shader, working texture samplers --- Cargo.toml | 12 +- README.md | 53 +- examples/data/rust.png | Bin 0 -> 42302 bytes examples/data/teapot.mtl | 14 +- examples/data/teapot.obj | 9711 +++++++++++++++++------------------ examples/spinning_cube.rs | 102 +- examples/teapot.rs | 133 +- examples/texture_mapping.rs | 73 +- examples/triangle.rs | 15 +- src/index.rs | 28 + src/lib.rs | 17 +- src/math.rs | 35 + src/pipeline.rs | 90 +- src/primitives.rs | 39 + src/rasterizer/mod.rs | 5 +- src/rasterizer/triangles.rs | 24 +- src/sampler/mod.rs | 27 +- src/texture.rs | 33 + 18 files changed, 5100 insertions(+), 5311 deletions(-) create mode 100644 examples/data/rust.png create mode 100644 src/index.rs create mode 100644 src/primitives.rs diff --git a/Cargo.toml b/Cargo.toml index 9a2daf5..064c446 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,22 +14,24 @@ exclude = [ ] [dependencies] -num-traits = { version = "0.2", default-features = false, optional = true } vek = { version = "0.12", default-features = false, features = [] } +image_ = { package = "image", version = "0.23", optional = true } [features] -default = ["std", "simd"] +default = ["std", "simd", "image"] std = ["vek/std"] -libm = ["vek/libm", "num-traits"] +libm = ["vek/libm"] nightly = [] simd = ["vek/repr_simd"] +image = ["image_"] [dev-dependencies] mini_gl_fb = "0.7.0" -tobj = "2.0.2" +wavefront = "0.1.3" criterion = "0.3.3" -image = "0.23.12" +image_ = { package = "image", version = "0.23" } vek = "0.12.1" +derive_more = "0.99" [lib] bench = false diff --git a/README.md b/README.md index 15361e1..d475b0b 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # Utah teapot, rendered with Euc -## Example +## Triangle Example ```rust struct Example; @@ -46,40 +46,55 @@ See `examples/` for more code examples. ## What is `euc`? -`euc` is a versatile, simple to use crate that allows 3D rendering on the CPU. It has a portable, compact design that makes it perfect for -prototyping ideas, unit testing, or even simple realtime applications. `euc` is currently under active development. +`euc` is a versatile, simple to use crate that allows 3D rendering on the CPU. +It has a portable, compact design that makes it perfect for prototyping ideas, +unit testing, or even simple realtime applications. `euc` is currently under +active development. ## Why? -- Modern graphics APIs are complex, verbose beasts. Rendering with the CPU means less complexity, less boilerplate and less verbosity: - perfect for testing ideas. +- Modern graphics APIs are complex, verbose beasts. Rendering with the CPU means + less complexity, less boilerplate and less verbosity: perfect for testing + ideas. -- Modern CPUs are fast enough to make simple 3D programs run at reasonable speeds (although they are of course no match for GPUs). It's - possible to write surprisingly complex realtime 3D software with the CPU only. +- Modern CPUs are fast enough to make simple 3D programs run at reasonable + speeds (although they are of course no match for GPUs). It's possible to write + surprisingly complex realtime 3D software with the CPU only. -- Not requiring a GPU interface means that `euc` is incredibly portable. As a result, `euc` is `no_std` (if you have a nightly compiler). +- Not requiring a GPU interface means that `euc` is incredibly portable. As a + result, `euc` is `no_std` (if you have a nightly compiler). -- `euc` has consistent cross-platform behaviour and doesn't require a GPU to run. This makes it perfect for use as a unit testing tool. +- `euc` has consistent cross-platform behaviour and doesn't require a GPU to + run. This makes it perfect for use as a unit testing tool. -- Running on the CPU allows a more dynamic approach to data access. - For applications in which performance is less of a concern, `euc` lowers the barrier of low-level 3D development and allows for more novel - approaches to graphics rendering to be realised. +- Running on the CPU allows a more dynamic approach to data access. For + applications in which performance is less of a concern, `euc` lowers the + barrier of low-level 3D development and allows for more novel approaches to + graphics rendering to be realised. + +- Unusual data types can be used when rendering. Want to create a command-line + 3D game? Now you use can use `char` as your pixel format directly! ## Coordinate System -Where possible, `euc` tries to use a coordinate system similar in nature to OpenGL. If you're used to OpenGL, you'll have no trouble working -with `euc`. +By default, `euc` uses a left-handed coordinate system with 0-1 z clipping (like +Vulkan). However, both of these properties can be changes independently and +`euc` provides coordinate system constants that correspond to those of common +graphics APIs such as `CoordinateMode::VULKAN` and `CoordinateMode::OPENGL`. ## Release Mode -Cargo, by default, compiles Rust code in debug mode. In this mode, very few optimisations are made upon the code, and as a result the -performance of software rendering tends to suffer. To experience this project with good performance, make sure to compile with the -`--release` flag. +Cargo, by default, compiles Rust code in debug mode. In this mode, very few +optimisations are made upon the code, and as a result the performance of +software rendering tends to suffer. To experience this project with good +performance, make sure to compile with the `--release` flag. ## `no_std` -`euc` can be compiled on platforms that lack standard library support. This makes it ideal for rendering 3D graphics on embedded devices. -You can enable `no_std` support by disabling the default features and enabling the `libm` feature in your `Cargo.toml` file like so: +`euc` can be compiled on platforms that lack standard library support. This +makes it ideal for rendering 3D graphics on embedded devices. You can enable +`no_std` support by disabling the default features and enabling the `libm` +feature in your `Cargo.toml` file like so: ```toml [dependencies] diff --git a/examples/data/rust.png b/examples/data/rust.png new file mode 100644 index 0000000000000000000000000000000000000000..461bae3b40ff769b57aed1c75bb906324ea91fce GIT binary patch literal 42302 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 IciEi#KccS_$N&HU literal 0 HcmV?d00001 diff --git a/examples/data/teapot.mtl b/examples/data/teapot.mtl index 70d3ba1..8e756d5 100644 --- a/examples/data/teapot.mtl +++ b/examples/data/teapot.mtl @@ -1,10 +1,12 @@ # Blender MTL File: 'None' # Material Count: 1 -newmtl None -Ns 0 -Ka 0.000000 0.000000 0.000000 -Kd 0.8 0.8 0.8 -Ks 0.8 0.8 0.8 -d 1 +newmtl Default_OBJ +Ns 225.000000 +Ka 1.000000 1.000000 1.000000 +Kd 0.800000 0.800000 0.800000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 illum 2 diff --git a/examples/data/teapot.obj b/examples/data/teapot.obj index c9114fe..61d798b 100644 --- a/examples/data/teapot.obj +++ b/examples/data/teapot.obj @@ -1,5049 +1,4670 @@ -# Blender v2.79 (sub 6) OBJ File: '' +# Blender v2.90.1 OBJ File: '' # www.blender.org mtllib teapot.mtl -o teapot.005 -v -0.508714 0.682112 0.071712 -v -0.517593 0.664661 0.063813 -v -0.499882 0.682112 0.139122 -v -0.700762 0.643448 0.000000 -v -0.614053 0.645774 0.000000 -v -0.614850 0.651067 0.039883 -v -0.704000 0.648569 0.039883 -v -0.769743 0.637131 0.000000 -v -0.774766 0.641786 0.039883 -v -0.820114 0.624834 0.000000 -v -0.826329 0.628576 0.039883 -v -0.850987 0.604560 0.000000 -v -0.857869 0.606800 0.039883 -v -0.861474 0.574316 0.000000 -v -0.868564 0.574316 0.039883 -v -0.526706 0.651362 0.039883 -v -0.517593 0.664661 0.063813 -v -0.616848 0.664299 0.063813 -v -0.712095 0.661370 0.063813 -v -0.787321 0.653419 0.063813 -v -0.841868 0.637931 0.063813 -v -0.875076 0.612403 0.063813 -v -0.886292 0.574316 0.063813 -v -0.508714 0.682112 0.071712 -v -0.619445 0.681503 0.071790 -v -0.722621 0.678012 0.071790 -v -0.803644 0.668539 0.071790 -v -0.862065 0.650094 0.071790 -v -0.897443 0.619684 0.071790 -v -0.909334 0.574316 0.071790 -v -0.461757 0.765755 -0.128512 -v -0.498530 0.712498 -0.039883 -v -0.478597 0.765755 0.000000 -v -0.622039 0.698704 0.063813 -v -0.733150 0.694655 0.063813 -v -0.819967 0.683663 0.063813 -v -0.882265 0.662257 0.063813 -v -0.919812 0.626965 0.063813 -v -0.932377 0.574316 0.063813 -v -0.501255 0.717792 0.000000 -v -0.498530 0.712498 0.039883 -v -0.624036 0.711938 0.039883 -v -0.741245 0.707456 0.039883 -v -0.832523 0.695296 0.039883 -v -0.897800 0.671612 0.039883 -v -0.937016 0.632565 0.039883 -v -0.950104 0.574316 0.039883 -v -0.624036 0.711938 -0.039883 -v -0.624834 0.717232 0.000000 -v -0.498530 0.712498 -0.039883 -v -0.744483 0.712577 0.000000 -v -0.837545 0.699948 0.000000 -v -0.904015 0.675354 0.000000 -v -0.943899 0.634805 0.000000 -v -0.957194 0.574316 0.000000 -v -0.499882 0.682112 -0.139122 -v -0.555408 0.599133 0.000000 -v -0.526706 0.651362 -0.039883 -v -0.741245 0.707456 -0.039883 -v -0.832523 0.695296 -0.039883 -v -0.897800 0.671612 -0.039883 -v -0.937016 0.632565 -0.039883 -v -0.950104 0.574316 -0.039883 -v -0.501666 0.699221 -0.063813 -v -0.508714 0.682112 -0.071712 -v -0.622039 0.698704 -0.063813 -v -0.733150 0.694655 -0.063813 -v -0.819967 0.683663 -0.063813 -v -0.882265 0.662257 -0.063813 -v -0.919812 0.626965 -0.063813 -v -0.932377 0.574316 -0.063813 -v -0.517593 0.664661 -0.063813 -v -0.614850 0.651067 -0.039883 -v -0.616848 0.664299 -0.063813 -v -0.526706 0.651362 -0.039883 -v -0.619445 0.681503 -0.071790 -v -0.722621 0.678012 -0.071790 -v -0.803644 0.668539 -0.071790 -v -0.862065 0.650094 -0.071790 -v -0.897443 0.619684 -0.071790 -v -0.909334 0.574316 -0.071790 -v -0.517593 0.664661 -0.063813 -v -0.508714 0.682112 -0.071712 -v -0.501667 0.699221 0.063813 -v -0.712095 0.661370 -0.063813 -v -0.787321 0.653419 -0.063813 -v -0.841868 0.637931 -0.063813 -v -0.875076 0.612403 -0.063813 -v -0.886292 0.574316 -0.063813 -v -0.535866 0.599133 0.149137 -v -0.526706 0.651362 0.039883 -v -0.704000 0.648569 -0.039883 -v -0.774766 0.641786 -0.039883 -v -0.826329 0.628576 -0.039883 -v -0.857869 0.606800 -0.039883 -v -0.868564 0.574316 -0.039883 -v -0.534329 0.646030 0.000000 -v -0.856009 0.533103 0.000000 -v -0.862808 0.530917 0.039883 -v -0.839022 0.483916 0.000000 -v -0.844976 0.480304 0.039883 -v -0.809626 0.430737 0.000000 -v -0.814205 0.426194 0.039883 -v -0.766936 0.377559 0.000000 -v -0.769651 0.372307 0.039883 -v -0.710066 0.328372 0.000000 -v -0.710455 0.322361 0.039883 -v -0.638129 0.287158 0.000000 -v -0.631184 0.277569 0.039883 -v -0.879811 0.525448 0.063813 -v -0.859854 0.471278 0.063813 -v -0.825653 0.414838 0.063813 -v -0.776434 0.359177 0.063813 -v -0.711425 0.307332 0.063813 -v -0.624828 0.259599 0.063813 -v -0.901913 0.518343 0.071790 -v -0.879202 0.459542 0.071790 -v -0.840534 0.400078 0.071790 -v -0.785253 0.342107 0.071790 -v -0.712688 0.287795 0.071790 -v -0.619922 0.238069 0.071790 -v -0.924011 0.511234 0.063813 -v -0.898547 0.447807 0.063813 -v -0.855419 0.385315 0.063813 -v -0.794072 0.325038 0.063813 -v -0.713952 0.268259 0.063813 -v -0.941014 0.505765 0.039883 -v -0.913428 0.438781 0.039883 -v -0.866867 0.373960 0.039883 -v -0.800855 0.311909 0.039883 -v -0.714922 0.253231 0.039883 -v -0.534329 0.646030 0.000000 -v -0.947813 0.503580 0.000000 -v -0.919378 0.435169 0.000000 -v -0.871445 0.369416 0.000000 -v -0.803571 0.306656 0.000000 -v -0.715311 0.247220 0.000000 -v -0.608883 0.198681 0.039883 -v -0.611709 0.194244 0.000000 -v -0.941014 0.505765 -0.039883 -v -0.913428 0.438781 -0.039883 -v -0.866867 0.373960 -0.039883 -v -0.800855 0.311909 -0.039883 -v -0.714922 0.253231 -0.039883 -v -0.608884 0.198682 -0.039883 -v -0.713952 0.268259 -0.063813 -v -0.615553 0.216807 -0.063813 -v -0.924011 0.511234 -0.063813 -v -0.898547 0.447807 -0.063813 -v -0.855419 0.385315 -0.063813 -v -0.794072 0.325038 -0.063813 -v -0.615553 0.216807 -0.063813 -v -0.619922 0.238069 -0.071790 -v -0.604277 0.221240 -0.168176 -v -0.712688 0.287795 -0.071790 -v -0.901913 0.518343 -0.071790 -v -0.879202 0.459542 -0.071790 -v -0.840534 0.400078 -0.071790 -v -0.785253 0.342107 -0.071790 -v -0.615480 0.216617 0.063578 -v -0.608883 0.198681 0.039883 -v -0.575771 0.166623 0.160243 -v -0.615480 0.216617 0.063578 -v -0.879811 0.525448 -0.063813 -v -0.859854 0.471278 -0.063813 -v -0.825653 0.414838 -0.063813 -v -0.776434 0.359177 -0.063813 -v -0.711425 0.307332 -0.063813 -v -0.619922 0.238069 -0.071790 -v -0.624826 0.259599 -0.063813 -v -0.862808 0.530917 -0.039883 -v -0.844976 0.480304 -0.039883 -v -0.814205 0.426194 -0.039883 -v -0.769651 0.372307 -0.039883 -v -0.710455 0.322361 -0.039883 -v -0.631184 0.277569 -0.039883 -v 0.592873 0.437827 -0.165003 -v 0.588275 0.517481 0.000000 -v 0.600959 0.444810 -0.085753 -v 0.656890 0.471064 0.000000 -v 0.605956 0.463769 0.000000 -v 0.600960 0.444810 0.085753 -v 0.724395 0.514048 0.000000 -v 0.661223 0.454734 0.083705 -v 0.730696 0.501576 0.073611 -v 0.761767 0.574316 0.000000 -v 0.768856 0.565896 0.060489 -v 0.785843 0.642561 0.000000 -v 0.793721 0.637899 0.047367 -v 0.813468 0.709475 0.000000 -v 0.823314 0.707784 0.037273 -v 0.861474 0.765755 0.000000 -v 0.875654 0.765755 0.033236 -v 0.672055 0.413907 0.133928 -v 0.746455 0.470391 0.117778 -v 0.786583 0.544847 0.096783 -v 0.813417 0.626247 0.075788 -v 0.847933 0.703560 0.059638 -v 0.911107 0.765755 0.053178 -v 0.686135 0.360830 0.150669 -v 0.605100 0.399712 0.137265 -v 0.613258 0.341675 0.154354 -v 0.605100 0.399712 0.137265 -v 0.609621 0.360830 0.169664 -v 0.766935 0.429850 0.132501 -v 0.809626 0.517481 0.108881 -v 0.839021 0.611098 0.085261 -v 0.879938 0.698065 0.067092 -v 0.957193 0.765755 0.059825 -v 0.700219 0.307756 0.133928 -v 0.787419 0.389310 0.117778 -v 0.832669 0.490118 0.096783 -v 0.864627 0.595949 0.075788 -v 0.911944 0.692571 0.059638 -v 1.003279 0.765755 0.053178 -v 0.711051 0.266929 0.083705 -v 0.803175 0.358128 0.073611 -v 0.850396 0.469070 0.060489 -v 0.884319 0.584297 0.047367 -v 0.936563 0.688344 0.037273 -v 1.038733 0.765755 0.033236 -v 0.715384 0.250599 0.000000 -v 0.809479 0.345652 0.000000 -v 0.857486 0.460650 0.000000 -v 0.892200 0.579635 0.000000 -v 0.946409 0.686653 0.000000 -v 1.052913 0.765755 0.000000 -v 0.625577 0.219883 0.000000 -v 0.604276 0.221240 0.168176 -v 0.596768 0.166623 0.000000 -v 0.625577 0.219883 0.000000 -v 0.711051 0.266929 -0.083705 -v 0.803175 0.358128 -0.073611 -v 0.850396 0.469070 -0.060489 -v 0.884319 0.584297 -0.047367 -v 0.936563 0.688344 -0.037273 -v 1.038733 0.765755 -0.033236 -v 0.700219 0.307756 -0.133928 -v 0.787419 0.389310 -0.117778 -v 0.832669 0.490118 -0.096783 -v 0.864627 0.595949 -0.075788 -v 0.911944 0.692571 -0.059638 -v 1.003279 0.765755 -0.053178 -v 0.686135 0.360830 -0.150669 -v 0.766935 0.429850 -0.132501 -v 0.809626 0.517481 -0.108881 -v 0.839021 0.611098 -0.085261 -v 0.879938 0.698065 -0.067092 -v 0.957193 0.765755 -0.059825 -v 0.615677 0.287158 -0.171349 -v 0.609621 0.360830 -0.169664 -v 0.613258 0.341675 -0.154354 -v 0.672055 0.413907 -0.133928 -v 0.746455 0.470391 -0.117778 -v 0.786583 0.544847 -0.096783 -v 0.813417 0.626247 -0.075788 -v 0.847933 0.703560 -0.059638 -v 0.911107 0.765755 -0.053178 -v 0.605101 0.399712 -0.137265 -v 0.661223 0.454734 -0.083705 -v 0.730696 0.501576 -0.073611 -v 0.768856 0.565896 -0.060489 -v 0.793721 0.637899 -0.047367 -v 0.823314 0.707784 -0.037273 -v 0.875654 0.765755 -0.033236 -v 0.877131 0.775726 0.000000 -v 0.892235 0.775943 0.032236 -v 0.891016 0.781708 0.000000 -v 0.906073 0.782101 0.029666 -v 0.901357 0.783702 0.000000 -v 0.915613 0.784200 0.026173 -v 0.906379 0.781708 0.000000 -v 0.919292 0.782200 0.022403 -v 0.904312 0.775726 0.000000 -v 0.915553 0.776064 0.019003 -v 0.893380 0.765755 0.000000 -v 0.902834 0.765755 0.016618 -v 0.929990 0.776479 0.051553 -v 0.943713 0.783087 0.047269 -v 0.951249 0.785448 0.041213 -v 0.951569 0.783431 0.034270 -v 0.943653 0.776909 0.027327 -v 0.926468 0.765755 0.021271 -v 0.979075 0.777181 0.057969 -v 0.992645 0.784366 0.052956 -v 0.997575 0.787068 0.045616 -v 0.993532 0.785033 0.036781 -v 0.980182 0.778010 0.027281 -v 0.957193 0.765755 0.017947 -v 1.028157 0.777879 0.051503 -v 1.041577 0.785649 0.046875 -v 1.043903 0.788686 0.039883 -v 1.035492 0.786631 0.031119 -v 1.016712 0.779111 0.021172 -v 0.987920 0.765755 0.010635 -v 1.065915 0.778419 0.032174 -v 1.079216 0.786631 0.029174 -v 1.079539 0.789934 0.024511 -v 1.067772 0.787863 0.018464 -v 1.044812 0.779957 0.011310 -v 1.011552 0.765755 0.003324 -v 1.081016 0.778632 0.000000 -v 1.094273 0.787027 0.000000 -v 1.093795 0.790431 0.000000 -v 1.080684 0.788354 0.000000 -v 1.056052 0.780295 0.000000 -v 1.021006 0.765755 0.000000 -v 1.065915 0.778419 -0.032251 -v 1.079216 0.786631 -0.029789 -v 1.079539 0.789934 -0.026589 -v 1.067772 0.787863 -0.023388 -v 1.044812 0.779957 -0.020926 -v 1.011552 0.765755 -0.019942 -v 1.028157 0.777879 -0.051602 -v 1.041577 0.785649 -0.047663 -v 1.043903 0.788686 -0.042542 -v 1.035492 0.786631 -0.037421 -v 1.016712 0.779111 -0.033482 -v 0.987920 0.765755 -0.031906 -v 0.979075 0.777181 -0.058052 -v 0.992645 0.784366 -0.053621 -v 0.997575 0.787068 -0.047860 -v 0.993532 0.785033 -0.042099 -v 0.980182 0.778010 -0.037667 -v 0.957193 0.765755 -0.035895 -v 0.929990 0.776479 -0.051602 -v 0.943713 0.783087 -0.047663 -v 0.951249 0.785448 -0.042542 -v 0.951569 0.783431 -0.037421 -v 0.943653 0.776909 -0.033482 -v 0.926468 0.765755 -0.031906 -v 0.892235 0.775943 -0.032251 -v 0.906073 0.782101 -0.029789 -v 0.915613 0.784200 -0.026589 -v 0.919292 0.782200 -0.023388 -v 0.915553 0.776064 -0.020926 -v 0.902834 0.765755 -0.019942 -v 0.886428 0.750924 0.014860 -v 0.936793 0.750924 -0.033795 -v 0.965261 0.750924 -0.030099 -v 0.987158 0.750924 -0.019014 -v 0.995918 0.750924 -0.000537 -v 0.936793 0.750924 0.016092 -v 0.987158 0.750924 0.002542 -v 0.886428 0.750924 -0.019014 -v 0.908324 0.750924 -0.030099 -v 0.877668 0.750924 -0.000537 -v 0.965261 0.750924 0.009317 -v 0.908324 0.750924 0.019171 -v 0.936793 0.750924 -0.007312 -v 0.440746 0.783205 0.000000 -v 0.446690 0.765755 0.000000 -v 0.430973 0.765755 0.119945 -v 0.425236 0.783205 0.118348 -v 0.441668 0.793673 0.000000 -v 0.426127 0.793673 0.118596 -v 0.447686 0.797164 0.000000 -v 0.431936 0.797164 0.120212 -v 0.457031 0.793673 0.000000 -v 0.440950 0.793673 0.122721 -v 0.467924 0.783205 0.000000 -v 0.451460 0.783205 0.125646 -v 0.478596 0.765755 0.000000 -v 0.461756 0.765755 0.128512 -v 0.386470 0.765755 0.226985 -v 0.381327 0.783205 0.223964 -v 0.382124 0.793673 0.224433 -v 0.387332 0.797164 0.227491 -v 0.395416 0.793673 0.232239 -v 0.404842 0.783205 0.237775 -v 0.414076 0.765755 0.243198 -v 0.317150 0.765755 0.317150 -v 0.312929 0.783205 0.312929 -v 0.313584 0.793673 0.313584 -v 0.317858 0.797164 0.317858 -v 0.324491 0.793673 0.324492 -v 0.332226 0.783205 0.332226 -v 0.339803 0.765755 0.339804 -v 0.226984 0.765755 0.386470 -v 0.223963 0.783205 0.381327 -v 0.224433 0.793673 0.382125 -v 0.227491 0.797164 0.387332 -v 0.232239 0.793673 0.395417 -v 0.237775 0.783205 0.404842 -v 0.243198 0.765755 0.414076 -v 0.119944 0.765755 0.430973 -v 0.118348 0.783205 0.425237 -v 0.118596 0.793673 0.426127 -v 0.120212 0.797164 0.431937 -v 0.122721 0.793673 0.440950 -v 0.125646 0.783205 0.451460 -v 0.128512 0.765755 0.461757 -v 0.000000 0.765755 0.446690 -v 0.000000 0.783205 0.440746 -v 0.000000 0.793673 0.441668 -v 0.000000 0.797164 0.447686 -v 0.000000 0.793673 0.457031 -v 0.000000 0.783205 0.467924 -v 0.000000 0.765755 0.478597 -v -0.119945 0.765755 0.430973 -v -0.118348 0.783205 0.425237 -v -0.118596 0.793673 0.426127 -v -0.120212 0.797164 0.431937 -v -0.122721 0.793673 0.440950 -v -0.125646 0.783205 0.451460 -v -0.128513 0.765755 0.461757 -v -0.226985 0.765755 0.386470 -v -0.223964 0.783205 0.381327 -v -0.224433 0.793673 0.382125 -v -0.227491 0.797164 0.387332 -v -0.232240 0.793673 0.395417 -v -0.237775 0.783205 0.404842 -v -0.243198 0.765755 0.414076 -v -0.317150 0.765755 0.317150 -v -0.312929 0.783205 0.312929 -v -0.313584 0.793673 0.313584 -v -0.317859 0.797164 0.317858 -v -0.324492 0.793673 0.324492 -v -0.332226 0.783205 0.332226 -v -0.339804 0.765755 0.339804 -v -0.386470 0.765755 0.226985 -v -0.381327 0.783205 0.223964 -v -0.382125 0.793673 0.224433 -v -0.387332 0.797164 0.227491 -v -0.395417 0.793673 0.232239 -v -0.404842 0.783205 0.237775 -v -0.414076 0.765755 0.243198 -v -0.430974 0.765755 0.119945 -v -0.425237 0.783205 0.118348 -v -0.426127 0.793673 0.118596 -v -0.431937 0.797164 0.120212 -v -0.440950 0.793673 0.122721 -v -0.451461 0.783205 0.125646 -v -0.461757 0.765755 0.128512 -v -0.446690 0.765755 0.000000 -v -0.440746 0.783205 0.000000 -v -0.441668 0.793673 0.000000 -v -0.447686 0.797164 0.000000 -v -0.457031 0.793673 0.000000 -v -0.467924 0.783205 0.000000 -v -0.430974 0.765755 -0.119945 -v -0.425237 0.783205 -0.118348 -v -0.426127 0.793673 -0.118596 -v -0.431937 0.797164 -0.120212 -v -0.440950 0.793673 -0.122721 -v -0.451461 0.783205 -0.125646 -v -0.386470 0.765755 -0.226985 -v -0.381327 0.783205 -0.223964 -v -0.382125 0.793673 -0.224433 -v -0.387332 0.797164 -0.227491 -v -0.395417 0.793673 -0.232239 -v -0.404842 0.783205 -0.237775 -v -0.414076 0.765755 -0.243198 -v -0.317150 0.765755 -0.317150 -v -0.312929 0.783205 -0.312929 -v -0.313584 0.793673 -0.313584 -v -0.317859 0.797164 -0.317858 -v -0.324492 0.793673 -0.324492 -v -0.332226 0.783205 -0.332226 -v -0.339804 0.765755 -0.339804 -v -0.226985 0.765755 -0.386470 -v -0.223964 0.783205 -0.381327 -v -0.224433 0.793673 -0.382125 -v -0.227491 0.797164 -0.387332 -v -0.232240 0.793673 -0.395417 -v -0.237775 0.783205 -0.404842 -v -0.243198 0.765755 -0.414076 -v -0.119945 0.765755 -0.430973 -v -0.118348 0.783205 -0.425237 -v -0.118596 0.793673 -0.426127 -v -0.120212 0.797164 -0.431937 -v -0.122721 0.793673 -0.440950 -v -0.125646 0.783205 -0.451460 -v -0.128513 0.765755 -0.461757 -v 0.000000 0.765755 -0.446690 -v 0.000000 0.783205 -0.440746 -v 0.000000 0.793673 -0.441668 -v 0.000000 0.797164 -0.447686 -v 0.000000 0.793673 -0.457031 -v 0.000000 0.783205 -0.467924 -v 0.000000 0.765755 -0.478597 -v 0.119944 0.765755 -0.430973 -v 0.118348 0.783205 -0.425237 -v 0.118596 0.793673 -0.426127 -v 0.120212 0.797164 -0.431937 -v 0.122721 0.793673 -0.440950 -v 0.125646 0.783205 -0.451460 -v 0.128512 0.765755 -0.461757 -v 0.226984 0.765755 -0.386470 -v 0.223963 0.783205 -0.381327 -v 0.224433 0.793673 -0.382125 -v 0.227491 0.797164 -0.387332 -v 0.232239 0.793673 -0.395417 -v 0.237775 0.783205 -0.404842 -v 0.243198 0.765755 -0.414076 -v 0.317150 0.765755 -0.317150 -v 0.312929 0.783205 -0.312929 -v 0.313584 0.793673 -0.313584 -v 0.317858 0.797164 -0.317858 -v 0.324491 0.793673 -0.324492 -v 0.332226 0.783205 -0.332226 -v 0.339803 0.765755 -0.339804 -v 0.386470 0.765755 -0.226985 -v 0.381327 0.783205 -0.223964 -v 0.382124 0.793673 -0.224433 -v 0.387332 0.797164 -0.227491 -v 0.395416 0.793673 -0.232239 -v 0.404842 0.783205 -0.237775 -v 0.414076 0.765755 -0.243198 -v 0.430973 0.765755 -0.119945 -v 0.425236 0.783205 -0.118348 -v 0.426127 0.793673 -0.118596 -v 0.431936 0.797164 -0.120212 -v 0.440950 0.793673 -0.122721 -v 0.451460 0.783205 -0.125646 -v 0.461756 0.765755 -0.128512 -v 0.518110 0.682112 0.000000 -v 0.499881 0.682112 0.139122 -v 0.555408 0.599133 0.000000 -v 0.535865 0.599133 0.149137 -v 0.567578 0.517485 0.157963 -v 0.448260 0.682112 0.263277 -v 0.480530 0.599133 0.282230 -v 0.508969 0.517485 0.298931 -v 0.592873 0.437827 0.165003 -v 0.531651 0.437827 0.312254 -v 0.546669 0.360830 0.321075 -v 0.615677 0.287158 0.171349 -v 0.552100 0.287158 0.324265 -v 0.367859 0.682112 0.367859 -v 0.394341 0.599133 0.394341 -v 0.417675 0.517481 0.417675 -v 0.436292 0.437827 0.436292 -v 0.448614 0.360830 0.448614 -v 0.453072 0.287158 0.453072 -v 0.263277 0.682112 0.448260 -v 0.282230 0.599133 0.480530 -v 0.298931 0.517485 0.508969 -v 0.312254 0.437827 0.531651 -v 0.321074 0.360830 0.546669 -v 0.324265 0.287158 0.552100 -v 0.139122 0.682112 0.499882 -v 0.149137 0.599133 0.535866 -v 0.157963 0.517481 0.567578 -v 0.165003 0.437827 0.592873 -v 0.169664 0.360830 0.609621 -v 0.171349 0.287158 0.615677 -v 0.000000 0.682112 0.518110 -v 0.000000 0.599133 0.555408 -v 0.000000 0.517481 0.588275 -v 0.000000 0.437827 0.614496 -v 0.000000 0.360830 0.631850 -v 0.000000 0.287158 0.638129 -v -0.139123 0.682112 0.499882 -v -0.149138 0.599133 0.535866 -v -0.157963 0.517485 0.567578 -v -0.165004 0.437827 0.592873 -v -0.169664 0.360830 0.609621 -v -0.171350 0.287158 0.615677 -v -0.263278 0.682112 0.448260 -v -0.282230 0.599133 0.480530 -v -0.298931 0.517485 0.508969 -v -0.312255 0.437827 0.531651 -v -0.321075 0.360830 0.546669 -v -0.324265 0.287158 0.552100 -v -0.367859 0.682112 0.367859 -v -0.394341 0.599133 0.394341 -v -0.417675 0.517481 0.417675 -v -0.436292 0.437827 0.436292 -v -0.448615 0.360830 0.448614 -v -0.453072 0.287158 0.453072 -v -0.448260 0.682112 0.263277 -v -0.480530 0.599133 0.282230 -v -0.508969 0.517485 0.298931 -v -0.531651 0.437827 0.312254 -v -0.546669 0.360830 0.321075 -v -0.552100 0.287158 0.324265 -v -0.567578 0.517481 0.157963 -v -0.592873 0.437827 0.165003 -v -0.609621 0.360830 0.169664 -v -0.615677 0.287158 0.171349 -v -0.588275 0.517481 0.000000 -v -0.614496 0.437827 0.000000 -v -0.631850 0.360830 0.000000 -v -0.535866 0.599133 -0.149137 -v -0.567578 0.517485 -0.157963 -v -0.592873 0.437827 -0.165003 -v -0.609621 0.360830 -0.169664 -v -0.448260 0.682112 -0.263277 -v -0.480530 0.599133 -0.282230 -v -0.508969 0.517485 -0.298931 -v -0.531651 0.437827 -0.312254 -v -0.546669 0.360830 -0.321075 -v -0.615677 0.287158 -0.171349 -v -0.552100 0.287158 -0.324265 -v -0.367859 0.682112 -0.367859 -v -0.394341 0.599133 -0.394341 -v -0.417675 0.517481 -0.417675 -v -0.436292 0.437827 -0.436292 -v -0.448615 0.360830 -0.448614 -v -0.453072 0.287158 -0.453072 -v -0.263278 0.682112 -0.448260 -v -0.282230 0.599133 -0.480530 -v -0.298931 0.517485 -0.508969 -v -0.312255 0.437827 -0.531651 -v -0.321075 0.360830 -0.546669 -v -0.324265 0.287158 -0.552100 -v -0.139123 0.682112 -0.499882 -v -0.149138 0.599133 -0.535866 -v -0.157963 0.517481 -0.567578 -v -0.165004 0.437827 -0.592873 -v -0.169664 0.360830 -0.609621 -v -0.171350 0.287158 -0.615677 -v 0.000000 0.682112 -0.518110 -v 0.000000 0.599133 -0.555408 -v 0.000000 0.517481 -0.588275 -v 0.000000 0.437827 -0.614496 -v 0.000000 0.360830 -0.631850 -v 0.000000 0.287158 -0.638129 -v 0.139122 0.682112 -0.499882 -v 0.149137 0.599133 -0.535866 -v 0.157963 0.517485 -0.567578 -v 0.165003 0.437827 -0.592873 -v 0.169664 0.360830 -0.609621 -v 0.171349 0.287158 -0.615677 -v 0.263277 0.682112 -0.448260 -v 0.282230 0.599133 -0.480530 -v 0.298931 0.517485 -0.508969 -v 0.312254 0.437827 -0.531651 -v 0.321074 0.360830 -0.546669 -v 0.324265 0.287158 -0.552100 -v 0.367859 0.682112 -0.367859 -v 0.394341 0.599133 -0.394341 -v 0.417675 0.517481 -0.417675 -v 0.436292 0.437827 -0.436292 -v 0.448614 0.360830 -0.448614 -v 0.453072 0.287158 -0.453072 -v 0.448260 0.682112 -0.263277 -v 0.480530 0.599133 -0.282230 -v 0.508969 0.517485 -0.298931 -v 0.531651 0.437827 -0.312254 -v 0.546669 0.360830 -0.321075 -v 0.552100 0.287158 -0.324265 -v 0.499881 0.682112 -0.139122 -v 0.535865 0.599133 -0.149137 -v 0.567578 0.517481 -0.157963 -v 0.575771 0.166623 0.160243 -v 0.558363 0.122640 0.000000 -v 0.538718 0.122640 0.149931 -v 0.519957 0.088629 0.000000 -v 0.501662 0.088629 0.139618 -v 0.490415 0.063924 0.000000 -v 0.473160 0.063924 0.131685 -v 0.478596 0.047860 0.000000 -v 0.461756 0.047860 0.128512 -v 0.541877 0.221240 0.318259 -v 0.516317 0.166623 0.303247 -v 0.483086 0.122640 0.283731 -v 0.449858 0.088629 0.264215 -v 0.424299 0.063924 0.249203 -v 0.414076 0.047860 0.243198 -v 0.444680 0.221240 0.444680 -v 0.423705 0.166623 0.423705 -v 0.396438 0.122640 0.396438 -v 0.369171 0.088629 0.369171 -v 0.348195 0.063924 0.348195 -v 0.339803 0.047860 0.339804 -v 0.318259 0.221240 0.541877 -v 0.303247 0.166623 0.516317 -v 0.283731 0.122640 0.483086 -v 0.264215 0.088629 0.449859 -v 0.249203 0.063924 0.424298 -v 0.243198 0.047860 0.414076 -v 0.168176 0.221240 0.604276 -v 0.160243 0.166623 0.575771 -v 0.149931 0.122640 0.538718 -v 0.139618 0.088629 0.501662 -v 0.131685 0.063924 0.473160 -v 0.128512 0.047860 0.461757 -v 0.000000 0.221240 0.626311 -v 0.000000 0.166623 0.596769 -v 0.000000 0.122640 0.558363 -v 0.000000 0.088629 0.519957 -v 0.000000 0.063924 0.490415 -v 0.000000 0.047860 0.478597 -v -0.168177 0.221240 0.604276 -v -0.160244 0.166623 0.575771 -v -0.149931 0.122640 0.538718 -v -0.139618 0.088629 0.501662 -v -0.131686 0.063924 0.473160 -v -0.128513 0.047860 0.461757 -v -0.318259 0.221240 0.541877 -v -0.303247 0.166623 0.516317 -v -0.283731 0.122640 0.483086 -v -0.264215 0.088629 0.449859 -v -0.249203 0.063924 0.424298 -v -0.243198 0.047860 0.414076 -v -0.444680 0.221240 0.444680 -v -0.423705 0.166623 0.423705 -v -0.396438 0.122640 0.396438 -v -0.369171 0.088629 0.369171 -v -0.348196 0.063924 0.348195 -v -0.339804 0.047860 0.339804 -v -0.541877 0.221240 0.318259 -v -0.516317 0.166623 0.303247 -v -0.483087 0.122640 0.283731 -v -0.449859 0.088629 0.264215 -v -0.424299 0.063924 0.249203 -v -0.414076 0.047860 0.243198 -v -0.604277 0.221240 0.168176 -v -0.538718 0.122640 0.149931 -v -0.501662 0.088629 0.139618 -v -0.473160 0.063924 0.131685 -v -0.461757 0.047860 0.128512 -v -0.596769 0.166623 0.000000 -v -0.558363 0.122640 0.000000 -v -0.519957 0.088629 0.000000 -v -0.490415 0.063924 0.000000 -v -0.478597 0.047860 0.000000 -v -0.575771 0.166623 -0.160243 -v -0.538718 0.122640 -0.149931 -v -0.501662 0.088629 -0.139618 -v -0.473160 0.063924 -0.131685 -v -0.461757 0.047860 -0.128512 -v -0.541877 0.221240 -0.318259 -v -0.516317 0.166623 -0.303247 -v -0.483087 0.122640 -0.283731 -v -0.449859 0.088629 -0.264215 -v -0.424299 0.063924 -0.249203 -v -0.414076 0.047860 -0.243198 -v -0.444680 0.221240 -0.444680 -v -0.423705 0.166623 -0.423705 -v -0.396438 0.122640 -0.396438 -v -0.369171 0.088629 -0.369171 -v -0.348196 0.063924 -0.348195 -v -0.339804 0.047860 -0.339804 -v -0.318259 0.221240 -0.541877 -v -0.303247 0.166623 -0.516317 -v -0.283731 0.122640 -0.483086 -v -0.264215 0.088629 -0.449859 -v -0.249203 0.063924 -0.424298 -v -0.243198 0.047860 -0.414076 -v -0.168177 0.221240 -0.604276 -v -0.160244 0.166623 -0.575771 -v -0.149931 0.122640 -0.538718 -v -0.139618 0.088629 -0.501662 -v -0.131686 0.063924 -0.473160 -v -0.128513 0.047860 -0.461757 -v 0.000000 0.221240 -0.626311 -v 0.000000 0.166623 -0.596769 -v 0.000000 0.122640 -0.558363 -v 0.000000 0.088629 -0.519957 -v 0.000000 0.063924 -0.490415 -v 0.000000 0.047860 -0.478597 -v 0.168176 0.221240 -0.604276 -v 0.160243 0.166623 -0.575771 -v 0.149931 0.122640 -0.538718 -v 0.139618 0.088629 -0.501662 -v 0.131685 0.063924 -0.473160 -v 0.128512 0.047860 -0.461757 -v 0.318259 0.221240 -0.541877 -v 0.303247 0.166623 -0.516317 -v 0.283731 0.122640 -0.483086 -v 0.264215 0.088629 -0.449859 -v 0.249203 0.063924 -0.424298 -v 0.243198 0.047860 -0.414076 -v 0.444680 0.221240 -0.444680 -v 0.423705 0.166623 -0.423705 -v 0.396438 0.122640 -0.396438 -v 0.369171 0.088629 -0.369171 -v 0.348195 0.063924 -0.348195 -v 0.339803 0.047860 -0.339804 -v 0.541877 0.221240 -0.318259 -v 0.516317 0.166623 -0.303247 -v 0.483086 0.122640 -0.283731 -v 0.449858 0.088629 -0.264215 -v 0.424299 0.063924 -0.249203 -v 0.414076 0.047860 -0.243198 -v 0.604276 0.221240 -0.168176 -v 0.575771 0.166623 -0.160243 -v 0.538718 0.122640 -0.149931 -v 0.501662 0.088629 -0.139618 -v 0.473160 0.063924 -0.131685 -v 0.461756 0.047860 -0.128512 -v 0.193322 0.001883 0.000000 -v 0.000000 0.000000 0.000000 -v 0.186520 0.001883 -0.051911 -v 0.326154 0.007090 0.000000 -v 0.314679 0.007090 -0.087579 -v 0.409797 0.014956 0.000000 -v 0.395378 0.014956 -0.110038 -v 0.455554 0.024816 0.000000 -v 0.439524 0.024816 -0.122325 -v 0.474720 0.036005 0.000000 -v 0.458017 0.036005 -0.127471 -v 0.167259 0.001883 -0.098236 -v 0.282184 0.007090 -0.165735 -v 0.354551 0.014956 -0.208238 -v 0.394137 0.024816 -0.231489 -v 0.410719 0.036005 -0.241228 -v 0.137258 0.001883 -0.137259 -v 0.231570 0.007090 -0.231570 -v 0.290957 0.014956 -0.290957 -v 0.323442 0.024816 -0.323442 -v 0.337050 0.036005 -0.337050 -v 0.098236 0.001883 -0.167259 -v 0.165735 0.007090 -0.282185 -v 0.208238 0.014956 -0.354551 -v 0.231489 0.024816 -0.394137 -v 0.241227 0.036005 -0.410719 -v 0.051910 0.001883 -0.186520 -v 0.087579 0.007090 -0.314679 -v 0.110038 0.014956 -0.395378 -v 0.122324 0.024816 -0.439524 -v 0.127471 0.036005 -0.458017 -v 0.000000 0.001883 -0.193322 -v 0.000000 0.007090 -0.326154 -v 0.000000 0.014956 -0.409797 -v 0.000000 0.024816 -0.455554 -v 0.000000 0.036005 -0.474720 -v -0.051911 0.001883 -0.186520 -v -0.087579 0.007090 -0.314679 -v -0.110038 0.014956 -0.395378 -v -0.122325 0.024816 -0.439524 -v -0.127471 0.036005 -0.458017 -v -0.098237 0.001883 -0.167259 -v -0.165735 0.007090 -0.282185 -v -0.208239 0.014956 -0.354551 -v -0.231489 0.024816 -0.394137 -v -0.241228 0.036005 -0.410719 -v -0.137259 0.001883 -0.137259 -v -0.231570 0.007090 -0.231570 -v -0.290957 0.014956 -0.290957 -v -0.323442 0.024816 -0.323442 -v -0.337051 0.036005 -0.337050 -v -0.167259 0.001883 -0.098236 -v -0.282184 0.007090 -0.165735 -v -0.354551 0.014956 -0.208238 -v -0.394137 0.024816 -0.231489 -v -0.410719 0.036005 -0.241228 -v -0.186520 0.001883 -0.051911 -v -0.314679 0.007090 -0.087579 -v -0.395378 0.014956 -0.110038 -v -0.439524 0.024816 -0.122325 -v -0.458017 0.036005 -0.127471 -v -0.193323 0.001883 0.000000 -v -0.326155 0.007090 0.000000 -v -0.409797 0.014956 0.000000 -v -0.455554 0.024816 0.000000 -v -0.474721 0.036005 0.000000 -v -0.186520 0.001883 0.051911 -v -0.314679 0.007090 0.087579 -v -0.395378 0.014956 0.110038 -v -0.439524 0.024816 0.122325 -v -0.458017 0.036005 0.127471 -v -0.167259 0.001883 0.098236 -v -0.282184 0.007090 0.165735 -v -0.354551 0.014956 0.208238 -v -0.394137 0.024816 0.231489 -v -0.410719 0.036005 0.241228 -v -0.137259 0.001883 0.137259 -v -0.231570 0.007090 0.231570 -v -0.290957 0.014956 0.290957 -v -0.323442 0.024816 0.323442 -v -0.337051 0.036005 0.337050 -v -0.098237 0.001883 0.167259 -v -0.165735 0.007090 0.282185 -v -0.208239 0.014956 0.354551 -v -0.231489 0.024816 0.394137 -v -0.241228 0.036005 0.410719 -v -0.051911 0.001883 0.186520 -v -0.087579 0.007090 0.314679 -v -0.110038 0.014956 0.395378 -v -0.122325 0.024816 0.439524 -v -0.127471 0.036005 0.458017 -v 0.000000 0.001883 0.193322 -v 0.000000 0.007090 0.326154 -v 0.000000 0.014956 0.409797 -v 0.000000 0.024816 0.455554 -v 0.000000 0.036005 0.474720 -v 0.051910 0.001883 0.186520 -v 0.087579 0.007090 0.314679 -v 0.110038 0.014956 0.395378 -v 0.122324 0.024816 0.439524 -v 0.127471 0.036005 0.458017 -v 0.098236 0.001883 0.167259 -v 0.165735 0.007090 0.282185 -v 0.208238 0.014956 0.354551 -v 0.231489 0.024816 0.394137 -v 0.241227 0.036005 0.410719 -v 0.137258 0.001883 0.137259 -v 0.231570 0.007090 0.231570 -v 0.290957 0.014956 0.290957 -v 0.323442 0.024816 0.323442 -v 0.337050 0.036005 0.337050 -v 0.167259 0.001883 0.098236 -v 0.282184 0.007090 0.165735 -v 0.354551 0.014956 0.208238 -v 0.394137 0.024816 0.231489 -v 0.410719 0.036005 0.241228 -v 0.186520 0.001883 0.051911 -v 0.314679 0.007090 0.087579 -v 0.395378 0.014956 0.110038 -v 0.439524 0.024816 0.122325 -v 0.458017 0.036005 0.127471 -v -0.230198 0.750009 0.391939 -v -0.321639 0.750009 0.321639 -v 0.000000 0.750009 -0.453012 -v 0.121642 0.750009 -0.437072 -v 0.391939 0.750009 -0.230197 -v -0.437073 0.750009 0.121642 -v 0.453011 0.750009 0.000000 -v -0.453012 0.750009 0.000000 -v 0.063813 0.861474 0.000000 -v 0.054654 0.888729 0.000000 -v 0.052734 0.888729 0.014691 -v 0.061568 0.861474 0.017135 -v 0.047296 0.888729 0.027792 -v 0.055210 0.861474 0.032427 -v 0.038821 0.888729 0.038821 -v 0.045307 0.861474 0.045307 -v 0.027792 0.888729 0.047296 -v 0.032426 0.861474 0.055210 -v 0.014691 0.888729 0.052735 -v 0.017135 0.861474 0.061568 -v 0.000000 0.888729 0.054654 -v 0.000000 0.861474 0.063813 -v -0.014691 0.888729 0.052735 -v -0.017135 0.861474 0.061568 -v -0.027793 0.888729 0.047296 -v -0.032427 0.861474 0.055210 -v -0.038821 0.888729 0.038821 -v -0.045307 0.861474 0.045307 -v -0.047296 0.888729 0.027792 -v -0.055210 0.861474 0.032427 -v -0.052735 0.888729 0.014691 -v -0.061568 0.861474 0.017135 -v -0.054655 0.888729 0.000000 -v -0.063813 0.861474 0.000000 -v -0.052735 0.888729 -0.014691 -v -0.061568 0.861474 -0.017135 -v -0.047296 0.888729 -0.027792 -v -0.055210 0.861474 -0.032427 -v -0.038821 0.888729 -0.038821 -v -0.045307 0.861474 -0.045307 -v -0.027793 0.888729 -0.047296 -v -0.032427 0.861474 -0.055210 -v -0.014691 0.888729 -0.052735 -v -0.017135 0.861474 -0.061568 -v 0.000000 0.888729 -0.054654 -v 0.000000 0.861474 -0.063813 -v 0.014691 0.888729 -0.052735 -v 0.017135 0.861474 -0.061568 -v 0.027792 0.888729 -0.047296 -v 0.032426 0.861474 -0.055210 -v 0.038821 0.888729 -0.038821 -v 0.045307 0.861474 -0.045307 -v 0.047296 0.888729 -0.027792 -v 0.055210 0.861474 -0.032427 -v 0.052734 0.888729 -0.014691 -v 0.061568 0.861474 -0.017135 -v 0.111968 0.841089 0.000000 -v 0.108028 0.841089 0.030065 -v 0.183167 0.826023 0.000000 -v 0.176722 0.826023 0.049184 -v 0.263228 0.813615 0.000000 -v 0.253966 0.813615 0.070682 -v 0.337972 0.801206 0.000000 -v 0.326081 0.801206 0.090752 -v 0.393218 0.786140 0.000000 -v 0.379380 0.786140 0.105586 -v 0.414784 0.765755 0.000000 -v 0.400190 0.765755 0.111377 -v 0.096873 0.841089 0.056896 -v 0.158473 0.826023 0.093076 -v 0.227741 0.813615 0.133759 -v 0.292408 0.801206 0.171740 -v 0.340206 0.786140 0.199813 -v 0.358865 0.765755 0.210772 -v 0.079497 0.841089 0.079497 -v 0.130048 0.826023 0.130048 -v 0.186892 0.813615 0.186892 -v 0.239960 0.801206 0.239960 -v 0.279184 0.786140 0.279184 -v 0.294497 0.765755 0.294497 -v 0.056896 0.841089 0.096873 -v 0.093076 0.826023 0.158473 -v 0.133759 0.813615 0.227741 -v 0.171740 0.801206 0.292408 -v 0.199813 0.786140 0.340206 -v 0.210772 0.765755 0.358865 -v 0.030065 0.841089 0.108029 -v 0.049184 0.826023 0.176722 -v 0.070682 0.813615 0.253966 -v 0.090752 0.801206 0.326081 -v 0.105586 0.786140 0.379381 -v 0.111377 0.765755 0.400190 -v 0.000000 0.841089 0.111968 -v 0.000000 0.826023 0.183167 -v 0.000000 0.813615 0.263228 -v 0.000000 0.801206 0.337972 -v 0.000000 0.786140 0.393218 -v 0.000000 0.765755 0.414784 -v -0.030066 0.841089 0.108029 -v -0.049184 0.826023 0.176722 -v -0.070682 0.813615 0.253966 -v -0.090752 0.801206 0.326081 -v -0.105586 0.786140 0.379381 -v -0.111377 0.765755 0.400190 -v -0.056896 0.841089 0.096873 -v -0.093076 0.826023 0.158473 -v -0.133759 0.813615 0.227741 -v -0.171740 0.801206 0.292408 -v -0.199813 0.786140 0.340206 -v -0.210772 0.765755 0.358865 -v -0.079497 0.841089 0.079497 -v -0.130049 0.826023 0.130048 -v -0.186892 0.813615 0.186892 -v -0.239960 0.801206 0.239960 -v -0.279185 0.786140 0.279184 -v -0.294497 0.765755 0.294497 -v -0.096873 0.841089 0.056896 -v -0.158473 0.826023 0.093076 -v -0.227741 0.813615 0.133759 -v -0.292408 0.801206 0.171740 -v -0.340206 0.786140 0.199813 -v -0.358865 0.765755 0.210772 -v -0.108029 0.841089 0.030065 -v -0.176722 0.826023 0.049184 -v -0.253966 0.813615 0.070682 -v -0.326081 0.801206 0.090752 -v -0.379381 0.786140 0.105586 -v -0.400190 0.765755 0.111377 -v -0.111968 0.841089 0.000000 -v -0.183167 0.826023 0.000000 -v -0.263228 0.813615 0.000000 -v -0.337972 0.801206 0.000000 -v -0.393219 0.786140 0.000000 -v -0.414784 0.765755 0.000000 -v -0.108029 0.841089 -0.030065 -v -0.176722 0.826023 -0.049184 -v -0.253966 0.813615 -0.070682 -v -0.326081 0.801206 -0.090752 -v -0.379381 0.786140 -0.105586 -v -0.400190 0.765755 -0.111377 -v -0.096873 0.841089 -0.056896 -v -0.158473 0.826023 -0.093076 -v -0.227741 0.813615 -0.133759 -v -0.292408 0.801206 -0.171740 -v -0.340206 0.786140 -0.199813 -v -0.358865 0.765755 -0.210772 -v -0.079497 0.841089 -0.079497 -v -0.130049 0.826023 -0.130048 -v -0.186892 0.813615 -0.186892 -v -0.239960 0.801206 -0.239960 -v -0.279185 0.786140 -0.279184 -v -0.294497 0.765755 -0.294497 -v -0.056896 0.841089 -0.096873 -v -0.093076 0.826023 -0.158473 -v -0.133759 0.813615 -0.227741 -v -0.171740 0.801206 -0.292408 -v -0.199813 0.786140 -0.340206 -v -0.210772 0.765755 -0.358865 -v -0.030066 0.841089 -0.108029 -v -0.049184 0.826023 -0.176722 -v -0.070682 0.813615 -0.253966 -v -0.090752 0.801206 -0.326081 -v -0.105586 0.786140 -0.379381 -v -0.111377 0.765755 -0.400190 -v 0.000000 0.841089 -0.111968 -v 0.000000 0.826023 -0.183167 -v 0.000000 0.813615 -0.263228 -v 0.000000 0.801206 -0.337972 -v 0.000000 0.786140 -0.393218 -v 0.000000 0.765755 -0.414784 -v 0.030065 0.841089 -0.108029 -v 0.049184 0.826023 -0.176722 -v 0.070682 0.813615 -0.253966 -v 0.090752 0.801206 -0.326081 -v 0.105586 0.786140 -0.379381 -v 0.111377 0.765755 -0.400190 -v 0.056896 0.841089 -0.096873 -v 0.093076 0.826023 -0.158473 -v 0.133759 0.813615 -0.227741 -v 0.171740 0.801206 -0.292408 -v 0.199813 0.786140 -0.340206 -v 0.210772 0.765755 -0.358865 -v 0.079497 0.841089 -0.079497 -v 0.130048 0.826023 -0.130048 -v 0.186892 0.813615 -0.186892 -v 0.239960 0.801206 -0.239960 -v 0.279184 0.786140 -0.279184 -v 0.294497 0.765755 -0.294497 -v 0.096873 0.841089 -0.056896 -v 0.158473 0.826023 -0.093076 -v 0.227741 0.813615 -0.133759 -v 0.292408 0.801206 -0.171740 -v 0.340206 0.786140 -0.199813 -v 0.358865 0.765755 -0.210772 -v 0.108028 0.841089 -0.030065 -v 0.176722 0.826023 -0.049184 -v 0.253966 0.813615 -0.070682 -v 0.326081 0.801206 -0.090752 -v 0.379380 0.786140 -0.105586 -v 0.400190 0.765755 -0.111377 -v 0.088924 0.997741 0.000000 -v 0.000000 1.005054 0.000000 -v 0.085811 0.997741 0.023955 -v 0.115809 0.978466 0.000000 -v 0.111754 0.978466 0.031195 -v 0.103696 0.951211 0.000000 -v 0.100064 0.951211 0.027927 -v 0.075630 0.919969 0.000000 -v 0.072979 0.919969 0.020357 -v 0.076985 0.997741 0.045285 -v 0.100259 0.978466 0.058974 -v 0.089769 0.951211 0.052799 -v 0.065466 0.919969 0.038494 -v 0.063219 0.997741 0.063219 -v 0.082330 0.978466 0.082330 -v 0.073714 0.951211 0.073714 -v 0.053751 0.919969 0.053751 -v 0.045285 0.997741 0.076986 -v 0.058974 0.978466 0.100259 -v 0.052799 0.951211 0.089769 -v 0.038494 0.919969 0.065466 -v 0.023955 0.997741 0.085811 -v 0.031195 0.978466 0.111754 -v 0.027927 0.951211 0.100064 -v 0.020357 0.919969 0.072979 -v 0.000000 0.997741 0.088925 -v 0.000000 0.978466 0.115809 -v 0.000000 0.951211 0.103696 -v 0.000000 0.919969 0.075630 -v -0.023955 0.997741 0.085811 -v -0.031195 0.978466 0.111754 -v -0.027927 0.951211 0.100064 -v -0.020357 0.919969 0.072979 -v -0.045285 0.997741 0.076986 -v -0.058974 0.978466 0.100259 -v -0.052799 0.951211 0.089769 -v -0.038494 0.919969 0.065466 -v -0.063220 0.997741 0.063219 -v -0.082331 0.978466 0.082330 -v -0.073714 0.951211 0.073714 -v -0.053751 0.919969 0.053751 -v -0.076986 0.997741 0.045285 -v -0.100259 0.978466 0.058974 -v -0.089770 0.951211 0.052799 -v -0.065466 0.919969 0.038494 -v -0.085811 0.997741 0.023955 -v -0.111754 0.978466 0.031195 -v -0.100064 0.951211 0.027927 -v -0.072979 0.919969 0.020357 -v -0.088925 0.997741 0.000000 -v -0.115809 0.978466 0.000000 -v -0.103696 0.951211 0.000000 -v -0.075630 0.919969 0.000000 -v -0.085811 0.997741 -0.023955 -v -0.111754 0.978466 -0.031195 -v -0.100064 0.951211 -0.027927 -v -0.072979 0.919969 -0.020357 -v -0.076986 0.997741 -0.045285 -v -0.100259 0.978466 -0.058974 -v -0.089770 0.951211 -0.052799 -v -0.065466 0.919969 -0.038494 -v -0.063220 0.997741 -0.063219 -v -0.082331 0.978466 -0.082330 -v -0.073714 0.951211 -0.073714 -v -0.053751 0.919969 -0.053751 -v -0.045285 0.997741 -0.076986 -v -0.058974 0.978466 -0.100259 -v -0.052799 0.951211 -0.089769 -v -0.038494 0.919969 -0.065466 -v -0.023955 0.997741 -0.085811 -v -0.031195 0.978466 -0.111754 -v -0.027927 0.951211 -0.100064 -v -0.020357 0.919969 -0.072979 -v 0.000000 0.997741 -0.088925 -v 0.000000 0.978466 -0.115809 -v 0.000000 0.951211 -0.103696 -v 0.000000 0.919969 -0.075630 -v 0.023955 0.997741 -0.085811 -v 0.031195 0.978466 -0.111754 -v 0.027927 0.951211 -0.100064 -v 0.020357 0.919969 -0.072979 -v 0.045285 0.997741 -0.076986 -v 0.058974 0.978466 -0.100259 -v 0.052799 0.951211 -0.089769 -v 0.038494 0.919969 -0.065466 -v 0.063219 0.997741 -0.063219 -v 0.082330 0.978466 -0.082330 -v 0.073714 0.951211 -0.073714 -v 0.053751 0.919969 -0.053751 -v 0.076985 0.997741 -0.045285 -v 0.100259 0.978466 -0.058974 -v 0.089769 0.951211 -0.052799 -v 0.065466 0.919969 -0.038494 -v 0.085811 0.997741 -0.023955 -v 0.111754 0.978466 -0.031195 -v 0.100064 0.951211 -0.027927 -v 0.072979 0.919969 -0.020357 -v -0.414952 0.750806 -0.115486 -v 0.115486 0.750806 0.414952 -v 0.414952 0.750806 0.115486 -v 0.218547 0.750806 0.372103 -v -0.218547 0.750806 -0.372103 -v -0.430085 0.750806 0.000000 -v -0.218547 0.750806 0.372103 -v 0.430085 0.750806 0.000000 -v -0.372103 0.750806 0.218547 -v -0.115486 0.750806 -0.414952 -v 0.000000 0.750806 -0.430085 -v -0.414952 0.750806 0.115486 -v 0.115486 0.750806 -0.414952 -v 0.000000 0.750806 0.430085 -v -0.372103 0.750806 -0.218547 -v 0.372103 0.750806 -0.218547 -v -0.115486 0.750806 0.414952 -v 0.372103 0.750806 0.218547 -v 0.414952 0.750806 -0.115486 -v 0.218547 0.750806 -0.372103 -v -0.453012 0.750009 0.000000 -v 0.437073 0.750009 -0.121642 -v 0.453011 0.750009 0.000000 -v 0.121642 0.750009 -0.437072 -v 0.000000 0.750009 -0.453012 -v -0.305360 0.750806 0.305360 -v -0.230198 0.750009 0.391939 -v 0.600960 0.444810 0.085753 -v -0.501255 0.717792 0.000000 -v -0.498530 0.712498 0.039883 -v -0.501667 0.699221 0.063813 -v -0.638129 0.287158 0.000000 -v -0.501666 0.699221 -0.063813 -v 0.605101 0.399712 -0.137265 -v 0.600959 0.444810 -0.085753 -v 0.605956 0.463769 0.000000 -v 0.613258 0.341675 -0.154354 -v 0.619427 0.283145 -0.137236 -v 0.617684 0.235930 -0.085941 -v 0.619427 0.283145 0.137236 -v 0.617684 0.235930 0.085941 -v -0.611709 0.194244 0.000000 -v -0.608884 0.198682 -0.039883 -v 0.619427 0.283145 -0.137236 -v 0.617684 0.235930 -0.085941 -v -0.624826 0.259599 -0.063813 -v -0.631184 0.277569 -0.039883 -v -0.619922 0.238069 0.071790 -v -0.631184 0.277569 0.039883 -v -0.624828 0.259599 0.063813 -v -0.437073 0.750009 -0.121642 -v 0.391939 0.750009 0.230197 -v 0.321638 0.750009 0.321639 -v 0.121642 0.750009 0.437072 -v 0.230197 0.750009 0.391940 -v 0.437073 0.750009 -0.121642 -v 0.000000 0.750009 0.453012 -v 0.321638 0.750009 -0.321639 -v -0.121642 0.750009 -0.437072 -v 0.437073 0.750009 0.121642 -v -0.391940 0.750009 0.230197 -v -0.321639 0.750009 -0.321639 -v -0.230198 0.750009 -0.391939 -v 0.230197 0.750009 -0.391940 -v -0.121642 0.750009 0.437072 -v -0.391940 0.750009 -0.230197 -v -0.437073 0.750009 0.121642 -v -0.305360 0.750806 -0.305360 -v -0.321639 0.750009 -0.321639 -v -0.391940 0.750009 -0.230197 -v 0.305360 0.750806 -0.305360 -v 0.321638 0.750009 -0.321639 -v 0.230197 0.750009 -0.391940 -v 0.391939 0.750009 -0.230197 -v 0.437073 0.750009 0.121642 -v -0.121642 0.750009 0.437072 -v 0.000000 0.750009 0.453012 -v -0.230198 0.750009 -0.391939 -v 0.305360 0.750806 0.305360 -v 0.391939 0.750009 0.230197 -v -0.121642 0.750009 -0.437072 -v 0.321638 0.750009 0.321639 -v 0.230197 0.750009 0.391940 -v 0.121642 0.750009 0.437072 -v -0.437073 0.750009 -0.121642 -v -0.321639 0.750009 0.321639 -v -0.391940 0.750009 0.230197 -v 0.613258 0.341675 0.154354 -v 0.617684 0.235930 0.085941 -v 0.619427 0.283145 0.137236 -vn -0.9019 0.4154 0.1182 -vn -0.9057 0.4071 0.1187 -vn -0.8771 0.4188 0.2353 -vn 0.0584 -0.9983 0.0007 -vn 0.0151 -0.9999 0.0002 -vn 0.0146 -0.9493 0.3140 -vn 0.0567 -0.9475 0.3146 -vn 0.1621 -0.9868 0.0020 -vn 0.1579 -0.9336 0.3216 -vn 0.3924 -0.9198 0.0043 -vn 0.3783 -0.8567 0.3507 -vn 0.7838 -0.6210 0.0053 -vn 0.7268 -0.5539 0.4061 -vn 0.9948 -0.1016 0.0020 -vn 0.9082 -0.0828 0.4103 -vn 0.0031 -0.9398 0.3417 -vn 0.0022 -0.6195 0.7850 -vn 0.0115 -0.6794 0.7336 -vn 0.0447 -0.6756 0.7359 -vn 0.1233 -0.6523 0.7478 -vn 0.2754 -0.5569 0.7836 -vn 0.4601 -0.3163 0.8296 -vn 0.5631 -0.0412 0.8254 -vn -0.0005 0.1222 0.9925 -vn 0.0004 0.0036 1.0000 -vn 0.0029 0.0118 0.9999 -vn 0.0049 0.0293 0.9996 -vn -0.0082 0.0535 0.9985 -vn -0.0465 0.0416 0.9981 -vn -0.0392 0.0031 0.9992 -vn -0.8506 0.4738 -0.2282 -vn -0.8979 0.4242 -0.1177 -vn -0.8809 0.4733 0.0000 -vn -0.0136 0.6824 0.7308 -vn -0.0539 0.6805 0.7308 -vn -0.1476 0.6568 0.7395 -vn -0.3260 0.5606 0.7612 -vn -0.5377 0.3158 0.7818 -vn -0.6116 0.0300 0.7906 -vn -0.9042 0.4271 -0.0000 -vn -0.8979 0.4242 0.1176 -vn -0.0201 0.9495 0.3132 -vn -0.0818 0.9455 0.3153 -vn -0.2277 0.9164 0.3292 -vn -0.5042 0.7853 0.3592 -vn -0.8106 0.4432 0.3826 -vn -0.9212 0.0397 0.3870 -vn -0.0206 0.9494 -0.3133 -vn -0.0217 0.9998 -0.0001 -vn -0.0043 0.9505 -0.3108 -vn -0.0883 0.9961 -0.0005 -vn -0.2469 0.9690 -0.0013 -vn -0.5497 0.8353 -0.0024 -vn -0.8807 0.4737 -0.0020 -vn -0.9991 0.0421 -0.0006 -vn -0.8771 0.4188 -0.2353 -vn -0.9203 0.3912 -0.0000 -vn -0.9057 0.4071 -0.1187 -vn -0.0832 0.9450 -0.3162 -vn -0.2302 0.9148 -0.3318 -vn -0.5056 0.7828 -0.3628 -vn -0.8087 0.4450 -0.3847 -vn -0.9208 0.0421 -0.3877 -vn -0.8979 0.4241 -0.1176 -vn -0.9019 0.4155 -0.1182 -vn -0.0142 0.6824 -0.7308 -vn -0.0554 0.6801 -0.7310 -vn -0.1500 0.6557 -0.7400 -vn -0.3276 0.5606 -0.7605 -vn -0.5375 0.3209 -0.7798 -vn -0.6120 0.0334 -0.7901 -vn 0.0152 -0.9493 -0.3139 -vn 0.0119 -0.6794 -0.7336 -vn 0.0033 -0.9398 -0.3416 -vn 0.0002 0.0042 -1.0000 -vn 0.0035 0.0140 -0.9999 -vn 0.0059 0.0359 -0.9993 -vn -0.0101 0.0643 -0.9979 -vn -0.0516 0.0485 -0.9975 -vn -0.0416 0.0040 -0.9991 -vn 0.0031 -0.6201 -0.7845 -vn -0.0001 0.1225 -0.9925 -vn -0.8979 0.4242 0.1177 -vn 0.0465 -0.6744 -0.7369 -vn 0.1260 -0.6490 -0.7503 -vn 0.2755 -0.5525 -0.7867 -vn 0.4555 -0.3205 -0.8305 -vn 0.5617 -0.0465 -0.8260 -vn -0.8887 0.3917 0.2384 -vn 0.0580 -0.9476 -0.3140 -vn 0.1600 -0.9339 -0.3199 -vn 0.3802 -0.8578 -0.3459 -vn 0.7256 -0.5610 -0.3986 -vn 0.9086 -0.0892 -0.4080 -vn 0.0033 -1.0000 0.0000 -vn 0.9732 0.2301 0.0008 -vn 0.8909 0.2117 0.4018 -vn 0.9129 0.4081 0.0025 -vn 0.8370 0.3809 0.3929 -vn 0.8290 0.5592 0.0038 -vn 0.7645 0.5286 0.3690 -vn 0.7187 0.6954 0.0039 -vn 0.6683 0.6637 0.3359 -vn 0.5796 0.8149 0.0028 -vn 0.5427 0.7797 0.3124 -vn 0.4952 0.8688 0.0023 -vn 0.4581 0.8206 0.3416 -vn 0.5612 0.1370 0.8162 -vn 0.5320 0.2535 0.8079 -vn 0.4976 0.3635 0.7876 -vn 0.4496 0.4721 0.7583 -vn 0.3737 0.5636 0.7367 -vn 0.2891 0.5311 0.7965 -vn -0.0232 -0.0053 0.9997 -vn -0.0168 -0.0103 0.9998 -vn -0.0115 -0.0129 0.9999 -vn -0.0098 -0.0133 0.9999 -vn -0.0148 -0.0139 0.9998 -vn -0.0898 -0.1762 0.9803 -vn -0.5858 -0.1524 0.7960 -vn -0.5389 -0.2888 0.7913 -vn -0.4842 -0.4079 0.7741 -vn -0.4246 -0.5098 0.7482 -vn -0.3559 -0.5848 0.7289 -vn -0.8898 -0.2372 0.3898 -vn -0.8088 -0.4469 0.3824 -vn -0.7025 -0.6133 0.3611 -vn -0.5906 -0.7349 0.3333 -vn -0.4833 -0.8168 0.3151 -vn -0.9121 0.4100 0.0000 -vn -0.9656 -0.2600 -0.0005 -vn -0.8725 -0.4887 -0.0015 -vn -0.7485 -0.6632 -0.0022 -vn -0.6216 -0.7833 -0.0022 -vn -0.5071 -0.8619 -0.0013 -vn -0.4382 -0.8544 0.2792 -vn -0.4561 -0.8899 -0.0008 -vn -0.8891 -0.2389 -0.3904 -vn -0.8070 -0.4485 -0.3841 -vn -0.7010 -0.6134 -0.3638 -vn -0.5905 -0.7338 -0.3360 -vn -0.4848 -0.8154 -0.3165 -vn -0.4410 -0.8529 -0.2793 -vn -0.3597 -0.5842 -0.7275 -vn -0.3581 -0.6823 -0.6374 -vn -0.5855 -0.1547 -0.7958 -vn -0.5385 -0.2917 -0.7905 -vn -0.4845 -0.4098 -0.7729 -vn -0.4265 -0.5101 -0.7469 -vn -0.9096 -0.3993 -0.1152 -vn -0.9712 -0.2047 -0.1219 -vn -0.9130 -0.3266 -0.2446 -vn -0.0205 -0.0179 -0.9996 -vn -0.0246 -0.0057 -0.9997 -vn -0.0209 -0.0122 -0.9997 -vn -0.0176 -0.0169 -0.9997 -vn -0.0168 -0.0181 -0.9997 -vn -0.9091 -0.4003 0.1151 -vn -0.8738 -0.4726 0.1145 -vn -0.7959 -0.5665 0.2136 -vn -0.3531 -0.6841 0.6382 -vn 0.5597 0.1397 -0.8168 -vn 0.5286 0.2555 -0.8095 -vn 0.4942 0.3630 -0.7899 -vn 0.4491 0.4693 -0.7603 -vn 0.3783 0.5609 -0.7364 -vn -0.0920 -0.1744 -0.9804 -vn 0.2953 0.5306 -0.7945 -vn 0.8905 0.2148 -0.4011 -vn 0.8366 0.3841 -0.3905 -vn 0.7652 0.5302 -0.3652 -vn 0.6711 0.6632 -0.3314 -vn 0.5480 0.7777 -0.3082 -vn 0.4645 0.8188 -0.3372 -vn 0.9315 0.2656 -0.2486 -vn 0.9396 0.3424 -0.0000 -vn 0.9476 0.2951 -0.1227 -vn -0.3514 0.9362 0.0020 -vn -0.1445 0.9895 0.0032 -vn -0.1268 0.8788 0.4600 -vn -0.7168 0.6973 -0.0010 -vn -0.3000 0.8383 0.4552 -vn -0.6219 0.6602 0.4212 -vn -0.9018 0.4321 -0.0045 -vn -0.8070 0.4434 0.3899 -vn -0.9302 0.3669 -0.0085 -vn -0.8246 0.3833 0.4161 -vn -0.8507 0.5255 -0.0116 -vn -0.7225 0.5090 0.4679 -vn -0.6685 0.7437 -0.0112 -vn -0.5315 0.6865 0.4962 -vn -0.1165 0.5055 0.8549 -vn -0.2584 0.4707 0.8436 -vn -0.4076 0.3970 0.8223 -vn -0.4503 0.3524 0.8204 -vn -0.3859 0.3958 0.8333 -vn -0.2707 0.4879 0.8299 -vn 0.1416 -0.0012 0.9899 -vn -0.0677 0.5254 0.8482 -vn 0.9896 0.1003 0.1031 -vn 0.9702 0.2133 0.1145 -vn 0.9604 0.1527 0.2329 -vn 0.2419 0.0925 0.9659 -vn 0.2093 0.1707 0.9628 -vn 0.0962 0.1786 0.9792 -vn 0.0096 0.1543 0.9880 -vn -0.0001 0.1520 0.9884 -vn 0.3613 -0.4773 0.8010 -vn 0.6080 -0.2826 0.7420 -vn 0.6792 -0.1068 0.7261 -vn 0.5839 -0.0785 0.8080 -vn 0.4027 -0.2053 0.8920 -vn 0.2795 -0.3387 0.8984 -vn 0.4886 -0.7687 0.4127 -vn 0.7846 -0.5015 0.3645 -vn 0.8939 -0.2796 0.3503 -vn 0.8614 -0.2853 0.4202 -vn 0.6794 -0.5405 0.4963 -vn 0.4584 -0.7546 0.4696 -vn 0.5242 -0.8516 -0.0002 -vn 0.8272 -0.5620 0.0017 -vn 0.9432 -0.3322 0.0035 -vn 0.9333 -0.3591 0.0058 -vn 0.7569 -0.6535 0.0065 -vn 0.4929 -0.8701 0.0041 -vn 0.3225 -0.9466 -0.0011 -vn 0.9129 -0.3266 0.2449 -vn 0.8244 -0.5660 -0.0000 -vn 0.8942 -0.4477 -0.0000 -vn 0.4866 -0.7705 -0.4118 -vn 0.7840 -0.5041 -0.3623 -vn 0.8952 -0.2819 -0.3452 -vn 0.8637 -0.2890 -0.4128 -vn 0.6822 -0.5443 -0.4883 -vn 0.4619 -0.7582 -0.4601 -vn 0.3575 -0.4792 -0.8016 -vn 0.6059 -0.2859 -0.7424 -vn 0.6797 -0.1087 -0.7254 -vn 0.5837 -0.0779 -0.8082 -vn 0.4015 -0.1992 -0.8939 -vn 0.2820 -0.3307 -0.9006 -vn 0.1352 -0.0024 -0.9908 -vn 0.2358 0.0911 -0.9675 -vn 0.2007 0.1707 -0.9647 -vn 0.0861 0.1846 -0.9790 -vn 0.0007 0.1743 -0.9847 -vn -0.0071 0.1690 -0.9856 -vn 0.9666 -0.0429 -0.2526 -vn 0.9604 0.1527 -0.2329 -vn 0.9896 0.1003 -0.1031 -vn -0.1193 0.5034 -0.8558 -vn -0.2613 0.4728 -0.8415 -vn -0.4141 0.3998 -0.8177 -vn -0.4597 0.3571 -0.8131 -vn -0.3940 0.4085 -0.8233 -vn -0.2779 0.4874 -0.8278 -vn -0.0722 0.5201 -0.8510 -vn -0.2987 0.8403 -0.4524 -vn -0.6175 0.6640 -0.4216 -vn -0.8013 0.4502 -0.3939 -vn -0.8194 0.3897 -0.4203 -vn -0.7213 0.5069 -0.4720 -vn -0.5384 0.6707 -0.5103 -vn -0.4820 0.8761 -0.0051 -vn -0.3947 0.8154 0.4236 -vn -0.3219 0.9468 -0.0026 -vn -0.2553 0.9220 0.2911 -vn 0.0043 1.0000 0.0004 -vn -0.0030 0.9999 -0.0101 -vn 0.8535 0.5211 -0.0071 -vn 0.3921 0.6890 -0.6096 -vn 0.8052 -0.5925 -0.0237 -vn 0.5888 -0.2061 -0.7816 -vn 0.6815 -0.7311 -0.0333 -vn 0.4850 -0.4413 -0.7550 -vn -0.2068 0.6382 0.7416 -vn -0.1295 0.8621 0.4900 -vn -0.0334 0.9992 0.0223 -vn 0.0477 0.8640 -0.5011 -vn 0.0993 0.5388 -0.8366 -vn 0.0536 0.2519 -0.9663 -vn 0.0201 0.3227 0.9463 -vn 0.0220 0.7489 0.6623 -vn -0.0260 0.9956 0.0899 -vn -0.0592 0.9309 -0.3604 -vn -0.0802 0.7845 -0.6150 -vn -0.1426 0.5576 -0.8178 -vn 0.2818 -0.1751 0.9434 -vn 0.3039 0.4442 0.8428 -vn 0.0343 0.9838 0.1760 -vn -0.1139 0.9564 -0.2688 -vn -0.1554 0.8589 -0.4880 -vn -0.2074 0.6419 -0.7382 -vn 0.4672 -0.6832 0.5611 -vn 0.6995 0.0044 0.7146 -vn 0.3553 0.8919 0.2796 -vn -0.1704 0.9715 -0.1649 -vn -0.2456 0.9012 -0.3571 -vn -0.2488 0.6511 -0.7171 -vn 0.4945 -0.8692 0.0063 -vn 0.9331 -0.3590 0.0199 -vn 0.7032 0.7106 0.0218 -vn -0.2037 0.9790 -0.0126 -vn -0.3264 0.9429 -0.0661 -vn -0.3976 0.8571 -0.3275 -vn 0.4606 -0.7126 -0.5292 -vn 0.6955 -0.0987 -0.7117 -vn 0.3976 0.8538 -0.3362 -vn -0.1983 0.9634 0.1802 -vn -0.3068 0.8885 0.3411 -vn -0.3937 0.8077 0.4389 -vn 0.2766 -0.2120 -0.9373 -vn 0.2946 0.3662 -0.8826 -vn 0.0466 0.9733 -0.2250 -vn -0.1468 0.9122 0.3826 -vn -0.2026 0.7008 0.6839 -vn -0.2327 0.5065 0.8302 -vn 0.0117 0.3094 -0.9508 -vn 0.0133 0.7087 -0.7054 -vn -0.0276 0.9958 -0.0877 -vn -0.0474 0.8294 0.5566 -vn -0.0153 0.3867 0.9221 -vn 0.0047 0.1195 0.9928 -vn -0.2111 0.6310 -0.7465 -vn -0.1388 0.8488 -0.5102 -vn -0.0250 0.9997 -0.0012 -vn 0.1205 0.7245 0.6786 -vn 0.2608 0.0065 0.9654 -vn 0.2725 -0.2559 0.9275 -vn -0.3956 0.8100 -0.4329 -vn -0.2588 0.9189 -0.2978 -vn 0.0047 0.9999 0.0130 -vn 0.4666 0.5999 0.6499 -vn 0.6219 -0.4009 0.6727 -vn 0.5511 -0.5922 0.5879 -vn 0.6491 0.3850 -0.6561 -vn 0.0560 0.6912 0.7205 -vn -0.1160 0.8046 0.5823 -vn -0.2622 0.8980 0.3535 -vn -0.3416 0.8850 -0.3163 -vn -0.0814 0.7931 -0.6036 -vn -0.1557 0.8578 -0.4899 -vn 0.7287 0.3660 0.5788 -vn 0.3187 0.5394 0.7794 -vn 0.9536 0.3002 -0.0244 -vn -0.1340 0.8368 -0.5308 -vn 0.0954 0.6680 -0.7381 +o teapot +v 0.605903 1.343409 0.000000 +v 0.000000 1.349312 0.000000 +v 0.584584 1.343410 0.162696 +v 0.524218 1.343410 0.307888 +v 0.430191 1.343410 0.430191 +v 0.307888 1.343411 0.524218 +v 0.162696 1.343411 0.584584 +v 0.000000 1.343411 0.605903 +v -0.162696 1.343411 0.584584 +v -0.307888 1.343411 0.524218 +v -0.430191 1.343410 0.430191 +v -0.524218 1.343410 0.307888 +v -0.584584 1.343410 0.162696 +v -0.605903 1.343409 0.000000 +v -0.584584 1.343408 -0.162696 +v -0.524218 1.343408 -0.307888 +v -0.430191 1.343407 -0.430191 +v -0.307888 1.343407 -0.524218 +v -0.162696 1.343407 -0.584584 +v 0.000000 1.343407 -0.605903 +v 0.162696 1.343407 -0.584584 +v 0.307888 1.343407 -0.524218 +v 0.430191 1.343407 -0.430191 +v 0.524218 1.343408 -0.307888 +v 0.584584 1.343408 -0.162696 +v 1.400000 -1.050688 0.000009 +v 1.350740 -1.050688 -0.375917 +v 1.332760 -1.105378 -0.370913 +v 1.381370 -1.105378 0.000009 +v 1.384260 -1.138188 0.000009 +v 1.335550 -1.138188 -0.371690 +v 1.403120 -1.149128 0.000009 +v 1.353760 -1.149128 -0.376756 +v 1.382010 -1.138188 -0.384619 +v 1.432410 -1.138188 0.000009 +v 1.414950 -1.105378 -0.393787 +v 1.466550 -1.105378 0.000009 +v 1.447220 -1.050688 -0.402769 +v 1.500000 -1.050688 0.000009 +v 1.211260 -1.050688 -0.711398 +v 1.195140 -1.105378 -0.701929 +v 1.197640 -1.138188 -0.703400 +v 1.213960 -1.149128 -0.712986 +v 1.239300 -1.138188 -0.727866 +v 1.268840 -1.105378 -0.745216 +v 1.297780 -1.050688 -0.762213 +v 0.994000 -1.050688 -0.993991 +v 0.980770 -1.105378 -0.980761 +v 0.982824 -1.138188 -0.982815 +v 0.996219 -1.149128 -0.996210 +v 1.017010 -1.138188 -1.017000 +v 1.041250 -1.105378 -1.041240 +v 1.065000 -1.050688 -1.064990 +v 0.711407 -1.050688 -1.211250 +v 0.701938 -1.105378 -1.195130 +v 0.703409 -1.138188 -1.197630 +v 0.712995 -1.149128 -1.213950 +v 0.727875 -1.138188 -1.239290 +v 0.745225 -1.105378 -1.268830 +v 0.762222 -1.050688 -1.297770 +v 0.375926 -1.050698 -1.350730 +v 0.370922 -1.105378 -1.332750 +v 0.371699 -1.138188 -1.335540 +v 0.376765 -1.149138 -1.353750 +v 0.384628 -1.138188 -1.382000 +v 0.393796 -1.105388 -1.414940 +v 0.402778 -1.050698 -1.447210 +v 0.000000 -1.050698 -1.399990 +v 0.000000 -1.105378 -1.381360 +v 0.000000 -1.138188 -1.384250 +v 0.000000 -1.149138 -1.403110 +v 0.000000 -1.138198 -1.432400 +v 0.000000 -1.105388 -1.466540 +v 0.000000 -1.050698 -1.499990 +v -0.375926 -1.050698 -1.350730 +v -0.370922 -1.105378 -1.332750 +v -0.371699 -1.138188 -1.335540 +v -0.376765 -1.149138 -1.353750 +v -0.384628 -1.138188 -1.382000 +v -0.393796 -1.105388 -1.414940 +v -0.402778 -1.050698 -1.447210 +v -0.711407 -1.050688 -1.211250 +v -0.701938 -1.105378 -1.195130 +v -0.703409 -1.138188 -1.197630 +v -0.712995 -1.149128 -1.213950 +v -0.727875 -1.138188 -1.239290 +v -0.745225 -1.105378 -1.268830 +v -0.762222 -1.050688 -1.297770 +v -0.994000 -1.050688 -0.993991 +v -0.980770 -1.105378 -0.980761 +v -0.982824 -1.138188 -0.982815 +v -0.996219 -1.149128 -0.996210 +v -1.017010 -1.138188 -1.017000 +v -1.041250 -1.105378 -1.041240 +v -1.065000 -1.050688 -1.064990 +v -1.211260 -1.050688 -0.711398 +v -1.195140 -1.105378 -0.701929 +v -1.197640 -1.138188 -0.703400 +v -1.213960 -1.149128 -0.712986 +v -1.239300 -1.138188 -0.727866 +v -1.268840 -1.105378 -0.745216 +v -1.297780 -1.050688 -0.762213 +v -1.350740 -1.050688 -0.375917 +v -1.332760 -1.105378 -0.370913 +v -1.335550 -1.138188 -0.371690 +v -1.353760 -1.149128 -0.376756 +v -1.382010 -1.138188 -0.384619 +v -1.414950 -1.105378 -0.393787 +v -1.447220 -1.050688 -0.402769 +v -1.400000 -1.050688 0.000009 +v -1.381370 -1.105378 0.000009 +v -1.384260 -1.138188 0.000009 +v -1.403120 -1.149128 0.000009 +v -1.432410 -1.138188 0.000009 +v -1.466550 -1.105378 0.000009 +v -1.500000 -1.050688 0.000009 +v -1.350740 -1.050688 0.375935 +v -1.332760 -1.105378 0.370931 +v -1.335550 -1.138188 0.371708 +v -1.353760 -1.149128 0.376774 +v -1.382010 -1.138188 0.384637 +v -1.414950 -1.105378 0.393805 +v -1.447220 -1.050688 0.402787 +v -1.211260 -1.050688 0.711416 +v -1.195140 -1.105378 0.701947 +v -1.197640 -1.138188 0.703418 +v -1.213960 -1.149128 0.713004 +v -1.239300 -1.138188 0.727884 +v -1.268840 -1.105378 0.745234 +v -1.297780 -1.050688 0.762231 +v -0.994000 -1.050688 0.994009 +v -0.980770 -1.105378 0.980779 +v -0.982824 -1.138188 0.982833 +v -0.996219 -1.149128 0.996228 +v -1.017010 -1.138188 1.017020 +v -1.041250 -1.105378 1.041260 +v -1.065000 -1.050688 1.065010 +v -0.711407 -1.050688 1.211270 +v -0.701938 -1.105378 1.195150 +v -0.703409 -1.138188 1.197650 +v -0.712995 -1.149128 1.213970 +v -0.727875 -1.138188 1.239310 +v -0.745225 -1.105378 1.268850 +v -0.762222 -1.050688 1.297790 +v -0.375926 -1.050688 1.350750 +v -0.370922 -1.105368 1.332770 +v -0.371699 -1.138178 1.335560 +v -0.376765 -1.149128 1.353770 +v -0.384628 -1.138178 1.382020 +v -0.393796 -1.105368 1.414960 +v -0.402778 -1.050678 1.447230 +v 0.000000 -1.050678 1.400010 +v 0.000000 -1.105368 1.381380 +v 0.000000 -1.138178 1.384270 +v 0.000000 -1.149118 1.403130 +v 0.000000 -1.138178 1.432420 +v 0.000000 -1.105368 1.466560 +v 0.000000 -1.050678 1.500010 +v 0.375926 -1.050688 1.350750 +v 0.370922 -1.105368 1.332770 +v 0.371699 -1.138178 1.335560 +v 0.376765 -1.149128 1.353770 +v 0.384628 -1.138178 1.382020 +v 0.393796 -1.105368 1.414960 +v 0.402778 -1.050678 1.447230 +v 0.711407 -1.050688 1.211270 +v 0.701938 -1.105378 1.195150 +v 0.703409 -1.138188 1.197650 +v 0.712995 -1.149128 1.213970 +v 0.727875 -1.138188 1.239310 +v 0.745225 -1.105378 1.268850 +v 0.762222 -1.050688 1.297790 +v 0.994000 -1.050688 0.994009 +v 0.980770 -1.105378 0.980779 +v 0.982824 -1.138188 0.982833 +v 0.996219 -1.149128 0.996228 +v 1.017010 -1.138188 1.017020 +v 1.041250 -1.105378 1.041260 +v 1.065000 -1.050688 1.065010 +v 1.211260 -1.050688 0.711416 +v 1.195140 -1.105378 0.701947 +v 1.197640 -1.138188 0.703418 +v 1.213960 -1.149128 0.713004 +v 1.239300 -1.138188 0.727884 +v 1.268840 -1.105378 0.745234 +v 1.297780 -1.050688 0.762231 +v 1.350740 -1.050688 0.375935 +v 1.332760 -1.105378 0.370931 +v 1.335550 -1.138188 0.371708 +v 1.353760 -1.149128 0.376774 +v 1.382010 -1.138188 0.384637 +v 1.414950 -1.105378 0.393805 +v 1.447220 -1.050688 0.402787 +v 1.566710 -0.788538 -0.436024 +v 1.623840 -0.788538 0.000008 +v 1.679490 -0.528468 -0.467414 +v 1.740740 -0.528468 0.000007 +v 1.778880 -0.272568 -0.495075 +v 1.843750 -0.272558 0.000006 +v 1.858160 -0.022908 -0.517142 +v 1.925930 -0.022908 0.000005 +v 1.910650 0.218412 -0.531750 +v 1.980320 0.218412 0.000004 +v 1.929630 0.449310 -0.537034 +v 2.000000 0.449312 0.000003 +v 1.404920 -0.788538 -0.825145 +v 1.506060 -0.528468 -0.884547 +v 1.595190 -0.272568 -0.936892 +v 1.666280 -0.022908 -0.978651 +v 1.713350 0.218412 -1.006300 +v 1.730370 0.449308 -1.016300 +v 1.152930 -0.788538 -1.152920 +v 1.235930 -0.528468 -1.235920 +v 1.309060 -0.272558 -1.309050 +v 1.367410 -0.022918 -1.367400 +v 1.406030 0.218402 -1.406030 +v 1.420000 0.449307 -1.420000 +v 0.825153 -0.788548 -1.404910 +v 0.884554 -0.528478 -1.506050 +v 0.936898 -0.272578 -1.595180 +v 0.978656 -0.022918 -1.666270 +v 1.006300 0.218402 -1.713350 +v 1.016300 0.449306 -1.730370 +v 0.436032 -0.788548 -1.566700 +v 0.467421 -0.528478 -1.679480 +v 0.495081 -0.272568 -1.778870 +v 0.517147 -0.022918 -1.858150 +v 0.531754 0.218402 -1.910650 +v 0.537037 0.449305 -1.929630 +v 0.000000 -0.788548 -1.623830 +v 0.000000 -0.528478 -1.740730 +v 0.000000 -0.272568 -1.843740 +v 0.000000 -0.022918 -1.925920 +v 0.000000 0.218402 -1.980320 +v 0.000000 0.449305 -2.000000 +v -0.436032 -0.788548 -1.566700 +v -0.467421 -0.528478 -1.679480 +v -0.495081 -0.272578 -1.778870 +v -0.517147 -0.022918 -1.858150 +v -0.531754 0.218402 -1.910650 +v -0.537037 0.449305 -1.929630 +v -0.825153 -0.788548 -1.404910 +v -0.884554 -0.528478 -1.506050 +v -0.936898 -0.272578 -1.595180 +v -0.978656 -0.022918 -1.666270 +v -1.006300 0.218402 -1.713350 +v -1.016300 0.449306 -1.730370 +v -1.152930 -0.788538 -1.152920 +v -1.235930 -0.528468 -1.235920 +v -1.309060 -0.272558 -1.309050 +v -1.367410 -0.022918 -1.367400 +v -1.406030 0.218402 -1.406030 +v -1.420000 0.449307 -1.420000 +v -1.404920 -0.788538 -0.825145 +v -1.506060 -0.528468 -0.884547 +v -1.595190 -0.272568 -0.936892 +v -1.666280 -0.022908 -0.978651 +v -1.713350 0.218412 -1.006300 +v -1.730370 0.449308 -1.016300 +v -1.566710 -0.788538 -0.436024 +v -1.679490 -0.528468 -0.467414 +v -1.778880 -0.272558 -0.495075 +v -1.858160 -0.022908 -0.517142 +v -1.910650 0.218412 -0.531750 +v -1.929630 0.449310 -0.537034 +v -1.623840 -0.788538 0.000008 +v -1.740740 -0.528468 0.000007 +v -1.843750 -0.272558 0.000006 +v -1.925930 -0.022908 0.000005 +v -1.980320 0.218412 0.000004 +v -2.000000 0.449312 0.000003 +v -1.566710 -0.788538 0.436040 +v -1.679490 -0.528468 0.467428 +v -1.778880 -0.272568 0.495087 +v -1.858160 -0.022908 0.517152 +v -1.910650 0.218412 0.531758 +v -1.929630 0.449314 0.537040 +v -1.404920 -0.788538 0.825161 +v -1.506060 -0.528468 0.884561 +v -1.595190 -0.272568 0.936904 +v -1.666280 -0.022908 0.978661 +v -1.713350 0.218412 1.006300 +v -1.730370 0.449316 1.016300 +v -1.152930 -0.788538 1.152940 +v -1.235930 -0.528468 1.235940 +v -1.309060 -0.272558 1.309070 +v -1.367410 -0.022908 1.367420 +v -1.406030 0.218422 1.406030 +v -1.420000 0.449317 1.420000 +v -0.825153 -0.788528 1.404930 +v -0.884554 -0.528458 1.506070 +v -0.936898 -0.272558 1.595200 +v -0.978656 -0.022898 1.666290 +v -1.006300 0.218422 1.713350 +v -1.016300 0.449318 1.730370 +v -0.436032 -0.788528 1.566720 +v -0.467421 -0.528458 1.679500 +v -0.495081 -0.272548 1.778890 +v -0.517147 -0.022898 1.858170 +v -0.531754 0.218422 1.910650 +v -0.537037 0.449319 1.929630 +v 0.000000 -0.788528 1.623850 +v 0.000000 -0.528458 1.740750 +v 0.000000 -0.272548 1.843760 +v 0.000000 -0.022898 1.925940 +v 0.000000 0.218422 1.980320 +v 0.000000 0.449319 2.000000 +v 0.436032 -0.788528 1.566720 +v 0.467421 -0.528458 1.679500 +v 0.495081 -0.272558 1.778890 +v 0.517147 -0.022898 1.858170 +v 0.531754 0.218422 1.910650 +v 0.537037 0.449319 1.929630 +v 0.825153 -0.788528 1.404930 +v 0.884554 -0.528458 1.506070 +v 0.936898 -0.272558 1.595200 +v 0.978656 -0.022898 1.666290 +v 1.006300 0.218422 1.713350 +v 1.016300 0.449318 1.730370 +v 1.152930 -0.788538 1.152940 +v 1.235930 -0.528468 1.235940 +v 1.309060 -0.272558 1.309070 +v 1.367410 -0.022908 1.367420 +v 1.406030 0.218422 1.406030 +v 1.420000 0.449317 1.420000 +v 1.404920 -0.788538 0.825161 +v 1.506060 -0.528468 0.884561 +v 1.595190 -0.272568 0.936904 +v 1.666280 -0.022908 0.978661 +v 1.713350 0.218412 1.006300 +v 1.730370 0.449316 1.016300 +v 1.566710 -0.788538 0.436040 +v 1.679490 -0.528468 0.467428 +v 1.778880 -0.272558 0.495087 +v 1.858160 -0.022908 0.517152 +v 1.910650 0.218412 0.531758 +v 1.929630 0.449314 0.537040 +v 1.893900 0.655907 -0.527089 +v 1.962960 0.655909 0.000003 +v 1.804560 0.827088 -0.502227 +v 1.870370 0.827090 0.000002 +v 1.688430 0.964935 -0.469906 +v 1.750000 0.964937 0.000001 +v 1.572290 1.071532 -0.437585 +v 1.629630 1.071534 0.000001 +v 1.482960 1.148963 -0.412722 +v 1.537040 1.148965 0.000001 +v 1.447220 1.199311 -0.402777 +v 1.500000 1.199312 0.000001 +v 1.698330 0.655905 -0.997473 +v 1.618220 0.827087 -0.950423 +v 1.514070 0.964934 -0.889258 +v 1.409930 1.071531 -0.828092 +v 1.329820 1.148962 -0.781042 +v 1.297780 1.199309 -0.762221 +v 1.393700 0.655904 -1.393700 +v 1.327960 0.827085 -1.327960 +v 1.242500 0.964932 -1.242500 +v 1.157040 1.071530 -1.157040 +v 1.091300 1.148961 -1.091300 +v 1.065000 1.199308 -1.065000 +v 0.997476 0.655903 -1.698330 +v 0.950425 0.827084 -1.618220 +v 0.889259 0.964931 -1.514070 +v 0.828093 1.071529 -1.409930 +v 0.781043 1.148960 -1.329820 +v 0.762222 1.199307 -1.297780 +v 0.527092 0.655902 -1.893900 +v 0.502229 0.827083 -1.804560 +v 0.469907 0.964931 -1.688430 +v 0.437586 1.071528 -1.572290 +v 0.412723 1.148960 -1.482960 +v 0.402778 1.199307 -1.447220 +v 0.000000 0.655902 -1.962960 +v 0.000000 0.827083 -1.870370 +v 0.000000 0.964931 -1.750000 +v 0.000000 1.071528 -1.629630 +v 0.000000 1.148959 -1.537040 +v 0.000000 1.199306 -1.500000 +v -0.527092 0.655902 -1.893900 +v -0.502229 0.827083 -1.804560 +v -0.469907 0.964931 -1.688430 +v -0.437586 1.071528 -1.572290 +v -0.412723 1.148960 -1.482960 +v -0.402778 1.199307 -1.447220 +v -0.997476 0.655903 -1.698330 +v -0.950425 0.827084 -1.618220 +v -0.889259 0.964931 -1.514070 +v -0.828093 1.071529 -1.409930 +v -0.781043 1.148960 -1.329820 +v -0.762222 1.199307 -1.297780 +v -1.393700 0.655904 -1.393700 +v -1.327960 0.827085 -1.327960 +v -1.242500 0.964932 -1.242500 +v -1.157040 1.071530 -1.157040 +v -1.091300 1.148961 -1.091300 +v -1.065000 1.199308 -1.065000 +v -1.698330 0.655905 -0.997473 +v -1.618220 0.827087 -0.950423 +v -1.514070 0.964934 -0.889258 +v -1.409930 1.071531 -0.828092 +v -1.329820 1.148962 -0.781042 +v -1.297780 1.199309 -0.762221 +v -1.893900 0.655907 -0.527089 +v -1.804560 0.827088 -0.502227 +v -1.688430 0.964935 -0.469906 +v -1.572290 1.071532 -0.437585 +v -1.482960 1.148963 -0.412722 +v -1.447220 1.199311 -0.402777 +v -1.962960 0.655909 0.000003 +v -1.870370 0.827090 0.000002 +v -1.750000 0.964937 0.000001 +v -1.629630 1.071534 0.000001 +v -1.537040 1.148965 0.000001 +v -1.500000 1.199312 0.000001 +v -1.893900 0.655911 0.527095 +v -1.804560 0.827092 0.502231 +v -1.688430 0.964939 0.469908 +v -1.572290 1.071536 0.437587 +v -1.482960 1.148967 0.412724 +v -1.447220 1.199313 0.402779 +v -1.698330 0.655913 0.997479 +v -1.618220 0.827094 0.950427 +v -1.514070 0.964940 0.889260 +v -1.409930 1.071537 0.828094 +v -1.329820 1.148968 0.781044 +v -1.297780 1.199315 0.762223 +v -1.393700 0.655914 1.393700 +v -1.327960 0.827095 1.327960 +v -1.242500 0.964942 1.242500 +v -1.157040 1.071538 1.157040 +v -1.091300 1.148969 1.091300 +v -1.065000 1.199316 1.065000 +v -0.997476 0.655915 1.698330 +v -0.950425 0.827096 1.618220 +v -0.889259 0.964943 1.514070 +v -0.828093 1.071539 1.409930 +v -0.781043 1.148970 1.329820 +v -0.762222 1.199317 1.297780 +v -0.527092 0.655916 1.893900 +v -0.502229 0.827097 1.804560 +v -0.469907 0.964943 1.688430 +v -0.437586 1.071540 1.572290 +v -0.412723 1.148970 1.482960 +v -0.402778 1.199317 1.447220 +v 0.000000 0.655916 1.962960 +v 0.000000 0.827097 1.870370 +v 0.000000 0.964943 1.750000 +v 0.000000 1.071540 1.629630 +v 0.000000 1.148971 1.537040 +v 0.000000 1.199318 1.500000 +v 0.527092 0.655916 1.893900 +v 0.502229 0.827097 1.804560 +v 0.469907 0.964943 1.688430 +v 0.437586 1.071540 1.572290 +v 0.412723 1.148970 1.482960 +v 0.402778 1.199317 1.447220 +v 0.997476 0.655915 1.698330 +v 0.950425 0.827096 1.618220 +v 0.889259 0.964943 1.514070 +v 0.828093 1.071539 1.409930 +v 0.781043 1.148970 1.329820 +v 0.762222 1.199317 1.297780 +v 1.393700 0.655914 1.393700 +v 1.327960 0.827095 1.327960 +v 1.242500 0.964942 1.242500 +v 1.157040 1.071538 1.157040 +v 1.091300 1.148969 1.091300 +v 1.065000 1.199316 1.065000 +v 1.698330 0.655913 0.997479 +v 1.618220 0.827094 0.950427 +v 1.514070 0.964940 0.889260 +v 1.409930 1.071537 0.828094 +v 1.329820 1.148968 0.781044 +v 1.297780 1.199315 0.762223 +v 1.893900 0.655911 0.527095 +v 1.804560 0.827092 0.502231 +v 1.688430 0.964939 0.469908 +v 1.572290 1.071536 0.437587 +v 1.482960 1.148967 0.412724 +v 1.447220 1.199313 0.402779 +v 1.022220 1.327090 0.000000 +v 0.986255 1.327091 0.274486 +v 1.284370 1.302437 0.000000 +v 1.239180 1.302438 0.344878 +v 1.427780 1.271534 0.000000 +v 1.377540 1.271535 0.383385 +v 1.487850 1.236465 0.000000 +v 1.435500 1.236466 0.399515 +v 0.884412 1.327092 0.519440 +v 1.111220 1.302439 0.652653 +v 1.235290 1.271537 0.725523 +v 1.287260 1.236468 0.756047 +v 0.725778 1.327093 0.725778 +v 0.911906 1.302440 0.911906 +v 1.013720 1.271538 1.013720 +v 1.056370 1.236469 1.056370 +v 0.519440 1.327093 0.884412 +v 0.652653 1.302441 1.111220 +v 0.725523 1.271538 1.235290 +v 0.756047 1.236470 1.287260 +v 0.274486 1.327093 0.986255 +v 0.344878 1.302441 1.239180 +v 0.383385 1.271539 1.377540 +v 0.399515 1.236470 1.435500 +v 0.000000 1.327094 1.022220 +v 0.000000 1.302442 1.284370 +v 0.000000 1.271539 1.427780 +v 0.000000 1.236470 1.487850 +v -0.274486 1.327093 0.986255 +v -0.344878 1.302441 1.239180 +v -0.383385 1.271539 1.377540 +v -0.399515 1.236470 1.435500 +v -0.519440 1.327093 0.884412 +v -0.652653 1.302441 1.111220 +v -0.725523 1.271538 1.235290 +v -0.756047 1.236470 1.287260 +v -0.725778 1.327093 0.725778 +v -0.911906 1.302440 0.911906 +v -1.013720 1.271538 1.013720 +v -1.056370 1.236469 1.056370 +v -0.884412 1.327092 0.519440 +v -1.111220 1.302439 0.652653 +v -1.235290 1.271537 0.725523 +v -1.287260 1.236468 0.756047 +v -0.986255 1.327091 0.274486 +v -1.239180 1.302438 0.344878 +v -1.377540 1.271535 0.383385 +v -1.435500 1.236466 0.399515 +v -1.022220 1.327090 0.000000 +v -1.284370 1.302437 0.000000 +v -1.427780 1.271534 0.000000 +v -1.487850 1.236465 0.000000 +v -0.986255 1.327089 -0.274486 +v -1.239180 1.302436 -0.344878 +v -1.377540 1.271533 -0.383385 +v -1.435500 1.236464 -0.399515 +v -0.884412 1.327088 -0.519440 +v -1.111220 1.302435 -0.652653 +v -1.235290 1.271531 -0.725523 +v -1.287260 1.236462 -0.756047 +v -0.725778 1.327087 -0.725778 +v -0.911906 1.302434 -0.911906 +v -1.013720 1.271530 -1.013720 +v -1.056370 1.236461 -1.056370 +v -0.519440 1.327087 -0.884412 +v -0.652653 1.302433 -1.111220 +v -0.725523 1.271529 -1.235290 +v -0.756047 1.236460 -1.287260 +v -0.274486 1.327086 -0.986255 +v -0.344878 1.302432 -1.239180 +v -0.383385 1.271529 -1.377540 +v -0.399515 1.236460 -1.435500 +v 0.000000 1.327086 -1.022220 +v 0.000000 1.302432 -1.284370 +v 0.000000 1.271529 -1.427780 +v 0.000000 1.236460 -1.487850 +v 0.274486 1.327086 -0.986255 +v 0.344878 1.302432 -1.239180 +v 0.383385 1.271529 -1.377540 +v 0.399515 1.236460 -1.435500 +v 0.519440 1.327087 -0.884412 +v 0.652653 1.302433 -1.111220 +v 0.725523 1.271529 -1.235290 +v 0.756047 1.236460 -1.287260 +v 0.725778 1.327087 -0.725778 +v 0.911906 1.302434 -0.911906 +v 1.013720 1.271530 -1.013720 +v 1.056370 1.236461 -1.056370 +v 0.884412 1.327088 -0.519440 +v 1.111220 1.302435 -0.652653 +v 1.235290 1.271531 -0.725523 +v 1.287260 1.236462 -0.756047 +v 0.986255 1.327089 -0.274486 +v 1.239180 1.302436 -0.344878 +v 1.377540 1.271533 -0.383385 +v 1.435500 1.236464 -0.399515 +v 0.192963 -1.350688 -0.053694 +v 0.200000 -1.350688 0.000010 +v 0.165279 -1.436108 -0.046035 +v 0.171296 -1.436108 0.000010 +v 0.173037 -1.350688 -0.101620 +v 0.148234 -1.436108 -0.087096 +v 0.142000 -1.350688 -0.141990 +v 0.121672 -1.436108 -0.121662 +v 0.101630 -1.350688 -0.173027 +v 0.087106 -1.436108 -0.148224 +v 0.053704 -1.350688 -0.192953 +v 0.046045 -1.436108 -0.165269 +v 0.000000 -1.350688 -0.199990 +v 0.000000 -1.436108 -0.171286 +v -0.053704 -1.350688 -0.192953 +v -0.046045 -1.436108 -0.165269 +v -0.101630 -1.350688 -0.173027 +v -0.087106 -1.436108 -0.148224 +v -0.142000 -1.350688 -0.141990 +v -0.121672 -1.436108 -0.121662 +v -0.173037 -1.350688 -0.101620 +v -0.148234 -1.436108 -0.087096 +v -0.192963 -1.350688 -0.053694 +v -0.165279 -1.436108 -0.046035 +v -0.200000 -1.350688 0.000010 +v -0.171296 -1.436108 0.000010 +v -0.192963 -1.350688 0.053714 +v -0.165279 -1.436108 0.046055 +v -0.173037 -1.350688 0.101640 +v -0.148234 -1.436108 0.087116 +v -0.142000 -1.350688 0.142010 +v -0.121672 -1.436108 0.121682 +v -0.101630 -1.350688 0.173047 +v -0.087106 -1.436108 0.148244 +v -0.053704 -1.350688 0.192973 +v -0.046045 -1.436108 0.165289 +v 0.000000 -1.350688 0.200010 +v 0.000000 -1.436108 0.171306 +v 0.053704 -1.350688 0.192973 +v 0.046045 -1.436108 0.165289 +v 0.101630 -1.350688 0.173047 +v 0.087106 -1.436108 0.148244 +v 0.142000 -1.350688 0.142010 +v 0.121672 -1.436108 0.121682 +v 0.173037 -1.350688 0.101640 +v 0.148234 -1.436108 0.087116 +v 0.192963 -1.350688 0.053714 +v 0.165279 -1.436108 0.046055 +v 0.338579 -1.286798 -0.094220 +v 0.350926 -1.286798 0.000010 +v 0.553875 -1.239578 -0.154140 +v 0.574074 -1.239578 0.000009 +v 0.795972 -1.200688 -0.221519 +v 0.825000 -1.200688 0.000009 +v 1.021990 -1.161798 -0.284422 +v 1.059260 -1.161798 0.000009 +v 1.189040 -1.114578 -0.330915 +v 1.232410 -1.114578 0.000009 +v 1.254260 -1.050688 -0.349065 +v 1.300000 -1.050688 0.000009 +v 0.303616 -1.286798 -0.178312 +v 0.496680 -1.239578 -0.291705 +v 0.713778 -1.200688 -0.419213 +v 0.916455 -1.161798 -0.538252 +v 1.066260 -1.114578 -0.626237 +v 1.124740 -1.050688 -0.660584 +v 0.249157 -1.286798 -0.249147 +v 0.407593 -1.239578 -0.407583 +v 0.585750 -1.200688 -0.585741 +v 0.752074 -1.161798 -0.752065 +v 0.875009 -1.114578 -0.875000 +v 0.923000 -1.050688 -0.922991 +v 0.178322 -1.286798 -0.303606 +v 0.291715 -1.239578 -0.496670 +v 0.419222 -1.200688 -0.713769 +v 0.538261 -1.161798 -0.916446 +v 0.626246 -1.114578 -1.066250 +v 0.660593 -1.050688 -1.124730 +v 0.094230 -1.286798 -0.338569 +v 0.154150 -1.239578 -0.553865 +v 0.221528 -1.200688 -0.795963 +v 0.284431 -1.161798 -1.021980 +v 0.330924 -1.114578 -1.189030 +v 0.349074 -1.050688 -1.254250 +v 0.000000 -1.286798 -0.350916 +v 0.000000 -1.239578 -0.574064 +v 0.000000 -1.200688 -0.824991 +v 0.000000 -1.161798 -1.059250 +v 0.000000 -1.114578 -1.232400 +v 0.000000 -1.050688 -1.299990 +v -0.094230 -1.286798 -0.338569 +v -0.154150 -1.239578 -0.553865 +v -0.221528 -1.200688 -0.795963 +v -0.284431 -1.161798 -1.021980 +v -0.330924 -1.114578 -1.189030 +v -0.349074 -1.050688 -1.254250 +v -0.178322 -1.286798 -0.303606 +v -0.291715 -1.239578 -0.496670 +v -0.419222 -1.200688 -0.713769 +v -0.538261 -1.161798 -0.916446 +v -0.626246 -1.114578 -1.066250 +v -0.660593 -1.050688 -1.124730 +v -0.249157 -1.286798 -0.249147 +v -0.407593 -1.239578 -0.407583 +v -0.585750 -1.200688 -0.585741 +v -0.752074 -1.161798 -0.752065 +v -0.875009 -1.114578 -0.875000 +v -0.923000 -1.050688 -0.922991 +v -0.303616 -1.286798 -0.178312 +v -0.496680 -1.239578 -0.291705 +v -0.713778 -1.200688 -0.419213 +v -0.916455 -1.161798 -0.538252 +v -1.066260 -1.114578 -0.626237 +v -1.124740 -1.050688 -0.660584 +v -0.338579 -1.286798 -0.094220 +v -0.553875 -1.239578 -0.154140 +v -0.795972 -1.200688 -0.221519 +v -1.021990 -1.161798 -0.284422 +v -1.189040 -1.114578 -0.330915 +v -1.254260 -1.050688 -0.349065 +v -0.350926 -1.286798 0.000010 +v -0.574074 -1.239578 0.000009 +v -0.825000 -1.200688 0.000009 +v -1.059260 -1.161798 0.000009 +v -1.232410 -1.114578 0.000009 +v -1.300000 -1.050688 0.000009 +v -0.338579 -1.286798 0.094240 +v -0.553875 -1.239578 0.154160 +v -0.795972 -1.200688 0.221537 +v -1.021990 -1.161798 0.284440 +v -1.189040 -1.114578 0.330933 +v -1.254260 -1.050688 0.349083 +v -0.303616 -1.286798 0.178332 +v -0.496680 -1.239578 0.291725 +v -0.713778 -1.200688 0.419231 +v -0.916455 -1.161798 0.538270 +v -1.066260 -1.114578 0.626255 +v -1.124740 -1.050688 0.660602 +v -0.249157 -1.286798 0.249167 +v -0.407593 -1.239578 0.407603 +v -0.585750 -1.200688 0.585759 +v -0.752074 -1.161798 0.752083 +v -0.875009 -1.114578 0.875018 +v -0.923000 -1.050688 0.923009 +v -0.178322 -1.286798 0.303626 +v -0.291715 -1.239578 0.496690 +v -0.419222 -1.200688 0.713787 +v -0.538261 -1.161798 0.916464 +v -0.626246 -1.114578 1.066270 +v -0.660593 -1.050688 1.124750 +v -0.094230 -1.286798 0.338589 +v -0.154150 -1.239578 0.553885 +v -0.221528 -1.200688 0.795981 +v -0.284431 -1.161798 1.022000 +v -0.330924 -1.114578 1.189050 +v -0.349074 -1.050688 1.254270 +v 0.000000 -1.286798 0.350936 +v 0.000000 -1.239578 0.574084 +v 0.000000 -1.200688 0.825009 +v 0.000000 -1.161798 1.059270 +v 0.000000 -1.114578 1.232420 +v 0.000000 -1.050688 1.300010 +v 0.094230 -1.286798 0.338589 +v 0.154150 -1.239578 0.553885 +v 0.221528 -1.200688 0.795981 +v 0.284431 -1.161798 1.022000 +v 0.330924 -1.114578 1.189050 +v 0.349074 -1.050688 1.254270 +v 0.178322 -1.286798 0.303626 +v 0.291715 -1.239578 0.496690 +v 0.419222 -1.200688 0.713787 +v 0.538261 -1.161798 0.916464 +v 0.626246 -1.114578 1.066270 +v 0.660593 -1.050688 1.124750 +v 0.249157 -1.286798 0.249167 +v 0.407593 -1.239578 0.407603 +v 0.585750 -1.200688 0.585759 +v 0.752074 -1.161798 0.752083 +v 0.875009 -1.114578 0.875018 +v 0.923000 -1.050688 0.923009 +v 0.303616 -1.286798 0.178332 +v 0.496680 -1.239578 0.291725 +v 0.713778 -1.200688 0.419231 +v 0.916455 -1.161798 0.538270 +v 1.066260 -1.114578 0.626255 +v 1.124740 -1.050688 0.660602 +v 0.338579 -1.286798 0.094240 +v 0.553875 -1.239578 0.154160 +v 0.795972 -1.200688 0.221537 +v 1.021990 -1.161798 0.284440 +v 1.189040 -1.114578 0.330933 +v 1.254260 -1.050688 0.349083 +v -1.924540 -0.674648 0.000007 +v -1.600000 -0.675688 0.000007 +v -1.927040 -0.691238 -0.124992 +v -1.592590 -0.692358 -0.124992 +v -2.196300 -0.667358 0.000007 +v -2.206450 -0.683408 -0.124992 +v -2.428240 -0.662148 -0.124993 +v -2.412500 -0.647558 0.000007 +v -2.589850 -0.620748 -0.124993 +v -2.570370 -0.609018 0.000007 +v -2.688700 -0.552498 -0.124993 +v -2.667130 -0.545478 0.000007 +v -2.722220 -0.450688 -0.124993 +v -2.700000 -0.450688 0.000007 +v -1.933300 -0.732708 -0.199992 +v -1.574070 -0.734018 -0.199992 +v -2.231820 -0.723528 -0.199992 +v -2.467590 -0.698608 -0.199992 +v -2.638550 -0.650068 -0.199993 +v -2.742630 -0.570058 -0.199993 +v -2.777780 -0.450688 -0.199993 +v -1.941440 -0.786628 -0.224992 +v -1.550000 -0.788188 -0.224992 +v -2.264810 -0.775688 -0.224992 +v -2.518750 -0.745998 -0.224992 +v -2.701850 -0.688188 -0.224992 +v -2.812730 -0.592878 -0.224993 +v -2.850000 -0.450688 -0.224993 +v -1.949570 -0.840538 -0.199992 +v -1.525930 -0.842358 -0.199992 +v -2.297810 -0.827848 -0.199992 +v -2.569910 -0.793398 -0.199992 +v -2.765160 -0.726308 -0.199992 +v -2.882840 -0.615698 -0.199993 +v -2.922220 -0.450688 -0.199993 +v -1.955830 -0.882018 -0.124992 +v -1.507410 -0.884018 -0.124992 +v -2.323180 -0.867968 -0.124992 +v -2.609260 -0.829858 -0.124992 +v -2.813850 -0.755628 -0.124992 +v -2.936760 -0.633248 -0.124993 +v -2.977780 -0.450688 -0.124993 +v -1.958330 -0.898608 0.000008 +v -1.500000 -0.900688 0.000008 +v -2.333330 -0.884018 0.000008 +v -2.625000 -0.844438 0.000008 +v -2.833330 -0.767358 0.000008 +v -2.958330 -0.640268 0.000007 +v -3.000000 -0.450688 0.000007 +v -1.507410 -0.884018 0.125008 +v -1.955830 -0.882018 0.125008 +v -2.323180 -0.867968 0.125008 +v -2.609260 -0.829858 0.125008 +v -2.813850 -0.755628 0.125008 +v -2.936760 -0.633248 0.125007 +v -2.977780 -0.450688 0.125007 +v -1.525930 -0.842358 0.200008 +v -1.949570 -0.840538 0.200008 +v -2.297810 -0.827848 0.200008 +v -2.569910 -0.793398 0.200008 +v -2.765160 -0.726308 0.200008 +v -2.882840 -0.615698 0.200007 +v -2.922220 -0.450688 0.200007 +v -1.550000 -0.788188 0.225008 +v -1.941440 -0.786628 0.225008 +v -2.264810 -0.775688 0.225008 +v -2.518750 -0.745998 0.225008 +v -2.701850 -0.688188 0.225008 +v -2.812730 -0.592878 0.225007 +v -2.850000 -0.450688 0.225007 +v -1.574070 -0.734018 0.200008 +v -1.933300 -0.732708 0.200008 +v -2.231820 -0.723528 0.200008 +v -2.467590 -0.698608 0.200008 +v -2.638550 -0.650068 0.200007 +v -2.742630 -0.570058 0.200007 +v -2.777780 -0.450688 0.200007 +v -1.592590 -0.692358 0.125008 +v -1.927040 -0.691238 0.125008 +v -2.206450 -0.683408 0.125008 +v -2.428240 -0.662148 0.125007 +v -2.589850 -0.620748 0.125007 +v -2.688700 -0.552498 0.125007 +v -2.722220 -0.450688 0.125007 +v -2.704180 -0.314668 -0.124994 +v -2.682870 -0.321518 0.000006 +v -2.648290 -0.156038 -0.124994 +v -2.629630 -0.167358 0.000006 +v -2.551850 0.013552 -0.124995 +v -2.537500 -0.000688 0.000005 +v -2.412210 0.182442 -0.124996 +v -2.403700 0.165982 0.000004 +v -2.226680 0.338982 -0.124996 +v -2.225460 0.320142 0.000004 +v -1.992590 0.471534 -0.124997 +v -2.000000 0.449312 0.000003 +v -2.757470 -0.297528 -0.199994 +v -2.694920 -0.127748 -0.199995 +v -2.587730 0.049142 -0.199995 +v -2.433470 0.223592 -0.199996 +v -2.229720 0.386084 -0.199996 +v -1.974070 0.527089 -0.199997 +v -2.826740 -0.275258 -0.224994 +v -2.755560 -0.090968 -0.224995 +v -2.634370 0.095402 -0.224995 +v -2.461110 0.277092 -0.224996 +v -2.233680 0.447314 -0.224997 +v -1.950000 0.599311 -0.224997 +v -2.896000 -0.252978 -0.199994 +v -2.816190 -0.054188 -0.199995 +v -2.681020 0.141672 -0.199996 +v -2.488750 0.330592 -0.199996 +v -2.237640 0.508545 -0.199997 +v -1.925930 0.671533 -0.199997 +v -2.949290 -0.235838 -0.124994 +v -2.862830 -0.025898 -0.124995 +v -2.716900 0.177262 -0.124996 +v -2.510010 0.371739 -0.124996 +v -2.240680 0.555646 -0.124997 +v -1.907410 0.727090 -0.124998 +v -2.970600 -0.228988 0.000006 +v -2.881480 -0.014578 0.000005 +v -2.731250 0.191502 0.000004 +v -2.518520 0.388201 0.000004 +v -2.241900 0.574486 0.000003 +v -1.900000 0.749312 0.000002 +v -2.949290 -0.235838 0.125006 +v -2.862830 -0.025898 0.125005 +v -2.716900 0.177262 0.125004 +v -2.510010 0.371740 0.125004 +v -2.240680 0.555646 0.125003 +v -1.907410 0.727090 0.125002 +v -2.896000 -0.252978 0.200006 +v -2.816190 -0.054188 0.200005 +v -2.681020 0.141672 0.200004 +v -2.488750 0.330592 0.200004 +v -2.237640 0.508547 0.200003 +v -1.925930 0.671535 0.200003 +v -2.826740 -0.275258 0.225006 +v -2.755560 -0.090968 0.225005 +v -2.634370 0.095402 0.225005 +v -2.461110 0.277092 0.225004 +v -2.233680 0.447316 0.225003 +v -1.950000 0.599313 0.225003 +v -2.757470 -0.297528 0.200006 +v -2.694920 -0.127748 0.200005 +v -2.587730 0.049142 0.200005 +v -2.433470 0.223592 0.200004 +v -2.229720 0.386086 0.200004 +v -1.974070 0.527091 0.200003 +v -2.704180 -0.314668 0.125006 +v -2.648290 -0.156038 0.125006 +v -2.551850 0.013552 0.125005 +v -2.412210 0.182442 0.125004 +v -2.226680 0.338982 0.125004 +v -1.992590 0.471534 0.125003 +v 1.700000 -0.075688 0.000005 +v 1.700000 -0.014578 -0.274995 +v 2.072380 -0.075898 -0.262341 +v 2.058800 -0.127078 0.000005 +v 2.290120 -0.222708 -0.230704 +v 2.270370 -0.261798 0.000006 +v 2.409720 -0.424298 -0.189576 +v 2.387500 -0.450688 0.000007 +v 2.487650 -0.649968 -0.148450 +v 2.462960 -0.664578 0.000007 +v 2.580400 -0.868998 -0.116813 +v 2.549540 -0.874298 0.000008 +v 2.700000 -1.050688 0.000009 +v 2.744440 -1.050688 -0.104158 +v 1.700000 0.138202 -0.439996 +v 2.106330 0.052062 -0.419748 +v 2.339510 -0.124968 -0.369131 +v 2.465280 -0.358328 -0.303327 +v 2.549380 -0.613448 -0.237524 +v 2.657560 -0.855758 -0.186906 +v 2.855560 -1.050688 -0.166658 +v 1.700000 0.336812 -0.494996 +v 2.150460 0.218412 -0.472218 +v 2.403700 0.002092 -0.415273 +v 2.537500 -0.272558 -0.341244 +v 2.629630 -0.565968 -0.267215 +v 2.757870 -0.838538 -0.210270 +v 3.000000 -1.050688 -0.187491 +v 1.700000 0.535421 -0.439997 +v 2.194600 0.384752 -0.419749 +v 2.467900 0.129152 -0.369132 +v 2.609720 -0.186798 -0.303327 +v 2.709880 -0.518488 -0.237524 +v 2.858180 -0.821318 -0.186906 +v 3.144440 -1.050688 -0.166658 +v 1.700000 0.688200 -0.274998 +v 2.228550 0.512711 -0.262343 +v 2.517280 0.226882 -0.230706 +v 2.665280 -0.120828 -0.189578 +v 2.771600 -0.481968 -0.148450 +v 2.935340 -0.808068 -0.116813 +v 3.255560 -1.050688 -0.104158 +v 1.700000 0.749312 0.000002 +v 2.242130 0.563895 0.000003 +v 2.537040 0.265982 0.000004 +v 2.687500 -0.094438 0.000005 +v 2.796300 -0.467358 0.000007 +v 2.966200 -0.802768 0.000008 +v 3.300000 -1.050688 0.000009 +v 1.700000 0.688202 0.275002 +v 2.228550 0.512713 0.262349 +v 2.517280 0.226882 0.230714 +v 2.665280 -0.120828 0.189588 +v 2.771600 -0.481968 0.148464 +v 2.935340 -0.808068 0.116829 +v 3.255560 -1.050688 0.104176 +v 1.700000 0.535425 0.440003 +v 2.194600 0.384756 0.419757 +v 2.467900 0.129152 0.369141 +v 2.609720 -0.186798 0.303339 +v 2.709880 -0.518488 0.237538 +v 2.858180 -0.821318 0.186922 +v 3.144440 -1.050688 0.166676 +v 1.700000 0.336812 0.495004 +v 2.150460 0.218412 0.472226 +v 2.403700 0.002092 0.415283 +v 2.537500 -0.272558 0.341256 +v 2.629630 -0.565968 0.267229 +v 2.757870 -0.838538 0.210286 +v 3.000000 -1.050688 0.187509 +v 1.700000 0.138202 0.440004 +v 2.106330 0.052062 0.419758 +v 2.339510 -0.124968 0.369141 +v 2.465280 -0.358328 0.303339 +v 2.549380 -0.613448 0.237538 +v 2.657560 -0.855758 0.186922 +v 2.855560 -1.050688 0.166676 +v 1.700000 -0.014578 0.275005 +v 2.072380 -0.075898 0.262351 +v 2.290120 -0.222708 0.230716 +v 2.409720 -0.424298 0.189590 +v 2.487650 -0.649968 0.148464 +v 2.580400 -0.868998 0.116829 +v 2.744440 -1.050688 0.104176 +v 2.749070 -1.081938 0.000009 +v 2.796410 -1.082618 -0.101023 +v 2.792590 -1.100688 0.000009 +v 2.839780 -1.101918 -0.092969 +v 2.825000 -1.106938 0.000009 +v 2.869680 -1.108498 -0.082022 +v 2.881210 -1.102228 -0.070207 +v 2.840740 -1.100688 0.000009 +v 2.869490 -1.082998 -0.059549 +v 2.834260 -1.081938 0.000009 +v 2.829630 -1.050688 -0.052074 +v 2.800000 -1.050688 0.000009 +v 2.914740 -1.084298 -0.161565 +v 2.957750 -1.105008 -0.148139 +v 2.981370 -1.112408 -0.129158 +v 2.982370 -1.106088 -0.107398 +v 2.957560 -1.085648 -0.085639 +v 2.903700 -1.050688 -0.066658 +v 3.068580 -1.086498 -0.181675 +v 3.111110 -1.109018 -0.165963 +v 3.126560 -1.117488 -0.142960 +v 3.113890 -1.111108 -0.115269 +v 3.072050 -1.089098 -0.085495 +v 3.000000 -1.050688 -0.056241 +v 3.222410 -1.088688 -0.161411 +v 3.264470 -1.113038 -0.146905 +v 3.271760 -1.122558 -0.124991 +v 3.245400 -1.116118 -0.097522 +v 3.186540 -1.092548 -0.066349 +v 3.096300 -1.050688 -0.033324 +v 3.340750 -1.090378 -0.100830 +v 3.382440 -1.116118 -0.091426 +v 3.383450 -1.126468 -0.076814 +v 3.346570 -1.119978 -0.057861 +v 3.274610 -1.095198 -0.035437 +v 3.170370 -1.050688 -0.010408 +v 3.388080 -1.091048 0.000009 +v 3.429630 -1.117358 0.000009 +v 3.428130 -1.128028 0.000009 +v 3.387040 -1.121518 0.000009 +v 3.309840 -1.096258 0.000009 +v 3.200000 -1.050688 0.000009 +v 3.340750 -1.090378 0.101089 +v 3.382440 -1.116118 0.093373 +v 3.383450 -1.126468 0.083342 +v 3.346570 -1.119978 0.073312 +v 3.274610 -1.095198 0.065595 +v 3.170370 -1.050688 0.062509 +v 3.222410 -1.088688 0.161737 +v 3.264470 -1.113038 0.149392 +v 3.271760 -1.122558 0.133342 +v 3.245400 -1.116118 0.117293 +v 3.186540 -1.092548 0.104947 +v 3.096300 -1.050688 0.100009 +v 3.068580 -1.086498 0.181953 +v 3.111110 -1.109018 0.168065 +v 3.126560 -1.117488 0.150009 +v 3.113890 -1.111108 0.131953 +v 3.072050 -1.089098 0.118065 +v 3.000000 -1.050688 0.112509 +v 2.914740 -1.084298 0.161737 +v 2.957750 -1.105008 0.149392 +v 2.981370 -1.112408 0.133342 +v 2.982370 -1.106088 0.117293 +v 2.957560 -1.085648 0.104947 +v 2.903700 -1.050688 0.100009 +v 2.796410 -1.082618 0.101089 +v 2.839780 -1.101918 0.093373 +v 2.869680 -1.108498 0.083342 +v 2.881210 -1.102228 0.073312 +v 2.869490 -1.082998 0.065595 +v 2.829630 -1.050688 0.062509 +v 0.278704 -1.777768 0.000011 +v 0.000000 -1.800688 0.000012 +v 0.268946 -1.777768 -0.075067 +v 0.241285 -1.777768 -0.141919 +v 0.198140 -1.777768 -0.198128 +v 0.141931 -1.777768 -0.241273 +v 0.075078 -1.777768 -0.268934 +v 0.000000 -1.777768 -0.278692 +v -0.075078 -1.777768 -0.268934 +v -0.141931 -1.777768 -0.241273 +v -0.198140 -1.777768 -0.198128 +v -0.241285 -1.777768 -0.141919 +v -0.268946 -1.777768 -0.075067 +v -0.278704 -1.777768 0.000011 +v -0.268946 -1.777768 0.075089 +v -0.241285 -1.777768 0.141943 +v -0.198140 -1.777768 0.198152 +v -0.141931 -1.777768 0.241297 +v -0.075078 -1.777768 0.268958 +v 0.000000 -1.777768 0.278716 +v 0.075078 -1.777768 0.268958 +v 0.141931 -1.777768 0.241297 +v 0.198140 -1.777768 0.198152 +v 0.241285 -1.777768 0.141943 +v 0.268946 -1.777768 0.075089 +v 0.350254 -1.717358 -0.097760 +v 0.362963 -1.717358 0.000011 +v 0.313617 -1.631938 -0.087518 +v 0.325000 -1.631938 0.000011 +v 0.228728 -1.534018 -0.063792 +v 0.237037 -1.534018 0.000011 +v 0.165279 -1.436108 -0.046035 +v 0.171296 -1.436108 0.000010 +v 0.314228 -1.717358 -0.184823 +v 0.281352 -1.631938 -0.165470 +v 0.205180 -1.534018 -0.120636 +v 0.148234 -1.436108 -0.087096 +v 0.258037 -1.717358 -0.258026 +v 0.231031 -1.631938 -0.231020 +v 0.168463 -1.534018 -0.168452 +v 0.121672 -1.436108 -0.121662 +v 0.184834 -1.717358 -0.314217 +v 0.165481 -1.631938 -0.281341 +v 0.120647 -1.534018 -0.205169 +v 0.087106 -1.436108 -0.148224 +v 0.097771 -1.717358 -0.350243 +v 0.087529 -1.631938 -0.313606 +v 0.063803 -1.534018 -0.228717 +v 0.046045 -1.436108 -0.165269 +v 0.000000 -1.717358 -0.362952 +v 0.000000 -1.631938 -0.324989 +v 0.000000 -1.534018 -0.237026 +v 0.000000 -1.436108 -0.171286 +v -0.097771 -1.717358 -0.350243 +v -0.087529 -1.631938 -0.313606 +v -0.063803 -1.534018 -0.228717 +v -0.046045 -1.436108 -0.165269 +v -0.184834 -1.717358 -0.314217 +v -0.165481 -1.631938 -0.281341 +v -0.120647 -1.534018 -0.205169 +v -0.087106 -1.436108 -0.148224 +v -0.258037 -1.717358 -0.258026 +v -0.231031 -1.631938 -0.231020 +v -0.168463 -1.534018 -0.168452 +v -0.121672 -1.436108 -0.121662 +v -0.314228 -1.717358 -0.184823 +v -0.281352 -1.631938 -0.165470 +v -0.205180 -1.534018 -0.120636 +v -0.148234 -1.436108 -0.087096 +v -0.350254 -1.717358 -0.097760 +v -0.313617 -1.631938 -0.087518 +v -0.228728 -1.534018 -0.063792 +v -0.165279 -1.436108 -0.046035 +v -0.362963 -1.717358 0.000011 +v -0.325000 -1.631938 0.000011 +v -0.237037 -1.534018 0.000011 +v -0.171296 -1.436108 0.000010 +v -0.350254 -1.717358 0.097782 +v -0.313617 -1.631938 0.087540 +v -0.228728 -1.534018 0.063814 +v -0.165279 -1.436108 0.046055 +v -0.314228 -1.717358 0.184845 +v -0.281352 -1.631938 0.165492 +v -0.205180 -1.534018 0.120658 +v -0.148234 -1.436108 0.087116 +v -0.258037 -1.717358 0.258048 +v -0.231031 -1.631938 0.231042 +v -0.168463 -1.534018 0.168474 +v -0.121672 -1.436108 0.121682 +v -0.184834 -1.717358 0.314239 +v -0.165481 -1.631938 0.281363 +v -0.120647 -1.534018 0.205191 +v -0.087106 -1.436108 0.148244 +v -0.097771 -1.717358 0.350265 +v -0.087529 -1.631938 0.313628 +v -0.063803 -1.534018 0.228739 +v -0.046045 -1.436108 0.165289 +v 0.000000 -1.717358 0.362974 +v 0.000000 -1.631938 0.325011 +v 0.000000 -1.534018 0.237048 +v 0.000000 -1.436108 0.171306 +v 0.097771 -1.717358 0.350265 +v 0.087529 -1.631938 0.313628 +v 0.063803 -1.534018 0.228739 +v 0.046045 -1.436108 0.165289 +v 0.184834 -1.717358 0.314239 +v 0.165481 -1.631938 0.281363 +v 0.120647 -1.534018 0.205191 +v 0.087106 -1.436108 0.148244 +v 0.258037 -1.717358 0.258048 +v 0.231031 -1.631938 0.231042 +v 0.168463 -1.534018 0.168474 +v 0.121672 -1.436108 0.121682 +v 0.314228 -1.717358 0.184845 +v 0.281352 -1.631938 0.165492 +v 0.205180 -1.534018 0.120658 +v 0.148234 -1.436108 0.087116 +v 0.350254 -1.717358 0.097782 +v 0.313617 -1.631938 0.087540 +v 0.228728 -1.534018 0.063814 +v 0.165279 -1.436108 0.046055 +vn -0.0251 -0.9997 -0.0041 +vn -0.0290 -0.9996 0.0025 +vn 0.0000 -1.0000 0.0000 +vn -0.0298 -0.9995 -0.0039 +vn -0.0276 -0.9996 -0.0057 +vn -0.0194 -0.9997 -0.0125 +vn -0.0142 -0.9997 -0.0185 +vn -0.0040 -0.9997 -0.0251 +vn -0.0023 -0.9996 -0.0289 +vn 0.0039 -0.9995 -0.0298 +vn 0.0057 -0.9996 -0.0276 +vn 0.0125 -0.9997 -0.0194 +vn 0.0184 -0.9997 -0.0143 +vn 0.0252 -0.9997 -0.0040 +vn 0.0290 -0.9996 -0.0023 +vn 0.0298 -0.9995 0.0039 +vn 0.0277 -0.9996 0.0056 +vn 0.0194 -0.9997 0.0125 +vn 0.0142 -0.9997 0.0185 +vn 0.0043 -0.9997 0.0251 +vn 0.0024 -0.9996 0.0290 +vn -0.0040 -0.9996 0.0297 +vn -0.0056 -0.9996 0.0277 +vn -0.0125 -0.9997 0.0194 +vn -0.0184 -0.9997 0.0143 +vn 0.9099 -0.4148 0.0051 +vn 0.9335 -0.1896 -0.3043 +vn 0.8694 -0.4093 -0.2768 +vn 0.9923 -0.0840 0.0914 +vn 0.5450 0.8380 0.0260 +vn 0.8238 0.5320 0.1960 +vn -0.3058 0.9521 0.0000 +vn 0.1020 0.9137 0.3934 +vn -0.5364 0.8206 0.1974 +vn -0.4144 0.9089 -0.0453 +vn -0.7481 0.6037 0.2755 +vn -0.6884 0.7242 -0.0404 +vn -0.8381 0.4638 0.2871 +vn -0.8311 0.5554 -0.0279 +vn 0.8233 -0.1876 -0.5356 +vn 0.7671 -0.4069 -0.4961 +vn 0.8465 0.5318 -0.0239 +vn 0.2000 0.9139 0.3531 +vn -0.4767 0.8235 0.3075 +vn -0.6591 0.6075 0.4433 +vn -0.7399 0.4661 0.4851 +vn 0.6756 -0.1534 -0.7211 +vn 0.6532 -0.3937 -0.6467 +vn 0.7979 0.5464 -0.2545 +vn 0.2455 0.9374 0.2470 +vn -0.4303 0.8419 0.3256 +vn -0.5621 0.6237 0.5432 +vn -0.6124 0.4740 0.6327 +vn 0.5036 -0.4094 -0.7608 +vn 0.4151 -0.0987 -0.9044 +vn 0.7100 0.5416 -0.4500 +vn 0.3293 0.9154 0.2315 +vn -0.2696 0.8588 0.4357 +vn -0.3718 0.6658 0.6469 +vn -0.4213 0.5172 0.7450 +vn 0.2807 -0.4119 -0.8669 +vn 0.2038 -0.0636 -0.9770 +vn 0.5731 0.5599 -0.5984 +vn 0.3784 0.9150 0.1403 +vn -0.1357 0.8702 0.4737 +vn -0.1844 0.6788 0.7108 +vn -0.2099 0.5258 0.8243 +vn -0.0000 -0.4146 -0.9100 +vn -0.0419 -0.1213 -0.9917 +vn 0.3489 0.5625 -0.7495 +vn 0.3465 0.9379 0.0177 +vn 0.0452 0.9090 0.4144 +vn 0.0404 0.7242 0.6884 +vn 0.0279 0.5554 0.8311 +vn -0.3015 -0.1862 -0.9351 +vn -0.2807 -0.4119 -0.8669 +vn 0.1958 0.5321 -0.8237 +vn 0.3937 0.9136 -0.1015 +vn 0.1974 0.8205 0.5365 +vn 0.2754 0.6037 0.7481 +vn 0.2872 0.4638 0.8381 +vn -0.5707 -0.1564 -0.8062 +vn -0.5036 -0.4094 -0.7608 +vn -0.0397 0.5284 -0.8481 +vn 0.3532 0.9141 -0.1994 +vn 0.3075 0.8236 0.4766 +vn 0.4433 0.6075 0.6591 +vn 0.4851 0.4661 0.7399 +vn -0.7310 -0.1016 -0.6748 +vn -0.6532 -0.3937 -0.6467 +vn -0.2527 0.5512 -0.7952 +vn 0.2456 0.9373 -0.2473 +vn 0.3257 0.8419 0.4303 +vn 0.5432 0.6237 0.5621 +vn 0.6326 0.4740 0.6125 +vn -0.7670 -0.4069 -0.4961 +vn -0.8918 -0.0632 -0.4479 +vn -0.4279 0.5606 -0.7090 +vn 0.2321 0.9153 -0.3291 +vn 0.4356 0.8588 0.2697 +vn 0.6469 0.6658 0.3718 +vn 0.7450 0.5172 0.4213 +vn -0.8694 -0.4093 -0.2768 +vn -0.9779 -0.0605 -0.2004 +vn -0.5980 0.5603 -0.5731 +vn 0.1406 0.9151 -0.3780 +vn 0.4737 0.8702 0.1356 +vn 0.7108 0.6788 0.1843 +vn 0.8243 0.5258 0.2100 +vn -0.9099 -0.4148 0.0051 +vn -0.9923 -0.0840 0.0914 +vn -0.7173 0.5796 -0.3867 +vn 0.0177 0.9381 -0.3460 +vn 0.4144 0.9089 -0.0453 +vn 0.6884 0.7242 -0.0404 +vn 0.8311 0.5554 -0.0279 +vn -0.9507 -0.1562 0.2677 +vn -0.8677 -0.4082 0.2837 +vn -0.8043 0.5658 -0.1816 +vn -0.1016 0.9137 -0.3934 +vn 0.5364 0.8205 -0.1974 +vn 0.7481 0.6037 -0.2755 +vn 0.8381 0.4638 -0.2872 +vn -0.8083 -0.1553 0.5679 +vn -0.7609 -0.4091 0.5037 +vn -0.8484 0.5279 0.0396 +vn -0.1994 0.9141 -0.3532 +vn 0.4767 0.8236 -0.3075 +vn 0.6591 0.6075 -0.4433 +vn 0.7399 0.4661 -0.4851 +vn -0.6749 -0.1015 0.7309 +vn -0.6467 -0.3937 0.6532 +vn -0.7952 0.5512 0.2527 +vn -0.2473 0.9373 -0.2457 +vn 0.4303 0.8419 -0.3256 +vn 0.5621 0.6237 -0.5432 +vn 0.6124 0.4740 -0.6327 +vn -0.4961 -0.4069 0.7670 +vn -0.4481 -0.0632 0.8918 +vn -0.7089 0.5606 0.4279 +vn -0.3291 0.9153 -0.2321 +vn 0.2697 0.8588 -0.4356 +vn 0.3718 0.6658 -0.6469 +vn 0.4213 0.5172 -0.7450 +vn -0.2770 -0.4091 0.8694 +vn -0.2056 -0.0614 0.9767 +vn -0.5730 0.5601 0.5983 +vn -0.3784 0.9149 -0.1408 +vn 0.1356 0.8701 -0.4738 +vn 0.1843 0.6788 -0.7108 +vn 0.2100 0.5257 -0.8243 +vn -0.0000 -0.4144 0.9101 +vn 0.0418 -0.1213 0.9917 +vn -0.3490 0.5627 0.7494 +vn -0.3463 0.9380 -0.0178 +vn -0.0452 0.9090 -0.4144 +vn -0.0404 0.7242 -0.6884 +vn -0.0279 0.5554 -0.8311 +vn 0.2993 -0.1882 0.9354 +vn 0.2769 -0.4091 0.8694 +vn -0.1957 0.5319 0.8239 +vn -0.3938 0.9135 0.1021 +vn -0.1975 0.8205 -0.5365 +vn -0.2755 0.6036 -0.7481 +vn -0.2871 0.4638 -0.8381 +vn 0.5355 -0.1876 0.8234 +vn 0.4961 -0.4069 0.7671 +vn 0.0239 0.5318 0.8465 +vn -0.3531 0.9140 0.2000 +vn -0.3075 0.8235 -0.4767 +vn -0.4433 0.6075 -0.6592 +vn -0.4851 0.4661 -0.7399 +vn 0.7210 -0.1534 0.6757 +vn 0.6467 -0.3937 0.6533 +vn 0.2545 0.5464 0.7979 +vn -0.2470 0.9374 0.2454 +vn -0.3256 0.8419 -0.4303 +vn -0.5431 0.6237 -0.5621 +vn -0.6326 0.4740 -0.6125 +vn 0.7609 -0.4091 0.5037 +vn 0.9029 -0.0994 0.4182 +vn 0.4496 0.5419 0.7101 +vn -0.2315 0.9154 0.3294 +vn -0.4357 0.8588 -0.2696 +vn -0.6470 0.6657 -0.3718 +vn -0.7450 0.5172 -0.4213 +vn 0.8677 -0.4082 0.2837 +vn 0.9661 -0.0898 0.2422 +vn 0.6118 0.5554 0.5632 +vn -0.1402 0.9151 0.3781 +vn -0.4737 0.8702 -0.1356 +vn -0.7108 0.6788 -0.1843 +vn -0.8243 0.5257 -0.2100 +vn -0.8715 0.4152 0.2610 +vn -0.8936 0.4479 -0.0286 +vn -0.8879 0.3904 0.2433 +vn -0.9145 0.4044 -0.0114 +vn -0.9076 0.3413 0.2443 +vn -0.9338 0.3576 -0.0116 +vn -0.9326 0.2642 0.2458 +vn -0.9580 0.2864 -0.0133 +vn -0.9604 0.1570 0.2303 +vn -0.9853 0.1704 -0.0098 +vn -0.9741 0.0395 0.2227 +vn -0.9962 -0.0849 0.0165 +vn -0.7764 0.4162 0.4733 +vn -0.7963 0.3908 0.4617 +vn -0.8161 0.3412 0.4664 +vn -0.8412 0.2647 0.4715 +vn -0.8710 0.1622 0.4637 +vn -0.8810 0.0391 0.4715 +vn -0.6381 0.4195 0.6456 +vn -0.6592 0.3931 0.6411 +vn -0.6816 0.3445 0.6455 +vn -0.7124 0.2678 0.6487 +vn -0.7530 0.1536 0.6399 +vn -0.7239 -0.0460 0.6884 +vn -0.4404 0.4349 0.7854 +vn -0.4613 0.3941 0.7949 +vn -0.4755 0.3409 0.8110 +vn -0.4921 0.2567 0.8318 +vn -0.5052 0.1173 0.8550 +vn -0.4413 -0.1102 0.8906 +vn -0.2175 0.4379 0.8723 +vn -0.2359 0.3964 0.8873 +vn -0.2444 0.3444 0.9064 +vn -0.2539 0.2634 0.9307 +vn -0.2656 0.1269 0.9557 +vn -0.1958 -0.1135 0.9740 +vn 0.0286 0.4479 0.8936 +vn 0.0114 0.4045 0.9145 +vn 0.0115 0.3578 0.9337 +vn 0.0133 0.2864 0.9580 +vn 0.0098 0.1705 0.9853 +vn -0.0165 -0.0849 0.9963 +vn 0.2610 0.4152 0.8715 +vn 0.2436 0.3901 0.8880 +vn 0.2444 0.3410 0.9078 +vn 0.2461 0.2643 0.9325 +vn 0.2304 0.1570 0.9604 +vn 0.2227 0.0395 0.9741 +vn 0.4734 0.4161 0.7764 +vn 0.4619 0.3905 0.7964 +vn 0.4665 0.3413 0.8160 +vn 0.4715 0.2647 0.8412 +vn 0.4637 0.1623 0.8710 +vn 0.4715 0.0391 0.8810 +vn 0.6457 0.4194 0.6381 +vn 0.6411 0.3931 0.6591 +vn 0.6455 0.3445 0.6816 +vn 0.6487 0.2678 0.7124 +vn 0.6399 0.1536 0.7530 +vn 0.6884 -0.0460 0.7239 +vn 0.7855 0.4347 0.4404 +vn 0.7954 0.3935 0.4610 +vn 0.8113 0.3409 0.4749 +vn 0.8319 0.2568 0.4920 +vn 0.8550 0.1173 0.5052 +vn 0.8906 -0.1102 0.4413 +vn 0.8724 0.4378 0.2175 +vn 0.8875 0.3958 0.2357 +vn 0.9066 0.3443 0.2439 +vn 0.9307 0.2635 0.2537 +vn 0.9557 0.1268 0.2656 +vn 0.9740 -0.1135 0.1958 +vn 0.8936 0.4479 -0.0286 +vn 0.9145 0.4045 -0.0114 +vn 0.9338 0.3577 -0.0115 +vn 0.9580 0.2864 -0.0133 +vn 0.9853 0.1704 -0.0098 +vn 0.9963 -0.0849 0.0165 +vn 0.8715 0.4152 -0.2610 +vn 0.8879 0.3903 -0.2434 +vn 0.9076 0.3413 -0.2444 +vn 0.9326 0.2643 -0.2460 +vn 0.9604 0.1570 -0.2303 +vn 0.9741 0.0395 -0.2226 +vn 0.7764 0.4161 -0.4734 +vn 0.7964 0.3902 -0.4620 +vn 0.8160 0.3412 -0.4666 +vn 0.8412 0.2647 -0.4715 +vn 0.8710 0.1622 -0.4638 +vn 0.8810 0.0391 -0.4715 +vn 0.6381 0.4194 -0.6457 +vn 0.6591 0.3931 -0.6412 +vn 0.6816 0.3445 -0.6455 +vn 0.7124 0.2678 -0.6487 +vn 0.7529 0.1536 -0.6399 +vn 0.7239 -0.0460 -0.6884 +vn 0.4403 0.4349 -0.7855 +vn 0.4613 0.3943 -0.7948 +vn 0.4753 0.3410 -0.8111 +vn 0.4920 0.2568 -0.8319 +vn 0.5052 0.1173 -0.8550 +vn 0.4414 -0.1101 -0.8905 +vn 0.2175 0.4378 -0.8723 +vn 0.2358 0.3959 -0.8875 +vn 0.2445 0.3444 -0.9064 +vn 0.2540 0.2633 -0.9307 +vn 0.2656 0.1269 -0.9557 +vn 0.1958 -0.1135 -0.9740 +vn -0.0286 0.4479 -0.8936 +vn -0.0114 0.4045 -0.9145 +vn -0.0114 0.3577 -0.9338 +vn -0.0133 0.2864 -0.9580 +vn -0.0097 0.1703 -0.9853 +vn 0.0165 -0.0849 -0.9963 +vn -0.2610 0.4152 -0.8715 +vn -0.2434 0.3903 -0.8879 +vn -0.2444 0.3412 -0.9077 +vn -0.2458 0.2642 -0.9326 +vn -0.2303 0.1570 -0.9604 +vn -0.2226 0.0395 -0.9741 +vn -0.4735 0.4161 -0.7763 +vn -0.4620 0.3902 -0.7964 +vn -0.4667 0.3414 -0.8159 +vn -0.4716 0.2647 -0.8411 +vn -0.4637 0.1622 -0.8710 +vn -0.4715 0.0391 -0.8810 +vn -0.6457 0.4194 -0.6381 +vn -0.6412 0.3931 -0.6591 +vn -0.6455 0.3445 -0.6816 +vn -0.6487 0.2678 -0.7124 +vn -0.6399 0.1536 -0.7529 +vn -0.6885 -0.0460 -0.7238 +vn -0.7855 0.4349 -0.4404 +vn -0.7950 0.3940 -0.4613 +vn -0.8110 0.3410 -0.4753 +vn -0.8319 0.2568 -0.4920 +vn -0.8550 0.1174 -0.5051 +vn -0.8906 -0.1102 -0.4413 +vn -0.8723 0.4379 -0.2176 +vn -0.8873 0.3962 -0.2360 +vn -0.9064 0.3444 -0.2445 +vn -0.9307 0.2636 -0.2536 +vn -0.9557 0.1269 -0.2656 +vn -0.9740 -0.1135 -0.1958 +vn -0.9099 -0.3360 0.2432 +vn -0.9398 -0.3287 0.0940 +vn -0.7982 -0.5583 0.2263 +vn -0.8212 -0.5684 0.0505 +vn -0.6979 -0.6886 0.1967 +vn -0.7114 -0.7027 0.0110 +vn -0.6634 -0.7255 0.1833 +vn -0.6561 -0.7527 -0.0541 +vn -0.7262 -0.6603 0.1913 +vn -0.7267 -0.6825 -0.0784 +vn -0.8152 -0.5387 0.2125 +vn -0.8828 -0.4654 0.0640 +vn -0.8212 -0.3180 0.4739 +vn -0.7176 -0.5444 0.4343 +vn -0.6256 -0.6830 0.3771 +vn -0.5904 -0.7268 0.3509 +vn -0.6465 -0.6666 0.3711 +vn -0.7308 -0.5425 0.4142 +vn -0.6659 -0.2456 0.7045 +vn -0.6016 -0.4869 0.6333 +vn -0.5248 -0.6554 0.5432 +vn -0.4798 -0.7274 0.4906 +vn -0.5095 -0.6938 0.5089 +vn -0.5872 -0.5635 0.5811 +vn -0.4650 -0.3308 0.8212 +vn -0.3903 -0.5569 0.7331 +vn -0.3346 -0.6955 0.6359 +vn -0.2871 -0.7539 0.5909 +vn -0.3322 -0.6927 0.6402 +vn -0.5177 -0.4714 0.7140 +vn -0.2549 -0.3250 0.9107 +vn -0.2041 -0.5579 0.8044 +vn -0.1666 -0.6968 0.6977 +vn -0.1229 -0.7545 0.6447 +vn -0.1474 -0.6919 0.7067 +vn -0.3112 -0.4701 0.8259 +vn -0.0940 -0.3286 0.9398 +vn -0.0505 -0.5684 0.8212 +vn -0.0110 -0.7027 0.7114 +vn 0.0541 -0.7528 0.6561 +vn 0.0784 -0.6825 0.7267 +vn -0.0641 -0.4654 0.8828 +vn 0.2434 -0.3362 0.9098 +vn 0.2263 -0.5583 0.7982 +vn 0.1967 -0.6886 0.6979 +vn 0.1833 -0.7255 0.6634 +vn 0.1913 -0.6603 0.7262 +vn 0.2125 -0.5387 0.8152 +vn 0.4738 -0.3180 0.8212 +vn 0.4343 -0.5444 0.7176 +vn 0.3771 -0.6830 0.6256 +vn 0.3509 -0.7268 0.5904 +vn 0.3711 -0.6666 0.6465 +vn 0.4142 -0.5425 0.7308 +vn 0.7044 -0.2456 0.6659 +vn 0.6333 -0.4869 0.6016 +vn 0.5432 -0.6554 0.5248 +vn 0.4905 -0.7274 0.4798 +vn 0.5089 -0.6939 0.5095 +vn 0.5811 -0.5635 0.5872 +vn 0.8212 -0.3307 0.4650 +vn 0.7331 -0.5570 0.3904 +vn 0.6358 -0.6955 0.3347 +vn 0.5909 -0.7539 0.2871 +vn 0.6402 -0.6927 0.3322 +vn 0.7140 -0.4714 0.5177 +vn 0.9107 -0.3252 0.2547 +vn 0.8044 -0.5579 0.2041 +vn 0.6977 -0.6968 0.1666 +vn 0.6446 -0.7545 0.1229 +vn 0.7067 -0.6919 0.1474 +vn 0.8259 -0.4701 0.3113 +vn 0.9398 -0.3286 0.0940 +vn 0.8212 -0.5684 0.0505 +vn 0.7114 -0.7027 0.0110 +vn 0.6561 -0.7528 -0.0541 +vn 0.7267 -0.6825 -0.0784 +vn 0.8828 -0.4654 0.0641 +vn 0.9098 -0.3362 -0.2434 +vn 0.7982 -0.5583 -0.2264 +vn 0.6979 -0.6886 -0.1967 +vn 0.6634 -0.7255 -0.1833 +vn 0.7263 -0.6603 -0.1913 +vn 0.8152 -0.5387 -0.2125 +vn 0.8212 -0.3180 -0.4739 +vn 0.7176 -0.5445 -0.4343 +vn 0.6256 -0.6830 -0.3770 +vn 0.5904 -0.7268 -0.3509 +vn 0.6465 -0.6666 -0.3711 +vn 0.7308 -0.5425 -0.4142 +vn 0.6659 -0.2456 -0.7044 +vn 0.6016 -0.4869 -0.6333 +vn 0.5248 -0.6554 -0.5431 +vn 0.4798 -0.7274 -0.4905 +vn 0.5095 -0.6939 -0.5089 +vn 0.5872 -0.5635 -0.5811 +vn 0.4650 -0.3305 -0.8213 +vn 0.3904 -0.5569 -0.7331 +vn 0.3345 -0.6955 -0.6359 +vn 0.2871 -0.7539 -0.5909 +vn 0.3322 -0.6927 -0.6402 +vn 0.5177 -0.4714 -0.7140 +vn 0.2548 -0.3250 -0.9107 +vn 0.2041 -0.5579 -0.8044 +vn 0.1666 -0.6968 -0.6977 +vn 0.1229 -0.7545 -0.6446 +vn 0.1474 -0.6919 -0.7067 +vn 0.3112 -0.4701 -0.8259 +vn 0.0940 -0.3286 -0.9398 +vn 0.0505 -0.5684 -0.8212 +vn 0.0110 -0.7027 -0.7114 +vn -0.0540 -0.7528 -0.6561 +vn -0.0784 -0.6825 -0.7266 +vn 0.0641 -0.4654 -0.8828 +vn -0.2434 -0.3362 -0.9098 +vn -0.2263 -0.5583 -0.7982 +vn -0.1967 -0.6886 -0.6979 +vn -0.1833 -0.7255 -0.6634 +vn -0.1913 -0.6603 -0.7262 +vn -0.2125 -0.5387 -0.8152 +vn -0.4740 -0.3181 -0.8211 +vn -0.4344 -0.5444 -0.7176 +vn -0.3770 -0.6830 -0.6256 +vn -0.3509 -0.7268 -0.5904 +vn -0.3711 -0.6666 -0.6465 +vn -0.4142 -0.5425 -0.7308 +vn -0.7045 -0.2456 -0.6659 +vn -0.6333 -0.4869 -0.6015 +vn -0.5432 -0.6554 -0.5248 +vn -0.4905 -0.7274 -0.4798 +vn -0.5088 -0.6940 -0.5094 +vn -0.5811 -0.5635 -0.5872 +vn -0.8212 -0.3307 -0.4650 +vn -0.7331 -0.5569 -0.3903 +vn -0.6359 -0.6955 -0.3346 +vn -0.5909 -0.7539 -0.2871 +vn -0.6402 -0.6927 -0.3322 +vn -0.7140 -0.4714 -0.5177 +vn -0.9107 -0.3250 -0.2549 +vn -0.8044 -0.5579 -0.2041 +vn -0.6977 -0.6968 -0.1666 +vn -0.6446 -0.7545 -0.1229 +vn -0.7068 -0.6920 -0.1473 +vn -0.8259 -0.4701 -0.3112 +vn -0.0718 -0.9974 0.0012 +vn -0.0729 -0.9973 -0.0057 +vn -0.1624 -0.9867 0.0049 +vn -0.1657 -0.9861 -0.0137 +vn -0.3990 -0.9154 0.0529 +vn -0.3791 -0.9214 -0.0857 +vn -0.9417 -0.3366 0.0000 +vn -0.6182 -0.5522 -0.5594 +vn -0.0713 -0.9972 -0.0208 +vn -0.1609 -0.9858 -0.0482 +vn -0.3506 -0.9217 -0.1659 +vn -0.4526 -0.5518 -0.7005 +vn -0.0699 -0.9973 -0.0202 +vn -0.1570 -0.9863 -0.0502 +vn -0.3252 -0.9280 -0.1819 +vn -0.2634 -0.5461 -0.7953 +vn -0.0251 -0.9987 -0.0436 +vn -0.0740 -0.9926 -0.0967 +vn -0.1856 -0.9394 -0.2884 +vn -0.0812 -0.4867 -0.8698 +vn -0.0138 -0.9985 -0.0532 +vn -0.0439 -0.9916 -0.1217 +vn -0.0933 -0.9361 -0.3392 +vn 0.1474 -0.4851 -0.8620 +vn 0.0012 -0.9974 -0.0719 +vn 0.0049 -0.9867 -0.1624 +vn 0.0529 -0.9154 -0.3991 +vn 0.3035 -0.5238 -0.7960 +vn 0.0057 -0.9973 -0.0728 +vn 0.0137 -0.9861 -0.1657 +vn 0.0857 -0.9214 -0.3791 +vn 0.5594 -0.5522 -0.6182 +vn 0.0207 -0.9972 -0.0713 +vn 0.0482 -0.9858 -0.1609 +vn 0.1659 -0.9217 -0.3506 +vn 0.7005 -0.5518 -0.4526 +vn 0.0202 -0.9973 -0.0699 +vn 0.0502 -0.9863 -0.1570 +vn 0.1819 -0.9280 -0.3252 +vn 0.7952 -0.5461 -0.2634 +vn 0.0436 -0.9987 -0.0251 +vn 0.0967 -0.9926 -0.0740 +vn 0.2884 -0.9394 -0.1856 +vn 0.8698 -0.4867 -0.0812 +vn 0.0531 -0.9985 -0.0137 +vn 0.1217 -0.9916 -0.0439 +vn 0.3392 -0.9361 -0.0933 +vn 0.8620 -0.4851 0.1474 +vn 0.0720 -0.9974 0.0012 +vn 0.1624 -0.9867 0.0049 +vn 0.3990 -0.9154 0.0529 +vn 0.7960 -0.5238 0.3035 +vn 0.0729 -0.9973 0.0056 +vn 0.1657 -0.9861 0.0137 +vn 0.3791 -0.9214 0.0857 +vn 0.6182 -0.5522 0.5594 +vn 0.0713 -0.9972 0.0207 +vn 0.1609 -0.9858 0.0482 +vn 0.3506 -0.9217 0.1659 +vn 0.4526 -0.5518 0.7005 +vn 0.0699 -0.9973 0.0203 +vn 0.1570 -0.9863 0.0502 +vn 0.3252 -0.9280 0.1819 +vn 0.2634 -0.5461 0.7952 +vn 0.0251 -0.9987 0.0436 +vn 0.0740 -0.9926 0.0967 +vn 0.1856 -0.9394 0.2884 +vn 0.0812 -0.4867 0.8698 +vn 0.0137 -0.9985 0.0531 +vn 0.0439 -0.9916 0.1217 +vn 0.0933 -0.9361 0.3392 +vn -0.1474 -0.4851 0.8620 +vn -0.0012 -0.9974 0.0718 +vn -0.0049 -0.9867 0.1625 +vn -0.0529 -0.9154 0.3991 +vn -0.3035 -0.5237 0.7960 +vn -0.0057 -0.9973 0.0729 +vn -0.0137 -0.9861 0.1657 +vn -0.0857 -0.9214 0.3791 +vn -0.5594 -0.5522 0.6182 +vn -0.0207 -0.9972 0.0714 +vn -0.0482 -0.9858 0.1609 +vn -0.1659 -0.9217 0.3506 +vn -0.7005 -0.5518 0.4526 +vn -0.0202 -0.9973 0.0700 +vn -0.0502 -0.9863 0.1570 +vn -0.1819 -0.9280 0.3252 +vn -0.7953 -0.5461 0.2634 +vn -0.0436 -0.9987 0.0251 +vn -0.0967 -0.9926 0.0740 +vn -0.2884 -0.9394 0.1856 +vn -0.8698 -0.4867 0.0812 +vn -0.0531 -0.9985 0.0137 +vn -0.1217 -0.9916 0.0439 +vn -0.3392 -0.9361 0.0933 +vn -0.8620 -0.4851 -0.1474 +vn -0.7180 0.6799 -0.1492 +vn -0.9620 0.1961 0.1899 +vn -0.4963 0.8561 0.1444 +vn -0.9729 0.2314 -0.0000 +vn -0.7320 0.6800 0.0426 +vn -0.8813 0.1813 0.4363 +vn -0.6753 0.6868 0.2688 +vn -0.6972 0.1665 0.6972 +vn -0.6134 0.6800 0.4016 +vn -0.4363 0.1813 0.8813 +vn -0.4884 0.6799 0.5469 +vn -0.1899 0.1961 0.9620 +vn -0.3032 0.6838 0.6638 +vn -0.0000 0.2313 0.9729 +vn -0.1492 0.6799 0.7180 +vn 0.1899 0.1961 0.9620 +vn 0.0426 0.6800 0.7320 +vn 0.4363 0.1813 0.8813 +vn 0.2688 0.6868 0.6753 +vn 0.6972 0.1665 0.6972 +vn 0.4016 0.6800 0.6134 +vn 0.8813 0.1813 0.4363 +vn 0.5469 0.6799 0.4884 +vn 0.9620 0.1961 0.1899 +vn 0.6638 0.6838 0.3032 +vn 0.9729 0.2314 -0.0000 +vn 0.7180 0.6799 0.1492 +vn 0.9620 0.1961 -0.1899 +vn 0.7320 0.6800 -0.0426 +vn 0.8813 0.1813 -0.4363 +vn 0.6753 0.6868 -0.2688 +vn 0.6972 0.1666 -0.6972 +vn 0.6134 0.6800 -0.4016 +vn 0.4363 0.1813 -0.8813 +vn 0.4884 0.6799 -0.5469 +vn 0.1899 0.1961 -0.9620 +vn 0.3032 0.6838 -0.6638 +vn 0.0000 0.2313 -0.9729 +vn 0.1492 0.6799 -0.7180 +vn -0.1899 0.1961 -0.9620 +vn -0.0426 0.6800 -0.7320 +vn -0.4363 0.1813 -0.8813 +vn -0.2688 0.6868 -0.6753 +vn -0.6972 0.1665 -0.6972 +vn -0.4016 0.6800 -0.6134 +vn -0.8813 0.1813 -0.4363 +vn -0.5469 0.6799 -0.4884 +vn -0.9620 0.1961 -0.1899 +vn -0.2773 0.9604 0.0261 +vn -0.3075 0.9470 0.0925 +vn -0.1705 0.9852 0.0194 +vn -0.1880 0.9813 0.0415 +vn -0.1581 0.9873 0.0165 +vn -0.1660 0.9860 0.0178 +vn -0.2282 0.9736 0.0024 +vn -0.2262 0.9740 0.0136 +vn -0.4892 0.8513 0.1896 +vn -0.4194 0.9038 -0.0857 +vn -0.7568 0.6094 0.2364 +vn -0.8059 0.5920 -0.0000 +vn -0.2579 0.9603 0.1066 +vn -0.1593 0.9850 0.0664 +vn -0.1509 0.9870 0.0552 +vn -0.2251 0.9729 0.0524 +vn -0.4272 0.8551 0.2939 +vn -0.6591 0.6215 0.4233 +vn -0.2015 0.9563 0.2119 +vn -0.1311 0.9843 0.1179 +vn -0.1346 0.9875 0.0824 +vn -0.2201 0.9733 0.0654 +vn -0.3716 0.8652 0.3366 +vn -0.5396 0.6462 0.5396 +vn -0.1854 0.9508 0.2480 +vn -0.1117 0.9824 0.1498 +vn -0.1059 0.9860 0.1290 +vn -0.1746 0.9690 0.1749 +vn -0.2175 0.8823 0.4174 +vn -0.4233 0.6215 0.6591 +vn -0.1249 0.9494 0.2882 +vn -0.0724 0.9819 0.1752 +vn -0.0662 0.9858 0.1542 +vn -0.1128 0.9702 0.2144 +vn -0.0902 0.8884 0.4502 +vn -0.2364 0.6094 0.7568 +vn -0.0925 0.9470 0.3075 +vn -0.0415 0.9813 0.1880 +vn -0.0178 0.9860 0.1660 +vn -0.0137 0.9740 0.2262 +vn 0.0856 0.9038 0.4193 +vn 0.0000 0.5920 0.8059 +vn 0.0261 0.9604 0.2773 +vn 0.0194 0.9852 0.1706 +vn 0.0165 0.9873 0.1581 +vn 0.0024 0.9736 0.2282 +vn 0.1895 0.8513 0.4892 +vn 0.2364 0.6094 0.7568 +vn 0.1066 0.9603 0.2579 +vn 0.0664 0.9850 0.1593 +vn 0.0552 0.9870 0.1509 +vn 0.0524 0.9729 0.2251 +vn 0.2939 0.8551 0.4272 +vn 0.4234 0.6215 0.6592 +vn 0.2119 0.9563 0.2015 +vn 0.1179 0.9843 0.1311 +vn 0.0824 0.9875 0.1346 +vn 0.0654 0.9733 0.2202 +vn 0.3366 0.8652 0.3716 +vn 0.5397 0.6462 0.5396 +vn 0.2479 0.9509 0.1855 +vn 0.1498 0.9824 0.1117 +vn 0.1290 0.9860 0.1059 +vn 0.1749 0.9690 0.1746 +vn 0.4174 0.8823 0.2175 +vn 0.6592 0.6215 0.4234 +vn 0.2882 0.9494 0.1249 +vn 0.1752 0.9819 0.0725 +vn 0.1542 0.9858 0.0662 +vn 0.2144 0.9702 0.1128 +vn 0.4502 0.8884 0.0902 +vn 0.7568 0.6094 0.2364 +vn 0.3075 0.9470 0.0925 +vn 0.1880 0.9813 0.0415 +vn 0.1660 0.9860 0.0178 +vn 0.2262 0.9740 0.0137 +vn 0.4193 0.9038 -0.0856 +vn 0.8059 0.5920 -0.0000 +vn 0.2772 0.9604 -0.0262 +vn 0.1705 0.9852 -0.0193 +vn 0.1581 0.9873 -0.0164 +vn 0.2282 0.9736 -0.0024 +vn 0.4892 0.8513 -0.1895 +vn 0.7568 0.6094 -0.2364 +vn 0.2579 0.9603 -0.1066 +vn 0.1593 0.9850 -0.0664 +vn 0.1509 0.9870 -0.0552 +vn 0.2251 0.9729 -0.0524 +vn 0.4272 0.8551 -0.2939 +vn 0.6591 0.6215 -0.4233 +vn 0.2015 0.9563 -0.2119 +vn 0.1311 0.9843 -0.1179 +vn 0.1346 0.9875 -0.0825 +vn 0.2201 0.9733 -0.0655 +vn 0.3716 0.8652 -0.3366 +vn 0.5396 0.6463 -0.5396 +vn 0.1854 0.9508 -0.2480 +vn 0.1117 0.9824 -0.1498 +vn 0.1059 0.9860 -0.1290 +vn 0.1746 0.9690 -0.1749 +vn 0.2175 0.8823 -0.4174 +vn 0.4233 0.6215 -0.6591 +vn 0.1249 0.9494 -0.2882 +vn 0.0723 0.9819 -0.1752 +vn 0.0662 0.9858 -0.1542 +vn 0.1128 0.9702 -0.2144 +vn 0.0902 0.8884 -0.4502 +vn 0.2364 0.6094 -0.7568 +vn 0.0925 0.9470 -0.3075 +vn 0.0415 0.9813 -0.1880 +vn 0.0178 0.9860 -0.1660 +vn 0.0137 0.9740 -0.2262 +vn -0.0856 0.9038 -0.4193 +vn -0.0000 0.5920 -0.8059 +vn -0.0261 0.9604 -0.2773 +vn -0.0194 0.9852 -0.1705 +vn -0.0164 0.9873 -0.1581 +vn -0.0024 0.9736 -0.2282 +vn -0.1895 0.8513 -0.4892 +vn -0.2364 0.6094 -0.7568 +vn -0.1066 0.9603 -0.2579 +vn -0.0664 0.9850 -0.1593 +vn -0.0552 0.9870 -0.1509 +vn -0.0524 0.9729 -0.2251 +vn -0.2939 0.8551 -0.4272 +vn -0.4233 0.6215 -0.6591 +vn -0.2119 0.9563 -0.2015 +vn -0.1179 0.9843 -0.1311 +vn -0.0825 0.9875 -0.1346 +vn -0.0655 0.9733 -0.2201 +vn -0.3366 0.8652 -0.3716 +vn -0.5396 0.6463 -0.5396 +vn -0.2480 0.9508 -0.1854 +vn -0.1498 0.9824 -0.1116 +vn -0.1290 0.9860 -0.1059 +vn -0.1749 0.9690 -0.1746 +vn -0.4174 0.8823 -0.2175 +vn -0.6591 0.6215 -0.4233 +vn -0.2883 0.9494 -0.1248 +vn -0.1752 0.9819 -0.0724 +vn -0.1542 0.9858 -0.0662 +vn -0.2144 0.9702 -0.1128 +vn -0.4502 0.8884 -0.0902 +vn -0.7568 0.6094 -0.2364 +vn -0.0157 -0.9996 -0.0236 +vn -0.0269 -0.9461 0.3228 +vn 0.0090 -1.0000 -0.0000 +vn -0.0054 -0.9156 0.4021 +vn -0.0624 -0.9968 -0.0494 +vn -0.0693 -0.9391 0.3367 +vn -0.1532 -0.9098 0.3858 +vn -0.1638 -0.9857 -0.0403 +vn -0.3109 -0.8481 0.4291 +vn -0.3774 -0.9259 0.0150 +vn -0.7344 -0.5301 0.4238 +vn -0.8621 -0.4672 0.1962 +vn -0.9141 -0.0493 0.4025 +vn -0.9766 -0.1998 0.0801 +vn 0.0108 -0.4372 0.8993 +vn 0.2031 -0.5004 0.8416 +vn 0.0383 -0.4496 0.8924 +vn -0.0091 -0.4513 0.8923 +vn -0.1079 -0.4475 0.8878 +vn -0.2620 -0.2157 0.9406 +vn -0.3744 -0.0531 0.9257 +vn 0.0606 0.4067 0.9116 +vn 0.2644 0.3488 0.8991 +vn 0.2120 0.3530 0.9113 +vn 0.2785 0.2985 0.9129 +vn 0.2777 0.2185 0.9355 +vn 0.3570 0.1217 0.9261 +vn 0.3102 0.0121 0.9506 +vn 0.0568 0.8663 0.4963 +vn 0.2179 0.7850 0.5799 +vn 0.2119 0.8327 0.5116 +vn 0.3754 0.7638 0.5250 +vn 0.5412 0.6133 0.5753 +vn 0.7779 0.3298 0.5349 +vn 0.7948 0.0763 0.6021 +vn 0.0133 0.9455 0.3253 +vn -0.0261 0.9754 0.2190 +vn 0.1408 0.9355 0.3242 +vn 0.2383 0.9174 0.3188 +vn 0.5669 0.7512 0.3381 +vn 0.8217 0.4120 0.3938 +vn 0.9153 0.1868 0.3567 +vn 0.0239 0.9997 0.0078 +vn -0.0057 1.0000 0.0000 +vn 0.0962 0.9948 0.0337 +vn 0.2330 0.9644 0.1251 +vn 0.5302 0.8309 0.1688 +vn 0.8525 0.4479 -0.2695 +vn 0.9991 0.0414 -0.0105 +vn -0.0457 0.9752 -0.2165 +vn 0.0377 0.9577 -0.2854 +vn 0.0248 0.9519 -0.3055 +vn 0.2156 0.9145 -0.3423 +vn 0.4479 0.8307 -0.3306 +vn 0.7972 0.4742 -0.3735 +vn 0.8753 -0.0703 -0.4785 +vn -0.1379 0.6450 -0.7516 +vn -0.0098 0.4339 -0.9009 +vn -0.0507 0.4405 -0.8963 +vn -0.0336 0.4337 -0.9004 +vn 0.1083 0.4275 -0.8975 +vn 0.2418 0.2379 -0.9407 +vn 0.3855 -0.0233 -0.9224 +vn -0.1766 -0.0845 -0.9806 +vn -0.0602 -0.3991 -0.9149 +vn -0.2157 -0.3290 -0.9194 +vn -0.2835 -0.2384 -0.9288 +vn -0.2576 -0.1152 -0.9594 +vn -0.2676 -0.0483 -0.9623 +vn -0.2348 -0.0066 -0.9720 +vn 0.1542 -0.6967 -0.7006 +vn -0.0516 -0.8611 -0.5058 +vn -0.1810 -0.8256 -0.5345 +vn -0.2884 -0.7687 -0.5709 +vn -0.4463 -0.6137 -0.6513 +vn -0.6405 -0.3759 -0.6697 +vn -0.7257 -0.0220 -0.6877 +vn -0.0025 -0.9226 -0.3857 +vn -0.0082 -0.9536 -0.3011 +vn -0.0625 -0.9553 -0.2888 +vn -0.2101 -0.9402 -0.2681 +vn -0.4505 -0.8503 -0.2722 +vn -0.7214 -0.5755 -0.3851 +vn -0.9117 -0.1139 -0.3948 +vn -0.8775 0.2641 0.4004 +vn -0.9784 0.2046 0.0283 +vn -0.8429 0.3674 0.3931 +vn -0.9112 0.4095 -0.0450 +vn -0.7693 0.5187 0.3731 +vn -0.8267 0.5601 -0.0535 +vn -0.6730 0.6595 0.3349 +vn -0.7167 0.6959 -0.0465 +vn -0.5961 0.7478 0.2923 +vn -0.5806 0.8142 -0.0040 +vn -0.3852 0.8555 0.3459 +vn -0.4547 0.8907 -0.0000 +vn -0.3301 0.0774 0.9407 +vn -0.3229 0.1403 0.9360 +vn -0.3107 0.2325 0.9217 +vn -0.3153 0.2683 0.9103 +vn -0.3010 0.3449 0.8891 +vn -0.0812 0.5557 0.8274 +vn 0.3246 -0.0978 0.9408 +vn 0.2977 -0.1740 0.9387 +vn 0.2275 -0.2259 0.9472 +vn 0.1828 -0.3520 0.9180 +vn 0.0384 -0.4183 0.9075 +vn -0.0342 0.0640 0.9974 +vn 0.7878 -0.2251 0.5734 +vn 0.7211 -0.4018 0.5644 +vn 0.6426 -0.5398 0.5438 +vn 0.5039 -0.7003 0.5056 +vn 0.2964 -0.7968 0.5266 +vn 0.2372 -0.6221 0.7461 +vn 0.9019 -0.2111 0.3769 +vn 0.8152 -0.4398 0.3769 +vn 0.7070 -0.6136 0.3518 +vn 0.5885 -0.7424 0.3201 +vn 0.4614 -0.8399 0.2856 +vn 0.4047 -0.8577 0.3171 +vn 0.9739 -0.2033 -0.1012 +vn 0.8911 -0.4453 -0.0874 +vn 0.7703 -0.6330 -0.0764 +vn 0.6408 -0.7650 -0.0650 +vn 0.5223 -0.8506 -0.0610 +vn 0.4135 -0.9105 -0.0000 +vn 0.8735 -0.2577 -0.4130 +vn 0.7992 -0.4533 -0.3947 +vn 0.6975 -0.6098 -0.3763 +vn 0.5954 -0.7232 -0.3500 +vn 0.5205 -0.7889 -0.3266 +vn 0.4317 -0.8464 -0.3119 +vn 0.3256 -0.0752 -0.9425 +vn 0.3011 -0.1584 -0.9403 +vn 0.2802 -0.2478 -0.9274 +vn 0.2865 -0.2891 -0.9134 +vn 0.3007 -0.3442 -0.8894 +vn 0.5290 -0.7132 -0.4599 +vn -0.2781 0.0865 -0.9567 +vn -0.2582 0.1508 -0.9542 +vn -0.1998 0.1983 -0.9596 +vn -0.1654 0.3271 -0.9304 +vn -0.0186 0.3924 -0.9196 +vn 0.2998 -0.4672 -0.8318 +vn -0.7473 0.1981 -0.6343 +vn -0.6920 0.3605 -0.6255 +vn -0.6368 0.4752 -0.6072 +vn -0.5136 0.6488 -0.5614 +vn -0.3715 0.7528 -0.5434 +vn -0.1513 0.3419 -0.9275 +vn -0.9019 0.1651 -0.3992 +vn -0.8268 0.3968 -0.3987 +vn -0.7568 0.5422 -0.3652 +vn -0.6673 0.6673 -0.3307 +vn -0.5140 0.7816 -0.3535 +vn -0.3899 0.8511 -0.3515 +vn -0.0164 0.9999 -0.0000 +vn 0.2512 0.8519 0.4595 +vn 0.0331 0.8364 0.5471 +vn 0.3458 0.9308 -0.1187 +vn 0.6481 0.6593 0.3812 +vn 0.7145 0.6993 -0.0212 +vn 0.7420 0.5196 0.4237 +vn 0.9010 0.4285 0.0675 +vn 0.8842 0.3364 0.3241 +vn 0.9222 0.3621 0.1355 +vn 0.7797 0.4984 0.3791 +vn 0.8507 0.5190 0.0840 +vn 0.6176 0.7858 -0.0327 +vn 0.5034 0.7225 0.4739 +vn -0.1687 0.4952 0.8522 +vn -0.1313 0.4577 0.8794 +vn -0.0105 0.4762 0.8793 +vn 0.1438 0.5537 0.8202 +vn 0.5475 0.3347 0.7669 +vn 0.4204 0.4386 0.7943 +vn 0.2769 0.5532 0.7857 +vn 0.0896 0.0084 0.9959 +vn -0.4204 -0.0737 0.9043 +vn -0.5068 0.1533 0.8483 +vn -0.4068 -0.1053 0.9074 +vn -0.3345 0.0251 0.9421 +vn -0.2267 0.0967 0.9692 +vn -0.2539 0.1660 0.9529 +vn -0.0004 -0.5317 0.8469 +vn -0.6117 -0.5044 0.6094 +vn -0.8206 -0.1414 0.5538 +vn -0.7875 -0.2984 0.5393 +vn -0.7539 -0.1827 0.6311 +vn -0.5937 -0.2524 0.7640 +vn -0.5227 -0.3307 0.7858 +vn -0.4300 -0.7689 0.4731 +vn -0.1358 -0.8953 0.4244 +vn -0.7875 -0.5155 0.3380 +vn -0.9110 -0.3064 0.2761 +vn -0.8405 -0.2526 0.4794 +vn -0.7734 -0.5551 0.3061 +vn -0.6146 -0.7332 0.2910 +vn -0.1271 -0.9919 0.0000 +vn -0.5228 -0.8222 0.2250 +vn -0.8291 -0.5543 0.0729 +vn -0.9406 -0.3356 -0.0508 +vn -0.9255 -0.3611 -0.1140 +vn -0.7480 -0.6619 -0.0483 +vn -0.4291 -0.9008 0.0660 +vn -0.4151 -0.8090 -0.4163 +vn -0.1358 -0.8953 -0.4243 +vn -0.7954 -0.4757 -0.3756 +vn -0.8997 -0.2068 -0.3845 +vn -0.8416 -0.3524 -0.4093 +vn -0.7706 -0.3514 -0.5317 +vn -0.3599 -0.8167 -0.4511 +vn -0.0004 -0.5317 -0.8469 +vn -0.3238 -0.1908 -0.9267 +vn -0.6810 0.0223 -0.7319 +vn -0.6700 0.1817 -0.7197 +vn -0.5611 0.1870 -0.8063 +vn -0.4708 0.0191 -0.8820 +vn -0.4563 -0.1412 -0.8785 +vn 0.1025 0.0095 -0.9947 +vn -0.0596 0.2927 -0.9543 +vn -0.0734 0.3251 -0.9428 +vn -0.2258 0.4918 -0.8410 +vn -0.0895 0.4497 -0.8887 +vn -0.0513 0.3677 -0.9285 +vn -0.1156 0.3675 -0.9228 +vn -0.0400 0.5910 -0.8057 +vn 0.1921 0.6890 -0.6988 +vn 0.3385 0.6604 -0.6703 +vn 0.3521 0.6595 -0.6642 +vn 0.5273 0.2707 -0.8054 +vn 0.4247 0.3413 -0.8385 +vn 0.3410 0.4865 -0.8043 +vn 0.0367 0.8470 -0.5303 +vn 0.3247 0.8504 -0.4139 +vn 0.6182 0.6349 -0.4634 +vn 0.8373 0.4538 -0.3049 +vn 0.8077 0.3116 -0.5006 +vn 0.7885 0.4282 -0.4414 +vn 0.5692 0.6788 -0.4638 +vn 0.4734 0.8798 -0.0427 +vn 0.3986 0.8239 0.4028 +vn 0.3218 0.9468 -0.0037 +vn 0.2880 0.9203 0.2649 +vn -0.2473 0.9689 -0.0014 +vn -0.0956 0.9669 0.2368 +vn -0.0349 0.5723 -0.8193 +vn -0.6182 0.7858 -0.0197 +vn -0.1825 -0.1188 -0.9760 +vn -0.9700 -0.2417 -0.0261 +vn -0.4736 -0.8325 -0.2876 +vn -0.4424 -0.8967 -0.0124 +vn 0.2176 0.6880 0.6923 +vn 0.1482 0.8583 0.4912 +vn -0.1929 0.9694 0.1521 +vn -0.0283 0.8714 -0.4897 +vn -0.0603 0.5500 -0.8330 +vn -0.2736 -0.0775 -0.9587 +vn -0.3762 0.2609 0.8891 +vn -0.0354 0.7356 0.6765 +vn -0.2285 0.9639 0.1367 +vn 0.0598 0.9323 -0.3568 +vn 0.0889 0.7929 -0.6028 +vn 0.0263 0.6282 -0.7776 +vn -0.6974 0.0020 0.7167 +vn -0.7583 0.0394 0.6507 +vn -0.3414 0.9253 0.1651 +vn 0.0947 0.9610 -0.2598 +vn 0.1329 0.8670 -0.4802 +vn 0.1329 0.7591 -0.6372 +vn -0.7825 -0.5134 0.3525 +vn -0.6756 -0.7156 0.1772 +vn -0.8004 0.5941 0.0796 +vn 0.1412 0.9821 -0.1248 +vn 0.2162 0.9183 -0.3315 +vn 0.2338 0.7548 -0.6129 +vn -0.4300 -0.8802 0.2010 +vn -0.6865 -0.0817 -0.7225 +vn -0.6127 0.1763 -0.7704 +vn 0.1178 0.9912 0.0603 +vn 0.3557 0.9346 0.0031 +vn 0.5468 0.7707 -0.3272 +vn -0.4609 -0.6051 -0.6492 +vn -0.1066 -0.5619 -0.8203 +vn 0.1539 0.7118 -0.6853 +vn 0.1834 0.9765 0.1130 +vn 0.3066 0.9051 0.2945 +vn 0.3399 0.7818 0.5228 +vn 0.1483 0.0619 -0.9870 +vn 0.2372 0.1561 -0.9588 +vn 0.3603 0.8892 -0.2820 +vn 0.1631 0.8917 0.4222 +vn 0.1810 0.6670 0.7227 +vn 0.1286 0.2529 0.9589 +vn -0.0304 0.2044 -0.9784 +vn -0.0014 0.7046 -0.7096 +vn 0.3673 0.9289 -0.0483 +vn 0.2826 0.7729 0.5682 +vn 0.0606 0.4108 0.9097 +vn -0.1116 -0.1024 0.9885 +vn 0.2024 0.5982 -0.7754 +vn 0.1083 0.8651 -0.4898 +vn 0.3219 0.9405 0.1084 +vn 0.2104 0.6386 0.7402 +vn -0.2627 -0.0897 0.9607 +vn -0.3470 -0.4668 0.8134 +vn 0.3878 0.8093 -0.4413 +vn 0.2138 0.9285 -0.3037 +vn 0.2043 0.9583 0.1998 +vn 0.0141 0.6005 0.7995 +vn -0.8056 -0.3468 0.4804 +vn -0.4900 -0.8134 0.3135 +vn -0.5969 0.8023 0.0000 +vn -0.3372 0.8814 0.3308 vn 0.0000 1.0000 0.0000 -vn -0.9925 -0.1220 0.0000 -vn -0.9374 -0.3483 0.0000 -vn -0.9052 -0.3489 -0.2429 -vn -0.9586 -0.1222 -0.2571 -vn -0.8321 0.5547 -0.0000 -vn -0.8032 0.5554 -0.2153 -vn -0.0486 0.9988 -0.0000 -vn -0.0463 0.9988 -0.0128 -vn 0.5443 0.8389 -0.0000 -vn 0.5254 0.8391 0.1409 -vn 0.7835 0.6214 -0.0000 -vn 0.7564 0.6219 0.2029 -vn 0.8809 0.4733 0.0000 -vn 0.8506 0.4738 0.2282 -vn -0.8109 -0.3494 -0.4694 -vn -0.8590 -0.1224 -0.4971 -vn -0.7197 0.5555 -0.4165 -vn -0.0417 0.9988 -0.0244 -vn 0.4701 0.8396 0.2720 -vn 0.6772 0.6227 0.3920 -vn 0.7618 0.4745 0.4410 -vn -0.6625 -0.3496 -0.6625 -vn -0.7018 -0.1225 -0.7018 -vn -0.5879 0.5556 -0.5879 -vn -0.0343 0.9988 -0.0343 -vn 0.3838 0.8398 0.3839 -vn 0.5532 0.6229 0.5531 -vn 0.6223 0.4748 0.6223 -vn -0.4694 -0.3494 -0.8109 -vn -0.4971 -0.1224 -0.8590 -vn -0.4164 0.5555 -0.7197 -vn -0.0244 0.9988 -0.0418 -vn 0.2720 0.8396 0.4701 -vn 0.3920 0.6226 0.6772 -vn 0.4410 0.4745 0.7618 -vn -0.2428 -0.3488 -0.9052 -vn -0.2571 -0.1222 -0.9586 -vn -0.2154 0.5554 -0.8032 -vn -0.0127 0.9988 -0.0462 -vn 0.1409 0.8391 0.5254 -vn 0.2029 0.6219 0.7564 -vn 0.2282 0.4738 0.8505 -vn -0.0000 -0.3484 -0.9374 -vn -0.0000 -0.1220 -0.9925 -vn 0.0000 0.5547 -0.8321 -vn 0.0000 0.9988 -0.0486 -vn 0.0000 0.8389 0.5443 -vn 0.0000 0.6214 0.7835 -vn -0.0000 0.4733 0.8809 -vn 0.2429 -0.3488 -0.9052 -vn 0.2571 -0.1222 -0.9586 -vn 0.2153 0.5554 -0.8032 -vn 0.0127 0.9988 -0.0462 -vn -0.1409 0.8391 0.5254 -vn -0.2029 0.6219 0.7564 -vn -0.2282 0.4738 0.8505 -vn 0.4694 -0.3494 -0.8109 -vn 0.4971 -0.1224 -0.8590 -vn 0.4165 0.5555 -0.7197 -vn 0.0244 0.9988 -0.0418 -vn -0.2720 0.8396 0.4701 -vn -0.3920 0.6226 0.6772 -vn -0.4410 0.4745 0.7618 -vn 0.6625 -0.3496 -0.6625 -vn 0.7018 -0.1225 -0.7018 -vn 0.5879 0.5557 -0.5879 -vn 0.0343 0.9988 -0.0343 -vn -0.3839 0.8398 0.3839 -vn -0.5532 0.6229 0.5532 -vn -0.6223 0.4748 0.6223 -vn 0.8109 -0.3494 -0.4694 -vn 0.8590 -0.1224 -0.4971 -vn 0.7197 0.5555 -0.4164 -vn 0.0418 0.9988 -0.0244 -vn -0.4701 0.8396 0.2720 -vn -0.6772 0.6226 0.3920 -vn -0.7618 0.4745 0.4410 -vn 0.9052 -0.3488 -0.2428 -vn 0.9586 -0.1222 -0.2571 -vn 0.8032 0.5554 -0.2154 -vn 0.0462 0.9988 -0.0127 -vn -0.5254 0.8391 0.1409 -vn -0.7564 0.6219 0.2029 -vn -0.8506 0.4738 0.2282 -vn 0.9374 -0.3484 -0.0000 -vn 0.9925 -0.1220 -0.0000 -vn 0.8321 0.5547 0.0000 -vn 0.0486 0.9988 0.0000 -vn -0.5443 0.8389 0.0000 -vn -0.7835 0.6214 0.0000 -vn 0.9052 -0.3488 0.2429 -vn 0.9586 -0.1222 0.2571 -vn 0.8032 0.5554 0.2153 -vn 0.0462 0.9988 0.0127 -vn -0.5254 0.8391 -0.1409 -vn -0.7564 0.6219 -0.2029 -vn 0.8109 -0.3494 0.4694 -vn 0.8590 -0.1224 0.4971 -vn 0.7197 0.5555 0.4165 -vn 0.0418 0.9988 0.0244 -vn -0.4701 0.8396 -0.2720 -vn -0.6772 0.6226 -0.3920 -vn -0.7618 0.4745 -0.4410 -vn 0.6625 -0.3496 0.6625 -vn 0.7018 -0.1225 0.7018 -vn 0.5879 0.5557 0.5879 -vn 0.0343 0.9988 0.0343 -vn -0.3839 0.8398 -0.3839 -vn -0.5532 0.6229 -0.5532 -vn -0.6223 0.4748 -0.6223 -vn 0.4694 -0.3494 0.8109 -vn 0.4971 -0.1224 0.8590 -vn 0.4165 0.5555 0.7197 -vn 0.0244 0.9988 0.0418 -vn -0.2720 0.8396 -0.4701 -vn -0.3920 0.6226 -0.6772 -vn -0.4410 0.4745 -0.7618 -vn 0.2429 -0.3488 0.9052 -vn 0.2571 -0.1222 0.9586 -vn 0.2154 0.5554 0.8032 -vn 0.0127 0.9988 0.0462 -vn -0.1409 0.8391 -0.5254 -vn -0.2029 0.6219 -0.7564 -vn -0.2282 0.4738 -0.8505 -vn 0.0000 -0.3484 0.9374 -vn 0.0000 -0.1220 0.9925 -vn -0.0000 0.5547 0.8321 -vn -0.0000 0.9988 0.0486 -vn -0.0000 0.8389 -0.5443 -vn -0.0000 0.6214 -0.7835 -vn 0.0000 0.4733 -0.8809 -vn -0.2429 -0.3488 0.9052 -vn -0.2571 -0.1222 0.9586 -vn -0.2153 0.5554 0.8032 -vn -0.0127 0.9988 0.0462 -vn 0.1409 0.8391 -0.5254 -vn 0.2029 0.6219 -0.7564 -vn 0.2282 0.4738 -0.8506 -vn -0.4694 -0.3494 0.8109 -vn -0.4971 -0.1224 0.8590 -vn -0.4165 0.5555 0.7197 -vn -0.0244 0.9988 0.0418 -vn 0.2720 0.8396 -0.4701 -vn 0.3920 0.6226 -0.6772 -vn 0.4410 0.4745 -0.7618 -vn -0.6625 -0.3496 0.6625 -vn -0.7018 -0.1225 0.7018 -vn -0.5879 0.5556 0.5879 -vn -0.0343 0.9988 0.0343 -vn 0.3839 0.8398 -0.3838 -vn 0.5532 0.6229 -0.5531 -vn 0.6223 0.4748 -0.6223 -vn -0.8109 -0.3494 0.4694 -vn -0.8590 -0.1224 0.4971 -vn -0.7197 0.5555 0.4165 -vn -0.0417 0.9988 0.0244 -vn 0.4701 0.8396 -0.2720 -vn 0.6772 0.6227 -0.3920 -vn 0.7618 0.4745 -0.4410 -vn -0.9052 -0.3489 0.2428 -vn -0.9586 -0.1222 0.2571 -vn -0.8032 0.5554 0.2154 -vn -0.0463 0.9988 0.0128 -vn 0.5254 0.8391 -0.1409 -vn 0.7564 0.6219 -0.2029 -vn 0.8506 0.4738 -0.2282 -vn 0.9083 0.4183 -0.0000 -vn 0.8771 0.4188 0.2353 -vn 0.9203 0.3912 0.0000 -vn 0.8887 0.3917 0.2384 -vn 0.9073 0.3428 0.2435 -vn 0.7857 0.4194 0.4548 -vn 0.7961 0.3923 0.4608 -vn 0.8128 0.3434 0.4705 -vn 0.9315 0.2656 0.2486 -vn 0.8342 0.2664 0.4829 -vn 0.8553 0.1524 0.4952 -vn 0.9666 -0.0429 0.2526 -vn 0.8645 -0.0458 0.5005 -vn 0.6418 0.4197 0.6418 -vn 0.6503 0.3925 0.6504 -vn 0.6641 0.3436 0.6641 -vn 0.6815 0.2666 0.6815 -vn 0.6988 0.1525 0.6988 -vn 0.7064 -0.0459 0.7064 -vn 0.4548 0.4194 0.7857 -vn 0.4608 0.3923 0.7961 -vn 0.4705 0.3434 0.8128 -vn 0.4829 0.2664 0.8342 -vn 0.4952 0.1524 0.8553 -vn 0.5005 -0.0458 0.8645 -vn 0.2353 0.4188 0.8771 -vn 0.2384 0.3917 0.8887 -vn 0.2435 0.3428 0.9073 -vn 0.2499 0.2659 0.9311 -vn 0.2562 0.1521 0.9546 -vn 0.2590 -0.0457 0.9648 -vn 0.0000 0.4183 0.9083 -vn 0.0000 0.3912 0.9203 -vn 0.0000 0.3424 0.9396 -vn -0.0000 0.2656 0.9641 -vn -0.0000 0.1519 0.9884 -vn 0.0000 -0.0457 0.9990 -vn -0.2353 0.4188 0.8771 -vn -0.2385 0.3917 0.8887 -vn -0.2435 0.3428 0.9073 -vn -0.2499 0.2659 0.9311 -vn -0.2562 0.1521 0.9546 -vn -0.2590 -0.0457 0.9648 -vn -0.4548 0.4194 0.7857 -vn -0.4608 0.3923 0.7961 -vn -0.4705 0.3434 0.8128 -vn -0.4829 0.2664 0.8342 -vn -0.4952 0.1524 0.8553 -vn -0.5005 -0.0458 0.8645 -vn -0.6418 0.4197 0.6418 -vn -0.6504 0.3925 0.6503 -vn -0.6641 0.3436 0.6641 -vn -0.6815 0.2666 0.6815 -vn -0.6988 0.1525 0.6988 -vn -0.7064 -0.0459 0.7064 -vn -0.7857 0.4194 0.4548 -vn -0.7961 0.3923 0.4608 -vn -0.8128 0.3434 0.4705 -vn -0.8342 0.2664 0.4829 -vn -0.8553 0.1524 0.4952 -vn -0.8645 -0.0458 0.5005 -vn -0.9073 0.3428 0.2435 -vn -0.9311 0.2659 0.2499 -vn -0.9546 0.1521 0.2562 -vn -0.9648 -0.0457 0.2590 -vn -0.9396 0.3424 0.0000 -vn -0.9641 0.2656 -0.0000 -vn -0.9884 0.1519 -0.0000 -vn -0.8887 0.3917 -0.2384 -vn -0.9073 0.3428 -0.2435 -vn -0.9311 0.2659 -0.2499 -vn -0.9546 0.1521 -0.2562 -vn -0.7857 0.4194 -0.4548 -vn -0.7961 0.3923 -0.4608 -vn -0.8128 0.3434 -0.4705 -vn -0.8342 0.2664 -0.4829 -vn -0.8553 0.1524 -0.4952 -vn -0.9648 -0.0457 -0.2590 -vn -0.8645 -0.0458 -0.5005 -vn -0.6418 0.4197 -0.6418 -vn -0.6504 0.3925 -0.6504 -vn -0.6641 0.3436 -0.6641 -vn -0.6815 0.2666 -0.6815 -vn -0.6988 0.1525 -0.6988 -vn -0.7064 -0.0459 -0.7064 -vn -0.4548 0.4194 -0.7857 -vn -0.4608 0.3923 -0.7961 -vn -0.4705 0.3434 -0.8128 -vn -0.4829 0.2664 -0.8342 -vn -0.4952 0.1524 -0.8553 -vn -0.5005 -0.0458 -0.8645 -vn -0.2353 0.4188 -0.8771 -vn -0.2384 0.3917 -0.8887 -vn -0.2435 0.3428 -0.9073 -vn -0.2499 0.2659 -0.9311 -vn -0.2562 0.1521 -0.9546 -vn -0.2590 -0.0457 -0.9648 -vn 0.0000 0.4183 -0.9083 -vn 0.0000 0.3912 -0.9203 -vn -0.0000 0.3424 -0.9396 -vn 0.0000 0.2656 -0.9641 -vn 0.0000 0.1519 -0.9884 -vn -0.0000 -0.0457 -0.9990 -vn 0.2353 0.4188 -0.8771 -vn 0.2384 0.3917 -0.8887 -vn 0.2435 0.3428 -0.9073 -vn 0.2499 0.2659 -0.9311 -vn 0.2562 0.1521 -0.9546 -vn 0.2590 -0.0457 -0.9648 -vn 0.4548 0.4194 -0.7857 -vn 0.4608 0.3923 -0.7961 -vn 0.4705 0.3434 -0.8128 -vn 0.4829 0.2664 -0.8342 -vn 0.4952 0.1524 -0.8553 -vn 0.5005 -0.0458 -0.8645 -vn 0.6418 0.4197 -0.6418 -vn 0.6504 0.3925 -0.6503 -vn 0.6641 0.3436 -0.6641 -vn 0.6815 0.2666 -0.6815 -vn 0.6988 0.1525 -0.6988 -vn 0.7064 -0.0459 -0.7064 -vn 0.7857 0.4194 -0.4548 -vn 0.7961 0.3923 -0.4608 -vn 0.8128 0.3434 -0.4705 -vn 0.8342 0.2664 -0.4829 -vn 0.8553 0.1524 -0.4952 -vn 0.8645 -0.0458 -0.5005 -vn 0.8771 0.4188 -0.2353 -vn 0.8887 0.3917 -0.2384 -vn 0.9073 0.3428 -0.2435 -vn 0.7959 -0.5665 0.2136 -vn 0.7122 -0.7020 -0.0000 -vn 0.6874 -0.7025 0.1844 -vn 0.6530 -0.7574 -0.0000 -vn 0.6302 -0.7578 0.1690 -vn 0.7240 -0.6898 -0.0000 -vn 0.6988 -0.6904 0.1874 -vn 0.8864 -0.4629 -0.0000 -vn 0.8559 -0.4635 0.2296 -vn 0.8178 -0.3272 0.4735 -vn 0.7128 -0.5672 0.4126 -vn 0.6154 -0.7032 0.3562 -vn 0.5641 -0.7584 0.3265 -vn 0.6257 -0.6909 0.3621 -vn 0.7666 -0.4641 0.4437 -vn 0.6681 -0.3274 0.6681 -vn 0.5822 -0.5675 0.5822 -vn 0.5026 -0.7034 0.5026 -vn 0.4606 -0.7587 0.4606 -vn 0.5110 -0.6912 0.5110 -vn 0.6262 -0.4644 0.6262 -vn 0.4735 -0.3272 0.8178 -vn 0.4126 -0.5672 0.7128 -vn 0.3562 -0.7032 0.6154 -vn 0.3265 -0.7585 0.5641 -vn 0.3621 -0.6909 0.6257 -vn 0.4437 -0.4641 0.7666 -vn 0.2450 -0.3266 0.9128 -vn 0.2136 -0.5665 0.7959 -vn 0.1844 -0.7025 0.6874 -vn 0.1690 -0.7578 0.6302 -vn 0.1874 -0.6903 0.6988 -vn 0.2296 -0.4635 0.8559 -vn 0.0000 -0.3263 0.9453 -vn 0.0000 -0.5660 0.8244 -vn -0.0000 -0.7020 0.7122 -vn 0.0000 -0.7574 0.6530 -vn 0.0000 -0.6898 0.7240 -vn 0.0000 -0.4629 0.8864 -vn -0.2450 -0.3266 0.9128 -vn -0.2136 -0.5665 0.7959 -vn -0.1844 -0.7025 0.6874 -vn -0.1690 -0.7578 0.6302 -vn -0.1874 -0.6903 0.6988 -vn -0.2296 -0.4635 0.8559 -vn -0.4735 -0.3272 0.8178 -vn -0.4126 -0.5672 0.7127 -vn -0.3562 -0.7032 0.6154 -vn -0.3265 -0.7585 0.5641 -vn -0.3621 -0.6909 0.6257 -vn -0.4437 -0.4641 0.7666 -vn -0.6681 -0.3274 0.6681 -vn -0.5822 -0.5675 0.5822 -vn -0.5026 -0.7034 0.5026 -vn -0.4606 -0.7587 0.4606 -vn -0.5110 -0.6912 0.5110 -vn -0.6262 -0.4644 0.6262 -vn -0.8178 -0.3272 0.4735 -vn -0.7128 -0.5672 0.4126 -vn -0.6154 -0.7032 0.3562 -vn -0.5641 -0.7585 0.3265 -vn -0.6257 -0.6909 0.3621 -vn -0.7666 -0.4641 0.4437 -vn -0.9130 -0.3266 0.2446 -vn -0.6874 -0.7025 0.1844 -vn -0.6302 -0.7578 0.1690 -vn -0.6988 -0.6903 0.1874 -vn -0.8559 -0.4635 0.2296 -vn -0.8244 -0.5660 0.0000 -vn -0.7122 -0.7020 -0.0000 -vn -0.6530 -0.7574 0.0000 -vn -0.7240 -0.6898 0.0000 -vn -0.8864 -0.4629 0.0000 -vn -0.7959 -0.5665 -0.2136 -vn -0.6874 -0.7025 -0.1844 -vn -0.6302 -0.7578 -0.1690 -vn -0.6988 -0.6903 -0.1874 -vn -0.8559 -0.4635 -0.2296 -vn -0.8178 -0.3272 -0.4735 -vn -0.7128 -0.5672 -0.4126 -vn -0.6154 -0.7032 -0.3562 -vn -0.5641 -0.7585 -0.3265 -vn -0.6257 -0.6909 -0.3621 -vn -0.7666 -0.4641 -0.4437 -vn -0.6681 -0.3274 -0.6681 -vn -0.5822 -0.5675 -0.5822 -vn -0.5026 -0.7034 -0.5026 -vn -0.4606 -0.7587 -0.4606 -vn -0.5110 -0.6912 -0.5110 -vn -0.6262 -0.4644 -0.6262 -vn -0.4735 -0.3272 -0.8178 -vn -0.4126 -0.5672 -0.7128 -vn -0.3562 -0.7032 -0.6154 -vn -0.3265 -0.7585 -0.5641 -vn -0.3621 -0.6909 -0.6257 -vn -0.4437 -0.4641 -0.7666 -vn -0.2450 -0.3266 -0.9128 -vn -0.2136 -0.5665 -0.7959 -vn -0.1844 -0.7025 -0.6874 -vn -0.1690 -0.7578 -0.6302 -vn -0.1874 -0.6903 -0.6988 -vn -0.2296 -0.4635 -0.8559 -vn -0.0000 -0.3263 -0.9453 -vn -0.0000 -0.5660 -0.8244 -vn 0.0000 -0.7020 -0.7122 -vn -0.0000 -0.7574 -0.6530 -vn -0.0000 -0.6898 -0.7240 -vn 0.0000 -0.4629 -0.8864 -vn 0.2450 -0.3266 -0.9128 -vn 0.2136 -0.5665 -0.7959 -vn 0.1844 -0.7025 -0.6874 -vn 0.1690 -0.7578 -0.6302 -vn 0.1874 -0.6903 -0.6988 -vn 0.2296 -0.4635 -0.8559 -vn 0.4735 -0.3272 -0.8178 -vn 0.4126 -0.5672 -0.7128 -vn 0.3562 -0.7032 -0.6154 -vn 0.3265 -0.7585 -0.5641 -vn 0.3621 -0.6909 -0.6257 -vn 0.4437 -0.4641 -0.7666 -vn 0.6681 -0.3274 -0.6681 -vn 0.5822 -0.5675 -0.5822 -vn 0.5026 -0.7034 -0.5026 -vn 0.4606 -0.7587 -0.4606 -vn 0.5110 -0.6912 -0.5110 -vn 0.6262 -0.4644 -0.6262 -vn 0.8178 -0.3272 -0.4735 -vn 0.7128 -0.5672 -0.4126 -vn 0.6154 -0.7032 -0.3562 -vn 0.5641 -0.7585 -0.3265 -vn 0.6257 -0.6909 -0.3621 -vn 0.7666 -0.4641 -0.4437 -vn 0.9129 -0.3266 -0.2449 -vn 0.7959 -0.5665 -0.2136 -vn 0.6874 -0.7025 -0.1844 -vn 0.6302 -0.7578 -0.1690 -vn 0.6988 -0.6904 -0.1874 -vn 0.8559 -0.4635 -0.2296 -vn 0.0257 -0.9997 -0.0000 -vn 0.0000 -1.0000 -0.0000 -vn 0.0248 -0.9997 -0.0066 -vn 0.0687 -0.9976 0.0000 -vn 0.0663 -0.9976 -0.0177 -vn 0.1572 -0.9876 0.0000 -vn 0.1517 -0.9876 -0.0406 -vn 0.3732 -0.9278 -0.0000 -vn 0.3601 -0.9279 -0.0965 -vn 0.7892 -0.6142 0.0000 -vn 0.7620 -0.6144 -0.2045 -vn 0.0222 -0.9997 -0.0128 -vn 0.0593 -0.9977 -0.0343 -vn 0.1356 -0.9876 -0.0785 -vn 0.3222 -0.9282 -0.1864 -vn 0.6823 -0.6151 -0.3950 -vn 0.0181 -0.9997 -0.0181 -vn 0.0484 -0.9977 -0.0484 -vn 0.1107 -0.9877 -0.1107 -vn 0.2630 -0.9283 -0.2630 -vn 0.5574 -0.6154 -0.5574 -vn 0.0128 -0.9997 -0.0222 -vn 0.0343 -0.9977 -0.0593 -vn 0.0785 -0.9876 -0.1356 -vn 0.1864 -0.9282 -0.3222 -vn 0.3950 -0.6152 -0.6823 -vn 0.0066 -0.9997 -0.0248 -vn 0.0177 -0.9976 -0.0663 -vn 0.0406 -0.9876 -0.1517 -vn 0.0965 -0.9279 -0.3601 -vn 0.2045 -0.6144 -0.7620 -vn -0.0000 -0.9997 -0.0257 -vn 0.0000 -0.9976 -0.0687 -vn 0.0000 -0.9876 -0.1572 -vn -0.0000 -0.9278 -0.3732 -vn -0.0000 -0.6142 -0.7892 -vn -0.0066 -0.9997 -0.0248 -vn -0.0177 -0.9976 -0.0663 -vn -0.0406 -0.9876 -0.1517 -vn -0.0965 -0.9279 -0.3601 -vn -0.2045 -0.6144 -0.7620 -vn -0.0128 -0.9997 -0.0222 -vn -0.0343 -0.9977 -0.0593 -vn -0.0785 -0.9876 -0.1356 -vn -0.1864 -0.9282 -0.3222 -vn -0.3950 -0.6151 -0.6823 -vn -0.0181 -0.9997 -0.0181 -vn -0.0484 -0.9977 -0.0484 -vn -0.1107 -0.9877 -0.1107 -vn -0.2630 -0.9283 -0.2630 -vn -0.5574 -0.6154 -0.5573 -vn -0.0222 -0.9997 -0.0128 -vn -0.0593 -0.9977 -0.0343 -vn -0.1356 -0.9876 -0.0785 -vn -0.3222 -0.9282 -0.1864 -vn -0.6823 -0.6151 -0.3950 -vn -0.0248 -0.9997 -0.0066 -vn -0.0663 -0.9976 -0.0177 -vn -0.1517 -0.9876 -0.0406 -vn -0.3601 -0.9279 -0.0965 -vn -0.7620 -0.6144 -0.2045 -vn -0.0257 -0.9997 0.0000 -vn -0.0687 -0.9976 -0.0000 -vn -0.1572 -0.9876 -0.0000 -vn -0.3731 -0.9278 0.0000 -vn -0.7892 -0.6142 0.0000 -vn -0.0248 -0.9997 0.0066 -vn -0.0663 -0.9976 0.0177 -vn -0.1517 -0.9876 0.0406 -vn -0.3601 -0.9279 0.0965 -vn -0.7620 -0.6144 0.2045 -vn -0.0222 -0.9997 0.0128 -vn -0.0593 -0.9977 0.0343 -vn -0.1356 -0.9876 0.0785 -vn -0.3222 -0.9282 0.1864 -vn -0.6823 -0.6151 0.3950 -vn -0.0181 -0.9997 0.0181 -vn -0.0484 -0.9977 0.0484 -vn -0.1107 -0.9877 0.1107 -vn -0.2630 -0.9283 0.2630 -vn -0.5574 -0.6154 0.5573 -vn -0.0128 -0.9997 0.0222 -vn -0.0343 -0.9977 0.0593 -vn -0.0785 -0.9876 0.1357 -vn -0.1864 -0.9282 0.3222 -vn -0.3950 -0.6151 0.6823 -vn -0.0066 -0.9997 0.0248 -vn -0.0177 -0.9976 0.0663 -vn -0.0406 -0.9876 0.1517 -vn -0.0965 -0.9279 0.3601 -vn -0.2045 -0.6144 0.7620 -vn 0.0000 -0.9997 0.0257 -vn -0.0000 -0.9976 0.0687 -vn -0.0000 -0.9876 0.1572 -vn 0.0000 -0.9278 0.3732 -vn 0.0000 -0.6142 0.7892 -vn 0.0066 -0.9997 0.0248 -vn 0.0177 -0.9976 0.0663 -vn 0.0406 -0.9876 0.1517 -vn 0.0965 -0.9279 0.3601 -vn 0.2045 -0.6144 0.7620 -vn 0.0128 -0.9997 0.0222 -vn 0.0343 -0.9977 0.0593 -vn 0.0785 -0.9876 0.1356 -vn 0.1864 -0.9282 0.3222 -vn 0.3950 -0.6152 0.6823 -vn 0.0181 -0.9997 0.0181 -vn 0.0484 -0.9977 0.0484 -vn 0.1107 -0.9877 0.1107 -vn 0.2630 -0.9283 0.2630 -vn 0.5574 -0.6154 0.5574 -vn 0.0222 -0.9997 0.0128 -vn 0.0593 -0.9977 0.0343 -vn 0.1356 -0.9876 0.0785 -vn 0.3222 -0.9282 0.1864 -vn 0.6823 -0.6151 0.3950 -vn 0.0248 -0.9997 0.0066 -vn 0.0663 -0.9976 0.0177 -vn 0.1517 -0.9876 0.0406 -vn 0.3601 -0.9279 0.0965 -vn 0.7620 -0.6144 0.2045 -vn 0.4648 -0.3736 -0.8027 -vn 0.6558 -0.3739 -0.6558 -vn -0.0000 -0.3726 0.9280 -vn -0.2407 -0.3730 0.8960 -vn -0.8027 -0.3736 0.4648 -vn 0.8961 -0.3730 -0.2407 -vn -0.9280 -0.3725 -0.0000 -vn 0.9280 -0.3726 0.0000 -vn 0.7171 0.6970 -0.0000 -vn 0.9904 -0.1383 -0.0001 -vn 0.9567 -0.1388 0.2558 -vn 0.6921 0.6975 0.1856 -vn 0.8573 -0.1393 0.4955 -vn 0.6201 0.6977 0.3588 -vn 0.7002 -0.1395 0.7002 -vn 0.5065 0.6977 0.5066 -vn 0.4955 -0.1393 0.8574 -vn 0.3588 0.6977 0.6200 -vn 0.2559 -0.1388 0.9567 -vn 0.1856 0.6975 0.6921 -vn 0.0001 -0.1383 0.9904 -vn 0.0000 0.6970 0.7171 -vn -0.2558 -0.1388 0.9567 -vn -0.1856 0.6975 0.6922 -vn -0.4956 -0.1393 0.8573 -vn -0.3589 0.6977 0.6201 -vn -0.7002 -0.1396 0.7001 -vn -0.5066 0.6977 0.5065 -vn -0.8574 -0.1393 0.4955 -vn -0.6200 0.6977 0.3588 -vn -0.9567 -0.1387 0.2559 -vn -0.6921 0.6975 0.1856 -vn -0.9904 -0.1383 0.0001 -vn -0.7171 0.6970 0.0000 -vn -0.9567 -0.1388 -0.2559 -vn -0.6921 0.6975 -0.1856 -vn -0.8573 -0.1393 -0.4956 -vn -0.6201 0.6977 -0.3588 -vn -0.7002 -0.1395 -0.7002 -vn -0.5065 0.6977 -0.5066 -vn -0.4955 -0.1393 -0.8574 -vn -0.3588 0.6977 -0.6200 -vn -0.2559 -0.1388 -0.9567 -vn -0.1856 0.6975 -0.6921 -vn -0.0001 -0.1383 -0.9904 -vn -0.0000 0.6970 -0.7171 -vn 0.2558 -0.1388 -0.9567 -vn 0.1856 0.6975 -0.6921 -vn 0.4956 -0.1393 -0.8573 -vn 0.3589 0.6977 -0.6201 -vn 0.7002 -0.1396 -0.7002 -vn 0.5066 0.6977 -0.5065 -vn 0.8574 -0.1393 -0.4955 -vn 0.6200 0.6977 -0.3588 -vn 0.9567 -0.1387 -0.2559 -vn 0.6921 0.6975 -0.1856 -vn 0.2925 0.9563 -0.0000 -vn 0.2821 0.9564 0.0757 -vn 0.1780 0.9840 0.0000 -vn 0.1716 0.9841 0.0460 -vn 0.1589 0.9873 0.0000 -vn 0.1533 0.9873 0.0411 -vn 0.2177 0.9760 -0.0000 -vn 0.2101 0.9761 0.0563 -vn 0.5047 0.8633 0.0000 -vn 0.4872 0.8635 0.1306 -vn 0.6933 0.7207 0.0000 -vn 0.6691 0.7212 0.1795 -vn 0.2524 0.9565 0.1461 -vn 0.1535 0.9841 0.0889 -vn 0.1371 0.9874 0.0793 -vn 0.1879 0.9762 0.1087 -vn 0.4359 0.8639 0.2522 -vn 0.5990 0.7218 0.3467 -vn 0.2061 0.9566 0.2061 -vn 0.1253 0.9842 0.1253 -vn 0.1119 0.9874 0.1119 -vn 0.1534 0.9762 0.1534 -vn 0.3559 0.8641 0.3559 -vn 0.4892 0.7221 0.4892 -vn 0.1461 0.9565 0.2524 -vn 0.0889 0.9841 0.1535 -vn 0.0793 0.9874 0.1371 -vn 0.1087 0.9762 0.1879 -vn 0.2522 0.8639 0.4359 -vn 0.3467 0.7218 0.5990 -vn 0.0757 0.9564 0.2821 -vn 0.0460 0.9841 0.1716 -vn 0.0411 0.9873 0.1533 -vn 0.0563 0.9761 0.2101 -vn 0.1306 0.8635 0.4872 -vn 0.1795 0.7212 0.6691 -vn 0.0000 0.9563 0.2925 -vn 0.0000 0.9840 0.1780 -vn -0.0000 0.9873 0.1589 -vn 0.0000 0.9760 0.2177 -vn -0.0000 0.8633 0.5047 -vn -0.0000 0.7207 0.6933 -vn -0.0757 0.9564 0.2821 -vn -0.0460 0.9841 0.1716 -vn -0.0411 0.9873 0.1533 -vn -0.0563 0.9761 0.2101 -vn -0.1306 0.8635 0.4872 -vn -0.1795 0.7212 0.6691 -vn -0.1461 0.9565 0.2524 -vn -0.0889 0.9841 0.1535 -vn -0.0793 0.9874 0.1371 -vn -0.1087 0.9762 0.1879 -vn -0.2522 0.8639 0.4359 -vn -0.3467 0.7218 0.5990 -vn -0.2061 0.9566 0.2061 -vn -0.1253 0.9842 0.1253 -vn -0.1119 0.9874 0.1119 -vn -0.1534 0.9762 0.1534 -vn -0.3559 0.8641 0.3559 -vn -0.4892 0.7221 0.4892 -vn -0.2524 0.9565 0.1461 -vn -0.1535 0.9841 0.0889 -vn -0.1371 0.9874 0.0793 -vn -0.1879 0.9762 0.1087 -vn -0.4359 0.8639 0.2522 -vn -0.5990 0.7218 0.3467 -vn -0.2821 0.9564 0.0757 -vn -0.1716 0.9841 0.0460 -vn -0.1533 0.9873 0.0411 -vn -0.2101 0.9761 0.0563 -vn -0.4872 0.8635 0.1306 -vn -0.6691 0.7212 0.1795 -vn -0.2925 0.9563 -0.0000 -vn -0.1780 0.9840 -0.0000 -vn -0.1589 0.9873 -0.0000 -vn -0.2177 0.9760 0.0000 -vn -0.5047 0.8633 -0.0000 -vn -0.6933 0.7207 -0.0000 -vn -0.2821 0.9564 -0.0757 -vn -0.1716 0.9841 -0.0460 -vn -0.1533 0.9873 -0.0411 -vn -0.2101 0.9761 -0.0563 -vn -0.4872 0.8635 -0.1306 -vn -0.6691 0.7212 -0.1795 -vn -0.2524 0.9565 -0.1461 -vn -0.1535 0.9841 -0.0889 -vn -0.1371 0.9874 -0.0793 -vn -0.1879 0.9762 -0.1087 -vn -0.4359 0.8639 -0.2522 -vn -0.5990 0.7218 -0.3467 -vn -0.2061 0.9566 -0.2061 -vn -0.1253 0.9842 -0.1253 -vn -0.1119 0.9874 -0.1119 -vn -0.1534 0.9762 -0.1534 -vn -0.3559 0.8641 -0.3559 -vn -0.4892 0.7221 -0.4892 -vn -0.1461 0.9565 -0.2524 -vn -0.0889 0.9841 -0.1535 -vn -0.0793 0.9874 -0.1371 -vn -0.1087 0.9762 -0.1879 -vn -0.2522 0.8639 -0.4359 -vn -0.3467 0.7218 -0.5990 -vn -0.0757 0.9564 -0.2821 -vn -0.0460 0.9841 -0.1716 -vn -0.0411 0.9873 -0.1533 -vn -0.0563 0.9761 -0.2101 -vn -0.1306 0.8635 -0.4872 -vn -0.1795 0.7212 -0.6691 -vn 0.0000 0.9563 -0.2925 -vn -0.0000 0.9840 -0.1780 -vn 0.0000 0.9873 -0.1589 -vn -0.0000 0.9760 -0.2177 -vn 0.0000 0.8633 -0.5047 -vn 0.0000 0.7207 -0.6933 -vn 0.0757 0.9564 -0.2821 -vn 0.0460 0.9841 -0.1716 -vn 0.0411 0.9873 -0.1533 -vn 0.0563 0.9761 -0.2101 -vn 0.1306 0.8635 -0.4872 -vn 0.1795 0.7212 -0.6691 -vn 0.1461 0.9565 -0.2524 -vn 0.0889 0.9841 -0.1535 -vn 0.0793 0.9874 -0.1371 -vn 0.1087 0.9762 -0.1879 -vn 0.2522 0.8639 -0.4359 -vn 0.3467 0.7218 -0.5990 -vn 0.2061 0.9566 -0.2061 -vn 0.1253 0.9842 -0.1253 -vn 0.1119 0.9874 -0.1119 -vn 0.1534 0.9762 -0.1534 -vn 0.3559 0.8641 -0.3559 -vn 0.4892 0.7221 -0.4892 -vn 0.2524 0.9565 -0.1461 -vn 0.1535 0.9841 -0.0889 -vn 0.1371 0.9874 -0.0793 -vn 0.1879 0.9762 -0.1087 -vn 0.4360 0.8639 -0.2522 -vn 0.5990 0.7218 -0.3467 -vn 0.2821 0.9564 -0.0757 -vn 0.1716 0.9841 -0.0460 -vn 0.1533 0.9873 -0.0411 -vn 0.2101 0.9761 -0.0563 -vn 0.4872 0.8635 -0.1306 -vn 0.6691 0.7212 -0.1795 -vn 0.3638 0.9315 -0.0000 -vn 0.3515 0.9315 0.0935 -vn 0.9683 0.2499 -0.0000 -vn 0.9354 0.2498 0.2502 -vn 0.8429 -0.5381 -0.0000 -vn 0.8140 -0.5387 0.2173 -vn 0.7868 -0.6172 -0.0000 -vn 0.7595 -0.6180 0.2029 -vn 0.3144 0.9318 0.1813 -vn 0.8384 0.2499 0.4844 -vn 0.7290 -0.5397 0.4209 -vn 0.6800 -0.6191 0.3927 -vn 0.2564 0.9319 0.2564 -vn 0.6847 0.2499 0.6847 -vn 0.5951 -0.5402 0.5951 -vn 0.5550 -0.6196 0.5551 -vn 0.1813 0.9318 0.3145 -vn 0.4844 0.2498 0.8384 -vn 0.4209 -0.5397 0.7291 -vn 0.3927 -0.6191 0.6800 -vn 0.0935 0.9315 0.3515 -vn 0.2502 0.2498 0.9354 -vn 0.2173 -0.5387 0.8140 -vn 0.2029 -0.6180 0.7595 -vn -0.0000 0.9315 0.3639 -vn 0.0000 0.2499 0.9683 -vn 0.0000 -0.5381 0.8429 -vn 0.0000 -0.6172 0.7868 -vn -0.0935 0.9315 0.3515 -vn -0.2502 0.2498 0.9354 -vn -0.2173 -0.5387 0.8140 -vn -0.2029 -0.6180 0.7595 -vn -0.1813 0.9318 0.3144 -vn -0.4844 0.2499 0.8384 -vn -0.4209 -0.5397 0.7290 -vn -0.3928 -0.6191 0.6801 -vn -0.2564 0.9319 0.2564 -vn -0.6847 0.2499 0.6847 -vn -0.5951 -0.5402 0.5951 -vn -0.5551 -0.6196 0.5550 -vn -0.3145 0.9318 0.1813 -vn -0.8384 0.2499 0.4844 -vn -0.7291 -0.5397 0.4209 -vn -0.6800 -0.6191 0.3927 -vn -0.3515 0.9315 0.0935 -vn -0.9354 0.2498 0.2502 -vn -0.8140 -0.5387 0.2173 -vn -0.7595 -0.6180 0.2029 -vn -0.3639 0.9315 -0.0000 -vn -0.9683 0.2499 0.0000 -vn -0.8429 -0.5381 0.0000 -vn -0.7868 -0.6172 0.0000 -vn -0.3515 0.9315 -0.0935 -vn -0.9354 0.2498 -0.2502 -vn -0.8140 -0.5387 -0.2173 -vn -0.7595 -0.6180 -0.2029 -vn -0.3144 0.9318 -0.1813 -vn -0.8384 0.2499 -0.4844 -vn -0.7291 -0.5397 -0.4209 -vn -0.6800 -0.6191 -0.3928 -vn -0.2564 0.9319 -0.2564 -vn -0.6847 0.2499 -0.6847 -vn -0.5951 -0.5402 -0.5951 -vn -0.5550 -0.6196 -0.5551 -vn -0.1813 0.9318 -0.3145 -vn -0.4844 0.2498 -0.8384 -vn -0.4209 -0.5397 -0.7291 -vn -0.3927 -0.6191 -0.6800 -vn -0.0935 0.9315 -0.3515 -vn -0.2502 0.2498 -0.9354 -vn -0.2173 -0.5387 -0.8140 -vn -0.2029 -0.6180 -0.7595 -vn 0.0000 0.9315 -0.3639 -vn -0.0000 0.2499 -0.9683 -vn -0.0000 -0.5381 -0.8429 -vn -0.0000 -0.6172 -0.7868 -vn 0.0935 0.9315 -0.3515 -vn 0.2502 0.2498 -0.9354 -vn 0.2173 -0.5387 -0.8140 -vn 0.2029 -0.6180 -0.7595 -vn 0.1813 0.9318 -0.3144 -vn 0.4844 0.2499 -0.8384 -vn 0.4209 -0.5397 -0.7290 -vn 0.3928 -0.6191 -0.6800 -vn 0.2564 0.9319 -0.2564 -vn 0.6847 0.2499 -0.6847 -vn 0.5951 -0.5402 -0.5951 -vn 0.5551 -0.6196 -0.5550 -vn 0.3144 0.9318 -0.1813 -vn 0.8384 0.2499 -0.4844 -vn 0.7291 -0.5397 -0.4209 -vn 0.6800 -0.6191 -0.3927 -vn 0.3515 0.9315 -0.0935 -vn 0.9354 0.2498 -0.2502 -vn 0.8140 -0.5387 -0.2173 -vn 0.7595 -0.6180 -0.2029 -vn -0.3542 0.9303 -0.0952 -vn 0.0952 0.9303 0.3542 -vn 0.3542 0.9303 0.0952 -vn 0.1837 0.9304 0.3172 -vn -0.1837 0.9304 -0.3172 -vn -0.3674 0.9300 -0.0000 -vn -0.1837 0.9304 0.3172 -vn 0.3674 0.9300 0.0000 -vn -0.3172 0.9304 0.1837 -vn -0.0952 0.9303 -0.3542 -vn 0.0000 0.9300 -0.3674 -vn -0.3542 0.9303 0.0952 -vn 0.0952 0.9303 -0.3542 -vn 0.0000 0.9300 0.3674 -vn -0.3172 0.9304 -0.1837 -vn 0.3172 0.9304 -0.1837 -vn -0.0952 0.9303 0.3542 -vn 0.3172 0.9304 0.1837 -vn 0.3542 0.9303 -0.0952 -vn 0.1837 0.9304 -0.3172 -vn -0.0347 0.9994 -0.0000 -vn 0.0335 0.9994 -0.0090 -vn 0.0347 0.9994 -0.0000 -vn 0.0090 0.9994 -0.0335 -vn 0.0000 0.9994 -0.0347 -vn -0.2592 0.9304 0.2592 -vn -0.0174 0.9994 0.0300 -vn 0.9476 0.2951 0.1227 -vn -0.0045 1.0000 -0.0000 -vn -0.0042 0.9505 0.3108 -vn -0.0030 0.7193 0.6947 -vn -0.9987 0.0507 0.0000 -vn -0.0032 0.7193 -0.6946 -vn 0.9702 0.2134 -0.1145 -vn -0.1363 0.8795 -0.4560 -vn 0.9499 0.3127 -0.0000 -vn 0.0558 -0.0176 -0.9983 -vn 0.2013 -0.5409 -0.8166 -vn 0.9745 -0.1861 -0.1256 -vn 0.9881 -0.0963 0.1200 -vn 0.9745 -0.1861 0.1256 -vn -0.8796 -0.4758 0.0000 -vn -0.8738 -0.4726 -0.1145 -vn 0.9881 -0.0963 -0.1200 -vn 0.2955 -0.8555 -0.4252 -vn -0.9762 -0.1750 -0.1279 -vn -0.9710 -0.2058 0.1217 -vn -0.9762 -0.1750 0.1279 -vn 0.8961 -0.3730 0.2407 -vn -0.8027 -0.3736 -0.4648 -vn -0.6558 -0.3739 -0.6558 -vn -0.2407 -0.3730 -0.8960 -vn -0.4648 -0.3737 -0.8027 -vn -0.8960 -0.3731 0.2407 -vn -0.0000 -0.3726 -0.9280 -vn -0.6558 -0.3739 0.6558 -vn 0.2407 -0.3730 0.8961 -vn -0.8960 -0.3731 -0.2407 -vn 0.8027 -0.3737 -0.4648 -vn 0.6558 -0.3739 0.6558 -vn 0.4648 -0.3737 0.8027 -vn -0.4648 -0.3737 0.8027 -vn 0.2407 -0.3730 -0.8961 -vn 0.8027 -0.3737 0.4648 -vn -0.0335 0.9994 0.0090 -vn -0.2592 0.9304 -0.2592 -vn -0.0245 0.9994 -0.0245 -vn -0.0300 0.9994 -0.0174 -vn 0.2592 0.9304 -0.2592 -vn 0.0245 0.9994 -0.0245 -vn 0.0174 0.9994 -0.0300 -vn 0.0300 0.9994 -0.0174 -vn 0.0335 0.9994 0.0090 -vn -0.0090 0.9994 0.0335 -vn 0.0000 0.9994 0.0347 -vn -0.0174 0.9994 -0.0300 -vn 0.2592 0.9304 0.2592 -vn 0.0300 0.9994 0.0174 -vn -0.0090 0.9994 -0.0335 -vn 0.0245 0.9994 0.0245 -vn 0.0174 0.9994 0.0300 -vn 0.0090 0.9994 0.0335 -vn -0.0335 0.9994 -0.0090 -vn -0.0245 0.9994 0.0245 -vn -0.0300 0.9994 0.0174 -vn 0.0558 -0.0176 0.9983 -vn 0.2942 -0.8554 0.4263 -vn 0.2013 -0.5409 0.8166 -usemtl None +vn -0.2395 0.8821 0.4057 +vn -0.1268 0.9042 0.4078 +vn 0.0338 0.9061 0.4218 +vn 0.1431 0.9054 0.3996 +vn 0.2623 0.8868 0.3805 +vn 0.3308 0.8814 0.3372 +vn 0.4057 0.8821 0.2395 +vn 0.4078 0.9042 0.1268 +vn 0.4218 0.9061 -0.0338 +vn 0.3996 0.9055 -0.1430 +vn 0.3805 0.8868 -0.2622 +vn 0.3372 0.8814 -0.3308 +vn 0.2395 0.8821 -0.4057 +vn 0.1268 0.9042 -0.4078 +vn -0.0338 0.9061 -0.4218 +vn -0.1431 0.9054 -0.3996 +vn -0.2622 0.8868 -0.3805 +vn -0.3308 0.8814 -0.3372 +vn -0.4057 0.8821 -0.2395 +vn -0.4078 0.9042 -0.1268 +vn -0.4218 0.9061 0.0338 +vn -0.3996 0.9054 0.1431 +vn -0.4458 0.1430 0.8837 +vn -0.7849 -0.3634 -0.5018 +vn -0.8362 -0.4575 0.3026 +vn -0.8436 -0.5359 -0.0322 +vn -0.7604 -0.6055 0.2351 +vn -0.7878 -0.6154 -0.0257 +vn -0.8389 -0.5194 0.1630 +vn -0.8512 -0.5249 -0.0000 +vn -0.1995 0.1425 0.9695 +vn -0.7268 -0.4524 0.5168 +vn -0.6733 -0.6040 0.4264 +vn -0.7680 -0.5196 0.3744 +vn 0.0340 0.1497 0.9881 +vn -0.5480 -0.3852 0.7425 +vn -0.5400 -0.5949 0.5954 +vn -0.6044 -0.5190 0.6044 +vn 0.3121 0.1463 0.9387 +vn -0.3097 -0.5235 0.7938 +vn -0.3635 -0.6118 0.7025 +vn -0.3744 -0.5196 0.7680 +vn 0.5436 0.1463 0.8265 +vn -0.1031 -0.5227 0.8463 +vn -0.1727 -0.6093 0.7739 +vn -0.1630 -0.5194 0.8389 +vn 0.7427 0.1438 0.6540 +vn 0.0321 -0.5359 0.8436 +vn 0.0257 -0.6154 0.7878 +vn -0.0000 -0.5249 0.8512 +vn 0.8837 0.1429 0.4457 +vn 0.3025 -0.4575 0.8362 +vn 0.2350 -0.6055 0.7604 +vn 0.1630 -0.5194 0.8389 +vn 0.9695 0.1425 0.1995 +vn 0.5168 -0.4524 0.7269 +vn 0.4264 -0.6040 0.6733 +vn 0.3744 -0.5196 0.7680 +vn 0.9881 0.1497 -0.0340 +vn 0.7425 -0.3851 0.5480 +vn 0.5954 -0.5948 0.5400 +vn 0.6044 -0.5190 0.6044 +vn 0.9387 0.1463 -0.3121 +vn 0.7938 -0.5234 0.3097 +vn 0.7025 -0.6118 0.3635 +vn 0.7680 -0.5196 0.3744 +vn 0.8265 0.1465 -0.5435 +vn 0.8463 -0.5227 0.1031 +vn 0.7739 -0.6093 0.1727 +vn 0.8389 -0.5194 0.1630 +vn 0.6540 0.1439 -0.7427 +vn 0.8437 -0.5359 -0.0322 +vn 0.7878 -0.6154 -0.0257 +vn 0.8512 -0.5249 0.0000 +vn 0.4458 0.1429 -0.8837 +vn 0.8362 -0.4575 -0.3025 +vn 0.7604 -0.6055 -0.2350 +vn 0.8389 -0.5194 -0.1630 +vn 0.1995 0.1425 -0.9695 +vn 0.7268 -0.4524 -0.5168 +vn 0.6733 -0.6040 -0.4264 +vn 0.7680 -0.5196 -0.3744 +vn -0.0340 0.1497 -0.9882 +vn 0.5480 -0.3852 -0.7425 +vn 0.5400 -0.5949 -0.5954 +vn 0.6044 -0.5190 -0.6044 +vn -0.3121 0.1463 -0.9387 +vn 0.3097 -0.5235 -0.7938 +vn 0.3635 -0.6118 -0.7025 +vn 0.3744 -0.5196 -0.7680 +vn -0.5435 0.1463 -0.8265 +vn 0.1031 -0.5227 -0.8463 +vn 0.1727 -0.6093 -0.7739 +vn 0.1630 -0.5194 -0.8388 +vn -0.7427 0.1438 -0.6541 +vn -0.0322 -0.5359 -0.8436 +vn -0.0257 -0.6154 -0.7878 +vn 0.0000 -0.5249 -0.8512 +vn -0.8837 0.1429 -0.4458 +vn -0.3025 -0.4575 -0.8362 +vn -0.2350 -0.6055 -0.7604 +vn -0.1629 -0.5194 -0.8388 +vn -0.9695 0.1425 -0.1995 +vn -0.5168 -0.4524 -0.7268 +vn -0.4264 -0.6040 -0.6733 +vn -0.3744 -0.5196 -0.7680 +vn -0.9881 0.1497 0.0340 +vn -0.7425 -0.3851 -0.5480 +vn -0.5955 -0.5948 -0.5400 +vn -0.6044 -0.5190 -0.6044 +vn -0.9387 0.1463 0.3121 +vn -0.7938 -0.5234 -0.3097 +vn -0.7025 -0.6118 -0.3635 +vn -0.7680 -0.5196 -0.3744 +vn -0.8265 0.1465 0.5435 +vn -0.8463 -0.5227 -0.1031 +vn -0.7739 -0.6093 -0.1728 +vn -0.8389 -0.5194 -0.1629 +usemtl Default_OBJ s 1 -f 1//1 2//2 3//3 -f 4//4 5//5 6//6 -f 4//4 6//6 7//7 -f 8//8 4//4 7//7 -f 8//8 7//7 9//9 -f 10//10 8//8 9//9 -f 10//10 9//9 11//11 -f 12//12 10//10 11//11 -f 12//12 11//11 13//13 -f 14//14 12//12 13//13 -f 14//14 13//13 15//15 -f 16//16 17//17 6//6 -f 7//7 6//6 18//18 -f 7//7 18//18 19//19 -f 9//9 7//7 19//19 -f 9//9 19//19 20//20 -f 11//11 9//9 20//20 -f 11//11 20//20 21//21 -f 13//13 11//11 21//21 -f 13//13 21//21 22//22 -f 15//15 13//13 22//22 -f 15//15 22//22 23//23 -f 24//24 25//25 18//18 -f 19//19 18//18 25//25 -f 19//19 25//25 26//26 -f 20//20 19//19 26//26 -f 20//20 26//26 27//27 -f 21//21 20//20 27//27 -f 21//21 27//27 28//28 -f 22//22 21//21 28//28 -f 22//22 28//28 29//29 -f 23//23 22//22 29//29 -f 23//23 29//29 30//30 -f 31//31 32//32 33//33 -f 26//26 25//25 34//34 -f 26//26 34//34 35//35 -f 27//27 26//26 35//35 -f 27//27 35//35 36//36 -f 28//28 27//27 36//36 -f 28//28 36//36 37//37 -f 29//29 28//28 37//37 -f 29//29 37//37 38//38 -f 30//30 29//29 38//38 -f 30//30 38//38 39//39 -f 33//33 40//40 41//41 -f 35//35 34//34 42//42 -f 35//35 42//42 43//43 -f 36//36 35//35 43//43 -f 36//36 43//43 44//44 -f 37//37 36//36 44//44 -f 37//37 44//44 45//45 -f 38//38 37//37 45//45 -f 38//38 45//45 46//46 -f 39//39 38//38 46//46 -f 39//39 46//46 47//47 -f 48//48 49//49 50//50 -f 43//43 42//42 49//49 -f 43//43 49//49 51//51 -f 44//44 43//43 51//51 -f 44//44 51//51 52//52 -f 45//45 44//44 52//52 -f 45//45 52//52 53//53 -f 46//46 45//45 53//53 -f 46//46 53//53 54//54 -f 47//47 46//46 54//54 -f 47//47 54//54 55//55 -f 56//56 57//57 58//58 -f 51//51 49//49 48//48 -f 51//51 48//48 59//59 -f 52//52 51//51 59//59 -f 52//52 59//59 60//60 -f 53//53 52//52 60//60 -f 53//53 60//60 61//61 -f 54//54 53//53 61//61 -f 54//54 61//61 62//62 -f 55//55 54//54 62//62 -f 55//55 62//62 63//63 -f 32//32 56//56 64//64 -f 65//65 64//64 56//56 -f 59//59 48//48 66//66 -f 59//59 66//66 67//67 -f 60//60 59//59 67//67 -f 60//60 67//67 68//68 -f 61//61 60//60 68//68 -f 61//61 68//68 69//69 -f 62//62 61//61 69//69 -f 62//62 69//69 70//70 -f 63//63 62//62 70//70 -f 63//63 70//70 71//71 -f 56//56 58//58 72//58 -f 73//72 74//73 75//74 -f 67//67 66//66 76//75 -f 67//67 76//75 77//76 -f 68//68 67//67 77//76 -f 68//68 77//76 78//77 -f 69//69 68//68 78//77 -f 69//69 78//77 79//78 -f 70//70 69//69 79//78 -f 70//70 79//78 80//79 -f 71//71 70//70 80//79 -f 71//71 80//79 81//80 -f 82//81 76//75 83//82 -f 84//83 1//1 3//3 -f 77//76 76//75 74//73 -f 77//76 74//73 85//84 -f 78//77 77//76 85//84 -f 78//77 85//84 86//85 -f 79//78 78//77 86//85 -f 79//78 86//85 87//86 -f 80//79 79//78 87//86 -f 80//79 87//86 88//87 -f 81//80 80//79 88//87 -f 81//80 88//87 89//88 -f 90//89 2//2 91//2 -f 90//89 91//2 57//57 -f 85//84 74//73 73//72 -f 85//84 73//72 92//90 -f 86//85 85//84 92//90 -f 86//85 92//90 93//91 -f 87//86 86//85 93//91 -f 87//86 93//91 94//92 -f 88//87 87//86 94//92 -f 88//87 94//92 95//93 -f 89//88 88//87 95//93 -f 89//88 95//93 96//94 -f 97//95 73//72 75//74 -f 92//90 73//72 5//5 -f 92//90 5//5 4//4 -f 93//91 92//90 4//4 -f 93//91 4//4 8//8 -f 94//92 93//91 8//8 -f 94//92 8//8 10//10 -f 95//93 94//92 10//10 -f 95//93 10//10 12//12 -f 96//94 95//93 12//12 -f 96//94 12//12 14//14 -f 98//96 14//14 15//15 -f 98//96 15//15 99//97 -f 100//98 98//96 99//97 -f 100//98 99//97 101//99 -f 102//100 100//98 101//99 -f 102//100 101//99 103//101 -f 104//102 102//100 103//101 -f 104//102 103//101 105//103 -f 106//104 104//102 105//103 -f 106//104 105//103 107//105 -f 108//106 106//104 107//105 -f 108//106 107//105 109//107 -f 99//97 15//15 23//23 -f 99//97 23//23 110//108 -f 101//99 99//97 110//108 -f 101//99 110//108 111//109 -f 103//101 101//99 111//109 -f 103//101 111//109 112//110 -f 105//103 103//101 112//110 -f 105//103 112//110 113//111 -f 107//105 105//103 113//111 -f 107//105 113//111 114//112 -f 109//107 107//105 114//112 -f 109//107 114//112 115//113 -f 110//108 23//23 30//30 -f 110//108 30//30 116//114 -f 111//109 110//108 116//114 -f 111//109 116//114 117//115 -f 112//110 111//109 117//115 -f 112//110 117//115 118//116 -f 113//111 112//110 118//116 -f 113//111 118//116 119//117 -f 114//112 113//111 119//117 -f 114//112 119//117 120//118 -f 115//113 114//112 120//118 -f 115//113 120//118 121//119 -f 116//114 30//30 39//39 -f 116//114 39//39 122//120 -f 117//115 116//114 122//120 -f 117//115 122//120 123//121 -f 118//116 117//115 123//121 -f 118//116 123//121 124//122 -f 119//117 118//116 124//122 -f 119//117 124//122 125//123 -f 120//118 119//117 125//123 -f 120//118 125//123 126//124 -f 121//119 120//118 126//124 -f 122//120 39//39 47//47 -f 122//120 47//47 127//125 -f 123//121 122//120 127//125 -f 123//121 127//125 128//126 -f 124//122 123//121 128//126 -f 124//122 128//126 129//127 -f 125//123 124//122 129//127 -f 125//123 129//127 130//128 -f 126//124 125//123 130//128 -f 126//124 130//128 131//129 -f 91//2 132//130 57//57 -f 127//125 47//47 55//55 -f 127//125 55//55 133//131 -f 128//126 127//125 133//131 -f 128//126 133//131 134//132 -f 129//127 128//126 134//132 -f 129//127 134//132 135//133 -f 130//128 129//127 135//133 -f 130//128 135//133 136//134 -f 131//129 130//128 136//134 -f 131//129 136//134 137//135 -f 5//5 73//72 97//95 -f 138//136 137//135 139//137 -f 133//131 55//55 63//63 -f 133//131 63//63 140//138 -f 134//132 133//131 140//138 -f 134//132 140//138 141//139 -f 135//133 134//132 141//139 -f 135//133 141//139 142//140 -f 136//134 135//133 142//140 -f 136//134 142//140 143//141 -f 137//135 136//134 143//141 -f 137//135 143//141 144//142 -f 145//143 146//144 147//145 -f 140//138 63//63 71//71 -f 140//138 71//71 148//146 -f 141//139 140//138 148//146 -f 141//139 148//146 149//147 -f 142//140 141//139 149//147 -f 142//140 149//147 150//148 -f 143//141 142//140 150//148 -f 143//141 150//148 151//149 -f 144//142 143//141 151//149 -f 144//142 151//149 146//144 -f 152//150 153//151 154//152 -f 146//144 155//153 147//145 -f 148//146 71//71 81//80 -f 148//146 81//80 156//154 -f 149//147 148//146 156//154 -f 149//147 156//154 157//155 -f 150//148 149//147 157//155 -f 150//148 157//155 158//156 -f 151//149 150//148 158//156 -f 151//149 158//156 159//157 -f 146//144 151//149 159//157 -f 146//144 159//157 155//153 -f 160//158 161//159 162//160 -f 163//161 131//129 138//136 -f 156//154 81//80 89//88 -f 156//154 89//88 164//162 -f 157//155 156//154 164//162 -f 157//155 164//162 165//163 -f 158//156 157//155 165//163 -f 158//156 165//163 166//164 -f 159//157 158//156 166//164 -f 159//157 166//164 167//165 -f 155//153 159//157 167//165 -f 155//153 167//165 168//166 -f 169//167 155//153 168//166 -f 169//167 168//166 170//168 -f 164//162 89//88 96//94 -f 164//162 96//94 171//169 -f 165//163 164//162 171//169 -f 165//163 171//169 172//170 -f 166//164 165//163 172//170 -f 166//164 172//170 173//171 -f 167//165 166//164 173//171 -f 167//165 173//171 174//172 -f 168//166 167//165 174//172 -f 168//166 174//172 175//173 -f 170//168 168//166 175//173 -f 170//168 175//173 176//174 -f 171//169 96//94 14//14 -f 171//169 14//14 98//96 -f 172//170 171//169 98//96 -f 172//170 98//96 100//98 -f 173//171 172//170 100//98 -f 173//171 100//98 102//100 -f 174//172 173//171 102//100 -f 174//172 102//100 104//102 -f 175//173 174//172 104//102 -f 175//173 104//102 106//104 -f 176//174 175//173 106//104 -f 106//104 108//106 176//174 -f 177//175 178//176 179//177 -f 180//178 181//179 182//180 -f 183//181 180//178 184//182 -f 183//181 184//182 185//183 -f 186//184 183//181 185//183 -f 186//184 185//183 187//185 -f 188//186 186//184 187//185 -f 188//186 187//185 189//187 -f 190//188 188//186 189//187 -f 190//188 189//187 191//189 -f 192//190 190//188 191//189 -f 192//190 191//189 193//191 -f 185//183 184//182 194//192 -f 185//183 194//192 195//193 -f 187//185 185//183 195//193 -f 187//185 195//193 196//194 -f 189//187 187//185 196//194 -f 189//187 196//194 197//195 -f 191//189 189//187 197//195 -f 191//189 197//195 198//196 -f 193//191 191//189 198//196 -f 193//191 198//196 199//197 -f 200//198 194//192 201//199 -f 202//200 203//201 204//202 -f 195//193 194//192 200//198 -f 195//193 200//198 205//203 -f 196//194 195//193 205//203 -f 196//194 205//203 206//204 -f 197//195 196//194 206//204 -f 197//195 206//204 207//205 -f 198//196 197//195 207//205 -f 198//196 207//205 208//206 -f 199//197 198//196 208//206 -f 199//197 208//206 209//207 -f 205//203 200//198 210//208 -f 205//203 210//208 211//209 -f 206//204 205//203 211//209 -f 206//204 211//209 212//210 -f 207//205 206//204 212//210 -f 207//205 212//210 213//211 -f 208//206 207//205 213//211 -f 208//206 213//211 214//212 -f 209//207 208//206 214//212 -f 209//207 214//212 215//213 -f 211//209 210//208 216//214 -f 211//209 216//214 217//215 -f 212//210 211//209 217//215 -f 212//210 217//215 218//216 -f 213//211 212//210 218//216 -f 213//211 218//216 219//217 -f 214//212 213//211 219//217 -f 214//212 219//217 220//218 -f 215//213 214//212 220//218 -f 215//213 220//218 221//219 -f 217//215 216//214 222//220 -f 217//215 222//220 223//221 -f 218//216 217//215 223//221 -f 218//216 223//221 224//222 -f 219//217 218//216 224//222 -f 219//217 224//222 225//223 -f 220//218 219//217 225//223 -f 220//218 225//223 226//224 -f 221//219 220//218 226//224 -f 221//219 226//224 227//225 -f 222//220 216//214 228//226 -f 229//227 230//228 231//229 -f 223//221 222//220 232//230 -f 223//221 232//230 233//231 -f 224//222 223//221 233//231 -f 224//222 233//231 234//232 -f 225//223 224//222 234//232 -f 225//223 234//232 235//233 -f 226//224 225//223 235//233 -f 226//224 235//233 236//234 -f 227//225 226//224 236//234 -f 227//225 236//234 237//235 -f 233//231 232//230 238//236 -f 233//231 238//236 239//237 -f 234//232 233//231 239//237 -f 234//232 239//237 240//238 -f 235//233 234//232 240//238 -f 235//233 240//238 241//239 -f 236//234 235//233 241//239 -f 236//234 241//239 242//240 -f 237//235 236//234 242//240 -f 237//235 242//240 243//241 -f 239//237 238//236 244//242 -f 239//237 244//242 245//243 -f 240//238 239//237 245//243 -f 240//238 245//243 246//244 -f 241//239 240//238 246//244 -f 241//239 246//244 247//245 -f 242//240 241//239 247//245 -f 242//240 247//245 248//246 -f 243//241 242//240 248//246 -f 243//241 248//246 249//247 -f 250//248 251//249 252//250 -f 245//243 244//242 253//251 -f 245//243 253//251 254//252 -f 246//244 245//243 254//252 -f 246//244 254//252 255//253 -f 247//245 246//244 255//253 -f 247//245 255//253 256//254 -f 248//246 247//245 256//254 -f 248//246 256//254 257//255 -f 249//247 248//246 257//255 -f 249//247 257//255 258//256 -f 253//251 244//242 259//257 -f 254//252 253//251 260//258 -f 254//252 260//258 261//259 -f 255//253 254//252 261//259 -f 255//253 261//259 262//260 -f 256//254 255//253 262//260 -f 256//254 262//260 263//261 -f 257//255 256//254 263//261 -f 257//255 263//261 264//262 -f 258//256 257//255 264//262 -f 258//256 264//262 265//263 -f 180//178 260//258 181//179 -f 260//258 180//178 261//259 -f 261//259 180//178 183//181 -f 262//260 261//259 183//181 -f 262//260 183//181 186//184 -f 263//261 262//260 186//184 -f 263//261 186//184 188//186 -f 264//262 263//261 188//186 -f 264//262 188//186 190//188 -f 265//263 264//262 190//188 -f 265//263 190//188 192//190 -f 266//264 192//190 193//191 -f 266//264 193//191 267//265 -f 268//266 266//264 267//265 -f 268//266 267//265 269//267 -f 270//268 268//266 269//267 -f 270//268 269//267 271//269 -f 272//270 270//268 271//269 -f 272//270 271//269 273//271 -f 274//272 272//270 273//271 -f 274//272 273//271 275//273 -f 276//274 274//272 275//273 -f 276//274 275//273 277//275 -f 267//265 193//191 199//197 -f 267//265 199//197 278//276 -f 269//267 267//265 278//276 -f 269//267 278//276 279//277 -f 271//269 269//267 279//277 -f 271//269 279//277 280//278 -f 273//271 271//269 280//278 -f 273//271 280//278 281//279 -f 275//273 273//271 281//279 -f 275//273 281//279 282//280 -f 277//275 275//273 282//280 -f 277//275 282//280 283//281 -f 278//276 199//197 209//207 -f 278//276 209//207 284//282 -f 279//277 278//276 284//282 -f 279//277 284//282 285//283 -f 280//278 279//277 285//283 -f 280//278 285//283 286//284 -f 281//279 280//278 286//284 -f 281//279 286//284 287//285 -f 282//280 281//279 287//285 -f 282//280 287//285 288//286 -f 283//281 282//280 288//286 -f 283//281 288//286 289//287 -f 284//282 209//207 215//213 -f 284//282 215//213 290//288 -f 285//283 284//282 290//288 -f 285//283 290//288 291//289 -f 286//284 285//283 291//289 -f 286//284 291//289 292//290 -f 287//285 286//284 292//290 -f 287//285 292//290 293//291 -f 288//286 287//285 293//291 -f 288//286 293//291 294//292 -f 289//287 288//286 294//292 -f 289//287 294//292 295//293 -f 290//288 215//213 221//219 -f 290//288 221//219 296//294 -f 291//289 290//288 296//294 -f 291//289 296//294 297//295 -f 292//290 291//289 297//295 -f 292//290 297//295 298//296 -f 293//291 292//290 298//296 -f 293//291 298//296 299//297 -f 294//292 293//291 299//297 -f 294//292 299//297 300//298 -f 295//293 294//292 300//298 -f 295//293 300//298 301//299 -f 296//294 221//219 227//225 -f 296//294 227//225 302//300 -f 297//295 296//294 302//300 -f 297//295 302//300 303//301 -f 298//296 297//295 303//301 -f 298//296 303//301 304//302 -f 299//297 298//296 304//302 -f 299//297 304//302 305//303 -f 300//298 299//297 305//303 -f 300//298 305//303 306//304 -f 301//299 300//298 306//304 -f 301//299 306//304 307//305 -f 302//300 227//225 237//235 -f 302//300 237//235 308//306 -f 303//301 302//300 308//306 -f 303//301 308//306 309//307 -f 304//302 303//301 309//307 -f 304//302 309//307 310//308 -f 305//303 304//302 310//308 -f 305//303 310//308 311//309 -f 306//304 305//303 311//309 -f 306//304 311//309 312//310 -f 307//305 306//304 312//310 -f 307//305 312//310 313//311 -f 308//306 237//235 243//241 -f 308//306 243//241 314//312 -f 309//307 308//306 314//312 -f 309//307 314//312 315//313 -f 310//308 309//307 315//313 -f 310//308 315//313 316//314 -f 311//309 310//308 316//314 -f 311//309 316//314 317//315 -f 312//310 311//309 317//315 -f 312//310 317//315 318//316 -f 313//311 312//310 318//316 -f 313//311 318//316 319//317 -f 314//312 243//241 249//247 -f 314//312 249//247 320//318 -f 315//313 314//312 320//318 -f 315//313 320//318 321//319 -f 316//314 315//313 321//319 -f 316//314 321//319 322//320 -f 317//315 316//314 322//320 -f 317//315 322//320 323//321 -f 318//316 317//315 323//321 -f 318//316 323//321 324//322 -f 319//317 318//316 324//322 -f 319//317 324//322 325//323 -f 320//318 249//247 258//256 -f 320//318 258//256 326//324 -f 321//319 320//318 326//324 -f 321//319 326//324 327//325 -f 322//320 321//319 327//325 -f 322//320 327//325 328//326 -f 323//321 322//320 328//326 -f 323//321 328//326 329//327 -f 324//322 323//321 329//327 -f 324//322 329//327 330//328 -f 325//323 324//322 330//328 -f 325//323 330//328 331//329 -f 326//324 258//256 265//263 -f 326//324 265//263 332//330 -f 327//325 326//324 332//330 -f 327//325 332//330 333//331 -f 328//326 327//325 333//331 -f 328//326 333//331 334//332 -f 329//327 328//326 334//332 -f 329//327 334//332 335//333 -f 330//328 329//327 335//333 -f 330//328 335//333 336//334 -f 331//329 330//328 336//334 -f 331//329 336//334 337//335 -f 332//330 265//263 192//190 -f 332//330 192//190 266//264 -f 333//331 332//330 266//264 -f 333//331 266//264 268//266 -f 334//332 333//331 268//266 -f 334//332 268//266 270//268 -f 335//333 334//332 270//268 -f 335//333 270//268 272//270 -f 336//334 335//333 272//270 -f 336//334 272//270 274//272 -f 337//335 336//334 274//272 -f 337//335 274//272 276//274 -f 276//274 277//275 338//336 -f 325//323 339//337 340//338 -f 313//311 341//339 342//340 -f 319//317 340//338 341//339 -f 283//281 289//287 343//341 -f 295//293 301//299 344//342 -f 337//335 345//343 346//344 -f 331//329 346//344 339//337 -f 276//274 347//345 345//343 -f 301//299 307//305 342//340 -f 289//287 295//293 348//346 -f 277//275 283//281 349//347 -f 349//347 343//341 350//348 -f 346//344 345//343 350//348 -f 338//336 349//347 350//348 -f 344//342 342//340 350//348 -f 339//337 346//344 350//348 -f 343//341 348//346 350//348 -f 347//345 338//336 350//348 -f 348//346 344//342 350//348 -f 341//339 340//338 350//348 -f 340//338 339//337 350//348 -f 345//343 347//345 350//348 -f 342//340 341//339 350//348 -f 351//349 352//350 353//351 -f 351//349 353//351 354//352 -f 355//353 351//349 354//352 -f 355//353 354//352 356//354 -f 357//355 355//353 356//354 -f 357//355 356//354 358//356 -f 359//357 357//355 358//356 -f 359//357 358//356 360//358 -f 361//359 359//357 360//358 -f 361//359 360//358 362//360 -f 363//361 361//359 362//360 -f 363//361 362//360 364//362 -f 354//352 353//351 365//363 -f 354//352 365//363 366//364 -f 356//354 354//352 366//364 -f 356//354 366//364 367//365 -f 358//356 356//354 367//365 -f 358//356 367//365 368//366 -f 360//358 358//356 368//366 -f 360//358 368//366 369//367 -f 362//360 360//358 369//367 -f 362//360 369//367 370//368 -f 364//362 362//360 370//368 -f 364//362 370//368 371//369 -f 366//364 365//363 372//370 -f 366//364 372//370 373//371 -f 367//365 366//364 373//371 -f 367//365 373//371 374//372 -f 368//366 367//365 374//372 -f 368//366 374//372 375//373 -f 369//367 368//366 375//373 -f 369//367 375//373 376//374 -f 370//368 369//367 376//374 -f 370//368 376//374 377//375 -f 371//369 370//368 377//375 -f 371//369 377//375 378//376 -f 373//371 372//370 379//377 -f 373//371 379//377 380//378 -f 374//372 373//371 380//378 -f 374//372 380//378 381//379 -f 375//373 374//372 381//379 -f 375//373 381//379 382//380 -f 376//374 375//373 382//380 -f 376//374 382//380 383//381 -f 377//375 376//374 383//381 -f 377//375 383//381 384//382 -f 378//376 377//375 384//382 -f 378//376 384//382 385//383 -f 380//378 379//377 386//384 -f 380//378 386//384 387//385 -f 381//379 380//378 387//385 -f 381//379 387//385 388//386 -f 382//380 381//379 388//386 -f 382//380 388//386 389//387 -f 383//381 382//380 389//387 -f 383//381 389//387 390//388 -f 384//382 383//381 390//388 -f 384//382 390//388 391//389 -f 385//383 384//382 391//389 -f 385//383 391//389 392//390 -f 387//385 386//384 393//391 -f 387//385 393//391 394//392 -f 388//386 387//385 394//392 -f 388//386 394//392 395//393 -f 389//387 388//386 395//393 -f 389//387 395//393 396//394 -f 390//388 389//387 396//394 -f 390//388 396//394 397//395 -f 391//389 390//388 397//395 -f 391//389 397//395 398//396 -f 392//390 391//389 398//396 -f 392//390 398//396 399//397 -f 394//392 393//391 400//398 -f 394//392 400//398 401//399 -f 395//393 394//392 401//399 -f 395//393 401//399 402//400 -f 396//394 395//393 402//400 -f 396//394 402//400 403//401 -f 397//395 396//394 403//401 -f 397//395 403//401 404//402 -f 398//396 397//395 404//402 -f 398//396 404//402 405//403 -f 399//397 398//396 405//403 -f 399//397 405//403 406//404 -f 401//399 400//398 407//405 -f 401//399 407//405 408//406 -f 402//400 401//399 408//406 -f 402//400 408//406 409//407 -f 403//401 402//400 409//407 -f 403//401 409//407 410//408 -f 404//402 403//401 410//408 -f 404//402 410//408 411//409 -f 405//403 404//402 411//409 -f 405//403 411//409 412//410 -f 406//404 405//403 412//410 -f 406//404 412//410 413//411 -f 408//406 407//405 414//412 -f 408//406 414//412 415//413 -f 409//407 408//406 415//413 -f 409//407 415//413 416//414 -f 410//408 409//407 416//414 -f 410//408 416//414 417//415 -f 411//409 410//408 417//415 -f 411//409 417//415 418//416 -f 412//410 411//409 418//416 -f 412//410 418//416 419//417 -f 413//411 412//410 419//417 -f 413//411 419//417 420//418 -f 415//413 414//412 421//419 -f 415//413 421//419 422//420 -f 416//414 415//413 422//420 -f 416//414 422//420 423//421 -f 417//415 416//414 423//421 -f 417//415 423//421 424//422 -f 418//416 417//415 424//422 -f 418//416 424//422 425//423 -f 419//417 418//416 425//423 -f 419//417 425//423 426//424 -f 420//418 419//417 426//424 -f 420//418 426//424 427//425 -f 422//420 421//419 428//426 -f 422//420 428//426 429//427 -f 423//421 422//420 429//427 -f 423//421 429//427 430//428 -f 424//422 423//421 430//428 -f 424//422 430//428 431//429 -f 425//423 424//422 431//429 -f 425//423 431//429 432//430 -f 426//424 425//423 432//430 -f 426//424 432//430 433//431 -f 427//425 426//424 433//431 -f 427//425 433//431 434//432 -f 429//427 428//426 435//433 -f 429//427 435//433 436//434 -f 430//428 429//427 436//434 -f 430//428 436//434 437//435 -f 431//429 430//428 437//435 -f 431//429 437//435 438//436 -f 432//430 431//429 438//436 -f 432//430 438//436 439//437 -f 433//431 432//430 439//437 -f 433//431 439//437 440//438 -f 434//432 433//431 440//438 -f 434//432 440//438 33//33 -f 436//434 435//433 441//439 -f 436//434 441//439 442//440 -f 437//435 436//434 442//440 -f 437//435 442//440 443//441 -f 438//436 437//435 443//441 -f 438//436 443//441 444//442 -f 439//437 438//436 444//442 -f 439//437 444//442 445//443 -f 440//438 439//437 445//443 -f 440//438 445//443 446//444 -f 33//33 440//438 446//444 -f 33//33 446//444 31//31 -f 442//440 441//439 447//445 -f 442//440 447//445 448//446 -f 443//441 442//440 448//446 -f 443//441 448//446 449//447 -f 444//442 443//441 449//447 -f 444//442 449//447 450//448 -f 445//443 444//442 450//448 -f 445//443 450//448 451//449 -f 446//444 445//443 451//449 -f 446//444 451//449 452//450 -f 31//31 446//444 452//450 -f 31//31 452//450 453//451 -f 448//446 447//445 454//452 -f 448//446 454//452 455//453 -f 449//447 448//446 455//453 -f 449//447 455//453 456//454 -f 450//448 449//447 456//454 -f 450//448 456//454 457//455 -f 451//449 450//448 457//455 -f 451//449 457//455 458//456 -f 452//450 451//449 458//456 -f 452//450 458//456 459//457 -f 453//451 452//450 459//457 -f 453//451 459//457 460//458 -f 455//453 454//452 461//459 -f 455//453 461//459 462//460 -f 456//454 455//453 462//460 -f 456//454 462//460 463//461 -f 457//455 456//454 463//461 -f 457//455 463//461 464//462 -f 458//456 457//455 464//462 -f 458//456 464//462 465//463 -f 459//457 458//456 465//463 -f 459//457 465//463 466//464 -f 460//458 459//457 466//464 -f 460//458 466//464 467//465 -f 462//460 461//459 468//466 -f 462//460 468//466 469//467 -f 463//461 462//460 469//467 -f 463//461 469//467 470//468 -f 464//462 463//461 470//468 -f 464//462 470//468 471//469 -f 465//463 464//462 471//469 -f 465//463 471//469 472//470 -f 466//464 465//463 472//470 -f 466//464 472//470 473//471 -f 467//465 466//464 473//471 -f 467//465 473//471 474//472 -f 469//467 468//466 475//473 -f 469//467 475//473 476//474 -f 470//468 469//467 476//474 -f 470//468 476//474 477//475 -f 471//469 470//468 477//475 -f 471//469 477//475 478//476 -f 472//470 471//469 478//476 -f 472//470 478//476 479//477 -f 473//471 472//470 479//477 -f 473//471 479//477 480//478 -f 474//472 473//471 480//478 -f 474//472 480//478 481//479 -f 476//474 475//473 482//480 -f 476//474 482//480 483//481 -f 477//475 476//474 483//481 -f 477//475 483//481 484//482 -f 478//476 477//475 484//482 -f 478//476 484//482 485//483 -f 479//477 478//476 485//483 -f 479//477 485//483 486//484 -f 480//478 479//477 486//484 -f 480//478 486//484 487//485 -f 481//479 480//478 487//485 -f 481//479 487//485 488//486 -f 483//481 482//480 489//487 -f 483//481 489//487 490//488 -f 484//482 483//481 490//488 -f 484//482 490//488 491//489 -f 485//483 484//482 491//489 -f 485//483 491//489 492//490 -f 486//484 485//483 492//490 -f 486//484 492//490 493//491 -f 487//485 486//484 493//491 -f 487//485 493//491 494//492 -f 488//486 487//485 494//492 -f 488//486 494//492 495//493 -f 490//488 489//487 496//494 -f 490//488 496//494 497//495 -f 491//489 490//488 497//495 -f 491//489 497//495 498//496 -f 492//490 491//489 498//496 -f 492//490 498//496 499//497 -f 493//491 492//490 499//497 -f 493//491 499//497 500//498 -f 494//492 493//491 500//498 -f 494//492 500//498 501//499 -f 495//493 494//492 501//499 -f 495//493 501//499 502//500 -f 497//495 496//494 503//501 -f 497//495 503//501 504//502 -f 498//496 497//495 504//502 -f 498//496 504//502 505//503 -f 499//497 498//496 505//503 -f 499//497 505//503 506//504 -f 500//498 499//497 506//504 -f 500//498 506//504 507//505 -f 501//499 500//498 507//505 -f 501//499 507//505 508//506 -f 502//500 501//499 508//506 -f 502//500 508//506 509//507 -f 504//502 503//501 510//508 -f 504//502 510//508 511//509 -f 505//503 504//502 511//509 -f 505//503 511//509 512//510 -f 506//504 505//503 512//510 -f 506//504 512//510 513//511 -f 507//505 506//504 513//511 -f 507//505 513//511 514//512 -f 508//506 507//505 514//512 -f 508//506 514//512 515//513 -f 509//507 508//506 515//513 -f 509//507 515//513 516//514 -f 511//509 510//508 352//350 -f 352//350 351//349 511//509 -f 511//509 351//349 512//510 -f 512//510 351//349 355//353 -f 513//511 512//510 355//353 -f 513//511 355//353 357//355 -f 514//512 513//511 357//355 -f 514//512 357//355 359//357 -f 515//513 514//512 359//357 -f 515//513 359//357 361//359 -f 516//514 515//513 361//359 -f 516//514 361//359 363//361 -f 517//515 363//361 364//362 -f 517//515 364//362 518//516 -f 519//517 517//515 518//516 -f 519//517 518//516 520//518 -f 178//176 519//517 520//518 -f 178//176 520//518 521//519 -f 518//516 364//362 371//369 -f 518//516 371//369 522//520 -f 520//518 518//516 522//520 -f 520//518 522//520 523//521 -f 521//519 520//518 523//521 -f 521//519 523//521 524//522 -f 525//523 521//519 524//522 -f 525//523 524//522 526//524 -f 204//202 525//523 526//524 -f 204//202 526//524 527//525 -f 528//526 204//202 527//525 -f 528//526 527//525 529//527 -f 522//520 371//369 378//376 -f 522//520 378//376 530//528 -f 523//521 522//520 530//528 -f 523//521 530//528 531//529 -f 524//522 523//521 531//529 -f 524//522 531//529 532//530 -f 526//524 524//522 532//530 -f 526//524 532//530 533//531 -f 527//525 526//524 533//531 -f 527//525 533//531 534//532 -f 529//527 527//525 534//532 -f 529//527 534//532 535//533 -f 530//528 378//376 385//383 -f 530//528 385//383 536//534 -f 531//529 530//528 536//534 -f 531//529 536//534 537//535 -f 532//530 531//529 537//535 -f 532//530 537//535 538//536 -f 533//531 532//530 538//536 -f 533//531 538//536 539//537 -f 534//532 533//531 539//537 -f 534//532 539//537 540//538 -f 535//533 534//532 540//538 -f 535//533 540//538 541//539 -f 536//534 385//383 392//390 -f 536//534 392//390 542//540 -f 537//535 536//534 542//540 -f 537//535 542//540 543//541 -f 538//536 537//535 543//541 -f 538//536 543//541 544//542 -f 539//537 538//536 544//542 -f 539//537 544//542 545//543 -f 540//538 539//537 545//543 -f 540//538 545//543 546//544 -f 541//539 540//538 546//544 -f 541//539 546//544 547//545 -f 542//540 392//390 399//397 -f 542//540 399//397 548//546 -f 543//541 542//540 548//546 -f 543//541 548//546 549//547 -f 544//542 543//541 549//547 -f 544//542 549//547 550//548 -f 545//543 544//542 550//548 -f 545//543 550//548 551//549 -f 546//544 545//543 551//549 -f 546//544 551//549 552//550 -f 547//545 546//544 552//550 -f 547//545 552//550 553//551 -f 548//546 399//397 406//404 -f 548//546 406//404 554//552 -f 549//547 548//546 554//552 -f 549//547 554//552 555//553 -f 550//548 549//547 555//553 -f 550//548 555//553 556//554 -f 551//549 550//548 556//554 -f 551//549 556//554 557//555 -f 552//550 551//549 557//555 -f 552//550 557//555 558//556 -f 553//551 552//550 558//556 -f 553//551 558//556 559//557 -f 554//552 406//404 413//411 -f 554//552 413//411 560//558 -f 555//553 554//552 560//558 -f 555//553 560//558 561//559 -f 556//554 555//553 561//559 -f 556//554 561//559 562//560 -f 557//555 556//554 562//560 -f 557//555 562//560 563//561 -f 558//556 557//555 563//561 -f 558//556 563//561 564//562 -f 559//557 558//556 564//562 -f 559//557 564//562 565//563 -f 560//558 413//411 420//418 -f 560//558 420//418 566//564 -f 561//559 560//558 566//564 -f 561//559 566//564 567//565 -f 562//560 561//559 567//565 -f 562//560 567//565 568//566 -f 563//561 562//560 568//566 -f 563//561 568//566 569//567 -f 564//562 563//561 569//567 -f 564//562 569//567 570//568 -f 565//563 564//562 570//568 -f 565//563 570//568 571//569 -f 566//564 420//418 427//425 -f 566//564 427//425 572//570 -f 567//565 566//564 572//570 -f 567//565 572//570 573//571 -f 568//566 567//565 573//571 -f 568//566 573//571 574//572 -f 569//567 568//566 574//572 -f 569//567 574//572 575//573 -f 570//568 569//567 575//573 -f 570//568 575//573 576//574 -f 571//569 570//568 576//574 -f 571//569 576//574 577//575 -f 572//570 427//425 434//432 -f 572//570 434//432 3//3 -f 573//571 572//570 3//3 -f 573//571 3//3 90//89 -f 574//572 573//571 90//89 -f 574//572 90//89 578//576 -f 575//573 574//572 578//576 -f 575//573 578//576 579//577 -f 576//574 575//573 579//577 -f 576//574 579//577 580//578 -f 577//575 576//574 580//578 -f 577//575 580//578 581//579 -f 3//3 434//432 33//33 -f 578//576 90//89 57//57 -f 578//576 57//57 582//580 -f 579//577 578//576 582//580 -f 579//577 582//580 583//581 -f 580//578 579//577 583//581 -f 580//578 583//581 584//582 -f 581//579 580//578 584//582 -f 57//57 56//56 585//583 -f 582//580 57//57 585//583 -f 582//580 585//583 586//584 -f 583//581 582//580 586//584 -f 583//581 586//584 587//585 -f 584//582 583//581 587//585 -f 584//582 587//585 588//586 -f 56//56 31//31 453//451 -f 56//56 453//451 589//587 -f 585//583 56//56 589//587 -f 585//583 589//587 590//588 -f 586//584 585//583 590//588 -f 586//584 590//588 591//589 -f 587//585 586//584 591//589 -f 587//585 591//589 592//590 -f 588//586 587//585 592//590 -f 588//586 592//590 593//591 -f 594//592 588//586 593//591 -f 594//592 593//591 595//593 -f 589//587 453//451 460//458 -f 589//587 460//458 596//594 -f 590//588 589//587 596//594 -f 590//588 596//594 597//595 -f 591//589 590//588 597//595 -f 591//589 597//595 598//596 -f 592//590 591//589 598//596 -f 592//590 598//596 599//597 -f 593//591 592//590 599//597 -f 593//591 599//597 600//598 -f 595//593 593//591 600//598 -f 595//593 600//598 601//599 -f 596//594 460//458 467//465 -f 596//594 467//465 602//600 -f 597//595 596//594 602//600 -f 597//595 602//600 603//601 -f 598//596 597//595 603//601 -f 598//596 603//601 604//602 -f 599//597 598//596 604//602 -f 599//597 604//602 605//603 -f 600//598 599//597 605//603 -f 600//598 605//603 606//604 -f 601//599 600//598 606//604 -f 601//599 606//604 607//605 -f 602//600 467//465 474//472 -f 602//600 474//472 608//606 -f 603//601 602//600 608//606 -f 603//601 608//606 609//607 -f 604//602 603//601 609//607 -f 604//602 609//607 610//608 -f 605//603 604//602 610//608 -f 605//603 610//608 611//609 -f 606//604 605//603 611//609 -f 606//604 611//609 612//610 -f 607//605 606//604 612//610 -f 607//605 612//610 613//611 -f 608//606 474//472 481//479 -f 608//606 481//479 614//612 -f 609//607 608//606 614//612 -f 609//607 614//612 615//613 -f 610//608 609//607 615//613 -f 610//608 615//613 616//614 -f 611//609 610//608 616//614 -f 611//609 616//614 617//615 -f 612//610 611//609 617//615 -f 612//610 617//615 618//616 -f 613//611 612//610 618//616 -f 613//611 618//616 619//617 -f 614//612 481//479 488//486 -f 614//612 488//486 620//618 -f 615//613 614//612 620//618 -f 615//613 620//618 621//619 -f 616//614 615//613 621//619 -f 616//614 621//619 622//620 -f 617//615 616//614 622//620 -f 617//615 622//620 623//621 -f 618//616 617//615 623//621 -f 618//616 623//621 624//622 -f 619//617 618//616 624//622 -f 619//617 624//622 625//623 -f 620//618 488//486 495//493 -f 620//618 495//493 626//624 -f 621//619 620//618 626//624 -f 621//619 626//624 627//625 -f 622//620 621//619 627//625 -f 622//620 627//625 628//626 -f 623//621 622//620 628//626 -f 623//621 628//626 629//627 -f 624//622 623//621 629//627 -f 624//622 629//627 630//628 -f 625//623 624//622 630//628 -f 625//623 630//628 631//629 -f 626//624 495//493 502//500 -f 626//624 502//500 632//630 -f 627//625 626//624 632//630 -f 627//625 632//630 633//631 -f 628//626 627//625 633//631 -f 628//626 633//631 634//632 -f 629//627 628//626 634//632 -f 629//627 634//632 635//633 -f 630//628 629//627 635//633 -f 630//628 635//633 636//634 -f 631//629 630//628 636//634 -f 631//629 636//634 637//635 -f 632//630 502//500 509//507 -f 632//630 509//507 638//636 -f 633//631 632//630 638//636 -f 633//631 638//636 639//637 -f 634//632 633//631 639//637 -f 634//632 639//637 640//638 -f 635//633 634//632 640//638 -f 635//633 640//638 641//639 -f 636//634 635//633 641//639 -f 636//634 641//639 642//640 -f 637//635 636//634 642//640 -f 637//635 642//640 643//641 -f 638//636 509//507 516//514 -f 638//636 516//514 644//642 -f 639//637 638//636 644//642 -f 639//637 644//642 645//643 -f 640//638 639//637 645//643 -f 640//638 645//643 646//644 -f 641//639 640//638 646//644 -f 641//639 646//644 177//175 -f 642//640 641//639 177//175 -f 642//640 177//175 251//249 -f 643//641 642//640 251//249 -f 643//641 251//249 250//248 -f 644//642 516//514 363//361 -f 644//642 363//361 517//515 -f 645//643 644//642 517//515 -f 645//643 517//515 519//517 -f 646//644 645//643 519//517 -f 646//644 519//517 178//176 -f 177//175 646//644 178//176 -f 230//228 229//227 647//645 -f 648//646 230//228 647//645 -f 648//646 647//645 649//647 -f 650//648 648//646 649//647 -f 650//648 649//647 651//649 -f 652//650 650//648 651//649 -f 652//650 651//649 653//651 -f 654//652 652//650 653//651 -f 654//652 653//651 655//653 -f 229//227 528//526 529//527 -f 229//227 529//527 656//654 -f 647//645 229//227 656//654 -f 647//645 656//654 657//655 -f 649//647 647//645 657//655 -f 649//647 657//655 658//656 -f 651//649 649//647 658//656 -f 651//649 658//656 659//657 -f 653//651 651//649 659//657 -f 653//651 659//657 660//658 -f 655//653 653//651 660//658 -f 655//653 660//658 661//659 -f 656//654 529//527 535//533 -f 656//654 535//533 662//660 -f 657//655 656//654 662//660 -f 657//655 662//660 663//661 -f 658//656 657//655 663//661 -f 658//656 663//661 664//662 -f 659//657 658//656 664//662 -f 659//657 664//662 665//663 -f 660//658 659//657 665//663 -f 660//658 665//663 666//664 -f 661//659 660//658 666//664 -f 661//659 666//664 667//665 -f 662//660 535//533 541//539 -f 662//660 541//539 668//666 -f 663//661 662//660 668//666 -f 663//661 668//666 669//667 -f 664//662 663//661 669//667 -f 664//662 669//667 670//668 -f 665//663 664//662 670//668 -f 665//663 670//668 671//669 -f 666//664 665//663 671//669 -f 666//664 671//669 672//670 -f 667//665 666//664 672//670 -f 667//665 672//670 673//671 -f 668//666 541//539 547//545 -f 668//666 547//545 674//672 -f 669//667 668//666 674//672 -f 669//667 674//672 675//673 -f 670//668 669//667 675//673 -f 670//668 675//673 676//674 -f 671//669 670//668 676//674 -f 671//669 676//674 677//675 -f 672//670 671//669 677//675 -f 672//670 677//675 678//676 -f 673//671 672//670 678//676 -f 673//671 678//676 679//677 -f 674//672 547//545 553//551 -f 674//672 553//551 680//678 -f 675//673 674//672 680//678 -f 675//673 680//678 681//679 -f 676//674 675//673 681//679 -f 676//674 681//679 682//680 -f 677//675 676//674 682//680 -f 677//675 682//680 683//681 -f 678//676 677//675 683//681 -f 678//676 683//681 684//682 -f 679//677 678//676 684//682 -f 679//677 684//682 685//683 -f 680//678 553//551 559//557 -f 680//678 559//557 686//684 -f 681//679 680//678 686//684 -f 681//679 686//684 687//685 -f 682//680 681//679 687//685 -f 682//680 687//685 688//686 -f 683//681 682//680 688//686 -f 683//681 688//686 689//687 -f 684//682 683//681 689//687 -f 684//682 689//687 690//688 -f 685//683 684//682 690//688 -f 685//683 690//688 691//689 -f 686//684 559//557 565//563 -f 686//684 565//563 692//690 -f 687//685 686//684 692//690 -f 687//685 692//690 693//691 -f 688//686 687//685 693//691 -f 688//686 693//691 694//692 -f 689//687 688//686 694//692 -f 689//687 694//692 695//693 -f 690//688 689//687 695//693 -f 690//688 695//693 696//694 -f 691//689 690//688 696//694 -f 691//689 696//694 697//695 -f 692//690 565//563 571//569 -f 692//690 571//569 698//696 -f 693//691 692//690 698//696 -f 693//691 698//696 699//697 -f 694//692 693//691 699//697 -f 694//692 699//697 700//698 -f 695//693 694//692 700//698 -f 695//693 700//698 701//699 -f 696//694 695//693 701//699 -f 696//694 701//699 702//700 -f 697//695 696//694 702//700 -f 697//695 702//700 703//701 -f 698//696 571//569 577//575 -f 698//696 577//575 704//702 -f 699//697 698//696 704//702 -f 699//697 704//702 705//703 -f 700//698 699//697 705//703 -f 700//698 705//703 706//704 -f 701//699 700//698 706//704 -f 701//699 706//704 707//705 -f 702//700 701//699 707//705 -f 702//700 707//705 708//706 -f 703//701 702//700 708//706 -f 703//701 708//706 709//707 -f 704//702 577//575 581//579 -f 704//702 581//579 710//708 -f 705//703 704//702 710//708 -f 705//703 710//708 162//160 -f 706//704 705//703 162//160 -f 706//704 162//160 711//709 -f 707//705 706//704 711//709 -f 707//705 711//709 712//710 -f 708//706 707//705 712//710 -f 708//706 712//710 713//711 -f 709//707 708//706 713//711 -f 709//707 713//711 714//712 -f 711//709 162//160 715//713 -f 711//709 715//713 716//714 -f 712//710 711//709 716//714 -f 712//710 716//714 717//715 -f 713//711 712//710 717//715 -f 713//711 717//715 718//716 -f 714//712 713//711 718//716 -f 714//712 718//716 719//717 -f 715//713 154//152 720//718 -f 716//714 715//713 720//718 -f 716//714 720//718 721//719 -f 717//715 716//714 721//719 -f 717//715 721//719 722//720 -f 718//716 717//715 722//720 -f 718//716 722//720 723//721 -f 719//717 718//716 723//721 -f 719//717 723//721 724//722 -f 154//152 594//592 595//593 -f 154//152 595//593 725//723 -f 720//718 154//152 725//723 -f 720//718 725//723 726//724 -f 721//719 720//718 726//724 -f 721//719 726//724 727//725 -f 722//720 721//719 727//725 -f 722//720 727//725 728//726 -f 723//721 722//720 728//726 -f 723//721 728//726 729//727 -f 724//722 723//721 729//727 -f 724//722 729//727 730//728 -f 725//723 595//593 601//599 -f 725//723 601//599 731//729 -f 726//724 725//723 731//729 -f 726//724 731//729 732//730 -f 727//725 726//724 732//730 -f 727//725 732//730 733//731 -f 728//726 727//725 733//731 -f 728//726 733//731 734//732 -f 729//727 728//726 734//732 -f 729//727 734//732 735//733 -f 730//728 729//727 735//733 -f 730//728 735//733 736//734 -f 731//729 601//599 607//605 -f 731//729 607//605 737//735 -f 732//730 731//729 737//735 -f 732//730 737//735 738//736 -f 733//731 732//730 738//736 -f 733//731 738//736 739//737 -f 734//732 733//731 739//737 -f 734//732 739//737 740//738 -f 735//733 734//732 740//738 -f 735//733 740//738 741//739 -f 736//734 735//733 741//739 -f 736//734 741//739 742//740 -f 737//735 607//605 613//611 -f 737//735 613//611 743//741 -f 738//736 737//735 743//741 -f 738//736 743//741 744//742 -f 739//737 738//736 744//742 -f 739//737 744//742 745//743 -f 740//738 739//737 745//743 -f 740//738 745//743 746//744 -f 741//739 740//738 746//744 -f 741//739 746//744 747//745 -f 742//740 741//739 747//745 -f 742//740 747//745 748//746 -f 743//741 613//611 619//617 -f 743//741 619//617 749//747 -f 744//742 743//741 749//747 -f 744//742 749//747 750//748 -f 745//743 744//742 750//748 -f 745//743 750//748 751//749 -f 746//744 745//743 751//749 -f 746//744 751//749 752//750 -f 747//745 746//744 752//750 -f 747//745 752//750 753//751 -f 748//746 747//745 753//751 -f 748//746 753//751 754//752 -f 749//747 619//617 625//623 -f 749//747 625//623 755//753 -f 750//748 749//747 755//753 -f 750//748 755//753 756//754 -f 751//749 750//748 756//754 -f 751//749 756//754 757//755 -f 752//750 751//749 757//755 -f 752//750 757//755 758//756 -f 753//751 752//750 758//756 -f 753//751 758//756 759//757 -f 754//752 753//751 759//757 -f 754//752 759//757 760//758 -f 755//753 625//623 631//629 -f 755//753 631//629 761//759 -f 756//754 755//753 761//759 -f 756//754 761//759 762//760 -f 757//755 756//754 762//760 -f 757//755 762//760 763//761 -f 758//756 757//755 763//761 -f 758//756 763//761 764//762 -f 759//757 758//756 764//762 -f 759//757 764//762 765//763 -f 760//758 759//757 765//763 -f 760//758 765//763 766//764 -f 761//759 631//629 637//635 -f 761//759 637//635 767//765 -f 762//760 761//759 767//765 -f 762//760 767//765 768//766 -f 763//761 762//760 768//766 -f 763//761 768//766 769//767 -f 764//762 763//761 769//767 -f 764//762 769//767 770//768 -f 765//763 764//762 770//768 -f 765//763 770//768 771//769 -f 766//764 765//763 771//769 -f 766//764 771//769 772//770 -f 767//765 637//635 643//641 -f 767//765 643//641 773//771 -f 768//766 767//765 773//771 -f 768//766 773//771 774//772 -f 769//767 768//766 774//772 -f 769//767 774//772 775//773 -f 770//768 769//767 775//773 -f 770//768 775//773 776//774 -f 771//769 770//768 776//774 -f 771//769 776//774 777//775 -f 772//770 771//769 777//775 -f 772//770 777//775 778//776 -f 773//771 643//641 250//248 -f 773//771 250//248 779//777 -f 774//772 773//771 779//777 -f 774//772 779//777 780//778 -f 775//773 774//772 780//778 -f 775//773 780//778 781//779 -f 776//774 775//773 781//779 -f 776//774 781//779 782//780 -f 777//775 776//774 782//780 -f 777//775 782//780 783//781 -f 778//776 777//775 783//781 -f 778//776 783//781 784//782 -f 781//779 780//778 230//228 -f 781//779 230//228 648//646 -f 782//780 781//779 648//646 -f 782//780 648//646 650//648 -f 783//781 782//780 650//648 -f 783//781 650//648 652//650 -f 784//782 783//781 652//650 -f 784//782 652//650 654//652 -f 785//783 786//784 787//785 -f 788//786 785//783 787//785 -f 788//786 787//785 789//787 -f 790//788 788//786 789//787 -f 790//788 789//787 791//789 -f 792//790 790//788 791//789 -f 792//790 791//789 793//791 -f 794//792 792//790 793//791 -f 794//792 793//791 795//793 -f 654//652 794//792 795//793 -f 654//652 795//793 784//782 -f 787//785 786//784 796//794 -f 789//787 787//785 796//794 -f 789//787 796//794 797//795 -f 791//789 789//787 797//795 -f 791//789 797//795 798//796 -f 793//791 791//789 798//796 -f 793//791 798//796 799//797 -f 795//793 793//791 799//797 -f 795//793 799//797 800//798 -f 784//782 795//793 800//798 -f 784//782 800//798 778//776 -f 796//794 786//784 801//799 -f 797//795 796//794 801//799 -f 797//795 801//799 802//800 -f 798//796 797//795 802//800 -f 798//796 802//800 803//801 -f 799//797 798//796 803//801 -f 799//797 803//801 804//802 -f 800//798 799//797 804//802 -f 800//798 804//802 805//803 -f 778//776 800//798 805//803 -f 778//776 805//803 772//770 -f 801//799 786//784 806//804 -f 802//800 801//799 806//804 -f 802//800 806//804 807//805 -f 803//801 802//800 807//805 -f 803//801 807//805 808//806 -f 804//802 803//801 808//806 -f 804//802 808//806 809//807 -f 805//803 804//802 809//807 -f 805//803 809//807 810//808 -f 772//770 805//803 810//808 -f 772//770 810//808 766//764 -f 806//804 786//784 811//809 -f 807//805 806//804 811//809 -f 807//805 811//809 812//810 -f 808//806 807//805 812//810 -f 808//806 812//810 813//811 -f 809//807 808//806 813//811 -f 809//807 813//811 814//812 -f 810//808 809//807 814//812 -f 810//808 814//812 815//813 -f 766//764 810//808 815//813 -f 766//764 815//813 760//758 -f 811//809 786//784 816//814 -f 812//810 811//809 816//814 -f 812//810 816//814 817//815 -f 813//811 812//810 817//815 -f 813//811 817//815 818//816 -f 814//812 813//811 818//816 -f 814//812 818//816 819//817 -f 815//813 814//812 819//817 -f 815//813 819//817 820//818 -f 760//758 815//813 820//818 -f 760//758 820//818 754//752 -f 816//814 786//784 821//819 -f 817//815 816//814 821//819 -f 817//815 821//819 822//820 -f 818//816 817//815 822//820 -f 818//816 822//820 823//821 -f 819//817 818//816 823//821 -f 819//817 823//821 824//822 -f 820//818 819//817 824//822 -f 820//818 824//822 825//823 -f 754//752 820//818 825//823 -f 754//752 825//823 748//746 -f 821//819 786//784 826//824 -f 822//820 821//819 826//824 -f 822//820 826//824 827//825 -f 823//821 822//820 827//825 -f 823//821 827//825 828//826 -f 824//822 823//821 828//826 -f 824//822 828//826 829//827 -f 825//823 824//822 829//827 -f 825//823 829//827 830//828 -f 748//746 825//823 830//828 -f 748//746 830//828 742//740 -f 826//824 786//784 831//829 -f 827//825 826//824 831//829 -f 827//825 831//829 832//830 -f 828//826 827//825 832//830 -f 828//826 832//830 833//831 -f 829//827 828//826 833//831 -f 829//827 833//831 834//832 -f 830//828 829//827 834//832 -f 830//828 834//832 835//833 -f 742//740 830//828 835//833 -f 742//740 835//833 736//734 -f 831//829 786//784 836//834 -f 832//830 831//829 836//834 -f 832//830 836//834 837//835 -f 833//831 832//830 837//835 -f 833//831 837//835 838//836 -f 834//832 833//831 838//836 -f 834//832 838//836 839//837 -f 835//833 834//832 839//837 -f 835//833 839//837 840//838 -f 736//734 835//833 840//838 -f 736//734 840//838 730//728 -f 836//834 786//784 841//839 -f 837//835 836//834 841//839 -f 837//835 841//839 842//840 -f 838//836 837//835 842//840 -f 838//836 842//840 843//841 -f 839//837 838//836 843//841 -f 839//837 843//841 844//842 -f 840//838 839//837 844//842 -f 840//838 844//842 845//843 -f 730//728 840//838 845//843 -f 730//728 845//843 724//722 -f 841//839 786//784 846//844 -f 842//840 841//839 846//844 -f 842//840 846//844 847//845 -f 843//841 842//840 847//845 -f 843//841 847//845 848//846 -f 844//842 843//841 848//846 -f 844//842 848//846 849//847 -f 845//843 844//842 849//847 -f 845//843 849//847 850//848 -f 724//722 845//843 850//848 -f 724//722 850//848 719//717 -f 846//844 786//784 851//849 -f 847//845 846//844 851//849 -f 847//845 851//849 852//850 -f 848//846 847//845 852//850 -f 848//846 852//850 853//851 -f 849//847 848//846 853//851 -f 849//847 853//851 854//852 -f 850//848 849//847 854//852 -f 850//848 854//852 855//853 -f 719//717 850//848 855//853 -f 719//717 855//853 714//712 -f 851//849 786//784 856//854 -f 852//850 851//849 856//854 -f 852//850 856//854 857//855 -f 853//851 852//850 857//855 -f 853//851 857//855 858//856 -f 854//852 853//851 858//856 -f 854//852 858//856 859//857 -f 855//853 854//852 859//857 -f 855//853 859//857 860//858 -f 714//712 855//853 860//858 -f 714//712 860//858 709//707 -f 856//854 786//784 861//859 -f 857//855 856//854 861//859 -f 857//855 861//859 862//860 -f 858//856 857//855 862//860 -f 858//856 862//860 863//861 -f 859//857 858//856 863//861 -f 859//857 863//861 864//862 -f 860//858 859//857 864//862 -f 860//858 864//862 865//863 -f 709//707 860//858 865//863 -f 709//707 865//863 703//701 -f 861//859 786//784 866//864 -f 862//860 861//859 866//864 -f 862//860 866//864 867//865 -f 863//861 862//860 867//865 -f 863//861 867//865 868//866 -f 864//862 863//861 868//866 -f 864//862 868//866 869//867 -f 865//863 864//862 869//867 -f 865//863 869//867 870//868 -f 703//701 865//863 870//868 -f 703//701 870//868 697//695 -f 866//864 786//784 871//869 -f 867//865 866//864 871//869 -f 867//865 871//869 872//870 -f 868//866 867//865 872//870 -f 868//866 872//870 873//871 -f 869//867 868//866 873//871 -f 869//867 873//871 874//872 -f 870//868 869//867 874//872 -f 870//868 874//872 875//873 -f 697//695 870//868 875//873 -f 697//695 875//873 691//689 -f 871//869 786//784 876//874 -f 872//870 871//869 876//874 -f 872//870 876//874 877//875 -f 873//871 872//870 877//875 -f 873//871 877//875 878//876 -f 874//872 873//871 878//876 -f 874//872 878//876 879//877 -f 875//873 874//872 879//877 -f 875//873 879//877 880//878 -f 691//689 875//873 880//878 -f 691//689 880//878 685//683 -f 876//874 786//784 881//879 -f 877//875 876//874 881//879 -f 877//875 881//879 882//880 -f 878//876 877//875 882//880 -f 878//876 882//880 883//881 -f 879//877 878//876 883//881 -f 879//877 883//881 884//882 -f 880//878 879//877 884//882 -f 880//878 884//882 885//883 -f 685//683 880//878 885//883 -f 685//683 885//883 679//677 -f 881//879 786//784 886//884 -f 882//880 881//879 886//884 -f 882//880 886//884 887//885 -f 883//881 882//880 887//885 -f 883//881 887//885 888//886 -f 884//882 883//881 888//886 -f 884//882 888//886 889//887 -f 885//883 884//882 889//887 -f 885//883 889//887 890//888 -f 679//677 885//883 890//888 -f 679//677 890//888 673//671 -f 886//884 786//784 891//889 -f 887//885 886//884 891//889 -f 887//885 891//889 892//890 -f 888//886 887//885 892//890 -f 888//886 892//890 893//891 -f 889//887 888//886 893//891 -f 889//887 893//891 894//892 -f 890//888 889//887 894//892 -f 890//888 894//892 895//893 -f 673//671 890//888 895//893 -f 673//671 895//893 667//665 -f 891//889 786//784 896//894 -f 892//890 891//889 896//894 -f 892//890 896//894 897//895 -f 893//891 892//890 897//895 -f 893//891 897//895 898//896 -f 894//892 893//891 898//896 -f 894//892 898//896 899//897 -f 895//893 894//892 899//897 -f 895//893 899//897 900//898 -f 667//665 895//893 900//898 -f 667//665 900//898 661//659 -f 896//894 786//784 901//899 -f 897//895 896//894 901//899 -f 897//895 901//899 902//900 -f 898//896 897//895 902//900 -f 898//896 902//900 903//901 -f 899//897 898//896 903//901 -f 899//897 903//901 904//902 -f 900//898 899//897 904//902 -f 900//898 904//902 905//903 -f 661//659 900//898 905//903 -f 661//659 905//903 655//653 -f 901//899 786//784 785//783 -f 902//900 901//899 785//783 -f 902//900 785//783 788//786 -f 903//901 902//900 788//786 -f 903//901 788//786 790//788 -f 904//902 903//901 790//788 -f 904//902 790//788 792//790 -f 905//903 904//902 792//790 -f 905//903 792//790 794//792 -f 655//653 905//903 794//792 -f 655//653 794//792 654//652 -f 414//412 906//904 907//905 -f 908//906 909//907 482//480 -f 910//908 503//501 496//494 -f 911//909 428//426 421//419 -f 912//910 352//350 510//508 -f 441//439 435//433 913//911 -f 914//912 915//913 916//914 -f 914//912 916//914 917//915 -f 917//915 916//914 918//916 -f 917//915 918//916 919//917 -f 919//917 918//916 920//918 -f 919//917 920//918 921//919 -f 921//919 920//918 922//920 -f 921//919 922//920 923//921 -f 923//921 922//920 924//922 -f 923//921 924//922 925//923 -f 925//923 924//922 926//924 -f 925//923 926//924 927//925 -f 927//925 926//924 928//926 -f 927//925 928//926 929//927 -f 929//927 928//926 930//928 -f 929//927 930//928 931//929 -f 931//929 930//928 932//930 -f 931//929 932//930 933//931 -f 933//931 932//930 934//932 -f 933//931 934//932 935//933 -f 935//933 934//932 936//934 -f 935//933 936//934 937//935 -f 937//935 936//934 938//936 -f 937//935 938//936 939//937 -f 939//937 938//936 940//938 -f 939//937 940//938 941//939 -f 941//939 940//938 942//940 -f 941//939 942//940 943//941 -f 943//941 942//940 944//942 -f 943//941 944//942 945//943 -f 945//943 944//942 946//944 -f 945//943 946//944 947//945 -f 947//945 946//944 948//946 -f 947//945 948//946 949//947 -f 949//947 948//946 950//948 -f 949//947 950//948 951//949 -f 951//949 950//948 952//950 -f 951//949 952//950 953//951 -f 953//951 952//950 954//952 -f 953//951 954//952 955//953 -f 955//953 954//952 956//954 -f 955//953 956//954 957//955 -f 957//955 956//954 958//956 -f 957//955 958//956 959//957 -f 959//957 958//956 960//958 -f 959//957 960//958 961//959 -f 961//959 960//958 915//913 -f 915//913 914//912 961//959 -f 962//960 914//912 917//915 -f 962//960 917//915 963//961 -f 964//962 962//960 963//961 -f 964//962 963//961 965//963 -f 966//964 964//962 965//963 -f 966//964 965//963 967//965 -f 968//966 966//964 967//965 -f 968//966 967//965 969//967 -f 970//968 968//966 969//967 -f 970//968 969//967 971//969 -f 972//970 970//968 971//969 -f 972//970 971//969 973//971 -f 963//961 917//915 919//917 -f 963//961 919//917 974//972 -f 965//963 963//961 974//972 -f 965//963 974//972 975//973 -f 967//965 965//963 975//973 -f 967//965 975//973 976//974 -f 969//967 967//965 976//974 -f 969//967 976//974 977//975 -f 971//969 969//967 977//975 -f 971//969 977//975 978//976 -f 973//971 971//969 978//976 -f 973//971 978//976 979//977 -f 974//972 919//917 921//919 -f 974//972 921//919 980//978 -f 975//973 974//972 980//978 -f 975//973 980//978 981//979 -f 976//974 975//973 981//979 -f 976//974 981//979 982//980 -f 977//975 976//974 982//980 -f 977//975 982//980 983//981 -f 978//976 977//975 983//981 -f 978//976 983//981 984//982 -f 979//977 978//976 984//982 -f 979//977 984//982 985//983 -f 980//978 921//919 923//921 -f 980//978 923//921 986//984 -f 981//979 980//978 986//984 -f 981//979 986//984 987//985 -f 982//980 981//979 987//985 -f 982//980 987//985 988//986 -f 983//981 982//980 988//986 -f 983//981 988//986 989//987 -f 984//982 983//981 989//987 -f 984//982 989//987 990//988 -f 985//983 984//982 990//988 -f 985//983 990//988 991//989 -f 986//984 923//921 925//923 -f 986//984 925//923 992//990 -f 987//985 986//984 992//990 -f 987//985 992//990 993//991 -f 988//986 987//985 993//991 -f 988//986 993//991 994//992 -f 989//987 988//986 994//992 -f 989//987 994//992 995//993 -f 990//988 989//987 995//993 -f 990//988 995//993 996//994 -f 991//989 990//988 996//994 -f 991//989 996//994 997//995 -f 992//990 925//923 927//925 -f 992//990 927//925 998//996 -f 993//991 992//990 998//996 -f 993//991 998//996 999//997 -f 994//992 993//991 999//997 -f 994//992 999//997 1000//998 -f 995//993 994//992 1000//998 -f 995//993 1000//998 1001//999 -f 996//994 995//993 1001//999 -f 996//994 1001//999 1002//1000 -f 997//995 996//994 1002//1000 -f 997//995 1002//1000 1003//1001 -f 998//996 927//925 929//927 -f 998//996 929//927 1004//1002 -f 999//997 998//996 1004//1002 -f 999//997 1004//1002 1005//1003 -f 1000//998 999//997 1005//1003 -f 1000//998 1005//1003 1006//1004 -f 1001//999 1000//998 1006//1004 -f 1001//999 1006//1004 1007//1005 -f 1002//1000 1001//999 1007//1005 -f 1002//1000 1007//1005 1008//1006 -f 1003//1001 1002//1000 1008//1006 -f 1003//1001 1008//1006 1009//1007 -f 1004//1002 929//927 931//929 -f 1004//1002 931//929 1010//1008 -f 1005//1003 1004//1002 1010//1008 -f 1005//1003 1010//1008 1011//1009 -f 1006//1004 1005//1003 1011//1009 -f 1006//1004 1011//1009 1012//1010 -f 1007//1005 1006//1004 1012//1010 -f 1007//1005 1012//1010 1013//1011 -f 1008//1006 1007//1005 1013//1011 -f 1008//1006 1013//1011 1014//1012 -f 1009//1007 1008//1006 1014//1012 -f 1009//1007 1014//1012 1015//1013 -f 1010//1008 931//929 933//931 -f 1010//1008 933//931 1016//1014 -f 1011//1009 1010//1008 1016//1014 -f 1011//1009 1016//1014 1017//1015 -f 1012//1010 1011//1009 1017//1015 -f 1012//1010 1017//1015 1018//1016 -f 1013//1011 1012//1010 1018//1016 -f 1013//1011 1018//1016 1019//1017 -f 1014//1012 1013//1011 1019//1017 -f 1014//1012 1019//1017 1020//1018 -f 1015//1013 1014//1012 1020//1018 -f 1015//1013 1020//1018 1021//1019 -f 1016//1014 933//931 935//933 -f 1016//1014 935//933 1022//1020 -f 1017//1015 1016//1014 1022//1020 -f 1017//1015 1022//1020 1023//1021 -f 1018//1016 1017//1015 1023//1021 -f 1018//1016 1023//1021 1024//1022 -f 1019//1017 1018//1016 1024//1022 -f 1019//1017 1024//1022 1025//1023 -f 1020//1018 1019//1017 1025//1023 -f 1020//1018 1025//1023 1026//1024 -f 1021//1019 1020//1018 1026//1024 -f 1021//1019 1026//1024 1027//1025 -f 1022//1020 935//933 937//935 -f 1022//1020 937//935 1028//1026 -f 1023//1021 1022//1020 1028//1026 -f 1023//1021 1028//1026 1029//1027 -f 1024//1022 1023//1021 1029//1027 -f 1024//1022 1029//1027 1030//1028 -f 1025//1023 1024//1022 1030//1028 -f 1025//1023 1030//1028 1031//1029 -f 1026//1024 1025//1023 1031//1029 -f 1026//1024 1031//1029 1032//1030 -f 1027//1025 1026//1024 1032//1030 -f 1027//1025 1032//1030 1033//1031 -f 1028//1026 937//935 939//937 -f 1028//1026 939//937 1034//1032 -f 1029//1027 1028//1026 1034//1032 -f 1029//1027 1034//1032 1035//1033 -f 1030//1028 1029//1027 1035//1033 -f 1030//1028 1035//1033 1036//1034 -f 1031//1029 1030//1028 1036//1034 -f 1031//1029 1036//1034 1037//1035 -f 1032//1030 1031//1029 1037//1035 -f 1032//1030 1037//1035 1038//1036 -f 1033//1031 1032//1030 1038//1036 -f 1033//1031 1038//1036 1039//1037 -f 1034//1032 939//937 941//939 -f 1034//1032 941//939 1040//1038 -f 1035//1033 1034//1032 1040//1038 -f 1035//1033 1040//1038 1041//1039 -f 1036//1034 1035//1033 1041//1039 -f 1036//1034 1041//1039 1042//1040 -f 1037//1035 1036//1034 1042//1040 -f 1037//1035 1042//1040 1043//1041 -f 1038//1036 1037//1035 1043//1041 -f 1038//1036 1043//1041 1044//1042 -f 1039//1037 1038//1036 1044//1042 -f 1039//1037 1044//1042 1045//1043 -f 1040//1038 941//939 943//941 -f 1040//1038 943//941 1046//1044 -f 1041//1039 1040//1038 1046//1044 -f 1041//1039 1046//1044 1047//1045 -f 1042//1040 1041//1039 1047//1045 -f 1042//1040 1047//1045 1048//1046 -f 1043//1041 1042//1040 1048//1046 -f 1043//1041 1048//1046 1049//1047 -f 1044//1042 1043//1041 1049//1047 -f 1044//1042 1049//1047 1050//1048 -f 1045//1043 1044//1042 1050//1048 -f 1045//1043 1050//1048 1051//1049 -f 1046//1044 943//941 945//943 -f 1046//1044 945//943 1052//1050 -f 1047//1045 1046//1044 1052//1050 -f 1047//1045 1052//1050 1053//1051 -f 1048//1046 1047//1045 1053//1051 -f 1048//1046 1053//1051 1054//1052 -f 1049//1047 1048//1046 1054//1052 -f 1049//1047 1054//1052 1055//1053 -f 1050//1048 1049//1047 1055//1053 -f 1050//1048 1055//1053 1056//1054 -f 1051//1049 1050//1048 1056//1054 -f 1051//1049 1056//1054 1057//1055 -f 1052//1050 945//943 947//945 -f 1052//1050 947//945 1058//1056 -f 1053//1051 1052//1050 1058//1056 -f 1053//1051 1058//1056 1059//1057 -f 1054//1052 1053//1051 1059//1057 -f 1054//1052 1059//1057 1060//1058 -f 1055//1053 1054//1052 1060//1058 -f 1055//1053 1060//1058 1061//1059 -f 1056//1054 1055//1053 1061//1059 -f 1056//1054 1061//1059 1062//1060 -f 1057//1055 1056//1054 1062//1060 -f 1057//1055 1062//1060 1063//1061 -f 1058//1056 947//945 949//947 -f 1058//1056 949//947 1064//1062 -f 1059//1057 1058//1056 1064//1062 -f 1059//1057 1064//1062 1065//1063 -f 1060//1058 1059//1057 1065//1063 -f 1060//1058 1065//1063 1066//1064 -f 1061//1059 1060//1058 1066//1064 -f 1061//1059 1066//1064 1067//1065 -f 1062//1060 1061//1059 1067//1065 -f 1062//1060 1067//1065 1068//1066 -f 1063//1061 1062//1060 1068//1066 -f 1063//1061 1068//1066 1069//1067 -f 1064//1062 949//947 951//949 -f 1064//1062 951//949 1070//1068 -f 1065//1063 1064//1062 1070//1068 -f 1065//1063 1070//1068 1071//1069 -f 1066//1064 1065//1063 1071//1069 -f 1066//1064 1071//1069 1072//1070 -f 1067//1065 1066//1064 1072//1070 -f 1067//1065 1072//1070 1073//1071 -f 1068//1066 1067//1065 1073//1071 -f 1068//1066 1073//1071 1074//1072 -f 1069//1067 1068//1066 1074//1072 -f 1069//1067 1074//1072 1075//1073 -f 1070//1068 951//949 953//951 -f 1070//1068 953//951 1076//1074 -f 1071//1069 1070//1068 1076//1074 -f 1071//1069 1076//1074 1077//1075 -f 1072//1070 1071//1069 1077//1075 -f 1072//1070 1077//1075 1078//1076 -f 1073//1071 1072//1070 1078//1076 -f 1073//1071 1078//1076 1079//1077 -f 1074//1072 1073//1071 1079//1077 -f 1074//1072 1079//1077 1080//1078 -f 1075//1073 1074//1072 1080//1078 -f 1075//1073 1080//1078 1081//1079 -f 1076//1074 953//951 955//953 -f 1076//1074 955//953 1082//1080 -f 1077//1075 1076//1074 1082//1080 -f 1077//1075 1082//1080 1083//1081 -f 1078//1076 1077//1075 1083//1081 -f 1078//1076 1083//1081 1084//1082 -f 1079//1077 1078//1076 1084//1082 -f 1079//1077 1084//1082 1085//1083 -f 1080//1078 1079//1077 1085//1083 -f 1080//1078 1085//1083 1086//1084 -f 1081//1079 1080//1078 1086//1084 -f 1081//1079 1086//1084 1087//1085 -f 1082//1080 955//953 957//955 -f 1082//1080 957//955 1088//1086 -f 1083//1081 1082//1080 1088//1086 -f 1083//1081 1088//1086 1089//1087 -f 1084//1082 1083//1081 1089//1087 -f 1084//1082 1089//1087 1090//1088 -f 1085//1083 1084//1082 1090//1088 -f 1085//1083 1090//1088 1091//1089 -f 1086//1084 1085//1083 1091//1089 -f 1086//1084 1091//1089 1092//1090 -f 1087//1085 1086//1084 1092//1090 -f 1087//1085 1092//1090 1093//1091 -f 1088//1086 957//955 959//957 -f 1088//1086 959//957 1094//1092 -f 1089//1087 1088//1086 1094//1092 -f 1089//1087 1094//1092 1095//1093 -f 1090//1088 1089//1087 1095//1093 -f 1090//1088 1095//1093 1096//1094 -f 1091//1089 1090//1088 1096//1094 -f 1091//1089 1096//1094 1097//1095 -f 1092//1090 1091//1089 1097//1095 -f 1092//1090 1097//1095 1098//1096 -f 1093//1091 1092//1090 1098//1096 -f 1093//1091 1098//1096 1099//1097 -f 1094//1092 959//957 961//959 -f 1094//1092 961//959 1100//1098 -f 1095//1093 1094//1092 1100//1098 -f 1095//1093 1100//1098 1101//1099 -f 1096//1094 1095//1093 1101//1099 -f 1096//1094 1101//1099 1102//1100 -f 1097//1095 1096//1094 1102//1100 -f 1097//1095 1102//1100 1103//1101 -f 1098//1096 1097//1095 1103//1101 -f 1098//1096 1103//1101 1104//1102 -f 1099//1097 1098//1096 1104//1102 -f 1099//1097 1104//1102 1105//1103 -f 961//959 914//912 1100//1098 -f 1100//1098 914//912 962//960 -f 1101//1099 1100//1098 962//960 -f 1101//1099 962//960 964//962 -f 1102//1100 1101//1099 964//962 -f 1102//1100 964//962 966//964 -f 1103//1101 1102//1100 966//964 -f 1103//1101 966//964 968//966 -f 1104//1102 1103//1101 968//966 -f 1104//1102 968//966 970//968 -f 1105//1103 1104//1102 970//968 -f 1105//1103 970//968 972//970 -f 1106//1104 1107//348 1108//1105 -f 1109//1106 1106//1104 1108//1105 -f 1109//1106 1108//1105 1110//1107 -f 1111//1108 1109//1106 1110//1107 -f 1111//1108 1110//1107 1112//1109 -f 1113//1110 1111//1108 1112//1109 -f 1113//1110 1112//1109 1114//1111 -f 915//913 1113//1110 1114//1111 -f 915//913 1114//1111 916//914 -f 1108//1105 1107//348 1115//1112 -f 1110//1107 1108//1105 1115//1112 -f 1110//1107 1115//1112 1116//1113 -f 1112//1109 1110//1107 1116//1113 -f 1112//1109 1116//1113 1117//1114 -f 1114//1111 1112//1109 1117//1114 -f 1114//1111 1117//1114 1118//1115 -f 916//914 1114//1111 1118//1115 -f 916//914 1118//1115 918//916 -f 1115//1112 1107//348 1119//1116 -f 1116//1113 1115//1112 1119//1116 -f 1116//1113 1119//1116 1120//1117 -f 1117//1114 1116//1113 1120//1117 -f 1117//1114 1120//1117 1121//1118 -f 1118//1115 1117//1114 1121//1118 -f 1118//1115 1121//1118 1122//1119 -f 918//916 1118//1115 1122//1119 -f 918//916 1122//1119 920//918 -f 1119//1116 1107//348 1123//1120 -f 1120//1117 1119//1116 1123//1120 -f 1120//1117 1123//1120 1124//1121 -f 1121//1118 1120//1117 1124//1121 -f 1121//1118 1124//1121 1125//1122 -f 1122//1119 1121//1118 1125//1122 -f 1122//1119 1125//1122 1126//1123 -f 920//918 1122//1119 1126//1123 -f 920//918 1126//1123 922//920 -f 1123//1120 1107//348 1127//1124 -f 1124//1121 1123//1120 1127//1124 -f 1124//1121 1127//1124 1128//1125 -f 1125//1122 1124//1121 1128//1125 -f 1125//1122 1128//1125 1129//1126 -f 1126//1123 1125//1122 1129//1126 -f 1126//1123 1129//1126 1130//1127 -f 922//920 1126//1123 1130//1127 -f 922//920 1130//1127 924//922 -f 1127//1124 1107//348 1131//1128 -f 1128//1125 1127//1124 1131//1128 -f 1128//1125 1131//1128 1132//1129 -f 1129//1126 1128//1125 1132//1129 -f 1129//1126 1132//1129 1133//1130 -f 1130//1127 1129//1126 1133//1130 -f 1130//1127 1133//1130 1134//1131 -f 924//922 1130//1127 1134//1131 -f 924//922 1134//1131 926//924 -f 1131//1128 1107//348 1135//1132 -f 1132//1129 1131//1128 1135//1132 -f 1132//1129 1135//1132 1136//1133 -f 1133//1130 1132//1129 1136//1133 -f 1133//1130 1136//1133 1137//1134 -f 1134//1131 1133//1130 1137//1134 -f 1134//1131 1137//1134 1138//1135 -f 926//924 1134//1131 1138//1135 -f 926//924 1138//1135 928//926 -f 1135//1132 1107//348 1139//1136 -f 1136//1133 1135//1132 1139//1136 -f 1136//1133 1139//1136 1140//1137 -f 1137//1134 1136//1133 1140//1137 -f 1137//1134 1140//1137 1141//1138 -f 1138//1135 1137//1134 1141//1138 -f 1138//1135 1141//1138 1142//1139 -f 928//926 1138//1135 1142//1139 -f 928//926 1142//1139 930//928 -f 1139//1136 1107//348 1143//1140 -f 1140//1137 1139//1136 1143//1140 -f 1140//1137 1143//1140 1144//1141 -f 1141//1138 1140//1137 1144//1141 -f 1141//1138 1144//1141 1145//1142 -f 1142//1139 1141//1138 1145//1142 -f 1142//1139 1145//1142 1146//1143 -f 930//928 1142//1139 1146//1143 -f 930//928 1146//1143 932//930 -f 1143//1140 1107//348 1147//1144 -f 1144//1141 1143//1140 1147//1144 -f 1144//1141 1147//1144 1148//1145 -f 1145//1142 1144//1141 1148//1145 -f 1145//1142 1148//1145 1149//1146 -f 1146//1143 1145//1142 1149//1146 -f 1146//1143 1149//1146 1150//1147 -f 932//930 1146//1143 1150//1147 -f 932//930 1150//1147 934//932 -f 1147//1144 1107//348 1151//1148 -f 1148//1145 1147//1144 1151//1148 -f 1148//1145 1151//1148 1152//1149 -f 1149//1146 1148//1145 1152//1149 -f 1149//1146 1152//1149 1153//1150 -f 1150//1147 1149//1146 1153//1150 -f 1150//1147 1153//1150 1154//1151 -f 934//932 1150//1147 1154//1151 -f 934//932 1154//1151 936//934 -f 1151//1148 1107//348 1155//1152 -f 1152//1149 1151//1148 1155//1152 -f 1152//1149 1155//1152 1156//1153 -f 1153//1150 1152//1149 1156//1153 -f 1153//1150 1156//1153 1157//1154 -f 1154//1151 1153//1150 1157//1154 -f 1154//1151 1157//1154 1158//1155 -f 936//934 1154//1151 1158//1155 -f 936//934 1158//1155 938//936 -f 1155//1152 1107//348 1159//1156 -f 1156//1153 1155//1152 1159//1156 -f 1156//1153 1159//1156 1160//1157 -f 1157//1154 1156//1153 1160//1157 -f 1157//1154 1160//1157 1161//1158 -f 1158//1155 1157//1154 1161//1158 -f 1158//1155 1161//1158 1162//1159 -f 938//936 1158//1155 1162//1159 -f 938//936 1162//1159 940//938 -f 1159//1156 1107//348 1163//1160 -f 1160//1157 1159//1156 1163//1160 -f 1160//1157 1163//1160 1164//1161 -f 1161//1158 1160//1157 1164//1161 -f 1161//1158 1164//1161 1165//1162 -f 1162//1159 1161//1158 1165//1162 -f 1162//1159 1165//1162 1166//1163 -f 940//938 1162//1159 1166//1163 -f 940//938 1166//1163 942//940 -f 1163//1160 1107//348 1167//1164 -f 1164//1161 1163//1160 1167//1164 -f 1164//1161 1167//1164 1168//1165 -f 1165//1162 1164//1161 1168//1165 -f 1165//1162 1168//1165 1169//1166 -f 1166//1163 1165//1162 1169//1166 -f 1166//1163 1169//1166 1170//1167 -f 942//940 1166//1163 1170//1167 -f 942//940 1170//1167 944//942 -f 1167//1164 1107//348 1171//1168 -f 1168//1165 1167//1164 1171//1168 -f 1168//1165 1171//1168 1172//1169 -f 1169//1166 1168//1165 1172//1169 -f 1169//1166 1172//1169 1173//1170 -f 1170//1167 1169//1166 1173//1170 -f 1170//1167 1173//1170 1174//1171 -f 944//942 1170//1167 1174//1171 -f 944//942 1174//1171 946//944 -f 1171//1168 1107//348 1175//1172 -f 1172//1169 1171//1168 1175//1172 -f 1172//1169 1175//1172 1176//1173 -f 1173//1170 1172//1169 1176//1173 -f 1173//1170 1176//1173 1177//1174 -f 1174//1171 1173//1170 1177//1174 -f 1174//1171 1177//1174 1178//1175 -f 946//944 1174//1171 1178//1175 -f 946//944 1178//1175 948//946 -f 1175//1172 1107//348 1179//1176 -f 1176//1173 1175//1172 1179//1176 -f 1176//1173 1179//1176 1180//1177 -f 1177//1174 1176//1173 1180//1177 -f 1177//1174 1180//1177 1181//1178 -f 1178//1175 1177//1174 1181//1178 -f 1178//1175 1181//1178 1182//1179 -f 948//946 1178//1175 1182//1179 -f 948//946 1182//1179 950//948 -f 1179//1176 1107//348 1183//1180 -f 1180//1177 1179//1176 1183//1180 -f 1180//1177 1183//1180 1184//1181 -f 1181//1178 1180//1177 1184//1181 -f 1181//1178 1184//1181 1185//1182 -f 1182//1179 1181//1178 1185//1182 -f 1182//1179 1185//1182 1186//1183 -f 950//948 1182//1179 1186//1183 -f 950//948 1186//1183 952//950 -f 1183//1180 1107//348 1187//1184 -f 1184//1181 1183//1180 1187//1184 -f 1184//1181 1187//1184 1188//1185 -f 1185//1182 1184//1181 1188//1185 -f 1185//1182 1188//1185 1189//1186 -f 1186//1183 1185//1182 1189//1186 -f 1186//1183 1189//1186 1190//1187 -f 952//950 1186//1183 1190//1187 -f 952//950 1190//1187 954//952 -f 1187//1184 1107//348 1191//1188 -f 1188//1185 1187//1184 1191//1188 -f 1188//1185 1191//1188 1192//1189 -f 1189//1186 1188//1185 1192//1189 -f 1189//1186 1192//1189 1193//1190 -f 1190//1187 1189//1186 1193//1190 -f 1190//1187 1193//1190 1194//1191 -f 954//952 1190//1187 1194//1191 -f 954//952 1194//1191 956//954 -f 1191//1188 1107//348 1195//1192 -f 1192//1189 1191//1188 1195//1192 -f 1192//1189 1195//1192 1196//1193 -f 1193//1190 1192//1189 1196//1193 -f 1193//1190 1196//1193 1197//1194 -f 1194//1191 1193//1190 1197//1194 -f 1194//1191 1197//1194 1198//1195 -f 956//954 1194//1191 1198//1195 -f 956//954 1198//1195 958//956 -f 1195//1192 1107//348 1199//1196 -f 1196//1193 1195//1192 1199//1196 -f 1196//1193 1199//1196 1200//1197 -f 1197//1194 1196//1193 1200//1197 -f 1197//1194 1200//1197 1201//1198 -f 1198//1195 1197//1194 1201//1198 -f 1198//1195 1201//1198 1202//1199 -f 958//956 1198//1195 1202//1199 -f 958//956 1202//1199 960//958 -f 1199//1196 1107//348 1106//1104 -f 1200//1197 1199//1196 1106//1104 -f 1200//1197 1106//1104 1109//1106 -f 1201//1198 1200//1197 1109//1106 -f 1201//1198 1109//1106 1111//1108 -f 1202//1199 1201//1198 1111//1108 -f 1202//1199 1111//1108 1113//1110 -f 960//958 1202//1199 1113//1110 -f 960//958 1113//1110 915//913 -f 1045//1043 1051//1049 1203//1200 -f 997//995 1204//1201 991//989 -f 973//971 979//977 1205//1202 -f 991//989 1206//1203 985//983 -f 1063//1061 1207//1204 1057//1055 -f 1039//1037 1208//1205 1033//1031 -f 1015//1013 1021//1019 1209//1206 -f 972//970 973//971 1210//1207 -f 972//970 1210//1207 1105//1103 -f 1027//1025 1211//1208 1021//1019 -f 1069//1067 1212//1209 1063//1061 -f 1075//1073 1213//1210 1069//1067 -f 1033//1031 1214//1211 1027//1025 -f 1081//1079 1087//1085 1215//1212 -f 1039//1037 1045//1043 1208//1205 -f 1003//1001 1216//1213 997//995 -f 1051//1049 1057//1055 1217//1214 -f 1099//1097 1218//1215 1093//1091 -f 1009//1007 1015//1013 1219//1216 -f 979//977 985//983 1220//1217 -f 1105//1103 1221//1218 1099//1097 -f 1075//1073 1081//1079 1213//1210 -f 1003//1001 1009//1007 1216//1213 -f 1087//1085 1093//1091 1222//1219 -f 1208//1205 1203//1200 1223//1220 -f 1224//1221 1221//1218 1225//1222 -f 1226//1223 1227//1224 1213//1210 -f 1212//1209 1213//1210 1227//1224 -f 1209//1206 1228//1225 1229//1226 -f 178//176 521//519 1230//1227 -f 1230//1227 521//519 525//523 -f 203//201 525//523 204//202 -f 204//202 528//526 202//200 -f 194//192 184//182 201//199 -f 49//49 42//42 1231//1228 -f 1231//1228 42//42 1232//1229 -f 76//75 66//66 83//82 -f 50//50 49//49 1231//1228 -f 32//32 40//40 33//33 -f 3//3 33//33 41//41 -f 1233//1230 34//34 25//25 -f 2//2 90//89 3//3 -f 18//18 6//6 17//17 -f 24//24 18//18 17//17 -f 581//579 584//582 1234//1231 -f 50//50 1235//1232 48//48 -f 66//66 48//48 1235//1232 -f 75//74 74//73 82//81 -f 83//82 66//66 1235//1232 -f 32//32 31//31 56//56 -f 3//3 41//41 84//83 -f 42//42 34//34 1232//1229 -f 6//6 5//5 16//16 -f 72//58 65//65 56//56 -f 74//73 76//75 82//81 -f 57//57 132//130 58//58 -f 1234//1231 584//582 588//586 -f 1234//1231 588//586 594//592 -f 177//175 179//177 1236//1233 -f 259//257 1237//1234 253//251 -f 178//176 1238//1235 179//177 -f 252//250 251//249 1236//1233 -f 1239//1236 259//257 244//242 -f 238//236 232//230 1240//1237 -f 1241//1238 231//229 779//777 -f 528//526 1242//1239 202//200 -f 184//182 180//178 182//180 -f 1243//1240 1242//1239 229//227 -f 16//16 5//5 97//95 -f 126//124 131//129 163//161 -f 131//129 137//135 138//136 -f 161//159 715//713 162//160 -f 161//159 1244//1241 715//713 -f 1245//1242 152//150 154//152 -f 154//152 715//713 1245//1242 -f 144//142 146//144 145//143 -f 779//777 1246//1243 1241//1238 -f 1246//1243 779//777 250//248 -f 232//230 222//220 1247//1244 -f 780//778 779//777 231//229 -f 228//226 1247//1244 222//220 -f 229//227 231//229 1243//1240 -f 230//228 780//778 231//229 -f 594//592 1248//1245 1249//1245 -f 153//151 594//592 154//152 -f 147//145 155//153 169//167 -f 710//708 1250//1246 160//158 -f 594//592 153//151 1248//1245 -f 581//579 1251//1247 1252//1247 -f 581//579 1234//1231 1251//1247 -f 414//412 407//405 906//904 -f 913//911 435//433 428//426 -f 353//351 352//350 912//910 -f 447//445 441//439 1253//1248 -f 372//370 1254//1249 1255//1250 -f 372//370 365//363 1254//1249 -f 482//480 475//473 908//906 -f 379//377 1256//1251 386//384 -f 379//377 1257//1252 1256//1251 -f 1258//1253 510//508 503//501 -f 908//906 475//473 468//466 -f 386//384 1259//1254 393//391 -f 386//384 1256//1251 1259//1254 -f 496//494 1260//1255 910//908 -f 1261//1256 468//466 461//459 -f 372//370 1257//1252 379//377 -f 372//370 1255//1250 1257//1252 -f 400//398 393//391 1259//1254 -f 365//363 353//351 1262//1257 -f 414//412 1263//1258 421//419 -f 414//412 907//905 1263//1258 -f 454//452 1264//1259 1265//1260 -f 1265//1260 461//459 454//452 -f 496//494 489//487 1266//1261 -f 1266//1261 1260//1255 496//494 -f 407//405 400//398 1267//1262 -f 454//452 447//445 1268//1263 -f 1268//1263 1264//1259 454//452 -f 489//487 482//480 909//907 -f 909//907 1266//1261 489//487 -f 1211//1208 1214//1211 1269//1264 -f 1270//1265 1271//1266 1217//1214 -f 1271//1266 1272//1267 1217//1214 -f 1273//1268 1274//1269 1222//1219 -f 1274//1269 1275//1270 1222//1219 -f 1274//1269 1273//1268 1276//1271 -f 1210//1207 1205//1202 1225//1222 -f 1277//1272 1205//1202 1220//1217 -f 1219//1216 1278//1273 1279//1274 -f 1279//1274 1216//1213 1219//1216 -f 1271//1266 1270//1265 1280//1275 -f 1220//1217 1281//1276 1282//1277 -f 1207//1204 1212//1209 1283//1278 -f 1281//1276 1206//1203 1284//1279 -f 1206//1203 1285//1280 1284//1279 -f 1204//1201 1216//1213 1286//1281 -f 1216//1213 1279//1274 1286//1281 -f 1215//1212 1226//1223 1213//1210 -f 1212//1209 1227//1224 1283//1278 -f 1206//1203 1204//1201 1285//1280 -f 1204//1201 1286//1281 1285//1280 -f 1222//1219 1275//1270 1215//1212 -f 1275//1270 1226//1223 1215//1212 -f 1269//1264 1214//1211 1223//1220 -f 1287//1282 1203//1200 1217//1214 -f 1278//1273 1219//1216 1209//1206 -f 1228//1225 1211//1208 1288//1283 -f 1211//1208 1289//1284 1288//1283 -f 1218//1215 1221//1218 1224//1221 -f 1244//1241 1245//1242 715//713 -f 139//137 144//142 145//143 -f 137//135 144//142 139//137 -f 1249//1245 1234//1231 594//592 -f 126//124 163//161 121//119 -f 710//708 1252//1247 1250//1246 -f 528//526 229//227 1242//1239 -f 232//230 1247//1244 1240//1237 -f 244//242 238//236 1239//1236 -f 252//250 1246//1243 250//248 -f 24//24 1233//1230 25//25 -f 1232//1229 34//34 1233//1230 -f 210//208 200//198 1290//1285 -f 1291//1286 216//214 1292//1287 -f 1230//1227 525//523 203//201 -f 182//180 201//199 184//182 -f 1290//1285 200//198 201//199 -f 216//214 1291//1286 228//226 -f 260//258 1237//1234 181//179 -f 347//345 276//274 338//336 -f 319//317 325//323 340//338 -f 307//305 313//311 342//340 -f 313//311 319//317 341//339 -f 349//347 283//281 343//341 -f 348//346 295//293 344//342 -f 331//329 337//335 346//344 -f 325//323 331//329 339//337 -f 337//335 276//274 345//343 -f 344//342 301//299 342//340 -f 343//341 289//287 348//346 -f 338//336 277//275 349//347 -f 1263//1258 911//909 421//419 -f 1258//1253 912//910 510//508 -f 1253//1248 441//439 913//911 -f 1051//1049 1217//1214 1203//1200 -f 1204//1201 1206//1203 991//989 -f 979//977 1220//1217 1205//1202 -f 1206//1203 1281//1276 985//983 -f 1207//1204 1270//1265 1057//1055 -f 1208//1205 1214//1211 1033//1031 -f 1021//1019 1228//1225 1209//1206 -f 973//971 1205//1202 1210//1207 -f 1210//1207 1221//1218 1105//1103 -f 1211//1208 1228//1225 1021//1019 -f 1212//1209 1207//1204 1063//1061 -f 1213//1210 1212//1209 1069//1067 -f 1214//1211 1211//1208 1027//1025 -f 1087//1085 1222//1219 1215//1212 -f 1045//1043 1203//1200 1208//1205 -f 1216//1213 1204//1201 997//995 -f 1057//1055 1270//1265 1217//1214 -f 1218//1215 1273//1268 1093//1091 -f 1015//1013 1209//1206 1219//1216 -f 985//983 1281//1276 1220//1217 -f 1221//1218 1218//1215 1099//1097 -f 1081//1079 1215//1212 1213//1210 -f 1009//1007 1219//1216 1216//1213 -f 1093//1091 1273//1268 1222//1219 -f 1203//1200 1287//1282 1223//1220 -f 1221//1218 1210//1207 1225//1222 -f 1228//1225 1288//1283 1229//1226 -f 1238//1235 178//176 1230//1227 -f 1237//1234 260//258 253//251 -f 251//249 177//175 1236//1233 -f 162//160 710//708 160//158 -f 710//708 581//579 1252//1247 -f 911//909 913//911 428//426 -f 1262//1257 353//351 912//910 -f 1268//1263 447//445 1253//1248 -f 910//908 1258//1253 503//501 -f 1261//1256 908//906 468//466 -f 1265//1260 1261//1256 461//459 -f 1267//1262 400//398 1259//1254 -f 1254//1249 365//363 1262//1257 -f 906//904 407//405 1267//1262 -f 1289//1284 1211//1208 1269//1264 -f 1273//1268 1218//1215 1276//1271 -f 1205//1202 1277//1272 1225//1222 -f 1282//1277 1277//1272 1220//1217 -f 1270//1265 1207//1204 1280//1275 -f 1281//1276 1284//1279 1282//1277 -f 1280//1275 1207//1204 1283//1278 -f 1214//1211 1208//1205 1223//1220 -f 1272//1267 1287//1282 1217//1214 -f 1229//1226 1278//1273 1209//1206 -f 1276//1271 1218//1215 1224//1221 -f 238//236 1240//1237 1239//1236 -f 1292//1287 210//208 1290//1285 -f 216//214 210//208 1292//1287 +f 1//1 3//2 2//3 +f 3//2 4//4 2//3 +f 4//4 5//5 2//3 +f 5//5 6//6 2//3 +f 6//6 7//7 2//3 +f 7//7 8//8 2//3 +f 8//8 9//9 2//3 +f 9//9 10//10 2//3 +f 10//10 11//11 2//3 +f 11//11 12//12 2//3 +f 12//12 13//13 2//3 +f 13//13 14//14 2//3 +f 14//14 15//15 2//3 +f 15//15 16//16 2//3 +f 16//16 17//17 2//3 +f 17//17 18//18 2//3 +f 18//18 19//19 2//3 +f 19//19 20//20 2//3 +f 20//20 21//21 2//3 +f 21//21 22//22 2//3 +f 22//22 23//23 2//3 +f 23//23 24//24 2//3 +f 24//24 25//25 2//3 +f 25//25 1//1 2//3 +f 26//26 28//27 27//28 +f 28//27 26//26 29//29 +f 29//29 30//30 28//27 +f 31//31 28//27 30//30 +f 30//30 32//32 31//31 +f 33//33 31//31 32//32 +f 34//34 33//33 35//35 +f 32//32 35//35 33//33 +f 36//36 34//34 37//37 +f 35//35 37//37 34//34 +f 38//38 36//36 39//39 +f 37//37 39//39 36//36 +f 27//28 41//40 40//41 +f 41//40 27//28 28//27 +f 28//27 31//31 41//40 +f 42//42 41//40 31//31 +f 31//31 33//33 42//42 +f 43//43 42//42 33//33 +f 44//44 43//43 34//34 +f 33//33 34//34 43//43 +f 45//45 44//44 36//36 +f 34//34 36//36 44//44 +f 46//46 45//45 38//38 +f 36//36 38//38 45//45 +f 40//41 48//47 47//48 +f 48//47 40//41 41//40 +f 41//40 42//42 48//47 +f 49//49 48//47 42//42 +f 42//42 43//43 49//49 +f 50//50 49//49 43//43 +f 51//51 50//50 44//44 +f 43//43 44//44 50//50 +f 52//52 51//51 45//45 +f 44//44 45//45 51//51 +f 53//53 52//52 46//46 +f 45//45 46//46 52//52 +f 47//48 48//47 54//54 +f 55//55 54//54 48//47 +f 48//47 49//49 55//55 +f 56//56 55//55 49//49 +f 49//49 57//57 56//56 +f 57//57 49//49 50//50 +f 58//58 50//50 51//51 +f 50//50 58//58 57//57 +f 59//59 51//51 52//52 +f 51//51 59//59 58//58 +f 60//60 52//52 53//53 +f 52//52 60//60 59//59 +f 54//54 55//55 61//61 +f 62//62 61//61 55//55 +f 55//55 63//63 62//62 +f 63//63 55//55 56//56 +f 56//56 64//64 63//63 +f 64//64 56//56 57//57 +f 65//65 57//57 58//58 +f 57//57 65//65 64//64 +f 66//66 58//58 59//59 +f 58//58 66//66 65//65 +f 67//67 59//59 60//60 +f 59//59 67//67 66//66 +f 61//61 62//62 68//68 +f 69//69 68//68 62//62 +f 62//62 70//70 69//69 +f 70//70 62//62 63//63 +f 63//63 71//71 70//70 +f 71//71 63//63 64//64 +f 72//72 64//64 65//65 +f 64//64 72//72 71//71 +f 73//73 65//65 66//66 +f 65//65 73//73 72//72 +f 74//74 66//66 67//67 +f 66//66 74//74 73//73 +f 68//68 76//75 75//76 +f 76//75 68//68 69//69 +f 69//69 70//70 76//75 +f 77//77 76//75 70//70 +f 70//70 71//71 77//77 +f 78//78 77//77 71//71 +f 79//79 78//78 72//72 +f 71//71 72//72 78//78 +f 80//80 79//79 73//73 +f 72//72 73//73 79//79 +f 81//81 80//80 74//74 +f 73//73 74//74 80//80 +f 75//76 83//82 82//83 +f 83//82 75//76 76//75 +f 76//75 77//77 83//82 +f 84//84 83//82 77//77 +f 77//77 78//78 84//84 +f 85//85 84//84 78//78 +f 86//86 85//85 79//79 +f 78//78 79//79 85//85 +f 87//87 86//86 80//80 +f 79//79 80//80 86//86 +f 88//88 87//87 81//81 +f 80//80 81//81 87//87 +f 82//83 90//89 89//90 +f 90//89 82//83 83//82 +f 83//82 91//91 90//89 +f 91//91 83//82 84//84 +f 84//84 85//85 91//91 +f 92//92 91//91 85//85 +f 93//93 92//92 86//86 +f 85//85 86//86 92//92 +f 94//94 93//93 87//87 +f 86//86 87//87 93//93 +f 95//95 94//94 88//88 +f 87//87 88//88 94//94 +f 89//90 90//89 96//96 +f 97//97 96//96 90//89 +f 90//89 98//98 97//97 +f 98//98 90//89 91//91 +f 91//91 99//99 98//98 +f 99//99 91//91 92//92 +f 100//100 92//92 93//93 +f 92//92 100//100 99//99 +f 101//101 93//93 94//94 +f 93//93 101//101 100//100 +f 102//102 94//94 95//95 +f 94//94 102//102 101//101 +f 96//96 97//97 103//103 +f 104//104 103//103 97//97 +f 97//97 105//105 104//104 +f 105//105 97//97 98//98 +f 98//98 106//106 105//105 +f 106//106 98//98 99//99 +f 107//107 99//99 100//100 +f 99//99 107//107 106//106 +f 108//108 100//100 101//101 +f 100//100 108//108 107//107 +f 109//109 101//101 102//102 +f 101//101 109//109 108//108 +f 103//103 104//104 110//110 +f 111//111 110//110 104//104 +f 104//104 112//112 111//111 +f 112//112 104//104 105//105 +f 105//105 113//113 112//112 +f 113//113 105//105 106//106 +f 114//114 106//106 107//107 +f 106//106 114//114 113//113 +f 115//115 107//107 108//108 +f 107//107 115//115 114//114 +f 116//116 108//108 109//109 +f 108//108 116//116 115//115 +f 110//110 118//117 117//118 +f 118//117 110//110 111//111 +f 111//111 119//119 118//117 +f 119//119 111//111 112//112 +f 112//112 113//113 119//119 +f 120//120 119//119 113//113 +f 121//121 120//120 114//114 +f 113//113 114//114 120//120 +f 122//122 121//121 115//115 +f 114//114 115//115 121//121 +f 123//123 122//122 116//116 +f 115//115 116//116 122//122 +f 117//118 125//124 124//125 +f 125//124 117//118 118//117 +f 118//117 119//119 125//124 +f 126//126 125//124 119//119 +f 119//119 120//120 126//126 +f 127//127 126//126 120//120 +f 128//128 127//127 121//121 +f 120//120 121//121 127//127 +f 129//129 128//128 122//122 +f 121//121 122//122 128//128 +f 130//130 129//129 123//123 +f 122//122 123//123 129//129 +f 124//125 132//131 131//132 +f 132//131 124//125 125//124 +f 125//124 133//133 132//131 +f 133//133 125//124 126//126 +f 126//126 127//127 133//133 +f 134//134 133//133 127//127 +f 135//135 134//134 128//128 +f 127//127 128//128 134//134 +f 136//136 135//135 129//129 +f 128//128 129//129 135//135 +f 137//137 136//136 130//130 +f 129//129 130//130 136//136 +f 131//132 132//131 138//138 +f 139//139 138//138 132//131 +f 132//131 140//140 139//139 +f 140//140 132//131 133//133 +f 133//133 141//141 140//140 +f 141//141 133//133 134//134 +f 142//142 134//134 135//135 +f 134//134 142//142 141//141 +f 143//143 135//135 136//136 +f 135//135 143//143 142//142 +f 144//144 136//136 137//137 +f 136//136 144//144 143//143 +f 138//138 139//139 145//145 +f 146//146 145//145 139//139 +f 139//139 147//147 146//146 +f 147//147 139//139 140//140 +f 140//140 148//148 147//147 +f 148//148 140//140 141//141 +f 149//149 141//141 142//142 +f 141//141 149//149 148//148 +f 150//150 142//142 143//143 +f 142//142 150//150 149//149 +f 151//151 143//143 144//144 +f 143//143 151//151 150//150 +f 145//145 146//146 152//152 +f 153//153 152//152 146//146 +f 146//146 154//154 153//153 +f 154//154 146//146 147//147 +f 147//147 155//155 154//154 +f 155//155 147//147 148//148 +f 156//156 148//148 149//149 +f 148//148 156//156 155//155 +f 157//157 149//149 150//150 +f 149//149 157//157 156//156 +f 158//158 150//150 151//151 +f 150//150 158//158 157//157 +f 152//152 160//159 159//160 +f 160//159 152//152 153//153 +f 153//153 154//154 160//159 +f 161//161 160//159 154//154 +f 154//154 155//155 161//161 +f 162//162 161//161 155//155 +f 163//163 162//162 156//156 +f 155//155 156//156 162//162 +f 164//164 163//163 157//157 +f 156//156 157//157 163//163 +f 165//165 164//164 158//158 +f 157//157 158//158 164//164 +f 159//160 167//166 166//167 +f 167//166 159//160 160//159 +f 160//159 161//161 167//166 +f 168//168 167//166 161//161 +f 161//161 162//162 168//168 +f 169//169 168//168 162//162 +f 170//170 169//169 163//163 +f 162//162 163//163 169//169 +f 171//171 170//170 164//164 +f 163//163 164//164 170//170 +f 172//172 171//171 165//165 +f 164//164 165//165 171//171 +f 166//167 174//173 173//174 +f 174//173 166//167 167//166 +f 167//166 168//168 174//173 +f 175//175 174//173 168//168 +f 168//168 169//169 175//175 +f 176//176 175//175 169//169 +f 177//177 176//176 170//170 +f 169//169 170//170 176//176 +f 178//178 177//177 171//171 +f 170//170 171//171 177//177 +f 179//179 178//178 172//172 +f 171//171 172//172 178//178 +f 173//174 174//173 180//180 +f 181//181 180//180 174//173 +f 174//173 175//175 181//181 +f 182//182 181//181 175//175 +f 175//175 183//183 182//182 +f 183//183 175//175 176//176 +f 184//184 176//176 177//177 +f 176//176 184//184 183//183 +f 185//185 177//177 178//178 +f 177//177 185//185 184//184 +f 186//186 178//178 179//179 +f 178//178 186//186 185//185 +f 180//180 181//181 187//187 +f 188//188 187//187 181//181 +f 181//181 189//189 188//188 +f 189//189 181//181 182//182 +f 182//182 190//190 189//189 +f 190//190 182//182 183//183 +f 191//191 183//183 184//184 +f 183//183 191//191 190//190 +f 192//192 184//184 185//185 +f 184//184 192//192 191//191 +f 193//193 185//185 186//186 +f 185//185 193//193 192//192 +f 187//187 188//188 26//26 +f 29//29 26//26 188//188 +f 188//188 189//189 29//29 +f 30//30 29//29 189//189 +f 189//189 32//32 30//30 +f 32//32 189//189 190//190 +f 35//35 190//190 191//191 +f 190//190 35//35 32//32 +f 37//37 191//191 192//192 +f 191//191 37//37 35//35 +f 39//39 192//192 193//193 +f 192//192 39//39 37//37 +f 194//194 38//38 195//195 +f 39//39 195//195 38//38 +f 196//196 194//194 197//197 +f 195//195 197//197 194//194 +f 198//198 196//196 199//199 +f 197//197 199//199 196//196 +f 200//200 198//198 201//201 +f 199//199 201//201 198//198 +f 202//202 200//200 203//203 +f 201//201 203//203 200//200 +f 204//204 202//202 205//205 +f 203//203 205//205 202//202 +f 206//206 46//46 194//194 +f 38//38 194//194 46//46 +f 207//207 206//206 196//196 +f 194//194 196//196 206//206 +f 208//208 207//207 198//198 +f 196//196 198//198 207//207 +f 209//209 208//208 200//200 +f 198//198 200//200 208//208 +f 210//210 209//209 202//202 +f 200//200 202//202 209//209 +f 211//211 210//210 204//204 +f 202//202 204//204 210//210 +f 212//212 53//53 206//206 +f 46//46 206//206 53//53 +f 213//213 212//212 207//207 +f 206//206 207//207 212//212 +f 214//214 213//213 208//208 +f 207//207 208//208 213//213 +f 215//215 214//214 209//209 +f 208//208 209//209 214//214 +f 216//216 215//215 210//210 +f 209//209 210//210 215//215 +f 217//217 216//216 211//211 +f 210//210 211//211 216//216 +f 218//218 53//53 212//212 +f 53//53 218//218 60//60 +f 219//219 212//212 213//213 +f 212//212 219//219 218//218 +f 220//220 213//213 214//214 +f 213//213 220//220 219//219 +f 221//221 214//214 215//215 +f 214//214 221//221 220//220 +f 222//222 215//215 216//216 +f 215//215 222//222 221//221 +f 223//223 216//216 217//217 +f 216//216 223//223 222//222 +f 224//224 60//60 218//218 +f 60//60 224//224 67//67 +f 225//225 218//218 219//219 +f 218//218 225//225 224//224 +f 226//226 219//219 220//220 +f 219//219 226//226 225//225 +f 227//227 220//220 221//221 +f 220//220 227//227 226//226 +f 228//228 221//221 222//222 +f 221//221 228//228 227//227 +f 229//229 222//222 223//223 +f 222//222 229//229 228//228 +f 230//230 67//67 224//224 +f 67//67 230//230 74//74 +f 231//231 224//224 225//225 +f 224//224 231//231 230//230 +f 232//232 225//225 226//226 +f 225//225 232//232 231//231 +f 233//233 226//226 227//227 +f 226//226 233//233 232//232 +f 234//234 227//227 228//228 +f 227//227 234//234 233//233 +f 235//235 228//228 229//229 +f 228//228 235//235 234//234 +f 236//236 81//81 230//230 +f 74//74 230//230 81//81 +f 237//237 236//236 231//231 +f 230//230 231//231 236//236 +f 238//238 237//237 232//232 +f 231//231 232//232 237//237 +f 239//239 238//238 233//233 +f 232//232 233//233 238//238 +f 240//240 239//239 234//234 +f 233//233 234//234 239//239 +f 241//241 240//240 235//235 +f 234//234 235//235 240//240 +f 242//242 88//88 236//236 +f 81//81 236//236 88//88 +f 243//243 242//242 237//237 +f 236//236 237//237 242//242 +f 244//244 243//243 238//238 +f 237//237 238//238 243//243 +f 245//245 244//244 239//239 +f 238//238 239//239 244//244 +f 246//246 245//245 240//240 +f 239//239 240//240 245//245 +f 247//247 246//246 241//241 +f 240//240 241//241 246//246 +f 248//248 95//95 242//242 +f 88//88 242//242 95//95 +f 249//249 248//248 243//243 +f 242//242 243//243 248//248 +f 250//250 249//249 244//244 +f 243//243 244//244 249//249 +f 251//251 250//250 245//245 +f 244//244 245//245 250//250 +f 252//252 251//251 246//246 +f 245//245 246//246 251//251 +f 253//253 252//252 247//247 +f 246//246 247//247 252//252 +f 254//254 95//95 248//248 +f 95//95 254//254 102//102 +f 255//255 248//248 249//249 +f 248//248 255//255 254//254 +f 256//256 249//249 250//250 +f 249//249 256//256 255//255 +f 257//257 250//250 251//251 +f 250//250 257//257 256//256 +f 258//258 251//251 252//252 +f 251//251 258//258 257//257 +f 259//259 252//252 253//253 +f 252//252 259//259 258//258 +f 260//260 102//102 254//254 +f 102//102 260//260 109//109 +f 261//261 254//254 255//255 +f 254//254 261//261 260//260 +f 262//262 255//255 256//256 +f 255//255 262//262 261//261 +f 263//263 256//256 257//257 +f 256//256 263//263 262//262 +f 264//264 257//257 258//258 +f 257//257 264//264 263//263 +f 265//265 258//258 259//259 +f 258//258 265//265 264//264 +f 266//266 109//109 260//260 +f 109//109 266//266 116//116 +f 267//267 260//260 261//261 +f 260//260 267//267 266//266 +f 268//268 261//261 262//262 +f 261//261 268//268 267//267 +f 269//269 262//262 263//263 +f 262//262 269//269 268//268 +f 270//270 263//263 264//264 +f 263//263 270//270 269//269 +f 271//271 264//264 265//265 +f 264//264 271//271 270//270 +f 272//272 123//123 266//266 +f 116//116 266//266 123//123 +f 273//273 272//272 267//267 +f 266//266 267//267 272//272 +f 274//274 273//273 268//268 +f 267//267 268//268 273//273 +f 275//275 274//274 269//269 +f 268//268 269//269 274//274 +f 276//276 275//275 270//270 +f 269//269 270//270 275//275 +f 277//277 276//276 271//271 +f 270//270 271//271 276//276 +f 278//278 130//130 272//272 +f 123//123 272//272 130//130 +f 279//279 278//278 273//273 +f 272//272 273//273 278//278 +f 280//280 279//279 274//274 +f 273//273 274//274 279//279 +f 281//281 280//280 275//275 +f 274//274 275//275 280//280 +f 282//282 281//281 276//276 +f 275//275 276//276 281//281 +f 283//283 282//282 277//277 +f 276//276 277//277 282//282 +f 284//284 137//137 278//278 +f 130//130 278//278 137//137 +f 285//285 284//284 279//279 +f 278//278 279//279 284//284 +f 286//286 285//285 280//280 +f 279//279 280//280 285//285 +f 287//287 286//286 281//281 +f 280//280 281//281 286//286 +f 288//288 287//287 282//282 +f 281//281 282//282 287//287 +f 289//289 288//288 283//283 +f 282//282 283//283 288//288 +f 290//290 137//137 284//284 +f 137//137 290//290 144//144 +f 291//291 284//284 285//285 +f 284//284 291//291 290//290 +f 292//292 285//285 286//286 +f 285//285 292//292 291//291 +f 293//293 286//286 287//287 +f 286//286 293//293 292//292 +f 294//294 287//287 288//288 +f 287//287 294//294 293//293 +f 295//295 288//288 289//289 +f 288//288 295//295 294//294 +f 296//296 144//144 290//290 +f 144//144 296//296 151//151 +f 297//297 290//290 291//291 +f 290//290 297//297 296//296 +f 298//298 291//291 292//292 +f 291//291 298//298 297//297 +f 299//299 292//292 293//293 +f 292//292 299//299 298//298 +f 300//300 293//293 294//294 +f 293//293 300//300 299//299 +f 301//301 294//294 295//295 +f 294//294 301//301 300//300 +f 302//302 151//151 296//296 +f 151//151 302//302 158//158 +f 303//303 296//296 297//297 +f 296//296 303//303 302//302 +f 304//304 297//297 298//298 +f 297//297 304//304 303//303 +f 305//305 298//298 299//299 +f 298//298 305//305 304//304 +f 306//306 299//299 300//300 +f 299//299 306//306 305//305 +f 307//307 300//300 301//301 +f 300//300 307//307 306//306 +f 308//308 165//165 302//302 +f 158//158 302//302 165//165 +f 309//309 308//308 303//303 +f 302//302 303//303 308//308 +f 310//310 309//309 304//304 +f 303//303 304//304 309//309 +f 311//311 310//310 305//305 +f 304//304 305//305 310//310 +f 312//312 311//311 306//306 +f 305//305 306//306 311//311 +f 313//313 312//312 307//307 +f 306//306 307//307 312//312 +f 314//314 172//172 308//308 +f 165//165 308//308 172//172 +f 315//315 314//314 309//309 +f 308//308 309//309 314//314 +f 316//316 315//315 310//310 +f 309//309 310//310 315//315 +f 317//317 316//316 311//311 +f 310//310 311//311 316//316 +f 318//318 317//317 312//312 +f 311//311 312//312 317//317 +f 319//319 318//318 313//313 +f 312//312 313//313 318//318 +f 320//320 179//179 314//314 +f 172//172 314//314 179//179 +f 321//321 320//320 315//315 +f 314//314 315//315 320//320 +f 322//322 321//321 316//316 +f 315//315 316//316 321//321 +f 323//323 322//322 317//317 +f 316//316 317//317 322//322 +f 324//324 323//323 318//318 +f 317//317 318//318 323//323 +f 325//325 324//324 319//319 +f 318//318 319//319 324//324 +f 326//326 179//179 320//320 +f 179//179 326//326 186//186 +f 327//327 320//320 321//321 +f 320//320 327//327 326//326 +f 328//328 321//321 322//322 +f 321//321 328//328 327//327 +f 329//329 322//322 323//323 +f 322//322 329//329 328//328 +f 330//330 323//323 324//324 +f 323//323 330//330 329//329 +f 331//331 324//324 325//325 +f 324//324 331//331 330//330 +f 332//332 186//186 326//326 +f 186//186 332//332 193//193 +f 333//333 326//326 327//327 +f 326//326 333//333 332//332 +f 334//334 327//327 328//328 +f 327//327 334//334 333//333 +f 335//335 328//328 329//329 +f 328//328 335//335 334//334 +f 336//336 329//329 330//330 +f 329//329 336//336 335//335 +f 337//337 330//330 331//331 +f 330//330 337//337 336//336 +f 195//195 193//193 332//332 +f 193//193 195//195 39//39 +f 197//197 332//332 333//333 +f 332//332 197//197 195//195 +f 199//199 333//333 334//334 +f 333//333 199//199 197//197 +f 201//201 334//334 335//335 +f 334//334 201//201 199//199 +f 203//203 335//335 336//336 +f 335//335 203//203 201//201 +f 205//205 336//336 337//337 +f 336//336 205//205 203//203 +f 338//338 205//205 339//339 +f 205//205 338//338 204//204 +f 340//340 339//339 341//341 +f 339//339 340//340 338//338 +f 342//342 341//341 343//343 +f 341//341 342//342 340//340 +f 344//344 343//343 345//345 +f 343//343 344//344 342//342 +f 346//346 345//345 347//347 +f 345//345 346//346 344//344 +f 348//348 347//347 349//349 +f 347//347 348//348 346//346 +f 350//350 204//204 338//338 +f 204//204 350//350 211//211 +f 351//351 338//338 340//340 +f 338//338 351//351 350//350 +f 352//352 340//340 342//342 +f 340//340 352//352 351//351 +f 353//353 342//342 344//344 +f 342//342 353//353 352//352 +f 354//354 344//344 346//346 +f 344//344 354//354 353//353 +f 355//355 346//346 348//348 +f 346//346 355//355 354//354 +f 356//356 211//211 350//350 +f 211//211 356//356 217//217 +f 357//357 350//350 351//351 +f 350//350 357//357 356//356 +f 358//358 351//351 352//352 +f 351//351 358//358 357//357 +f 359//359 352//352 353//353 +f 352//352 359//359 358//358 +f 360//360 353//353 354//354 +f 353//353 360//360 359//359 +f 361//361 354//354 355//355 +f 354//354 361//361 360//360 +f 362//362 223//223 356//356 +f 217//217 356//356 223//223 +f 363//363 362//362 357//357 +f 356//356 357//357 362//362 +f 364//364 363//363 358//358 +f 357//357 358//358 363//363 +f 365//365 364//364 359//359 +f 358//358 359//359 364//364 +f 366//366 365//365 360//360 +f 359//359 360//360 365//365 +f 367//367 366//366 361//361 +f 360//360 361//361 366//366 +f 368//368 229//229 362//362 +f 223//223 362//362 229//229 +f 369//369 368//368 363//363 +f 362//362 363//363 368//368 +f 370//370 369//369 364//364 +f 363//363 364//364 369//369 +f 371//371 370//370 365//365 +f 364//364 365//365 370//370 +f 372//372 371//371 366//366 +f 365//365 366//366 371//371 +f 373//373 372//372 367//367 +f 366//366 367//367 372//372 +f 374//374 235//235 368//368 +f 229//229 368//368 235//235 +f 375//375 374//374 369//369 +f 368//368 369//369 374//374 +f 376//376 375//375 370//370 +f 369//369 370//370 375//375 +f 377//377 376//376 371//371 +f 370//370 371//371 376//376 +f 378//378 377//377 372//372 +f 371//371 372//372 377//377 +f 379//379 378//378 373//373 +f 372//372 373//373 378//378 +f 380//380 235//235 374//374 +f 235//235 380//380 241//241 +f 381//381 374//374 375//375 +f 374//374 381//381 380//380 +f 382//382 375//375 376//376 +f 375//375 382//382 381//381 +f 383//383 376//376 377//377 +f 376//376 383//383 382//382 +f 384//384 377//377 378//378 +f 377//377 384//384 383//383 +f 385//385 378//378 379//379 +f 378//378 385//385 384//384 +f 386//386 241//241 380//380 +f 241//241 386//386 247//247 +f 387//387 380//380 381//381 +f 380//380 387//387 386//386 +f 388//388 381//381 382//382 +f 381//381 388//388 387//387 +f 389//389 382//382 383//383 +f 382//382 389//389 388//388 +f 390//390 383//383 384//384 +f 383//383 390//390 389//389 +f 391//391 384//384 385//385 +f 384//384 391//391 390//390 +f 392//392 247//247 386//386 +f 247//247 392//392 253//253 +f 393//393 386//386 387//387 +f 386//386 393//393 392//392 +f 394//394 387//387 388//388 +f 387//387 394//394 393//393 +f 395//395 388//388 389//389 +f 388//388 395//395 394//394 +f 396//396 389//389 390//390 +f 389//389 396//396 395//395 +f 397//397 390//390 391//391 +f 390//390 397//397 396//396 +f 398//398 259//259 392//392 +f 253//253 392//392 259//259 +f 399//399 398//398 393//393 +f 392//392 393//393 398//398 +f 400//400 399//399 394//394 +f 393//393 394//394 399//399 +f 401//401 400//400 395//395 +f 394//394 395//395 400//400 +f 402//402 401//401 396//396 +f 395//395 396//396 401//401 +f 403//403 402//402 397//397 +f 396//396 397//397 402//402 +f 404//404 265//265 398//398 +f 259//259 398//398 265//265 +f 405//405 404//404 399//399 +f 398//398 399//399 404//404 +f 406//406 405//405 400//400 +f 399//399 400//400 405//405 +f 407//407 406//406 401//401 +f 400//400 401//401 406//406 +f 408//408 407//407 402//402 +f 401//401 402//402 407//407 +f 409//409 408//408 403//403 +f 402//402 403//403 408//408 +f 410//410 271//271 404//404 +f 265//265 404//404 271//271 +f 411//411 410//410 405//405 +f 404//404 405//405 410//410 +f 412//412 411//411 406//406 +f 405//405 406//406 411//411 +f 413//413 412//412 407//407 +f 406//406 407//407 412//412 +f 414//414 413//413 408//408 +f 407//407 408//408 413//413 +f 415//415 414//414 409//409 +f 408//408 409//409 414//414 +f 416//416 271//271 410//410 +f 271//271 416//416 277//277 +f 417//417 410//410 411//411 +f 410//410 417//417 416//416 +f 418//418 411//411 412//412 +f 411//411 418//418 417//417 +f 419//419 412//412 413//413 +f 412//412 419//419 418//418 +f 420//420 413//413 414//414 +f 413//413 420//420 419//419 +f 421//421 414//414 415//415 +f 414//414 421//421 420//420 +f 422//422 277//277 416//416 +f 277//277 422//422 283//283 +f 423//423 416//416 417//417 +f 416//416 423//423 422//422 +f 424//424 417//417 418//418 +f 417//417 424//424 423//423 +f 425//425 418//418 419//419 +f 418//418 425//425 424//424 +f 426//426 419//419 420//420 +f 419//419 426//426 425//425 +f 427//427 420//420 421//421 +f 420//420 427//427 426//426 +f 428//428 283//283 422//422 +f 283//283 428//428 289//289 +f 429//429 422//422 423//423 +f 422//422 429//429 428//428 +f 430//430 423//423 424//424 +f 423//423 430//430 429//429 +f 431//431 424//424 425//425 +f 424//424 431//431 430//430 +f 432//432 425//425 426//426 +f 425//425 432//432 431//431 +f 433//433 426//426 427//427 +f 426//426 433//433 432//432 +f 434//434 295//295 428//428 +f 289//289 428//428 295//295 +f 435//435 434//434 429//429 +f 428//428 429//429 434//434 +f 436//436 435//435 430//430 +f 429//429 430//430 435//435 +f 437//437 436//436 431//431 +f 430//430 431//431 436//436 +f 438//438 437//437 432//432 +f 431//431 432//432 437//437 +f 439//439 438//438 433//433 +f 432//432 433//433 438//438 +f 440//440 301//301 434//434 +f 295//295 434//434 301//301 +f 441//441 440//440 435//435 +f 434//434 435//435 440//440 +f 442//442 441//441 436//436 +f 435//435 436//436 441//441 +f 443//443 442//442 437//437 +f 436//436 437//437 442//442 +f 444//444 443//443 438//438 +f 437//437 438//438 443//443 +f 445//445 444//444 439//439 +f 438//438 439//439 444//444 +f 446//446 307//307 440//440 +f 301//301 440//440 307//307 +f 447//447 446//446 441//441 +f 440//440 441//441 446//446 +f 448//448 447//447 442//442 +f 441//441 442//442 447//447 +f 449//449 448//448 443//443 +f 442//442 443//443 448//448 +f 450//450 449//449 444//444 +f 443//443 444//444 449//449 +f 451//451 450//450 445//445 +f 444//444 445//445 450//450 +f 452//452 307//307 446//446 +f 307//307 452//452 313//313 +f 453//453 446//446 447//447 +f 446//446 453//453 452//452 +f 454//454 447//447 448//448 +f 447//447 454//454 453//453 +f 455//455 448//448 449//449 +f 448//448 455//455 454//454 +f 456//456 449//449 450//450 +f 449//449 456//456 455//455 +f 457//457 450//450 451//451 +f 450//450 457//457 456//456 +f 458//458 313//313 452//452 +f 313//313 458//458 319//319 +f 459//459 452//452 453//453 +f 452//452 459//459 458//458 +f 460//460 453//453 454//454 +f 453//453 460//460 459//459 +f 461//461 454//454 455//455 +f 454//454 461//461 460//460 +f 462//462 455//455 456//456 +f 455//455 462//462 461//461 +f 463//463 456//456 457//457 +f 456//456 463//463 462//462 +f 464//464 319//319 458//458 +f 319//319 464//464 325//325 +f 465//465 458//458 459//459 +f 458//458 465//465 464//464 +f 466//466 459//459 460//460 +f 459//459 466//466 465//465 +f 467//467 460//460 461//461 +f 460//460 467//467 466//466 +f 468//468 461//461 462//462 +f 461//461 468//468 467//467 +f 469//469 462//462 463//463 +f 462//462 469//469 468//468 +f 470//470 331//331 464//464 +f 325//325 464//464 331//331 +f 471//471 470//470 465//465 +f 464//464 465//465 470//470 +f 472//472 471//471 466//466 +f 465//465 466//466 471//471 +f 473//473 472//472 467//467 +f 466//466 467//467 472//472 +f 474//474 473//473 468//468 +f 467//467 468//468 473//473 +f 475//475 474//474 469//469 +f 468//468 469//469 474//474 +f 476//476 337//337 470//470 +f 331//331 470//470 337//337 +f 477//477 476//476 471//471 +f 470//470 471//471 476//476 +f 478//478 477//477 472//472 +f 471//471 472//472 477//477 +f 479//479 478//478 473//473 +f 472//472 473//473 478//478 +f 480//480 479//479 474//474 +f 473//473 474//474 479//479 +f 481//481 480//480 475//475 +f 474//474 475//475 480//480 +f 339//339 205//205 476//476 +f 337//337 476//476 205//205 +f 341//341 339//339 477//477 +f 476//476 477//477 339//339 +f 343//343 341//341 478//478 +f 477//477 478//478 341//341 +f 345//345 343//343 479//479 +f 478//478 479//479 343//343 +f 347//347 345//345 480//480 +f 479//479 480//480 345//345 +f 349//349 347//347 481//481 +f 480//480 481//481 347//347 +f 1//1 482//482 3//2 +f 483//483 3//2 482//482 +f 482//482 484//484 483//483 +f 485//485 483//483 484//484 +f 484//484 486//486 485//485 +f 487//487 485//485 486//486 +f 486//486 488//488 487//487 +f 489//489 487//487 488//488 +f 488//488 349//349 489//489 +f 481//481 489//489 349//349 +f 3//2 483//483 4//4 +f 490//490 4//4 483//483 +f 483//483 485//485 490//490 +f 491//491 490//490 485//485 +f 485//485 487//487 491//491 +f 492//492 491//491 487//487 +f 487//487 489//489 492//492 +f 493//493 492//492 489//489 +f 489//489 481//481 493//493 +f 475//475 493//493 481//481 +f 4//4 490//490 5//5 +f 494//494 5//5 490//490 +f 490//490 491//491 494//494 +f 495//495 494//494 491//491 +f 491//491 492//492 495//495 +f 496//496 495//495 492//492 +f 492//492 493//493 496//496 +f 497//497 496//496 493//493 +f 493//493 475//475 497//497 +f 469//469 497//497 475//475 +f 5//5 498//498 6//6 +f 498//498 5//5 494//494 +f 494//494 499//499 498//498 +f 499//499 494//494 495//495 +f 495//495 500//500 499//499 +f 500//500 495//495 496//496 +f 496//496 501//501 500//500 +f 501//501 496//496 497//497 +f 497//497 463//463 501//501 +f 463//463 497//497 469//469 +f 6//6 502//502 7//7 +f 502//502 6//6 498//498 +f 498//498 503//503 502//502 +f 503//503 498//498 499//499 +f 499//499 504//504 503//503 +f 504//504 499//499 500//500 +f 500//500 505//505 504//504 +f 505//505 500//500 501//501 +f 501//501 457//457 505//505 +f 457//457 501//501 463//463 +f 7//7 506//506 8//8 +f 506//506 7//7 502//502 +f 502//502 507//507 506//506 +f 507//507 502//502 503//503 +f 503//503 508//508 507//507 +f 508//508 503//503 504//504 +f 504//504 509//509 508//508 +f 509//509 504//504 505//505 +f 505//505 451//451 509//509 +f 451//451 505//505 457//457 +f 8//8 506//506 9//9 +f 510//510 9//9 506//506 +f 506//506 507//507 510//510 +f 511//511 510//510 507//507 +f 507//507 508//508 511//511 +f 512//512 511//511 508//508 +f 508//508 509//509 512//512 +f 513//513 512//512 509//509 +f 509//509 451//451 513//513 +f 445//445 513//513 451//451 +f 9//9 510//510 10//10 +f 514//514 10//10 510//510 +f 510//510 511//511 514//514 +f 515//515 514//514 511//511 +f 511//511 512//512 515//515 +f 516//516 515//515 512//512 +f 512//512 513//513 516//516 +f 517//517 516//516 513//513 +f 513//513 445//445 517//517 +f 439//439 517//517 445//445 +f 10//10 514//514 11//11 +f 518//518 11//11 514//514 +f 514//514 515//515 518//518 +f 519//519 518//518 515//515 +f 515//515 516//516 519//519 +f 520//520 519//519 516//516 +f 516//516 517//517 520//520 +f 521//521 520//520 517//517 +f 517//517 439//439 521//521 +f 433//433 521//521 439//439 +f 11//11 522//522 12//12 +f 522//522 11//11 518//518 +f 518//518 523//523 522//522 +f 523//523 518//518 519//519 +f 519//519 524//524 523//523 +f 524//524 519//519 520//520 +f 520//520 525//525 524//524 +f 525//525 520//520 521//521 +f 521//521 427//427 525//525 +f 427//427 521//521 433//433 +f 12//12 526//526 13//13 +f 526//526 12//12 522//522 +f 522//522 527//527 526//526 +f 527//527 522//522 523//523 +f 523//523 528//528 527//527 +f 528//528 523//523 524//524 +f 524//524 529//529 528//528 +f 529//529 524//524 525//525 +f 525//525 421//421 529//529 +f 421//421 525//525 427//427 +f 13//13 530//530 14//14 +f 530//530 13//13 526//526 +f 526//526 531//531 530//530 +f 531//531 526//526 527//527 +f 527//527 532//532 531//531 +f 532//532 527//527 528//528 +f 528//528 533//533 532//532 +f 533//533 528//528 529//529 +f 529//529 415//415 533//533 +f 415//415 529//529 421//421 +f 14//14 530//530 15//15 +f 534//534 15//15 530//530 +f 530//530 531//531 534//534 +f 535//535 534//534 531//531 +f 531//531 532//532 535//535 +f 536//536 535//535 532//532 +f 532//532 533//533 536//536 +f 537//537 536//536 533//533 +f 533//533 415//415 537//537 +f 409//409 537//537 415//415 +f 15//15 534//534 16//16 +f 538//538 16//16 534//534 +f 534//534 535//535 538//538 +f 539//539 538//538 535//535 +f 535//535 536//536 539//539 +f 540//540 539//539 536//536 +f 536//536 537//537 540//540 +f 541//541 540//540 537//537 +f 537//537 409//409 541//541 +f 403//403 541//541 409//409 +f 16//16 538//538 17//17 +f 542//542 17//17 538//538 +f 538//538 539//539 542//542 +f 543//543 542//542 539//539 +f 539//539 540//540 543//543 +f 544//544 543//543 540//540 +f 540//540 541//541 544//544 +f 545//545 544//544 541//541 +f 541//541 403//403 545//545 +f 397//397 545//545 403//403 +f 17//17 546//546 18//18 +f 546//546 17//17 542//542 +f 542//542 547//547 546//546 +f 547//547 542//542 543//543 +f 543//543 548//548 547//547 +f 548//548 543//543 544//544 +f 544//544 549//549 548//548 +f 549//549 544//544 545//545 +f 545//545 391//391 549//549 +f 391//391 545//545 397//397 +f 18//18 550//550 19//19 +f 550//550 18//18 546//546 +f 546//546 551//551 550//550 +f 551//551 546//546 547//547 +f 547//547 552//552 551//551 +f 552//552 547//547 548//548 +f 548//548 553//553 552//552 +f 553//553 548//548 549//549 +f 549//549 385//385 553//553 +f 385//385 549//549 391//391 +f 19//19 554//554 20//20 +f 554//554 19//19 550//550 +f 550//550 555//555 554//554 +f 555//555 550//550 551//551 +f 551//551 556//556 555//555 +f 556//556 551//551 552//552 +f 552//552 557//557 556//556 +f 557//557 552//552 553//553 +f 553//553 379//379 557//557 +f 379//379 553//553 385//385 +f 20//20 554//554 21//21 +f 558//558 21//21 554//554 +f 554//554 555//555 558//558 +f 559//559 558//558 555//555 +f 555//555 556//556 559//559 +f 560//560 559//559 556//556 +f 556//556 557//557 560//560 +f 561//561 560//560 557//557 +f 557//557 379//379 561//561 +f 373//373 561//561 379//379 +f 21//21 558//558 22//22 +f 562//562 22//22 558//558 +f 558//558 559//559 562//562 +f 563//563 562//562 559//559 +f 559//559 560//560 563//563 +f 564//564 563//563 560//560 +f 560//560 561//561 564//564 +f 565//565 564//564 561//561 +f 561//561 373//373 565//565 +f 367//367 565//565 373//373 +f 22//22 562//562 23//23 +f 566//566 23//23 562//562 +f 562//562 563//563 566//566 +f 567//567 566//566 563//563 +f 563//563 564//564 567//567 +f 568//568 567//567 564//564 +f 564//564 565//565 568//568 +f 569//569 568//568 565//565 +f 565//565 367//367 569//569 +f 361//361 569//569 367//367 +f 23//23 570//570 24//24 +f 570//570 23//23 566//566 +f 566//566 571//571 570//570 +f 571//571 566//566 567//567 +f 567//567 572//572 571//571 +f 572//572 567//567 568//568 +f 568//568 573//573 572//572 +f 573//573 568//568 569//569 +f 569//569 355//355 573//573 +f 355//355 569//569 361//361 +f 24//24 574//574 25//25 +f 574//574 24//24 570//570 +f 570//570 575//575 574//574 +f 575//575 570//570 571//571 +f 571//571 576//576 575//575 +f 576//576 571//571 572//572 +f 572//572 577//577 576//576 +f 577//577 572//572 573//573 +f 573//573 348//348 577//577 +f 348//348 573//573 355//355 +f 25//25 482//482 1//1 +f 482//482 25//25 574//574 +f 574//574 484//484 482//482 +f 484//484 574//574 575//575 +f 575//575 486//486 484//484 +f 486//486 575//575 576//576 +f 576//576 488//488 486//486 +f 488//488 576//576 577//577 +f 577//577 349//349 488//488 +f 349//349 577//577 348//348 +s 2 +f 578//578 580//579 579//580 +f 581//581 579//580 580//579 +f 582//582 583//583 578//578 +f 580//579 578//578 583//583 +f 584//584 585//585 582//582 +f 583//583 582//582 585//585 +f 586//586 585//585 584//584 +f 585//585 586//586 587//587 +f 588//588 587//587 586//586 +f 587//587 588//588 589//589 +f 590//590 589//589 588//588 +f 589//589 590//590 591//591 +f 592//592 593//593 590//590 +f 591//591 590//590 593//593 +f 594//594 595//595 592//592 +f 593//593 592//592 595//595 +f 596//596 597//597 594//594 +f 595//595 594//594 597//597 +f 598//598 597//597 596//596 +f 597//597 598//598 599//599 +f 600//600 599//599 598//598 +f 599//599 600//600 601//601 +f 602//602 601//601 600//600 +f 601//601 602//602 603//603 +f 604//604 605//605 602//602 +f 603//603 602//602 605//605 +f 606//606 607//607 604//604 +f 605//605 604//604 607//607 +f 608//608 609//609 606//606 +f 607//607 606//606 609//609 +f 610//610 609//609 608//608 +f 609//609 610//610 611//611 +f 612//612 611//611 610//610 +f 611//611 612//612 613//613 +f 614//614 613//613 612//612 +f 613//613 614//614 615//615 +f 616//616 617//617 614//614 +f 615//615 614//614 617//617 +f 618//618 619//619 616//616 +f 617//617 616//616 619//619 +f 620//620 621//621 618//618 +f 619//619 618//618 621//621 +f 622//622 621//621 620//620 +f 621//621 622//622 623//623 +f 624//624 623//623 622//622 +f 623//623 624//624 625//625 +f 579//580 625//625 624//624 +f 625//625 579//580 581//581 +f 626//626 578//578 627//627 +f 579//580 627//627 578//578 +f 628//628 626//626 629//629 +f 627//627 629//629 626//626 +f 630//630 628//628 631//631 +f 629//629 631//631 628//628 +f 632//632 630//630 633//633 +f 631//631 633//633 630//630 +f 634//634 632//632 635//635 +f 633//633 635//635 632//632 +f 636//636 634//634 637//637 +f 635//635 637//637 634//634 +f 638//638 582//582 626//626 +f 578//578 626//626 582//582 +f 639//639 638//638 628//628 +f 626//626 628//628 638//638 +f 640//640 639//639 630//630 +f 628//628 630//630 639//639 +f 641//641 640//640 632//632 +f 630//630 632//632 640//640 +f 642//642 641//641 634//634 +f 632//632 634//634 641//641 +f 643//643 642//642 636//636 +f 634//634 636//636 642//642 +f 644//644 584//584 638//638 +f 582//582 638//638 584//584 +f 645//645 644//644 639//639 +f 638//638 639//639 644//644 +f 646//646 645//645 640//640 +f 639//639 640//640 645//645 +f 647//647 646//646 641//641 +f 640//640 641//641 646//646 +f 648//648 647//647 642//642 +f 641//641 642//642 647//647 +f 649//649 648//648 643//643 +f 642//642 643//643 648//648 +f 650//650 584//584 644//644 +f 584//584 650//650 586//586 +f 651//651 644//644 645//645 +f 644//644 651//651 650//650 +f 652//652 645//645 646//646 +f 645//645 652//652 651//651 +f 653//653 646//646 647//647 +f 646//646 653//653 652//652 +f 654//654 647//647 648//648 +f 647//647 654//654 653//653 +f 655//655 648//648 649//649 +f 648//648 655//655 654//654 +f 656//656 586//586 650//650 +f 586//586 656//656 588//588 +f 657//657 650//650 651//651 +f 650//650 657//657 656//656 +f 658//658 651//651 652//652 +f 651//651 658//658 657//657 +f 659//659 652//652 653//653 +f 652//652 659//659 658//658 +f 660//660 653//653 654//654 +f 653//653 660//660 659//659 +f 661//661 654//654 655//655 +f 654//654 661//661 660//660 +f 662//662 588//588 656//656 +f 588//588 662//662 590//590 +f 663//663 656//656 657//657 +f 656//656 663//663 662//662 +f 664//664 657//657 658//658 +f 657//657 664//664 663//663 +f 665//665 658//658 659//659 +f 658//658 665//665 664//664 +f 666//666 659//659 660//660 +f 659//659 666//666 665//665 +f 667//667 660//660 661//661 +f 660//660 667//667 666//666 +f 668//668 592//592 662//662 +f 590//590 662//662 592//592 +f 669//669 668//668 663//663 +f 662//662 663//663 668//668 +f 670//670 669//669 664//664 +f 663//663 664//664 669//669 +f 671//671 670//670 665//665 +f 664//664 665//665 670//670 +f 672//672 671//671 666//666 +f 665//665 666//666 671//671 +f 673//673 672//672 667//667 +f 666//666 667//667 672//672 +f 674//674 594//594 668//668 +f 592//592 668//668 594//594 +f 675//675 674//674 669//669 +f 668//668 669//669 674//674 +f 676//676 675//675 670//670 +f 669//669 670//670 675//675 +f 677//677 676//676 671//671 +f 670//670 671//671 676//676 +f 678//678 677//677 672//672 +f 671//671 672//672 677//677 +f 679//679 678//678 673//673 +f 672//672 673//673 678//678 +f 680//680 596//596 674//674 +f 594//594 674//674 596//596 +f 681//681 680//680 675//675 +f 674//674 675//675 680//680 +f 682//682 681//681 676//676 +f 675//675 676//676 681//681 +f 683//683 682//682 677//677 +f 676//676 677//677 682//682 +f 684//684 683//683 678//678 +f 677//677 678//678 683//683 +f 685//685 684//684 679//679 +f 678//678 679//679 684//684 +f 686//686 596//596 680//680 +f 596//596 686//686 598//598 +f 687//687 680//680 681//681 +f 680//680 687//687 686//686 +f 688//688 681//681 682//682 +f 681//681 688//688 687//687 +f 689//689 682//682 683//683 +f 682//682 689//689 688//688 +f 690//690 683//683 684//684 +f 683//683 690//690 689//689 +f 691//691 684//684 685//685 +f 684//684 691//691 690//690 +f 692//692 598//598 686//686 +f 598//598 692//692 600//600 +f 693//693 686//686 687//687 +f 686//686 693//693 692//692 +f 694//694 687//687 688//688 +f 687//687 694//694 693//693 +f 695//695 688//688 689//689 +f 688//688 695//695 694//694 +f 696//696 689//689 690//690 +f 689//689 696//696 695//695 +f 697//697 690//690 691//691 +f 690//690 697//697 696//696 +f 698//698 600//600 692//692 +f 600//600 698//698 602//602 +f 699//699 692//692 693//693 +f 692//692 699//699 698//698 +f 700//700 693//693 694//694 +f 693//693 700//700 699//699 +f 701//701 694//694 695//695 +f 694//694 701//701 700//700 +f 702//702 695//695 696//696 +f 695//695 702//702 701//701 +f 703//703 696//696 697//697 +f 696//696 703//703 702//702 +f 704//704 604//604 698//698 +f 602//602 698//698 604//604 +f 705//705 704//704 699//699 +f 698//698 699//699 704//704 +f 706//706 705//705 700//700 +f 699//699 700//700 705//705 +f 707//707 706//706 701//701 +f 700//700 701//701 706//706 +f 708//708 707//707 702//702 +f 701//701 702//702 707//707 +f 709//709 708//708 703//703 +f 702//702 703//703 708//708 +f 710//710 606//606 704//704 +f 604//604 704//704 606//606 +f 711//711 710//710 705//705 +f 704//704 705//705 710//710 +f 712//712 711//711 706//706 +f 705//705 706//706 711//711 +f 713//713 712//712 707//707 +f 706//706 707//707 712//712 +f 714//714 713//713 708//708 +f 707//707 708//708 713//713 +f 715//715 714//714 709//709 +f 708//708 709//709 714//714 +f 716//716 608//608 710//710 +f 606//606 710//710 608//608 +f 717//717 716//716 711//711 +f 710//710 711//711 716//716 +f 718//718 717//717 712//712 +f 711//711 712//712 717//717 +f 719//719 718//718 713//713 +f 712//712 713//713 718//718 +f 720//720 719//719 714//714 +f 713//713 714//714 719//719 +f 721//721 720//720 715//715 +f 714//714 715//715 720//720 +f 722//722 608//608 716//716 +f 608//608 722//722 610//610 +f 723//723 716//716 717//717 +f 716//716 723//723 722//722 +f 724//724 717//717 718//718 +f 717//717 724//724 723//723 +f 725//725 718//718 719//719 +f 718//718 725//725 724//724 +f 726//726 719//719 720//720 +f 719//719 726//726 725//725 +f 727//727 720//720 721//721 +f 720//720 727//727 726//726 +f 728//728 610//610 722//722 +f 610//610 728//728 612//612 +f 729//729 722//722 723//723 +f 722//722 729//729 728//728 +f 730//730 723//723 724//724 +f 723//723 730//730 729//729 +f 731//731 724//724 725//725 +f 724//724 731//731 730//730 +f 732//732 725//725 726//726 +f 725//725 732//732 731//731 +f 733//733 726//726 727//727 +f 726//726 733//733 732//732 +f 734//734 612//612 728//728 +f 612//612 734//734 614//614 +f 735//735 728//728 729//729 +f 728//728 735//735 734//734 +f 736//736 729//729 730//730 +f 729//729 736//736 735//735 +f 737//737 730//730 731//731 +f 730//730 737//737 736//736 +f 738//738 731//731 732//732 +f 731//731 738//738 737//737 +f 739//739 732//732 733//733 +f 732//732 739//739 738//738 +f 740//740 616//616 734//734 +f 614//614 734//734 616//616 +f 741//741 740//740 735//735 +f 734//734 735//735 740//740 +f 742//742 741//741 736//736 +f 735//735 736//736 741//741 +f 743//743 742//742 737//737 +f 736//736 737//737 742//742 +f 744//744 743//743 738//738 +f 737//737 738//738 743//743 +f 745//745 744//744 739//739 +f 738//738 739//739 744//744 +f 746//746 618//618 740//740 +f 616//616 740//740 618//618 +f 747//747 746//746 741//741 +f 740//740 741//741 746//746 +f 748//748 747//747 742//742 +f 741//741 742//742 747//747 +f 749//749 748//748 743//743 +f 742//742 743//743 748//748 +f 750//750 749//749 744//744 +f 743//743 744//744 749//749 +f 751//751 750//750 745//745 +f 744//744 745//745 750//750 +f 752//752 620//620 746//746 +f 618//618 746//746 620//620 +f 753//753 752//752 747//747 +f 746//746 747//747 752//752 +f 754//754 753//753 748//748 +f 747//747 748//748 753//753 +f 755//755 754//754 749//749 +f 748//748 749//749 754//754 +f 756//756 755//755 750//750 +f 749//749 750//750 755//755 +f 757//757 756//756 751//751 +f 750//750 751//751 756//756 +f 758//758 620//620 752//752 +f 620//620 758//758 622//622 +f 759//759 752//752 753//753 +f 752//752 759//759 758//758 +f 760//760 753//753 754//754 +f 753//753 760//760 759//759 +f 761//761 754//754 755//755 +f 754//754 761//761 760//760 +f 762//762 755//755 756//756 +f 755//755 762//762 761//761 +f 763//763 756//756 757//757 +f 756//756 763//763 762//762 +f 764//764 622//622 758//758 +f 622//622 764//764 624//624 +f 765//765 758//758 759//759 +f 758//758 765//765 764//764 +f 766//766 759//759 760//760 +f 759//759 766//766 765//765 +f 767//767 760//760 761//761 +f 760//760 767//767 766//766 +f 768//768 761//761 762//762 +f 761//761 768//768 767//767 +f 769//769 762//762 763//763 +f 762//762 769//769 768//768 +f 627//627 624//624 764//764 +f 624//624 627//627 579//580 +f 629//629 764//764 765//765 +f 764//764 629//629 627//627 +f 631//631 765//765 766//766 +f 765//765 631//631 629//629 +f 633//633 766//766 767//767 +f 766//766 633//633 631//631 +f 635//635 767//767 768//768 +f 767//767 635//635 633//633 +f 637//637 768//768 769//769 +f 768//768 637//637 635//635 +s 3 +f 770//770 772//771 771//772 +f 773//773 771//772 772//771 +f 774//774 775//775 770//770 +f 772//771 770//770 775//775 +f 776//776 774//774 777//777 +f 774//774 776//776 775//775 +f 778//778 776//776 779//779 +f 777//777 779//779 776//776 +f 780//780 778//778 781//781 +f 779//779 781//781 778//778 +f 782//782 780//780 783//783 +f 781//781 783//783 780//780 +f 772//771 784//784 773//773 +f 785//785 773//773 784//784 +f 775//775 786//786 772//771 +f 784//784 772//771 786//786 +f 776//776 787//787 775//775 +f 786//786 775//775 787//787 +f 788//788 776//776 778//778 +f 776//776 788//788 787//787 +f 789//789 788//788 780//780 +f 778//778 780//780 788//788 +f 790//790 789//789 782//782 +f 780//780 782//782 789//789 +f 784//784 791//791 785//785 +f 792//792 785//785 791//791 +f 786//786 793//793 784//784 +f 791//791 784//784 793//793 +f 787//787 794//794 786//786 +f 793//793 786//786 794//794 +f 795//795 787//787 788//788 +f 787//787 795//795 794//794 +f 796//796 795//795 789//789 +f 788//788 789//789 795//795 +f 797//797 796//796 790//790 +f 789//789 790//790 796//796 +f 791//791 798//798 792//792 +f 799//799 792//792 798//798 +f 793//793 800//800 791//791 +f 798//798 791//791 800//800 +f 794//794 801//801 793//793 +f 800//800 793//793 801//801 +f 802//802 794//794 795//795 +f 794//794 802//802 801//801 +f 803//803 802//802 796//796 +f 795//795 796//796 802//802 +f 804//804 803//803 797//797 +f 796//796 797//797 803//803 +f 798//798 805//805 799//799 +f 806//806 799//799 805//805 +f 800//800 807//807 798//798 +f 805//805 798//798 807//807 +f 801//801 808//808 800//800 +f 807//807 800//800 808//808 +f 809//809 801//801 802//802 +f 801//801 809//809 808//808 +f 810//810 809//809 803//803 +f 802//802 803//803 809//809 +f 811//811 810//810 804//804 +f 803//803 804//804 810//810 +f 805//805 812//812 806//806 +f 813//813 806//806 812//812 +f 807//807 814//814 805//805 +f 812//812 805//805 814//814 +f 815//815 814//814 808//808 +f 807//807 808//808 814//814 +f 816//816 815//815 809//809 +f 808//808 809//809 815//815 +f 817//817 816//816 810//810 +f 809//809 810//810 816//816 +f 818//818 817//817 811//811 +f 810//810 811//811 817//817 +f 819//819 812//812 820//820 +f 812//812 819//819 813//813 +f 820//820 814//814 821//821 +f 814//814 820//820 812//812 +f 822//822 814//814 815//815 +f 814//814 822//822 821//821 +f 823//823 815//815 816//816 +f 815//815 823//823 822//822 +f 824//824 816//816 817//817 +f 816//816 824//824 823//823 +f 825//825 817//817 818//818 +f 817//817 825//825 824//824 +f 826//826 820//820 827//827 +f 820//820 826//826 819//819 +f 827//827 821//821 828//828 +f 821//821 827//827 820//820 +f 828//828 822//822 829//829 +f 822//822 828//828 821//821 +f 830//830 829//829 823//823 +f 822//822 823//823 829//829 +f 831//831 823//823 824//824 +f 823//823 831//831 830//830 +f 832//832 824//824 825//825 +f 824//824 832//832 831//831 +f 833//833 827//827 834//834 +f 827//827 833//833 826//826 +f 834//834 828//828 835//835 +f 828//828 834//834 827//827 +f 835//835 829//829 836//836 +f 829//829 835//835 828//828 +f 837//837 836//836 830//830 +f 829//829 830//830 836//836 +f 838//838 830//830 831//831 +f 830//830 838//838 837//837 +f 839//839 831//831 832//832 +f 831//831 839//839 838//838 +f 840//840 834//834 841//841 +f 834//834 840//840 833//833 +f 841//841 835//835 842//842 +f 835//835 841//841 834//834 +f 842//842 836//836 843//843 +f 836//836 842//842 835//835 +f 844//844 843//843 837//837 +f 836//836 837//837 843//843 +f 845//845 837//837 838//838 +f 837//837 845//845 844//844 +f 846//846 838//838 839//839 +f 838//838 846//846 845//845 +f 847//847 841//841 848//848 +f 841//841 847//847 840//840 +f 848//848 842//842 849//849 +f 842//842 848//848 841//841 +f 849//849 843//843 850//850 +f 843//843 849//849 842//842 +f 851//851 850//850 844//844 +f 843//843 844//844 850//850 +f 852//852 844//844 845//845 +f 844//844 852//852 851//851 +f 853//853 845//845 846//846 +f 845//845 853//853 852//852 +f 771//772 848//848 770//770 +f 848//848 771//772 847//847 +f 770//770 849//849 774//774 +f 849//849 770//770 848//848 +f 777//777 774//774 850//850 +f 849//849 850//850 774//774 +f 779//779 850//850 851//851 +f 850//850 779//779 777//777 +f 781//781 851//851 852//852 +f 851//851 781//781 779//779 +f 783//783 852//852 853//853 +f 852//852 783//783 781//781 +f 854//854 782//782 855//855 +f 783//783 855//855 782//782 +f 856//856 855//855 857//857 +f 855//855 856//856 854//854 +f 858//858 857//857 859//859 +f 857//857 858//858 856//856 +f 860//860 859//859 861//861 +f 859//859 860//860 858//858 +f 862//862 861//861 863//863 +f 861//861 862//862 860//860 +f 864//864 863//863 865//865 +f 863//863 864//864 862//862 +f 866//866 782//782 854//854 +f 782//782 866//866 790//790 +f 867//867 854//854 856//856 +f 854//854 867//867 866//866 +f 868//868 856//856 858//858 +f 856//856 868//868 867//867 +f 869//869 858//858 860//860 +f 858//858 869//869 868//868 +f 870//870 860//860 862//862 +f 860//860 870//870 869//869 +f 871//871 870//870 864//864 +f 862//862 864//864 870//870 +f 872//872 790//790 866//866 +f 790//790 872//872 797//797 +f 873//873 866//866 867//867 +f 866//866 873//873 872//872 +f 874//874 867//867 868//868 +f 867//867 874//874 873//873 +f 875//875 874//874 869//869 +f 868//868 869//869 874//874 +f 876//876 875//875 870//870 +f 869//869 870//870 875//875 +f 877//877 876//876 871//871 +f 870//870 871//871 876//876 +f 878//878 797//797 872//872 +f 797//797 878//878 804//804 +f 879//879 872//872 873//873 +f 872//872 879//879 878//878 +f 880//880 873//873 874//874 +f 873//873 880//880 879//879 +f 881//881 880//880 875//875 +f 874//874 875//875 880//880 +f 882//882 881//881 876//876 +f 875//875 876//876 881//881 +f 883//883 882//882 877//877 +f 876//876 877//877 882//882 +f 884//884 804//804 878//878 +f 804//804 884//884 811//811 +f 885//885 878//878 879//879 +f 878//878 885//885 884//884 +f 886//886 879//879 880//880 +f 879//879 886//886 885//885 +f 887//887 880//880 881//881 +f 880//880 887//887 886//886 +f 888//888 881//881 882//882 +f 881//881 888//888 887//887 +f 889//889 888//888 883//883 +f 882//882 883//883 888//888 +f 890//890 811//811 884//884 +f 811//811 890//890 818//818 +f 891//891 884//884 885//885 +f 884//884 891//891 890//890 +f 892//892 885//885 886//886 +f 885//885 892//892 891//891 +f 893//893 886//886 887//887 +f 886//886 893//893 892//892 +f 894//894 887//887 888//888 +f 887//887 894//894 893//893 +f 895//895 888//888 889//889 +f 888//888 895//895 894//894 +f 896//896 825//825 890//890 +f 818//818 890//890 825//825 +f 897//897 896//896 891//891 +f 890//890 891//891 896//896 +f 898//898 897//897 892//892 +f 891//891 892//892 897//897 +f 899//899 898//898 893//893 +f 892//892 893//893 898//898 +f 900//900 899//899 894//894 +f 893//893 894//894 899//899 +f 901//901 900//900 895//895 +f 894//894 895//895 900//900 +f 902//902 832//832 896//896 +f 825//825 896//896 832//832 +f 903//903 902//902 897//897 +f 896//896 897//897 902//902 +f 904//904 903//903 898//898 +f 897//897 898//898 903//903 +f 905//905 904//904 899//899 +f 898//898 899//899 904//904 +f 906//906 905//905 900//900 +f 899//899 900//900 905//905 +f 907//907 900//900 901//901 +f 900//900 907//907 906//906 +f 908//908 839//839 902//902 +f 832//832 902//902 839//839 +f 909//909 908//908 903//903 +f 902//902 903//903 908//908 +f 910//910 909//909 904//904 +f 903//903 904//904 909//909 +f 911//911 904//904 905//905 +f 904//904 911//911 910//910 +f 912//912 905//905 906//906 +f 905//905 912//912 911//911 +f 913//913 906//906 907//907 +f 906//906 913//913 912//912 +f 914//914 846//846 908//908 +f 839//839 908//908 846//846 +f 915//915 914//914 909//909 +f 908//908 909//909 914//914 +f 916//916 915//915 910//910 +f 909//909 910//910 915//915 +f 917//917 910//910 911//911 +f 910//910 917//917 916//916 +f 918//918 911//911 912//912 +f 911//911 918//918 917//917 +f 919//919 912//912 913//913 +f 912//912 919//919 918//918 +f 920//920 853//853 914//914 +f 846//846 914//914 853//853 +f 921//921 920//920 915//915 +f 914//914 915//915 920//920 +f 922//922 921//921 916//916 +f 915//915 916//916 921//921 +f 923//923 922//922 917//917 +f 916//916 917//917 922//922 +f 924//924 923//923 918//918 +f 917//917 918//918 923//923 +f 925//925 918//918 919//919 +f 918//918 925//925 924//924 +f 855//855 853//853 920//920 +f 853//853 855//855 783//783 +f 857//857 855//855 921//921 +f 920//920 921//921 855//855 +f 859//859 857//857 922//922 +f 921//921 922//922 857//857 +f 861//861 859//859 923//923 +f 922//922 923//923 859//859 +f 863//863 861//861 924//924 +f 923//923 924//924 861//861 +f 865//865 863//863 925//925 +f 924//924 925//925 863//863 +s 4 +f 926//926 928//927 927//928 +f 928//927 926//926 929//929 +f 929//929 930//930 928//927 +f 930//930 929//929 931//931 +f 931//931 932//932 930//930 +f 932//932 931//931 933//933 +f 933//933 934//934 932//932 +f 934//934 933//933 935//935 +f 935//935 936//936 934//934 +f 936//936 935//935 937//937 +f 937//937 938//938 936//936 +f 939//939 936//936 938//938 +f 940//940 927//928 941//941 +f 928//927 941//941 927//928 +f 928//927 942//942 941//941 +f 942//942 928//927 930//930 +f 930//930 943//943 942//942 +f 943//943 930//930 932//932 +f 932//932 944//944 943//943 +f 944//944 932//932 934//934 +f 934//934 936//936 944//944 +f 945//945 944//944 936//936 +f 936//936 939//939 945//945 +f 946//946 945//945 939//939 +f 947//947 940//940 948//948 +f 941//941 948//948 940//940 +f 941//941 949//949 948//948 +f 949//949 941//941 942//942 +f 942//942 943//943 949//949 +f 950//950 949//949 943//943 +f 943//943 944//944 950//950 +f 951//951 950//950 944//944 +f 944//944 945//945 951//951 +f 952//952 951//951 945//945 +f 945//945 946//946 952//952 +f 953//953 952//952 946//946 +f 954//954 947//947 955//955 +f 948//948 955//955 947//947 +f 948//948 956//956 955//955 +f 956//956 948//948 949//949 +f 949//949 950//950 956//956 +f 957//957 956//956 950//950 +f 950//950 951//951 957//957 +f 958//958 957//957 951//951 +f 951//951 952//952 958//958 +f 959//959 958//958 952//952 +f 952//952 953//953 959//959 +f 960//960 959//959 953//953 +f 954//954 962//961 961//962 +f 962//961 954//954 955//955 +f 955//955 956//956 962//961 +f 963//963 962//961 956//956 +f 956//956 957//957 963//963 +f 964//964 963//963 957//957 +f 957//957 958//958 964//964 +f 965//965 964//964 958//958 +f 958//958 959//959 965//965 +f 966//966 965//965 959//959 +f 959//959 960//960 966//966 +f 967//967 966//966 960//960 +f 961//962 962//961 968//968 +f 969//969 968//968 962//961 +f 962//961 963//963 969//969 +f 970//970 969//969 963//963 +f 963//963 964//964 970//970 +f 971//971 970//970 964//964 +f 964//964 965//965 971//971 +f 972//972 971//971 965//965 +f 965//965 966//966 972//972 +f 973//973 972//972 966//966 +f 966//966 967//967 973//973 +f 974//974 973//973 967//967 +f 968//968 976//975 975//976 +f 976//975 968//968 969//969 +f 969//969 977//977 976//975 +f 977//977 969//969 970//970 +f 970//970 978//978 977//977 +f 978//978 970//970 971//971 +f 971//971 979//979 978//978 +f 979//979 971//971 972//972 +f 972//972 980//980 979//979 +f 980//980 972//972 973//973 +f 973//973 981//981 980//980 +f 981//981 973//973 974//974 +f 975//976 976//975 982//982 +f 983//983 982//982 976//975 +f 976//975 984//984 983//983 +f 984//984 976//975 977//977 +f 977//977 985//985 984//984 +f 985//985 977//977 978//978 +f 978//978 986//986 985//985 +f 986//986 978//978 979//979 +f 979//979 987//987 986//986 +f 987//987 979//979 980//980 +f 980//980 988//988 987//987 +f 988//988 980//980 981//981 +f 983//983 989//989 982//982 +f 989//989 983//983 990//990 +f 983//983 984//984 990//990 +f 991//991 990//990 984//984 +f 984//984 992//992 991//991 +f 992//992 984//984 985//985 +f 985//985 993//993 992//992 +f 993//993 985//985 986//986 +f 986//986 994//994 993//993 +f 994//994 986//986 987//987 +f 987//987 995//995 994//994 +f 995//995 987//987 988//988 +f 990//990 996//996 989//989 +f 996//996 990//990 997//997 +f 990//990 991//991 997//997 +f 998//998 997//997 991//991 +f 991//991 999//999 998//998 +f 999//999 991//991 992//992 +f 992//992 1000//1000 999//999 +f 1000//1000 992//992 993//993 +f 993//993 1001//1001 1000//1000 +f 1001//1001 993//993 994//994 +f 994//994 1002//1002 1001//1001 +f 1002//1002 994//994 995//995 +f 997//997 1003//1003 996//996 +f 1003//1003 997//997 1004//1004 +f 997//997 998//998 1004//1004 +f 1005//1005 1004//1004 998//998 +f 998//998 999//999 1005//1005 +f 1006//1006 1005//1005 999//999 +f 999//999 1000//1000 1006//1006 +f 1007//1007 1006//1006 1000//1000 +f 1000//1000 1008//1008 1007//1007 +f 1008//1008 1000//1000 1001//1001 +f 1001//1001 1009//1009 1008//1008 +f 1009//1009 1001//1001 1002//1002 +f 1003//1003 1004//1004 926//926 +f 929//929 926//926 1004//1004 +f 1004//1004 1005//1005 929//929 +f 931//931 929//929 1005//1005 +f 1005//1005 1006//1006 931//931 +f 933//933 931//931 1006//1006 +f 1006//1006 1007//1007 933//933 +f 935//935 933//933 1007//1007 +f 1007//1007 1008//1008 935//935 +f 937//937 935//935 1008//1008 +f 1008//1008 938//938 937//937 +f 938//938 1008//1008 1009//1009 +f 938//938 1010//1010 939//939 +f 1011//1011 939//939 1010//1010 +f 1010//1010 1012//1012 1011//1011 +f 1013//1013 1011//1011 1012//1012 +f 1012//1012 1014//1014 1013//1013 +f 1015//1015 1013//1013 1014//1014 +f 1016//1016 1014//1014 1017//1017 +f 1014//1014 1016//1016 1015//1015 +f 1018//1018 1017//1017 1019//1019 +f 1017//1017 1018//1018 1016//1016 +f 1020//1020 1019//1019 1021//1021 +f 1019//1019 1020//1020 1018//1018 +f 939//939 1011//1011 946//946 +f 1022//1022 946//946 1011//1011 +f 1011//1011 1013//1013 1022//1022 +f 1023//1023 1022//1022 1013//1013 +f 1013//1013 1015//1015 1023//1023 +f 1024//1024 1023//1023 1015//1015 +f 1025//1025 1015//1015 1016//1016 +f 1015//1015 1025//1025 1024//1024 +f 1026//1026 1016//1016 1018//1018 +f 1016//1016 1026//1026 1025//1025 +f 1027//1027 1018//1018 1020//1020 +f 1018//1018 1027//1027 1026//1026 +f 946//946 1022//1022 953//953 +f 1028//1028 953//953 1022//1022 +f 1022//1022 1023//1023 1028//1028 +f 1029//1029 1028//1028 1023//1023 +f 1023//1023 1024//1024 1029//1029 +f 1030//1030 1029//1029 1024//1024 +f 1031//1031 1024//1024 1025//1025 +f 1024//1024 1031//1031 1030//1030 +f 1032//1032 1025//1025 1026//1026 +f 1025//1025 1032//1032 1031//1031 +f 1033//1033 1026//1026 1027//1027 +f 1026//1026 1033//1033 1032//1032 +f 953//953 1028//1028 960//960 +f 1034//1034 960//960 1028//1028 +f 1028//1028 1029//1029 1034//1034 +f 1035//1035 1034//1034 1029//1029 +f 1029//1029 1030//1030 1035//1035 +f 1036//1036 1035//1035 1030//1030 +f 1037//1037 1030//1030 1031//1031 +f 1030//1030 1037//1037 1036//1036 +f 1038//1038 1031//1031 1032//1032 +f 1031//1031 1038//1038 1037//1037 +f 1039//1039 1032//1032 1033//1033 +f 1032//1032 1039//1039 1038//1038 +f 960//960 1034//1034 967//967 +f 1040//1040 967//967 1034//1034 +f 1034//1034 1035//1035 1040//1040 +f 1041//1041 1040//1040 1035//1035 +f 1035//1035 1036//1036 1041//1041 +f 1042//1042 1041//1041 1036//1036 +f 1043//1043 1036//1036 1037//1037 +f 1036//1036 1043//1043 1042//1042 +f 1044//1044 1037//1037 1038//1038 +f 1037//1037 1044//1044 1043//1043 +f 1045//1045 1038//1038 1039//1039 +f 1038//1038 1045//1045 1044//1044 +f 967//967 1040//1040 974//974 +f 1046//1046 974//974 1040//1040 +f 1040//1040 1041//1041 1046//1046 +f 1047//1047 1046//1046 1041//1041 +f 1041//1041 1042//1042 1047//1047 +f 1048//1048 1047//1047 1042//1042 +f 1049//1049 1042//1042 1043//1043 +f 1042//1042 1049//1049 1048//1048 +f 1050//1050 1043//1043 1044//1044 +f 1043//1043 1050//1050 1049//1049 +f 1051//1051 1044//1044 1045//1045 +f 1044//1044 1051//1051 1050//1050 +f 974//974 1052//1052 981//981 +f 1052//1052 974//974 1046//1046 +f 1046//1046 1053//1053 1052//1052 +f 1053//1053 1046//1046 1047//1047 +f 1047//1047 1054//1054 1053//1053 +f 1054//1054 1047//1047 1048//1048 +f 1055//1055 1054//1054 1049//1049 +f 1048//1048 1049//1049 1054//1054 +f 1056//1056 1055//1055 1050//1050 +f 1049//1049 1050//1050 1055//1055 +f 1057//1057 1056//1056 1051//1051 +f 1050//1050 1051//1051 1056//1056 +f 981//981 1058//1058 988//988 +f 1058//1058 981//981 1052//1052 +f 1052//1052 1059//1059 1058//1058 +f 1059//1059 1052//1052 1053//1053 +f 1053//1053 1060//1060 1059//1059 +f 1060//1060 1053//1053 1054//1054 +f 1061//1061 1060//1060 1055//1055 +f 1054//1054 1055//1055 1060//1060 +f 1062//1062 1061//1061 1056//1056 +f 1055//1055 1056//1056 1061//1061 +f 1063//1063 1062//1062 1057//1057 +f 1056//1056 1057//1057 1062//1062 +f 988//988 1064//1064 995//995 +f 1064//1064 988//988 1058//1058 +f 1058//1058 1065//1065 1064//1064 +f 1065//1065 1058//1058 1059//1059 +f 1059//1059 1066//1066 1065//1065 +f 1066//1066 1059//1059 1060//1060 +f 1067//1067 1066//1066 1061//1061 +f 1060//1060 1061//1061 1066//1066 +f 1068//1068 1067//1067 1062//1062 +f 1061//1061 1062//1062 1067//1067 +f 1069//1069 1068//1068 1063//1063 +f 1062//1062 1063//1063 1068//1068 +f 995//995 1070//1070 1002//1002 +f 1070//1070 995//995 1064//1064 +f 1064//1064 1071//1071 1070//1070 +f 1071//1071 1064//1064 1065//1065 +f 1065//1065 1072//1072 1071//1071 +f 1072//1072 1065//1065 1066//1066 +f 1073//1073 1072//1072 1067//1067 +f 1066//1066 1067//1067 1072//1072 +f 1074//1074 1073//1073 1068//1068 +f 1067//1067 1068//1068 1073//1073 +f 1075//1075 1074//1074 1069//1069 +f 1068//1068 1069//1069 1074//1074 +f 1002//1002 1076//1076 1009//1009 +f 1076//1076 1002//1002 1070//1070 +f 1070//1070 1077//1077 1076//1076 +f 1077//1077 1070//1070 1071//1071 +f 1071//1071 1078//1078 1077//1077 +f 1078//1078 1071//1071 1072//1072 +f 1079//1079 1078//1078 1073//1073 +f 1072//1072 1073//1073 1078//1078 +f 1080//1080 1079//1079 1074//1074 +f 1073//1073 1074//1074 1079//1079 +f 1081//1081 1080//1080 1075//1075 +f 1074//1074 1075//1075 1080//1080 +f 1009//1009 1010//1010 938//938 +f 1010//1010 1009//1009 1076//1076 +f 1076//1076 1012//1012 1010//1010 +f 1012//1012 1076//1076 1077//1077 +f 1077//1077 1014//1014 1012//1012 +f 1014//1014 1077//1077 1078//1078 +f 1017//1017 1014//1014 1079//1079 +f 1078//1078 1079//1079 1014//1014 +f 1019//1019 1017//1017 1080//1080 +f 1079//1079 1080//1080 1017//1017 +f 1021//1021 1019//1019 1081//1081 +f 1080//1080 1081//1081 1019//1019 +s 5 +f 1082//1082 1084//1083 1083//1084 +f 1084//1083 1085//1085 1083//1084 +f 1085//1085 1086//1086 1083//1084 +f 1086//1086 1087//1087 1083//1084 +f 1087//1087 1088//1088 1083//1084 +f 1088//1088 1089//1089 1083//1084 +f 1089//1089 1090//1090 1083//1084 +f 1090//1090 1091//1091 1083//1084 +f 1091//1091 1092//1092 1083//1084 +f 1092//1092 1093//1093 1083//1084 +f 1093//1093 1094//1094 1083//1084 +f 1094//1094 1095//1095 1083//1084 +f 1095//1095 1096//1096 1083//1084 +f 1096//1096 1097//1097 1083//1084 +f 1097//1097 1098//1098 1083//1084 +f 1098//1098 1099//1099 1083//1084 +f 1099//1099 1100//1100 1083//1084 +f 1100//1100 1101//1101 1083//1084 +f 1101//1101 1102//1102 1083//1084 +f 1102//1102 1103//1103 1083//1084 +f 1103//1103 1104//1104 1083//1084 +f 1104//1104 1105//1105 1083//1084 +f 1105//1105 1106//1106 1083//1084 +f 1106//1106 1082//1082 1083//1084 +f 1107//1107 1084//1083 1108//1108 +f 1082//1082 1108//1108 1084//1083 +f 1109//1109 1108//1108 1110//1110 +f 1108//1108 1109//1109 1107//1107 +f 1111//1111 1110//1110 1112//1112 +f 1110//1110 1111//1111 1109//1109 +f 1113//1113 1112//1112 1114//1114 +f 1112//1112 1113//1113 1111//1111 +f 1115//1115 1085//1085 1107//1107 +f 1084//1083 1107//1107 1085//1085 +f 1116//1116 1107//1107 1109//1109 +f 1107//1107 1116//1116 1115//1115 +f 1117//1117 1109//1109 1111//1111 +f 1109//1109 1117//1117 1116//1116 +f 1118//1118 1111//1111 1113//1113 +f 1111//1111 1118//1118 1117//1117 +f 1119//1119 1086//1086 1115//1115 +f 1085//1085 1115//1115 1086//1086 +f 1120//1120 1115//1115 1116//1116 +f 1115//1115 1120//1120 1119//1119 +f 1121//1121 1116//1116 1117//1117 +f 1116//1116 1121//1121 1120//1120 +f 1122//1122 1117//1117 1118//1118 +f 1117//1117 1122//1122 1121//1121 +f 1123//1123 1086//1086 1119//1119 +f 1086//1086 1123//1123 1087//1087 +f 1124//1124 1123//1123 1120//1120 +f 1119//1119 1120//1120 1123//1123 +f 1125//1125 1124//1124 1121//1121 +f 1120//1120 1121//1121 1124//1124 +f 1126//1126 1125//1125 1122//1122 +f 1121//1121 1122//1122 1125//1125 +f 1127//1127 1087//1087 1123//1123 +f 1087//1087 1127//1127 1088//1088 +f 1128//1128 1127//1127 1124//1124 +f 1123//1123 1124//1124 1127//1127 +f 1129//1129 1128//1128 1125//1125 +f 1124//1124 1125//1125 1128//1128 +f 1130//1130 1129//1129 1126//1126 +f 1125//1125 1126//1126 1129//1129 +f 1131//1131 1088//1088 1127//1127 +f 1088//1088 1131//1131 1089//1089 +f 1132//1132 1131//1131 1128//1128 +f 1127//1127 1128//1128 1131//1131 +f 1133//1133 1132//1132 1129//1129 +f 1128//1128 1129//1129 1132//1132 +f 1134//1134 1133//1133 1130//1130 +f 1129//1129 1130//1130 1133//1133 +f 1135//1135 1090//1090 1131//1131 +f 1089//1089 1131//1131 1090//1090 +f 1136//1136 1131//1131 1132//1132 +f 1131//1131 1136//1136 1135//1135 +f 1137//1137 1132//1132 1133//1133 +f 1132//1132 1137//1137 1136//1136 +f 1138//1138 1133//1133 1134//1134 +f 1133//1133 1138//1138 1137//1137 +f 1139//1139 1091//1091 1135//1135 +f 1090//1090 1135//1135 1091//1091 +f 1140//1140 1135//1135 1136//1136 +f 1135//1135 1140//1140 1139//1139 +f 1141//1141 1136//1136 1137//1137 +f 1136//1136 1141//1141 1140//1140 +f 1142//1142 1137//1137 1138//1138 +f 1137//1137 1142//1142 1141//1141 +f 1143//1143 1092//1092 1139//1139 +f 1091//1091 1139//1139 1092//1092 +f 1144//1144 1139//1139 1140//1140 +f 1139//1139 1144//1144 1143//1143 +f 1145//1145 1140//1140 1141//1141 +f 1140//1140 1145//1145 1144//1144 +f 1146//1146 1141//1141 1142//1142 +f 1141//1141 1146//1146 1145//1145 +f 1147//1147 1092//1092 1143//1143 +f 1092//1092 1147//1147 1093//1093 +f 1148//1148 1147//1147 1144//1144 +f 1143//1143 1144//1144 1147//1147 +f 1149//1149 1148//1148 1145//1145 +f 1144//1144 1145//1145 1148//1148 +f 1150//1150 1149//1149 1146//1146 +f 1145//1145 1146//1146 1149//1149 +f 1151//1151 1093//1093 1147//1147 +f 1093//1093 1151//1151 1094//1094 +f 1152//1152 1151//1151 1148//1148 +f 1147//1147 1148//1148 1151//1151 +f 1153//1153 1152//1152 1149//1149 +f 1148//1148 1149//1149 1152//1152 +f 1154//1154 1153//1153 1150//1150 +f 1149//1149 1150//1150 1153//1153 +f 1155//1155 1094//1094 1151//1151 +f 1094//1094 1155//1155 1095//1095 +f 1156//1156 1155//1155 1152//1152 +f 1151//1151 1152//1152 1155//1155 +f 1157//1157 1156//1156 1153//1153 +f 1152//1152 1153//1153 1156//1156 +f 1158//1158 1157//1157 1154//1154 +f 1153//1153 1154//1154 1157//1157 +f 1159//1159 1096//1096 1155//1155 +f 1095//1095 1155//1155 1096//1096 +f 1160//1160 1155//1155 1156//1156 +f 1155//1155 1160//1160 1159//1159 +f 1161//1161 1156//1156 1157//1157 +f 1156//1156 1161//1161 1160//1160 +f 1162//1162 1157//1157 1158//1158 +f 1157//1157 1162//1162 1161//1161 +f 1163//1163 1097//1097 1159//1159 +f 1096//1096 1159//1159 1097//1097 +f 1164//1164 1159//1159 1160//1160 +f 1159//1159 1164//1164 1163//1163 +f 1165//1165 1160//1160 1161//1161 +f 1160//1160 1165//1165 1164//1164 +f 1166//1166 1161//1161 1162//1162 +f 1161//1161 1166//1166 1165//1165 +f 1167//1167 1098//1098 1163//1163 +f 1097//1097 1163//1163 1098//1098 +f 1168//1168 1163//1163 1164//1164 +f 1163//1163 1168//1168 1167//1167 +f 1169//1169 1164//1164 1165//1165 +f 1164//1164 1169//1169 1168//1168 +f 1170//1170 1165//1165 1166//1166 +f 1165//1165 1170//1170 1169//1169 +f 1171//1171 1098//1098 1167//1167 +f 1098//1098 1171//1171 1099//1099 +f 1172//1172 1171//1171 1168//1168 +f 1167//1167 1168//1168 1171//1171 +f 1173//1173 1172//1172 1169//1169 +f 1168//1168 1169//1169 1172//1172 +f 1174//1174 1173//1173 1170//1170 +f 1169//1169 1170//1170 1173//1173 +f 1175//1175 1099//1099 1171//1171 +f 1099//1099 1175//1175 1100//1100 +f 1176//1176 1175//1175 1172//1172 +f 1171//1171 1172//1172 1175//1175 +f 1177//1177 1176//1176 1173//1173 +f 1172//1172 1173//1173 1176//1176 +f 1178//1178 1177//1177 1174//1174 +f 1173//1173 1174//1174 1177//1177 +f 1179//1179 1100//1100 1175//1175 +f 1100//1100 1179//1179 1101//1101 +f 1180//1180 1179//1179 1176//1176 +f 1175//1175 1176//1176 1179//1179 +f 1181//1181 1180//1180 1177//1177 +f 1176//1176 1177//1177 1180//1180 +f 1182//1182 1181//1181 1178//1178 +f 1177//1177 1178//1178 1181//1181 +f 1183//1183 1102//1102 1179//1179 +f 1101//1101 1179//1179 1102//1102 +f 1184//1184 1179//1179 1180//1180 +f 1179//1179 1184//1184 1183//1183 +f 1185//1185 1180//1180 1181//1181 +f 1180//1180 1185//1185 1184//1184 +f 1186//1186 1181//1181 1182//1182 +f 1181//1181 1186//1186 1185//1185 +f 1187//1187 1103//1103 1183//1183 +f 1102//1102 1183//1183 1103//1103 +f 1188//1188 1183//1183 1184//1184 +f 1183//1183 1188//1188 1187//1187 +f 1189//1189 1184//1184 1185//1185 +f 1184//1184 1189//1189 1188//1188 +f 1190//1190 1185//1185 1186//1186 +f 1185//1185 1190//1190 1189//1189 +f 1191//1191 1104//1104 1187//1187 +f 1103//1103 1187//1187 1104//1104 +f 1192//1192 1187//1187 1188//1188 +f 1187//1187 1192//1192 1191//1191 +f 1193//1193 1188//1188 1189//1189 +f 1188//1188 1193//1193 1192//1192 +f 1194//1194 1189//1189 1190//1190 +f 1189//1189 1194//1194 1193//1193 +f 1195//1195 1104//1104 1191//1191 +f 1104//1104 1195//1195 1105//1105 +f 1196//1196 1195//1195 1192//1192 +f 1191//1191 1192//1192 1195//1195 +f 1197//1197 1196//1196 1193//1193 +f 1192//1192 1193//1193 1196//1196 +f 1198//1198 1197//1197 1194//1194 +f 1193//1193 1194//1194 1197//1197 +f 1199//1199 1105//1105 1195//1195 +f 1105//1105 1199//1199 1106//1106 +f 1200//1200 1199//1199 1196//1196 +f 1195//1195 1196//1196 1199//1199 +f 1201//1201 1200//1200 1197//1197 +f 1196//1196 1197//1197 1200//1200 +f 1202//1202 1201//1201 1198//1198 +f 1197//1197 1198//1198 1201//1201 +f 1108//1108 1106//1106 1199//1199 +f 1106//1106 1108//1108 1082//1082 +f 1110//1110 1108//1108 1200//1200 +f 1199//1199 1200//1200 1108//1108 +f 1112//1112 1110//1110 1201//1201 +f 1200//1200 1201//1201 1110//1110 +f 1114//1114 1112//1112 1202//1202 +f 1201//1201 1202//1202 1112//1112 diff --git a/examples/spinning_cube.rs b/examples/spinning_cube.rs index 67e5282..e690657 100644 --- a/examples/spinning_cube.rs +++ b/examples/spinning_cube.rs @@ -1,96 +1,75 @@ use vek::*; -use euc::{ - buffer2::Buffer2d, - pipeline2::Pipeline, - texture::{Empty, Target}, - rasterizer2, - DepthStrategy, -}; +use euc::{Pipeline, Buffer2d, Target, TriangleList, CullMode, IndexedVertices}; struct Cube { mvp: Mat4, } impl Pipeline for Cube { - type Vertex = (usize, Vec4); - type VsOut = Vec4; + type Vertex = (Vec4, Rgba); + type VsOut = Rgba; + type Primitives = TriangleList; type Fragment = u32; #[inline(always)] - fn vertex_shader(&self, (v_index, v_color): &Self::Vertex) -> ([f32; 4], Self::VsOut) { - ((self.mvp * VERTICES[*v_index]).into_array(), *v_color) + fn vertex_shader(&self, (pos, color): &Self::Vertex) -> ([f32; 4], Self::VsOut) { + ((self.mvp * *pos).into_array(), *color) } #[inline(always)] - fn fragment_shader(&self, v_color: Self::VsOut) -> Self::Fragment { - let bytes = v_color.map(|e| (e * 255.0) as u8).into_array(); - (bytes[2] as u32) << 0 - | (bytes[1] as u32) << 8 - | (bytes[0] as u32) << 16 - | (bytes[3] as u32) << 24 + fn fragment_shader(&self, color: Self::VsOut) -> Self::Fragment { + u32::from_le_bytes((color * 255.0).as_().into_array()) } } -const W: usize = 640; -const H: usize = 480; - -const VERTICES: &[Vec4] = &[ - Vec4::new(-1.0, -1.0, -1.0, 1.0), - Vec4::new(-1.0, -1.0, 1.0, 1.0), - Vec4::new(-1.0, 1.0, -1.0, 1.0), - Vec4::new(-1.0, 1.0, 1.0, 1.0), - Vec4::new( 1.0, -1.0, -1.0, 1.0), - Vec4::new( 1.0, -1.0, 1.0, 1.0), - Vec4::new( 1.0, 1.0, -1.0, 1.0), - Vec4::new( 1.0, 1.0, 1.0, 1.0), +const R: Rgba = Rgba::new(1.0, 0.0, 0.0, 1.0); +const Y: Rgba = Rgba::new(1.0, 1.0, 0.0, 1.0); +const G: Rgba = Rgba::new(0.0, 1.0, 0.0, 1.0); +const B: Rgba = Rgba::new(0.0, 0.0, 1.0, 1.0); + +const VERTICES: &[(Vec4, Rgba)] = &[ + (Vec4::new(-1.0, -1.0, -1.0, 1.0), R), + (Vec4::new(-1.0, -1.0, 1.0, 1.0), Y), + (Vec4::new(-1.0, 1.0, -1.0, 1.0), G), + (Vec4::new(-1.0, 1.0, 1.0, 1.0), B), + (Vec4::new( 1.0, -1.0, -1.0, 1.0), B), + (Vec4::new( 1.0, -1.0, 1.0, 1.0), G), + (Vec4::new( 1.0, 1.0, -1.0, 1.0), Y), + (Vec4::new( 1.0, 1.0, 1.0, 1.0), R), ]; -const RED: Vec4 = Vec4::new(1.0, 0.0, 0.0, 1.0); -const GREEN: Vec4 = Vec4::new(0.0, 1.0, 0.0, 1.0); -const BLUE: Vec4 = Vec4::new(0.0, 0.0, 1.0, 1.0); - -const INDICES: &[(usize, Vec4)] = &[ - // -x - (0, GREEN), (3, BLUE ), (2, RED ), - (0, GREEN), (1, RED ), (3, BLUE ), - // +x - (7, BLUE ), (4, GREEN), (6, RED ), - (5, RED ), (4, GREEN), (7, BLUE ), - // -y - (5, BLUE ), (0, RED ), (4, GREEN), - (1, GREEN), (0, RED ), (5, BLUE ), - // +y - (2, RED ), (7, BLUE ), (6, GREEN), - (2, RED ), (3, GREEN), (7, BLUE ), - // -z - (0, RED ), (6, GREEN), (4, BLUE ), - (0, RED ), (2, BLUE ), (6, GREEN), - // +z - (7, GREEN), (1, RED ), (5, BLUE ), - (3, BLUE ), (1, RED ), (7, GREEN), +const INDICES: &[usize] = &[ + 0, 3, 2, 0, 1, 3, // -x + 7, 4, 6, 5, 4, 7, // +x + 5, 0, 4, 1, 0, 5, // -y + 2, 7, 6, 2, 3, 7, // +y + 0, 6, 4, 0, 2, 6, // -z + 7, 1, 5, 3, 1, 7, // +z ]; fn main() { - let mut color = Buffer2d::fill([W, H], 0); - let mut depth = Buffer2d::fill([W, H], 1.0); + let [w, h] = [800, 600]; - let mut win = mini_gl_fb::gotta_go_fast("Cube", W as f64, H as f64); + let mut color = Buffer2d::fill([w, h], 0); + let mut depth = Buffer2d::fill([w, h], 1.0); + + let mut win = mini_gl_fb::gotta_go_fast("Cube", w as f64, h as f64); let mut i = 0; win.glutin_handle_basic_input(|win, input| { - let mvp = Mat4::perspective_fov_rh_no(1.3, W as f32, H as f32, 0.01, 100.0) - * Mat4::translation_3d(Vec3::new(0.0, 0.0, -2.0)) - * Mat4::::scaling_3d(0.6) + let mvp = Mat4::perspective_fov_lh_zo(1.3, w as f32, h as f32, 0.01, 100.0) + * Mat4::translation_3d(Vec3::new(0.0, 0.0, 3.0)) * Mat4::rotation_x((i as f32 * 0.002).sin() * 8.0) * Mat4::rotation_y((i as f32 * 0.004).cos() * 4.0) - * Mat4::rotation_z((i as f32 * 0.008).sin() * 2.0); + * Mat4::rotation_z((i as f32 * 0.008).sin() * 2.0) + * Mat4::scaling_3d(Vec3::new(1.0, -1.0, 1.0)); color.clear(0); depth.clear(1.0); Cube { mvp }.render( - rasterizer2::Triangles, - INDICES, + IndexedVertices::new(INDICES, VERTICES), + CullMode::Back, &mut color, &mut depth, ); @@ -99,7 +78,6 @@ fn main() { win.redraw(); i += 1; - true }); } diff --git a/examples/teapot.rs b/examples/teapot.rs index 261c58f..ba5d7dd 100644 --- a/examples/teapot.rs +++ b/examples/teapot.rs @@ -1,100 +1,83 @@ -use std::path::Path; use vek::*; -use euc::{Pipeline, Buffer2d, Target, DepthMode, Triangles, CullMode}; +use derive_more::{Add, Mul}; +use euc::{Pipeline, Buffer2d, Target, DepthMode, TriangleList, CullMode}; +use std::marker::PhantomData; struct Teapot<'a> { - mvp: Mat4, - positions: &'a [Vec3], - normals: &'a [Vec3], - light_dir: Vec3, + m: Mat4, + v: Mat4, + p: Mat4, + phantom: PhantomData<&'a ()>, +} + +#[derive(Add, Mul, Clone)] +struct VsOut { + wpos: Vec3, + wnorm: Vec3, } impl<'a> Pipeline for Teapot<'a> { - type Vertex = usize; // Vertex index - type VsOut = Rgba; // Color - type Fragment = u32; // BGRA + type Vertex = wavefront::Vertex<'a>; + type VsOut = VsOut; + type Primitives = TriangleList; + type Fragment = u32; fn depth_mode(&self) -> DepthMode { DepthMode::LESS_WRITE } #[inline(always)] - fn vertex_shader(&self, index: &Self::Vertex) -> ([f32; 4], Self::VsOut) { - let pos = self.mvp * Vec4::from_point(self.positions[*index]); - let norm = self.normals[*index]; - - let ambient = 0.2; - let diffuse = norm.dot(self.light_dir).max(0.0) * 0.5; - let specular = self - .light_dir - .reflected(Vec3::from(self.mvp * Vec4::from(norm)).normalized()) - .dot(-Vec3::unit_z()) - .powf(20.0); - let light = ambient + diffuse + specular; - - let color = (Rgba::new(1.0, 0.7, 0.1, 1.0) * light).clamped(Rgba::zero(), Rgba::one()); - - (pos.into_array(), color) + fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VsOut) { + let wpos = self.m * Vec4::from_point(Vec3::from(vertex.position())); + let wnorm = self.m * Vec4::from_direction(-Vec3::from(vertex.normal().unwrap())); + ( + (self.p * self.v * wpos).into_array(), + VsOut { wpos: wpos.xyz(), wnorm: wnorm.xyz() }, + ) } #[inline(always)] - fn fragment_shader(&self, color: Self::VsOut) -> Self::Fragment { - let bytes = (color * 255.0).map(|e| e as u8).into_array(); - (bytes[0] as u32) << 0 - | (bytes[1] as u32) << 8 - | (bytes[2] as u32) << 16 - | (bytes[3] as u32) << 24 + fn fragment_shader(&self, VsOut { wpos, wnorm }: Self::VsOut) -> Self::Fragment { + let wnorm = wnorm.normalized(); + let light_dir = Vec3::::new(0.0, 1.0, 1.0).normalized(); + let cam_pos = Vec3::zero(); + let cam_dir = (wpos - cam_pos).normalized(); + let surf_color = Rgba::new(1.0, 0.35, 0.3, 1.0); + + // Phong reflection model + let ambient = 0.1; + let diffuse = wnorm.dot(-light_dir).max(0.0) * 0.5; + let specular = light_dir.reflected(wnorm).dot(cam_dir).max(0.0).powf(150.0) * 5.0; + let light = ambient + diffuse + specular; + + let color = surf_color * light; + + u32::from_le_bytes(color.map(|e| e.min(1.0) * 255.0).as_().into_array()) } } -const W: usize = 800; -const H: usize = 600; - fn main() { - let mut color = Buffer2d::fill([W, H], 0); - let mut depth = Buffer2d::fill([W, H], 1.0); - - let obj = tobj::load_obj(&Path::new("examples/data/teapot.obj"), false).unwrap(); - let indices = obj.0[0] - .mesh - .indices - .iter() - .map(|i| *i as usize) - .collect::>(); - let positions = obj.0[0] - .mesh - .positions - .chunks(3) - .map(|sl| Vec3::from_slice(sl) - Vec3::unit_y() * 0.5) // Center model - .collect::>(); - let normals = obj.0[0] - .mesh - .normals - .chunks(3) - .map(|sl| Vec3::from_slice(sl)) - .collect::>(); - - let mut win = mini_gl_fb::gotta_go_fast("Teapot", W as f64, H as f64); + let [w, h] = [800, 600]; + + let mut color = Buffer2d::fill([w, h], 0x0); + let mut depth = Buffer2d::fill([w, h], 1.0); + + let model = wavefront::Obj::from_file("examples/data/teapot.obj").unwrap(); + + let mut win = mini_gl_fb::gotta_go_fast("Teapot", w as f64, h as f64); let mut i = 0; win.glutin_handle_basic_input(|win, input| { - let mvp = Mat4::perspective_fov_rh_no(1.3, W as f32, H as f32, 0.01, 100.0) - * Mat4::translation_3d(Vec3::new(0.0, 0.0, -1.5)) - * Mat4::::scaling_3d(0.8) - * Mat4::rotation_x((i as f32 * 0.002) * 8.0) - * Mat4::rotation_y((i as f32 * 0.004) * 4.0) - * Mat4::rotation_z((i as f32 * 0.008) * 2.0); - - color.clear(0); + let p = Mat4::perspective_fov_lh_zo(1.3, w as f32, h as f32, 0.01, 100.0); + let v = Mat4::translation_3d(Vec3::new(0.0, 0.0, 6.0)); + let m = Mat4::rotation_x((i as f32 * 0.03).sin() * 0.4) + * Mat4::rotation_y((i as f32 * 0.005) * 4.0) + * Mat4::rotation_z((i as f32 * 0.04).cos() * 0.4); + + color.clear(0x0); depth.clear(1.0); - Teapot { - mvp, - positions: &positions, - normals: &normals, - light_dir: Vec3::new(1.0, 1.0, 1.0).normalized(), - } - .render( - Triangles(CullMode::Back), - indices.as_slice(), + Teapot { m, v, p, phantom: PhantomData }.render( + model.vertices(), + CullMode::Back, &mut color, &mut depth, ); diff --git a/examples/texture_mapping.rs b/examples/texture_mapping.rs index f3528f0..c97de80 100644 --- a/examples/texture_mapping.rs +++ b/examples/texture_mapping.rs @@ -1,52 +1,39 @@ -use euc::{buffer::Buffer2d, rasterizer, Pipeline, Target}; -use image::RgbImage; -use minifb::Window; +use euc::{Buffer2d, Pipeline, Target, TriangleList, CullMode, Sampler, Nearest}; +use image_::RgbaImage; use vek::{Mat4, Vec2, Vec3, Vec4}; struct Cube<'a> { - mvp: &'a Mat4, + mvp: Mat4, positions: &'a [Vec4], uvs: &'a [Vec2], - texture: &'a RgbImage, + sampler: &'a Nearest, } impl<'a> Pipeline for Cube<'a> { type Vertex = usize; type VsOut = Vec2; - type Pixel = u32; + type Primitives = TriangleList; + type Fragment = u32; #[inline] - fn vert(&self, v_index: &Self::Vertex) -> ([f32; 4], Self::VsOut) { + fn vertex_shader(&self, v_index: &Self::Vertex) -> ([f32; 4], Self::VsOut) { ( - (*self.mvp * self.positions[*v_index]).into_array(), + (self.mvp * self.positions[*v_index]).into_array(), self.uvs[*v_index], ) } #[inline] - fn frag(&self, v_uv: &Self::VsOut) -> Self::Pixel { - // Convert interpolated uv coordinate to texture coordinate - let (width, height) = (self.texture.width() as f32, self.texture.height() as f32); - let x = f32::min(f32::max(0.0, v_uv.x * width), width - 1.0); - let y = f32::min(f32::max(0.0, v_uv.y * height), height - 1.0); - // Lookup pixel and convert to appropriate format - let rgb = self.texture.get_pixel(x as u32, y as u32); - 255 << 24 | (rgb[0] as u32) << 16 | (rgb[1] as u32) << 8 | (rgb[2] as u32) << 0 + fn fragment_shader(&self, v_uv: Self::VsOut) -> Self::Fragment { + u32::from_le_bytes(self.sampler.sample(v_uv.into_array()).0) } } -const W: usize = 800; -const H: usize = 600; - fn main() { - let mut color = Buffer2d::new([W, H], 0); - let mut depth = Buffer2d::new([W, H], 1.0); + let [w, h] = [800, 600]; - let mut win = Window::new("Cube", W, H, minifb::WindowOptions::default()).unwrap(); - let vp = Mat4::perspective_fov_rh_no(1.4, W as f32, H as f32, 0.01, 100.0) - * Mat4::::translation_3d(Vec3::new(0.0, 0.0, -2.0)) - * Mat4::::scaling_3d(0.6) - * Mat4::rotation_x(0.6); + let mut color = Buffer2d::fill([w, h], 0); + let mut depth = Buffer2d::fill([w, h], 1.0); let positions = [ // z = 1 @@ -112,28 +99,38 @@ fn main() { Vec2::new(1.0, 0.0), Vec2::new(1.0, 1.0), ]; - let texture = match image::open("examples/data/checkerboard.png") { - Ok(image) => image.to_rgb8(), + + let texture = match image_::open("examples/data/rust.png") { + Ok(image) => image.to_rgba8(), Err(err) => { eprintln!("{}", err); return; } }; + let sampler = Nearest::new(texture); + + let mut win = mini_gl_fb::gotta_go_fast("Cube", w as f64, h as f64); let mut i = 0; - while win.is_open() { - let mvp = vp * Mat4::rotation_y(-i as f32 * 0.006); + win.glutin_handle_basic_input(|win, input| { + let p = Mat4::perspective_fov_rh_no(1.4, w as f32, h as f32, 0.01, 100.0); + let v = Mat4::::translation_3d(Vec3::new(0.0, 0.0, -2.0)) + * Mat4::::scaling_3d(0.6) + * Mat4::rotation_x(0.6); + let m = Mat4::rotation_x((i as f32 * 0.04).sin() * 0.4) + * Mat4::rotation_y((i as f32 * 0.008) * 4.0) + * Mat4::rotation_z((i as f32 * 0.06).cos() * 0.4); color.clear(180); depth.clear(1.0); let cube = Cube { - mvp: &mvp, + mvp: p * v * m, positions: &positions, uvs: &uvs, - texture: &texture, + sampler: &sampler, }; - cube.draw::, _>( + cube.render( &[ // z = 1 0, 3, 1, 1, 3, 2, // z = -1 @@ -143,11 +140,15 @@ fn main() { 16, 17, 19, 17, 18, 19, // x = -1, 20, 23, 21, 21, 23, 22, ], + CullMode::Back, &mut color, - Some(&mut depth), + &mut depth, ); - win.update_with_buffer(color.as_ref(), W, H).unwrap(); + win.update_buffer(color.raw()); + win.redraw(); + i += 1; - } + true + }); } diff --git a/examples/triangle.rs b/examples/triangle.rs index 3d53f8a..00a24c3 100644 --- a/examples/triangle.rs +++ b/examples/triangle.rs @@ -1,9 +1,9 @@ use euc::{ - buffer2::Buffer2d, - pipeline2::{Pipeline, CullMode, CoordinateMode}, - texture::Empty, - rasterizer2, - DepthStrategy, + Buffer2d, + Pipeline, + TriangleList, + CullMode, + Empty, }; use vek::*; @@ -12,10 +12,9 @@ struct Triangle; impl Pipeline for Triangle { type Vertex = [f32; 4]; type VsOut = Vec2; + type Primitives = TriangleList; type Fragment = u32; - fn cull_mode(&self) -> CullMode { CullMode::None } - // Vertex shader // - Returns the 3D vertex location, and the VsOut value to be passed to the fragment shader #[inline(always)] @@ -43,12 +42,12 @@ fn main() { let mut color = Buffer2d::fill([W, H], 0); Triangle.render( - rasterizer2::Triangles, &[ [-1.0, -1.0, 0.0, 1.0], [1.0, -1.0, 0.0, 1.0], [0.0, 1.0, 0.0, 1.0], ], + CullMode::None, &mut color, Empty::default(), ); diff --git a/src/index.rs b/src/index.rs new file mode 100644 index 0000000..68e4442 --- /dev/null +++ b/src/index.rs @@ -0,0 +1,28 @@ +use core::{ + borrow::Borrow, + marker::PhantomData, +}; + +/// A helper type that makes indexed vertex access easier. +pub struct IndexedVertices<'a, Is, Vs, I, V>(Is, Vs, PhantomData<&'a (I, V)>); + +impl<'a, Is, Vs, I, V> IndexedVertices<'a, Is, Vs, I, V> { + pub fn new(is: Is, vs: Vs) -> Self { + Self(is, vs, PhantomData) + } +} + +impl<'a, Is, Vs, I, V> IntoIterator for IndexedVertices<'a, Is, Vs, I, V> +where + I: Borrow, + Is: IntoIterator + 'a, + Vs: Borrow<&'a [V]> + 'a, +{ + type Item = &'a V; + type IntoIter = impl Iterator; + + fn into_iter(self) -> Self::IntoIter { + let verts = self.1; + self.0.into_iter().map(move |i| &verts.borrow()[*i.borrow()]) + } +} diff --git a/src/lib.rs b/src/lib.rs index 2af7370..918cab5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,27 +40,34 @@ #![no_std] -#![feature(min_const_generics, alloc_prelude, array_map)] +#![feature(min_const_generics, alloc_prelude, array_map, type_alias_impl_trait)] extern crate alloc; /// N-dimensional buffers that may be used as textures and render targets. pub mod buffer; +/// Index buffer features. +pub mod index; /// Math-related functionality. pub mod math; /// Pipeline definitions. pub mod pipeline; -/// Texture and target definitions. -pub mod texture; +/// Primitive definitions. +pub mod primitives; /// Rasterization algorithms. pub mod rasterizer; /// Texture samplers. pub mod sampler; +/// Texture and target definitions. +pub mod texture; // Reexports pub use crate::{ buffer::{Buffer, Buffer1d, Buffer2d, Buffer3d, Buffer4d}, - pipeline::{Pipeline, DepthMode, CoordinateMode}, + pipeline::{Pipeline, DepthMode, CoordinateMode, Handedness}, + primitives::TriangleList, texture::{Texture, Target, Empty}, - rasterizer::{Triangles, CullMode}, + rasterizer::CullMode, + sampler::{Sampler, Nearest}, + index::IndexedVertices, }; diff --git a/src/math.rs b/src/math.rs index 01ab2db..e17484a 100644 --- a/src/math.rs +++ b/src/math.rs @@ -62,3 +62,38 @@ impl Truncate for f64 { fn truncate(self) -> u16 { self as u16 } fn detrunc impl Truncate for f64 { fn truncate(self) -> u32 { self as u32 } fn detruncate(x: u32) -> Self { x as f64 } } impl Truncate for f64 { fn truncate(self) -> u64 { self as u64 } fn detruncate(x: u64) -> Self { x as f64 } } impl Truncate for f64 { fn truncate(self) -> usize { self as usize } fn detruncate(x: usize) -> Self { x as f64 } } + +pub trait Denormalize: Sized { + fn denormalize_to(self, scale: T) -> T; + fn denormalize_array(this: [Self; N], other: [T; N]) -> [T; N]; +} + +macro_rules! impl_denormalize { + ($this:ty, $other:ty) => { + impl Denormalize<$other> for $this { + fn denormalize_to(self, scale: $other) -> $other { + ((self * scale as $this).max(0.0) as $other).min(scale - 1) + } + + fn denormalize_array(this: [Self; N], other: [$other; N]) -> [$other; N] { + let mut out = [0; N]; + (0..N).for_each(|i| out[i] = this[i].denormalize_to(other[i])); + out + } + } + }; +} + +impl_denormalize!(f32, u8); +impl_denormalize!(f32, u16); +impl_denormalize!(f32, u32); +impl_denormalize!(f32, u64); +impl_denormalize!(f32, u128); +impl_denormalize!(f32, usize); + +impl_denormalize!(f64, u8); +impl_denormalize!(f64, u16); +impl_denormalize!(f64, u32); +impl_denormalize!(f64, u64); +impl_denormalize!(f64, u128); +impl_denormalize!(f64, usize); diff --git a/src/pipeline.rs b/src/pipeline.rs index f996813..1ccf3bd 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -1,12 +1,13 @@ use crate::{ - math::*, texture::Target, rasterizer::Rasterizer, + primitives::PrimitiveKind, }; use alloc::collections::VecDeque; use core::{ cmp::Ordering, - ops::{Add, Mul}, + ops::{Add, Mul, Range}, + borrow::Borrow, }; /// Defines how a [`Pipeline`] will interact with the depth target. @@ -58,18 +59,38 @@ impl DepthMode { } } -/// The coordinate space used during rasterization. +/// The handedness of the coordinate space used by a pipeline. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub enum CoordinateMode { - /// right = +x, up = +y, out = -z (used by OpenGL and DirectX), default - Right, - /// right = +x, up = -y, out = -z (used by Vulkan) +pub enum Handedness { + /// Left-handed coordinate space Left, + /// Right-handed coordinate space + Right, +} + +/// The configuration of the coordinate system used by a pipeline. +pub struct CoordinateMode { + pub handedness: Handedness, + pub clip_range: Range, +} + +impl CoordinateMode { + /// OpenGL-like coordinates + pub const OPENGL: Self = Self { + handedness: Handedness::Right, + clip_range: -1.0..1.0, + }; + + /// Vulkan-like coordinates + pub const VULKAN: Self = Self { + handedness: Handedness::Left, + clip_range: 0.0..1.0, + }; } impl Default for CoordinateMode { fn default() -> Self { - CoordinateMode::Right + Self::VULKAN } } @@ -82,6 +103,7 @@ impl Default for CoordinateMode { pub trait Pipeline: Sized { type Vertex; type VsOut: Clone + Mul + Add; + type Primitives: PrimitiveKind; type Fragment; /// Returns the [`DepthMode`] of this pipeline. @@ -92,25 +114,30 @@ pub trait Pipeline: Sized { /// Transforms a [`Pipeline::Vertex`] into homogeneous NDCs (Normalised Device Coordinates) for the vertex and a /// [`Pipeline::VsOut`] to be interpolated and passed to the fragment shader. + /// + /// This stage is executed at the beginning of pipeline execution. fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VsOut); - /// Intercepts the [Pipeline::vertex_shader] and emit additional vertices into the pipeline on the fly. + /// Turn a primitive into many primitives. /// - /// This function will be repeatedly called until there are no more vertex inputs to receive. For this reason, it - /// should never fail to pull at least one vertex from the `input` iterator. - fn geometry_shader(&self, input: I, output: O) + /// This stage sits between the vertex shader and the fragment shader. + fn geometry_shader(&self, primitive: >::Primitive, mut output: O) where - I: Iterator, - O: FnMut(([f32; 4], Self::VsOut)), + O: FnMut(>::Primitive), { - input.for_each(output); + output(primitive); } /// Transforms a [`Pipeline::VsOut`] into a fragment to be rendered to a pixel target. + /// + /// This stage is executed for every fragment generated by the rasterizer. fn fragment_shader(&self, vs_out: Self::VsOut) -> Self::Fragment; /// Blend an old fragment with a new fragment. /// + /// This stage is executed after rasterization and defines how a fragment may be blended into an existing fragment + /// from the pixel target. + /// /// The default implementation simply returns the new fragment and ignores the old one. However, this may be used /// to implement techniques such as alpha blending. fn blend_shader(&self, a: Self::Fragment, b: Self::Fragment) -> Self::Fragment { b } @@ -118,17 +145,17 @@ pub trait Pipeline: Sized { /// Render a stream of vertices to given provided pixel target and depth target using the rasterizer. /// /// **Do not implement this method** - fn render<'a, R, V, T, D>( + fn render<'a, S, V, P, D>( &'a self, - rasterizer: R, - vertex_stream: V, - mut pixels: T, + vertices: S, + rasterizer_config: <>::Rasterizer as Rasterizer>::Config, + mut pixels: P, mut depth: D, ) where - R: Rasterizer + 'a, - V: IntoIterator, - T: Target + 'a, + S: IntoIterator, + V: Borrow, + P: Target + 'a, D: Target + 'a, { let depth_mode = self.depth_mode(); @@ -141,14 +168,20 @@ pub trait Pipeline: Sized { assert_eq!(target_size, depth.size(), "Depth target size is compatible with the size of other target(s)"); } - let mut vertices = vertex_stream.into_iter().map(|v| self.vertex_shader(v)).peekable(); - let mut vert_queue = VecDeque::new(); + let mut vert_outs = vertices.into_iter().map(|v| self.vertex_shader(v.borrow())).peekable(); + let mut vert_out_queue = VecDeque::new(); let fetch_vertex = move || { loop { - match vert_queue.pop_front() { + match vert_out_queue.pop_front() { Some(v) => break Some(v), - None if vertices.peek().is_none() => break None, - None => self.geometry_shader(&mut vertices, |v| vert_queue.push_back(v)), + None if vert_outs.peek().is_none() => break None, + None => { + let prim = Self::Primitives::collect_primitive(&mut vert_outs)?; + self.geometry_shader( + prim, + |prim| Self::Primitives::primitive_vertices(prim, |v| vert_out_queue.push_back(v)), + ); + }, } } }; @@ -180,11 +213,12 @@ pub trait Pipeline: Sized { }; unsafe { - rasterizer.rasterize( + >::Rasterizer::default().rasterize( self, core::iter::from_fn(fetch_vertex), target_size, principal_x, + rasterizer_config, emit_fragment, ); } diff --git a/src/primitives.rs b/src/primitives.rs new file mode 100644 index 0000000..84b8e46 --- /dev/null +++ b/src/primitives.rs @@ -0,0 +1,39 @@ +use crate::rasterizer::{Rasterizer, Triangles}; + +pub trait PrimitiveKind { + type Rasterizer: Rasterizer; + type Primitive; + + /// Collect a single primitive from an iterator of vertices. + fn collect_primitive(iter: I) -> Option + where + I: Iterator; + + /// Emit a primitive as a series of vertices 'as-is'. + fn primitive_vertices(primitive: Self::Primitive, output: O) + where + O: FnMut(([f32; 4], V)); +} + +pub struct TriangleList; + +impl PrimitiveKind for TriangleList { + type Rasterizer = Triangles; + type Primitive = [([f32; 4], V); 3]; + + fn collect_primitive(mut iter: I) -> Option + where + I: Iterator + { + Some([iter.next()?, iter.next()?, iter.next()?]) + } + + fn primitive_vertices([a, b, c]: Self::Primitive, mut output: O) + where + O: FnMut(([f32; 4], V)) + { + output(a); + output(b); + output(c); + } +} diff --git a/src/rasterizer/mod.rs b/src/rasterizer/mod.rs index 2f21ff1..1eecab7 100644 --- a/src/rasterizer/mod.rs +++ b/src/rasterizer/mod.rs @@ -25,7 +25,9 @@ impl Default for CullMode { /// /// Rasterizers take an iterator of vertices and emit fragment positions. They do not, by themselves, perform shader /// execution, depth testing, etc. -pub trait Rasterizer { +pub trait Rasterizer: Default { + type Config; + /// Rasterize the given vertices into fragments. /// /// - `target_size`: The size of the render target(s) in pixels @@ -45,6 +47,7 @@ pub trait Rasterizer { vertices: I, target_size: [usize; 2], principal_x: bool, + config: Self::Config, emit_fragment: F, ) where diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index eaf2c6e..20b0f4f 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -1,18 +1,21 @@ use super::*; -use crate::CoordinateMode; +use crate::Handedness; use vek::*; /// A rasterizer that produces filled triangles. #[derive(Copy, Clone, Debug, Default)] -pub struct Triangles(pub CullMode); +pub struct Triangles; impl Rasterizer for Triangles { + type Config = CullMode; + unsafe fn rasterize( &self, pipeline: &P, mut vertices: I, target_size: [usize; 2], principal_x: bool, + cull_mode: CullMode, mut emit_fragment: F, ) where @@ -20,15 +23,17 @@ impl Rasterizer for Triangles { I: Iterator, F: FnMut([usize; 2], &[f32], &[P::VsOut], f32), { - let cull_dir = match self.0 { + let cull_dir = match cull_mode { CullMode::None => None, CullMode::Back => Some(1.0), CullMode::Front => Some(-1.0), }; - let flip = match pipeline.coordinate_mode() { - CoordinateMode::Left => Vec2::new(1.0, 1.0), - CoordinateMode::Right => Vec2::new(1.0, -1.0), + let coord_mode = pipeline.coordinate_mode(); + + let flip = match coord_mode.handedness { + Handedness::Left => Vec2::new(1.0, 1.0), + Handedness::Right => Vec2::new(1.0, -1.0), }; let size = Vec2::from(target_size).map(|e: usize| e as f32); @@ -60,7 +65,7 @@ impl Rasterizer for Triangles { // Culling and correcting for winding let (verts_hom, verts_euc, verts_out) = if cull_dir - .map(|cull_dir| winding * cull_dir > 0.0) + .map(|cull_dir| winding * cull_dir < 0.0) .unwrap_or(false) { continue; // Cull the triangle @@ -126,7 +131,10 @@ impl Rasterizer for Triangles { // Calculate the interpolated z coordinate for the depth target let z: f32 = verts_hom.map2(w, |v, w| v.z * w).sum() * w_hom.z; - emit_fragment([x, y], w.as_slice(), verts_out.as_slice(), z); + // Don't use `.contains(&z)`, it isn't inclusive + if z >= coord_mode.clip_range.start && z <= coord_mode.clip_range.end { + emit_fragment([x, y], w.as_slice(), verts_out.as_slice(), z); + } } } } diff --git a/src/sampler/mod.rs b/src/sampler/mod.rs index f457a45..129ee12 100644 --- a/src/sampler/mod.rs +++ b/src/sampler/mod.rs @@ -3,14 +3,17 @@ use crate::{ math::*, }; use core::{ - ops::{Div, Deref}, + ops::Mul, marker::PhantomData, }; -/// A trait implemented by texture samplers. +/// A trait that describes a sampler of a texture. +/// +/// Samplers use normalised coordinates (between 0 and 1) to sample textures. Often, samplers will combine this with +/// a sampling algorithm such as filtering or domain warping. pub trait Sampler where - Self::Index: Truncate<>::Index>, + Self::Index: Denormalize<>::Index>, { /// The type used to perform sampling. type Index: Clone; @@ -53,29 +56,27 @@ impl Nearest { } } -impl<'a, T: Deref, I, const N: usize> Sampler for Nearest +impl<'a, T, I, const N: usize> Sampler for Nearest where - T::Target: Texture, - I: Clone + Div + Truncate<>::Index>, + T: Texture, + I: Clone + Mul + Denormalize, { type Index = I; - type Sample = >::Texel; + type Sample = T::Texel; - type Texture = T::Target; + type Texture = T; #[inline(always)] fn raw_texture(&self) -> &Self::Texture { &self.0 } #[inline(always)] - fn sample(&self, mut index: [Self::Index; N]) -> Self::Sample { - let size = self.raw_texture().size(); - (0..N).for_each(|i| index[i] = index[i].clone() / I::detruncate(size[i].clone())); - self.raw_texture().read(index.map(|x| x.truncate())) + fn sample(&self, index: [Self::Index; N]) -> Self::Sample { + self.raw_texture().read(I::denormalize_array(index, self.raw_texture().size())) } #[inline(always)] unsafe fn sample_unchecked(&self, index: [Self::Index; N]) -> Self::Sample { - self.raw_texture().read_unchecked(index.map(|x| x.truncate())) + self.raw_texture().read_unchecked(I::denormalize_array(index, self.raw_texture().size())) } } diff --git a/src/texture.rs b/src/texture.rs index 65db3b0..b3e6f45 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -115,3 +115,36 @@ impl Texture for Empty { impl Target for Empty { fn write(&mut self, _: [usize; 2], _: Self::Texel) { panic!("Cannot write to an empty texture"); } } + +#[cfg(feature = "image")] +impl Texture<2> for image_::ImageBuffer +where + P: image_::Pixel + Clone + 'static, + C: core::ops::Deref, +{ + type Index = usize; + type Texel = P; + + fn size(&self) -> [Self::Index; 2] { + [self.width() as usize, self.height() as usize] + } + + fn read(&self, [x, y]: [Self::Index; 2]) -> Self::Texel { + self.get_pixel(x as u32, y as u32).clone() + } +} + +#[cfg(feature = "image")] +impl Target for image_::ImageBuffer +where + P: image_::Pixel + 'static, + C: core::ops::DerefMut, +{ + fn write(&mut self, [x, y]: [usize; 2], texel: Self::Texel) { + self.put_pixel(x as u32, y as u32, texel); + } + + unsafe fn write_unchecked(&mut self, [x, y]: [usize; 2], texel: Self::Texel) { + image_::GenericImage::unsafe_put_pixel(self, x as u32, y as u32, texel); + } +} From 17faedd127b4f51b9365560f9115bf354af807e8 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 19 Dec 2020 22:25:19 +0000 Subject: [PATCH 04/37] Reduced coupling between API components, fixed specular light in teapot example --- examples/spinning_cube.rs | 6 +++--- examples/teapot.rs | 27 +++++++++++++-------------- examples/texture_mapping.rs | 17 ++++++++--------- examples/triangle.rs | 6 +++--- src/pipeline.rs | 24 ++++++++++++------------ src/rasterizer/mod.rs | 11 +++++------ src/rasterizer/triangles.rs | 17 +++++++---------- src/sampler/mod.rs | 2 +- 8 files changed, 52 insertions(+), 58 deletions(-) diff --git a/examples/spinning_cube.rs b/examples/spinning_cube.rs index e690657..ad5acdb 100644 --- a/examples/spinning_cube.rs +++ b/examples/spinning_cube.rs @@ -7,17 +7,17 @@ struct Cube { impl Pipeline for Cube { type Vertex = (Vec4, Rgba); - type VsOut = Rgba; + type VertexAttr = Rgba; type Primitives = TriangleList; type Fragment = u32; #[inline(always)] - fn vertex_shader(&self, (pos, color): &Self::Vertex) -> ([f32; 4], Self::VsOut) { + fn vertex_shader(&self, (pos, color): &Self::Vertex) -> ([f32; 4], Self::VertexAttr) { ((self.mvp * *pos).into_array(), *color) } #[inline(always)] - fn fragment_shader(&self, color: Self::VsOut) -> Self::Fragment { + fn fragment_shader(&self, color: Self::VertexAttr) -> Self::Fragment { u32::from_le_bytes((color * 255.0).as_().into_array()) } } diff --git a/examples/teapot.rs b/examples/teapot.rs index ba5d7dd..33c01d7 100644 --- a/examples/teapot.rs +++ b/examples/teapot.rs @@ -11,46 +11,44 @@ struct Teapot<'a> { } #[derive(Add, Mul, Clone)] -struct VsOut { +struct VertexAttr { wpos: Vec3, wnorm: Vec3, } impl<'a> Pipeline for Teapot<'a> { type Vertex = wavefront::Vertex<'a>; - type VsOut = VsOut; + type VertexAttr = VertexAttr; type Primitives = TriangleList; type Fragment = u32; fn depth_mode(&self) -> DepthMode { DepthMode::LESS_WRITE } #[inline(always)] - fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VsOut) { + fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexAttr) { let wpos = self.m * Vec4::from_point(Vec3::from(vertex.position())); let wnorm = self.m * Vec4::from_direction(-Vec3::from(vertex.normal().unwrap())); ( (self.p * self.v * wpos).into_array(), - VsOut { wpos: wpos.xyz(), wnorm: wnorm.xyz() }, + VertexAttr { wpos: wpos.xyz(), wnorm: wnorm.xyz() }, ) } #[inline(always)] - fn fragment_shader(&self, VsOut { wpos, wnorm }: Self::VsOut) -> Self::Fragment { + fn fragment_shader(&self, VertexAttr { wpos, wnorm }: Self::VertexAttr) -> Self::Fragment { let wnorm = wnorm.normalized(); - let light_dir = Vec3::::new(0.0, 1.0, 1.0).normalized(); + let light_dir = Vec3::::new(1.0, 1.0, 1.0).normalized(); let cam_pos = Vec3::zero(); let cam_dir = (wpos - cam_pos).normalized(); - let surf_color = Rgba::new(1.0, 0.35, 0.3, 1.0); + let surf_color = Rgba::new(0.8, 1.0, 0.7, 1.0); // Phong reflection model let ambient = 0.1; let diffuse = wnorm.dot(-light_dir).max(0.0) * 0.5; - let specular = light_dir.reflected(wnorm).dot(cam_dir).max(0.0).powf(150.0) * 5.0; - let light = ambient + diffuse + specular; + let specular = light_dir.reflected(wnorm).dot(-cam_dir).max(0.0).powf(30.0) * 3.0; - let color = surf_color * light; - - u32::from_le_bytes(color.map(|e| e.min(1.0) * 255.0).as_().into_array()) + let color = surf_color * (ambient + diffuse + specular); + u32::from_le_bytes(color.map(|e| e.clamped(0.0, 1.0) * 255.0).as_().into_array()) } } @@ -67,8 +65,9 @@ fn main() { let mut i = 0; win.glutin_handle_basic_input(|win, input| { let p = Mat4::perspective_fov_lh_zo(1.3, w as f32, h as f32, 0.01, 100.0); - let v = Mat4::translation_3d(Vec3::new(0.0, 0.0, 6.0)); - let m = Mat4::rotation_x((i as f32 * 0.03).sin() * 0.4) + let v = Mat4::identity(); + let m = Mat4::::translation_3d(Vec3::new(0.0, 0.0, 6.0)) + * Mat4::rotation_x((i as f32 * 0.03).sin() * 0.4) * Mat4::rotation_y((i as f32 * 0.005) * 4.0) * Mat4::rotation_z((i as f32 * 0.04).cos() * 0.4); diff --git a/examples/texture_mapping.rs b/examples/texture_mapping.rs index c97de80..562dbe0 100644 --- a/examples/texture_mapping.rs +++ b/examples/texture_mapping.rs @@ -11,12 +11,12 @@ struct Cube<'a> { impl<'a> Pipeline for Cube<'a> { type Vertex = usize; - type VsOut = Vec2; + type VertexAttr = Vec2; type Primitives = TriangleList; type Fragment = u32; #[inline] - fn vertex_shader(&self, v_index: &Self::Vertex) -> ([f32; 4], Self::VsOut) { + fn vertex_shader(&self, v_index: &Self::Vertex) -> ([f32; 4], Self::VertexAttr) { ( (self.mvp * self.positions[*v_index]).into_array(), self.uvs[*v_index], @@ -24,7 +24,7 @@ impl<'a> Pipeline for Cube<'a> { } #[inline] - fn fragment_shader(&self, v_uv: Self::VsOut) -> Self::Fragment { + fn fragment_shader(&self, v_uv: Self::VertexAttr) -> Self::Fragment { u32::from_le_bytes(self.sampler.sample(v_uv.into_array()).0) } } @@ -132,12 +132,11 @@ fn main() { }; cube.render( &[ - // z = 1 - 0, 3, 1, 1, 3, 2, // z = -1 - 4, 5, 7, 5, 6, 7, // y = 1 - 8, 11, 9, 9, 11, 10, // y = -1, - 12, 13, 15, 13, 14, 15, // x = 1, - 16, 17, 19, 17, 18, 19, // x = -1, + 0, 3, 1, 1, 3, 2, + 4, 5, 7, 5, 6, 7, + 8, 11, 9, 9, 11, 10, + 12, 13, 15, 13, 14, 15, + 16, 17, 19, 17, 18, 19, 20, 23, 21, 21, 23, 22, ], CullMode::Back, diff --git a/examples/triangle.rs b/examples/triangle.rs index 00a24c3..d0b233d 100644 --- a/examples/triangle.rs +++ b/examples/triangle.rs @@ -11,21 +11,21 @@ struct Triangle; impl Pipeline for Triangle { type Vertex = [f32; 4]; - type VsOut = Vec2; + type VertexAttr = Vec2; type Primitives = TriangleList; type Fragment = u32; // Vertex shader // - Returns the 3D vertex location, and the VsOut value to be passed to the fragment shader #[inline(always)] - fn vertex_shader(&self, pos: &[f32; 4]) -> ([f32; 4], Self::VsOut) { + fn vertex_shader(&self, pos: &[f32; 4]) -> ([f32; 4], Self::VertexAttr) { (*pos, Vec2::new(pos[0], pos[1])) } // Fragment shader // - Returns (in this case) a u32 #[inline(always)] - fn fragment_shader(&self, xy: Self::VsOut) -> Self::Fragment { + fn fragment_shader(&self, xy: Self::VertexAttr) -> Self::Fragment { let bytes = [(xy.x * 255.0) as u8, (xy.y * 255.0) as u8, 0, 255]; // Red (bytes[0] as u32) << 0 diff --git a/src/pipeline.rs b/src/pipeline.rs index 1ccf3bd..9f7bbc6 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -102,8 +102,8 @@ impl Default for CoordinateMode { /// the behaviour of the pipeline even further. pub trait Pipeline: Sized { type Vertex; - type VsOut: Clone + Mul + Add; - type Primitives: PrimitiveKind; + type VertexAttr: Clone + Mul + Add; + type Primitives: PrimitiveKind; type Fragment; /// Returns the [`DepthMode`] of this pipeline. @@ -113,25 +113,25 @@ pub trait Pipeline: Sized { fn coordinate_mode(&self) -> CoordinateMode { CoordinateMode::default() } /// Transforms a [`Pipeline::Vertex`] into homogeneous NDCs (Normalised Device Coordinates) for the vertex and a - /// [`Pipeline::VsOut`] to be interpolated and passed to the fragment shader. + /// [`Pipeline::VertexAttr`] to be interpolated and passed to the fragment shader. /// /// This stage is executed at the beginning of pipeline execution. - fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VsOut); + fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexAttr); /// Turn a primitive into many primitives. /// /// This stage sits between the vertex shader and the fragment shader. - fn geometry_shader(&self, primitive: >::Primitive, mut output: O) + fn geometry_shader(&self, primitive: >::Primitive, mut output: O) where - O: FnMut(>::Primitive), + O: FnMut(>::Primitive), { output(primitive); } - /// Transforms a [`Pipeline::VsOut`] into a fragment to be rendered to a pixel target. + /// Transforms a [`Pipeline::VertexAttr`] into a fragment to be rendered to a pixel target. /// /// This stage is executed for every fragment generated by the rasterizer. - fn fragment_shader(&self, vs_out: Self::VsOut) -> Self::Fragment; + fn fragment_shader(&self, vs_out: Self::VertexAttr) -> Self::Fragment; /// Blend an old fragment with a new fragment. /// @@ -148,7 +148,7 @@ pub trait Pipeline: Sized { fn render<'a, S, V, P, D>( &'a self, vertices: S, - rasterizer_config: <>::Rasterizer as Rasterizer>::Config, + rasterizer_config: <>::Rasterizer as Rasterizer>::Config, mut pixels: P, mut depth: D, ) @@ -186,7 +186,7 @@ pub trait Pipeline: Sized { } }; - let emit_fragment = move |pos, w: &[f32], vs_out: &[Self::VsOut], z: f32| { + let emit_fragment = move |pos, w: &[f32], vs_out: &[Self::VertexAttr], z: f32| { // Should we attempt to render the fragment at all? let should_render = if let Some(test) = depth_mode.test { let old_z = unsafe { depth.read_unchecked(pos) }; @@ -213,11 +213,11 @@ pub trait Pipeline: Sized { }; unsafe { - >::Rasterizer::default().rasterize( - self, + >::Rasterizer::default().rasterize( core::iter::from_fn(fetch_vertex), target_size, principal_x, + self.coordinate_mode(), rasterizer_config, emit_fragment, ); diff --git a/src/rasterizer/mod.rs b/src/rasterizer/mod.rs index 1eecab7..ed074cd 100644 --- a/src/rasterizer/mod.rs +++ b/src/rasterizer/mod.rs @@ -2,7 +2,7 @@ pub mod triangles; pub use self::triangles::Triangles; -use crate::Pipeline; +use crate::CoordinateMode; /// The face culling strategy used during rendering. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] @@ -41,17 +41,16 @@ pub trait Rasterizer: Default { /// /// `emit_fragment` must only be called with fragment positions that are valid for the `target_size` parameter /// provided. Undefined behaviour can be assumed to occur if this is not upheld. - unsafe fn rasterize( + unsafe fn rasterize( &self, - pipeline: &P, vertices: I, target_size: [usize; 2], principal_x: bool, + coordinate_mode: CoordinateMode, config: Self::Config, emit_fragment: F, ) where - P: Pipeline, - I: Iterator, - F: FnMut([usize; 2], &[f32], &[P::VsOut], f32); + I: Iterator, + F: FnMut([usize; 2], &[f32], &[V], f32); } diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index 20b0f4f..f05bc8e 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -1,5 +1,5 @@ use super::*; -use crate::Handedness; +use crate::{Handedness, CoordinateMode}; use vek::*; /// A rasterizer that produces filled triangles. @@ -9,19 +9,18 @@ pub struct Triangles; impl Rasterizer for Triangles { type Config = CullMode; - unsafe fn rasterize( + unsafe fn rasterize( &self, - pipeline: &P, mut vertices: I, target_size: [usize; 2], principal_x: bool, + coordinate_mode: CoordinateMode, cull_mode: CullMode, mut emit_fragment: F, ) where - P: Pipeline, - I: Iterator, - F: FnMut([usize; 2], &[f32], &[P::VsOut], f32), + I: Iterator, + F: FnMut([usize; 2], &[f32], &[V], f32), { let cull_dir = match cull_mode { CullMode::None => None, @@ -29,9 +28,7 @@ impl Rasterizer for Triangles { CullMode::Front => Some(-1.0), }; - let coord_mode = pipeline.coordinate_mode(); - - let flip = match coord_mode.handedness { + let flip = match coordinate_mode.handedness { Handedness::Left => Vec2::new(1.0, 1.0), Handedness::Right => Vec2::new(1.0, -1.0), }; @@ -132,7 +129,7 @@ impl Rasterizer for Triangles { let z: f32 = verts_hom.map2(w, |v, w| v.z * w).sum() * w_hom.z; // Don't use `.contains(&z)`, it isn't inclusive - if z >= coord_mode.clip_range.start && z <= coord_mode.clip_range.end { + if z >= coordinate_mode.clip_range.start && z <= coordinate_mode.clip_range.end { emit_fragment([x, y], w.as_slice(), verts_out.as_slice(), z); } } diff --git a/src/sampler/mod.rs b/src/sampler/mod.rs index 129ee12..c4fe841 100644 --- a/src/sampler/mod.rs +++ b/src/sampler/mod.rs @@ -72,7 +72,7 @@ where #[inline(always)] fn sample(&self, index: [Self::Index; N]) -> Self::Sample { - self.raw_texture().read(I::denormalize_array(index, self.raw_texture().size())) + unsafe { self.raw_texture().read_unchecked(I::denormalize_array(index, self.raw_texture().size())) } } #[inline(always)] From 487667c3d3dee2dd40d5eb587c8ea69ab207b2f1 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sun, 20 Dec 2020 02:22:07 +0000 Subject: [PATCH 05/37] Optimised triangle rasteriser, documented unsafe APIs --- Cargo.toml | 4 +- src/buffer.rs | 23 ++++++-- src/lib.rs | 2 +- src/pipeline.rs | 91 +++++++++++++++++------------ src/rasterizer/mod.rs | 12 ++-- src/rasterizer/triangles.rs | 111 ++++++++++++++++++++++-------------- src/texture.rs | 85 +++++++++++++++++++-------- 7 files changed, 212 insertions(+), 116 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 064c446..af3ba27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,14 +16,16 @@ exclude = [ [dependencies] vek = { version = "0.12", default-features = false, features = [] } image_ = { package = "image", version = "0.23", optional = true } +rayon = { version = "1.5", optional = true } [features] -default = ["std", "simd", "image"] +default = ["std", "simd", "image", "par"] std = ["vek/std"] libm = ["vek/libm"] nightly = [] simd = ["vek/repr_simd"] image = ["image_"] +par = ["rayon"] [dev-dependencies] mini_gl_fb = "0.7.0" diff --git a/src/buffer.rs b/src/buffer.rs index 5592f76..10f9a2e 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,5 +1,6 @@ use crate::texture::{Texture, Target}; use alloc::vec::Vec; +use core::cell::UnsafeCell; /// A generic 1-dimensional buffer that may be used as a texture. pub type Buffer1d = Buffer; @@ -78,17 +79,29 @@ impl Texture for Buffer { impl Target for Buffer { #[inline(always)] - fn write(&mut self, index: [usize; 2], texel: Self::Texel) { - let idx = self.linear_index(index); - self.items[idx] = texel; + unsafe fn read_exclusive_unchecked(&self, index: [Self::Index; 2]) -> Self::Texel { + // TODO: This is *technically* UB since the `self` reference is still live in this context. + // Really, this cast is incorrect and the `items` `Vec` should just contain `UnsafeCell`. + let items = &*(self.items.as_slice() as *const _ as *const [UnsafeCell]); + (&*items.get_unchecked(self.linear_index(index)).get()).clone() + } + + #[inline(always)] + unsafe fn write_exclusive_unchecked(&self, index: [usize; 2], texel: Self::Texel) { + // TODO: This is *technically* UB since the `self` reference is still live in this context. + // Really, this cast is incorrect and the `items` `Vec` should just contain `UnsafeCell`. + let items = &*(self.items.as_slice() as *const _ as *const [UnsafeCell]); + *items.get_unchecked(self.linear_index(index)).get() = texel; } + #[inline(always)] - unsafe fn write_unchecked(&mut self, index: [usize; 2], texel: Self::Texel) { + fn write(&mut self, index: [usize; 2], texel: Self::Texel) { let idx = self.linear_index(index); - *self.items.get_unchecked_mut(idx) = texel; + self.items[idx] = texel; } + #[inline(always)] fn clear(&mut self, texel: Self::Texel) { self.items.iter_mut().for_each(|item| *item = texel.clone()); } diff --git a/src/lib.rs b/src/lib.rs index 918cab5..f49d699 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,7 +64,7 @@ pub mod texture; // Reexports pub use crate::{ buffer::{Buffer, Buffer1d, Buffer2d, Buffer3d, Buffer4d}, - pipeline::{Pipeline, DepthMode, CoordinateMode, Handedness}, + pipeline::{Pipeline, DepthMode, CoordinateMode, Handedness, YAxisDirection}, primitives::TriangleList, texture::{Texture, Target, Empty}, rasterizer::CullMode, diff --git a/src/pipeline.rs b/src/pipeline.rs index 9f7bbc6..9497709 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -46,12 +46,6 @@ impl DepthMode { }; } -impl Default for DepthMode { - fn default() -> Self { - Self::LESS_WRITE - } -} - impl DepthMode { /// Determine whether the depth mode needs to interact with the depth target at all. pub fn uses_depth(&self) -> bool { @@ -62,29 +56,55 @@ impl DepthMode { /// The handedness of the coordinate space used by a pipeline. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum Handedness { - /// Left-handed coordinate space + /// Left-handed coordinate space (used by Vulkan and DirectX) Left, - /// Right-handed coordinate space + /// Right-handed coordinate space (used by OpenGL and Metal) Right, } +/// The direction represented by +y in screen space. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum YAxisDirection { + // +y points down towards the bottom of the screen (i.e: -y is up). + Down, + // +y points up towards the top of the screen (i.e: -y is down). + Up, +} + /// The configuration of the coordinate system used by a pipeline. pub struct CoordinateMode { pub handedness: Handedness, - pub clip_range: Range, + pub y_axis_direction: YAxisDirection, + pub z_clip_range: Range, } impl CoordinateMode { - /// OpenGL-like coordinates + /// OpenGL-like coordinates (right-handed, y = up, -1 to 1 z clip range). pub const OPENGL: Self = Self { handedness: Handedness::Right, - clip_range: -1.0..1.0, + y_axis_direction: YAxisDirection::Up, + z_clip_range: -1.0..1.0, }; - /// Vulkan-like coordinates + /// Vulkan-like coordinates (left-handed, y = down, 0 to 1 z clip range). pub const VULKAN: Self = Self { handedness: Handedness::Left, - clip_range: 0.0..1.0, + y_axis_direction: YAxisDirection::Down, + z_clip_range: 0.0..1.0, + }; + + /// Metal-like coordinates (right-handed, y = down, 0 to 1 z clip range). + pub const METAL: Self = Self { + handedness: Handedness::Right, + y_axis_direction: YAxisDirection::Down, + z_clip_range: 0.0..1.0, + }; + + /// DirectX-like coordinates (left-handed, y = up, 0 to 1 z clip range). + pub const DIRECTX: Self = Self { + handedness: Handedness::Left, + y_axis_direction: YAxisDirection::Up, + z_clip_range: 0.0..1.0, }; } @@ -102,7 +122,7 @@ impl Default for CoordinateMode { /// the behaviour of the pipeline even further. pub trait Pipeline: Sized { type Vertex; - type VertexAttr: Clone + Mul + Add; + type VertexAttr: Clone + Mul + Add + Send + Sync; type Primitives: PrimitiveKind; type Fragment; @@ -149,14 +169,15 @@ pub trait Pipeline: Sized { &'a self, vertices: S, rasterizer_config: <>::Rasterizer as Rasterizer>::Config, - mut pixels: P, - mut depth: D, + pixels: &mut P, + depth: &mut D, ) where + Self: Send + Sync, S: IntoIterator, V: Borrow, - P: Target + 'a, - D: Target + 'a, + P: Target + Send + Sync + 'a, + D: Target + Send + Sync + 'a, { let depth_mode = self.depth_mode(); let target_size = pixels.size(); @@ -186,29 +207,26 @@ pub trait Pipeline: Sized { } }; - let emit_fragment = move |pos, w: &[f32], vs_out: &[Self::VertexAttr], z: f32| { - // Should we attempt to render the fragment at all? - let should_render = if let Some(test) = depth_mode.test { - let old_z = unsafe { depth.read_unchecked(pos) }; + let pixels = &*pixels; + let depth = &*depth; + + let test_depth = move |pos, z: f32| { + if let Some(test) = depth_mode.test { + let old_z = unsafe { depth.read_exclusive_unchecked(pos) }; z.partial_cmp(&old_z) == Some(test) } else { true - }; - - if should_render { - let vs_out_lerped = w[1..] - .iter() - .zip(vs_out[1..].iter()) - .fold(vs_out[0].clone() * w[0], |acc, (w, vs_out)| acc + vs_out.clone() * *w); + } + }; - let frag = self.fragment_shader(vs_out_lerped); - let old_px = unsafe { pixels.read_unchecked(pos) }; - let blended_px = self.blend_shader(old_px, frag); - unsafe { pixels.write_unchecked(pos, blended_px); } + let emit_fragment = move |pos, vs_out_lerped: Self::VertexAttr, z: f32| { + let frag = self.fragment_shader(vs_out_lerped); + let old_px = unsafe { pixels.read_exclusive_unchecked(pos) }; + let blended_px = self.blend_shader(old_px, frag); + unsafe { pixels.write_exclusive_unchecked(pos, blended_px); } - if depth_mode.write { - unsafe { depth.write_unchecked(pos, z); } - } + if depth_mode.write { + unsafe { depth.write_exclusive_unchecked(pos, z); } } }; @@ -219,6 +237,7 @@ pub trait Pipeline: Sized { principal_x, self.coordinate_mode(), rasterizer_config, + test_depth, emit_fragment, ); } diff --git a/src/rasterizer/mod.rs b/src/rasterizer/mod.rs index ed074cd..4ce9fe2 100644 --- a/src/rasterizer/mod.rs +++ b/src/rasterizer/mod.rs @@ -3,6 +3,7 @@ pub mod triangles; pub use self::triangles::Triangles; use crate::CoordinateMode; +use core::ops::{Mul, Add}; /// The face culling strategy used during rendering. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] @@ -25,7 +26,7 @@ impl Default for CullMode { /// /// Rasterizers take an iterator of vertices and emit fragment positions. They do not, by themselves, perform shader /// execution, depth testing, etc. -pub trait Rasterizer: Default { +pub trait Rasterizer: Default + Send + Sync { type Config; /// Rasterize the given vertices into fragments. @@ -41,16 +42,19 @@ pub trait Rasterizer: Default { /// /// `emit_fragment` must only be called with fragment positions that are valid for the `target_size` parameter /// provided. Undefined behaviour can be assumed to occur if this is not upheld. - unsafe fn rasterize( + unsafe fn rasterize( &self, vertices: I, target_size: [usize; 2], principal_x: bool, coordinate_mode: CoordinateMode, config: Self::Config, - emit_fragment: F, + test_depth: F, + emit_fragment: G, ) where + V: Clone + Mul + Add + Send + Sync, I: Iterator, - F: FnMut([usize; 2], &[f32], &[V], f32); + F: Fn([usize; 2], f32) -> bool + Send + Sync, + G: Fn([usize; 2], V, f32) + Send + Sync; } diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index f05bc8e..9682442 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -1,5 +1,7 @@ use super::*; -use crate::{Handedness, CoordinateMode}; +use crate::{CoordinateMode, YAxisDirection}; +use core::ops::{Mul, Add}; +use alloc::vec::Vec; use vek::*; /// A rasterizer that produces filled triangles. @@ -9,18 +11,21 @@ pub struct Triangles; impl Rasterizer for Triangles { type Config = CullMode; - unsafe fn rasterize( + unsafe fn rasterize( &self, mut vertices: I, target_size: [usize; 2], principal_x: bool, coordinate_mode: CoordinateMode, cull_mode: CullMode, - mut emit_fragment: F, + test_depth: F, + emit_fragment: G, ) where + V: Clone + Mul + Add + Send + Sync, I: Iterator, - F: FnMut([usize; 2], &[f32], &[V], f32), + F: Fn([usize; 2], f32) -> bool + Send + Sync, + G: Fn([usize; 2], V, f32) + Send + Sync, { let cull_dir = match cull_mode { CullMode::None => None, @@ -28,9 +33,9 @@ impl Rasterizer for Triangles { CullMode::Front => Some(-1.0), }; - let flip = match coordinate_mode.handedness { - Handedness::Left => Vec2::new(1.0, 1.0), - Handedness::Right => Vec2::new(1.0, -1.0), + let flip = match coordinate_mode.y_axis_direction { + YAxisDirection::Down => Vec2::new(1.0, 1.0), + YAxisDirection::Up => Vec2::new(1.0, -1.0), }; let size = Vec2::from(target_size).map(|e: usize| e as f32); @@ -41,13 +46,11 @@ impl Rasterizer for Triangles { [0.0, 0.0, 1.0], ]); - loop { - let verts_hom_out = Vec3::new( - if let Some(v) = vertices.next() { v } else { break }, - if let Some(v) = vertices.next() { v } else { break }, - if let Some(v) = vertices.next() { v } else { break }, - ); + let verts_hom_outs = core::iter::from_fn(move || { + Some(Vec3::new(vertices.next()?, vertices.next()?, vertices.next()?)) + }); + verts_hom_outs.for_each(|verts_hom_out: Vec3<([f32; 4], V)>| { // Calculate vertex shader outputs and vertex homogeneous coordinates let verts_hom = Vec3::new(verts_hom_out.x.0, verts_hom_out.y.0, verts_hom_out.z.0).map(Vec4::::from); let verts_out = Vec3::new(verts_hom_out.x.1, verts_hom_out.y.1, verts_hom_out.z.1); @@ -65,7 +68,7 @@ impl Rasterizer for Triangles { .map(|cull_dir| winding * cull_dir < 0.0) .unwrap_or(false) { - continue; // Cull the triangle + return; // Cull the triangle } else if winding < 0.0 { // Reverse vertex order (verts_hom.zyx(), verts_euc.zyx(), verts_out.zyx()) @@ -100,39 +103,59 @@ impl Rasterizer for Triangles { max: Vec2::min(verts_screen.reduce(|a, b| Vec2::partial_max(a, b)).as_() + 1, Vec2::from(target_size) - 1), }; - // Choose an iteration order based on the principal axis - let (xs, ys) = ( - (tri_bounds.min.x, tri_bounds.max.x), - (tri_bounds.min.y, tri_bounds.max.y), - ); - let coords = (if principal_x { ys.0..ys.1 } else { xs.0..xs.1 }) - .map(|j| (if principal_x { xs.0..xs.1 } else { ys.0..ys.1 }) - .map(move |i| if principal_x { (i, j) } else { (j, i) })) - .flatten(); - // Iterate over fragment candidates within the triangle's bounding box - for (x, y) in coords { - // Calculate fragment center - let p = Vec3::new(x as f32 + 0.5, y as f32 + 0.5, 1.0); - - // Calculate vertex weights to determine vs_out lerping and intersection - let w_hom = coords_to_weights * p; - let w = Vec2::new(w_hom.x / w_hom.z, w_hom.y / w_hom.z); - let w = Vec3::new(w.x, w.y, 1.0 - w.x - w.y); - - // Test the weights to determine whether the fragment is outside the triangle - if w.map(|e| e < 0.0).reduce_or() { - continue; - } + (tri_bounds.min.y..tri_bounds.max.y).for_each(|y| { + // Only perform this optimisation if it looks to be worth it. + let row_range = if tri_bounds.max.x - tri_bounds.min.x > 8 { + // Calculate a precise for which the pixels of the triangle appear on this row + let edges = verts_screen.zip(Vec3::new(verts_screen.y, verts_screen.z, verts_screen.x)); + let row_range = edges + .map(|(a, b)| { + let x = Lerp::lerp(a.x, b.x, (y as f32 - a.y) / (b.y - a.y)); + let x2 = Lerp::lerp(a.x, b.x, (y as f32 + 1.0 - a.y) / (b.y - a.y)); + let (x_min, x_max) = (x.min(x2), x.max(x2)); + Vec2::new( + if x < tri_bounds.min.x as f32 { tri_bounds.max.x as f32 } else { x_min }, + if x > tri_bounds.max.x as f32 { tri_bounds.min.x as f32 } else { x_max }, + ) + }) + .reduce(|a, b| Vec2::new(a.x.min(b.x), a.y.max(b.y))) + .map(|e| e as usize); + Vec2::new( + row_range.x.saturating_sub(1), + (row_range.y + 1).min(target_size[0]), + ) + } else { + Vec2::new(tri_bounds.min.x, tri_bounds.max.x) + }; + + for x in row_range.x..row_range.y { + // Calculate fragment center + let p = Vec3::new(x as f32 + 0.5, y as f32 + 0.5, 1.0); + + // Calculate vertex weights to determine vs_out lerping and intersection + let w_hom = coords_to_weights * p; + let w = Vec2::new(w_hom.x / w_hom.z, w_hom.y / w_hom.z); + let w = Vec3::new(w.x, w.y, 1.0 - w.x - w.y); + + // Test the weights to determine whether the fragment is outside the triangle + if w.map(|e| e < 0.0).reduce_or() { + continue; + } + + // Calculate the interpolated z coordinate for the depth target + let z: f32 = verts_hom.map2(w, |v, w| v.z * w).sum() * w_hom.z; - // Calculate the interpolated z coordinate for the depth target - let z: f32 = verts_hom.map2(w, |v, w| v.z * w).sum() * w_hom.z; + // Don't use `.contains(&z)`, it isn't inclusive + if z >= coordinate_mode.z_clip_range.start && z <= coordinate_mode.z_clip_range.end { + if test_depth([x, y], z) { + let vert_out_lerped = verts_out.clone().map2(w, |vo, w| vo * w).sum(); - // Don't use `.contains(&z)`, it isn't inclusive - if z >= coordinate_mode.clip_range.start && z <= coordinate_mode.clip_range.end { - emit_fragment([x, y], w.as_slice(), verts_out.as_slice(), z); + emit_fragment([x, y], vert_out_lerped, z); + } + } } - } - } + }); + }); } } diff --git a/src/texture.rs b/src/texture.rs index b3e6f45..2242ea8 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -61,23 +61,55 @@ impl<'a, T: Texture, const N: usize> Texture for &'a mut T { } /// A trait implemented by 2-dimensional textures that may be treated as render targets. +/// +/// Targets necessarily require additional invariants to be upheld than textures for safe use. Because access to them +/// may be parallelised, it is essential that there is a 1:1 mapping between each index and a unique memory location. +/// If this is not upheld, Rust's 1 writer / many readers aliasing model may be broken. The `read_exclusive_unchecked` +/// and `write_exclusive_unchecked` methods may only be called by callers that have already ensured that nothing else +/// can access the target at the same time. In addition, the target must guarantee that no reads or writes escape +/// either method. This can be done by having each texel be an `UnsafeCell`. pub trait Target: Texture<2, Index = usize> { - /// Write a texel at the given index. + /// Read a texel at the given assumed-valid index. /// - /// # Panics + /// # Safety /// - /// The behaviour of this function is *unspecified* (but not *undefined*) when the index is out of bounds. The - /// implementation is free to panic, write to an entirely different texel, or do nothing. - fn write(&mut self, index: [usize; 2], texel: Self::Texel); + /// If the index is invalid, undefined behaviour can be assumed to occur. Ensure that the index is valid before + /// use. Access to this index *must* be exclusive to avoid undefined behaviour (i.e: nothing else may be reading or + /// writing to this index during the duration of this call). The caller must enforce this through a lock or some + /// other such mechanism with mutual exclusion properties. + unsafe fn read_exclusive_unchecked(&self, index: [Self::Index; 2]) -> Self::Texel; /// Write a texel at the given assumed-valid index. /// /// # Safety /// /// If the index is invalid, undefined behaviour can be assumed to occur. Ensure that the index is valid before - /// use. + /// use. Access to this index *must* be exclusive to avoid undefined behaviour (i.e: nothing else may be reading or + /// writing to this index during the duration of this call). The caller must enforce this through a lock or some + /// other such mechanism with mutual exclusion properties. + unsafe fn write_exclusive_unchecked(&self, index: [usize; 2], texel: Self::Texel); + + /// Write a texel at the given assumed-valid index. + /// + /// # Safety + /// + /// If the index is invalid, undefined behaviour can be assumed to occur. Ensure that the index is valid before + /// use. Access to this index *must* be exclusive to avoid undefined behaviour (i.e: nothing else may be reading or + /// writing to this index during the duration of this call). unsafe fn write_unchecked(&mut self, index: [usize; 2], texel: Self::Texel) { - self.write(index, texel); + self.write_exclusive_unchecked(index, texel); + } + + /// Write a texel at the given index. + /// + /// # Panics + /// + /// The behaviour of this function is *unspecified* (but not *undefined*) when the index is out of bounds. The + /// implementation is free to panic, write to an entirely different texel, or do nothing. + fn write(&mut self, [x, y]: [usize; 2], texel: Self::Texel) { + if x < self.size()[0] && y < self.size()[1] { + unsafe { self.write_unchecked([x, y], texel); } + } } /// Clears the entire target with the given texel. @@ -91,9 +123,11 @@ pub trait Target: Texture<2, Index = usize> { } impl<'a, T: Target> Target for &'a mut T { - fn write(&mut self, index: [usize; 2], texel: Self::Texel) { (**self).write(index, texel); } - unsafe fn write_unchecked(&mut self, index: [usize; 2], texel: Self::Texel) { (**self).write_unchecked(index, texel); } - fn clear(&mut self, texel: Self::Texel) { (**self).clear(texel); } + unsafe fn read_exclusive_unchecked(&self, index: [Self::Index; 2]) -> Self::Texel { T::read_exclusive_unchecked(self, index) } + unsafe fn write_exclusive_unchecked(&self, index: [usize; 2], texel: Self::Texel) { T::write_exclusive_unchecked(self, index, texel) } + unsafe fn write_unchecked(&mut self, index: [usize; 2], texel: Self::Texel) { T::write_unchecked(self, index, texel) } + fn write(&mut self, index: [usize; 2], texel: Self::Texel) { T::write(self, index, texel); } + fn clear(&mut self, texel: Self::Texel) { T::clear(self, texel); } } /// An always-empty texture. Useful as a placeholder for an unused target. @@ -113,7 +147,8 @@ impl Texture for Empty { } impl Target for Empty { - fn write(&mut self, _: [usize; 2], _: Self::Texel) { panic!("Cannot write to an empty texture"); } + unsafe fn read_exclusive_unchecked(&self, _: [Self::Index; 2]) -> Self::Texel { panic!("Cannot read from an empty texture"); } + unsafe fn write_exclusive_unchecked(&self, _: [usize; 2], _: Self::Texel) { panic!("Cannot write to an empty texture"); } } #[cfg(feature = "image")] @@ -134,17 +169,17 @@ where } } -#[cfg(feature = "image")] -impl Target for image_::ImageBuffer -where - P: image_::Pixel + 'static, - C: core::ops::DerefMut, -{ - fn write(&mut self, [x, y]: [usize; 2], texel: Self::Texel) { - self.put_pixel(x as u32, y as u32, texel); - } - - unsafe fn write_unchecked(&mut self, [x, y]: [usize; 2], texel: Self::Texel) { - image_::GenericImage::unsafe_put_pixel(self, x as u32, y as u32, texel); - } -} +// #[cfg(feature = "image")] +// impl Target for image_::ImageBuffer +// where +// P: image_::Pixel + 'static, +// C: core::ops::DerefMut, +// { +// fn write(&mut self, [x, y]: [usize; 2], texel: Self::Texel) { +// self.put_pixel(x as u32, y as u32, texel); +// } + +// unsafe fn write_unchecked(&mut self, [x, y]: [usize; 2], texel: Self::Texel) { +// image_::GenericImage::unsafe_put_pixel(self, x as u32, y as u32, texel); +// } +// } From aaf6081783a653038bae7b5caada01405646974a Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 22 Dec 2020 13:59:03 +0000 Subject: [PATCH 06/37] Clarified safety, removed redundant features, better API naming --- examples/spinning_cube.rs | 6 +++--- examples/teapot.rs | 10 +++++----- examples/texture_mapping.rs | 6 +++--- examples/triangle.rs | 6 +++--- src/buffer.rs | 18 ++++++++++-------- src/lib.rs | 10 +++++----- src/pipeline.rs | 25 ++++++++++++++----------- src/rasterizer/triangles.rs | 1 - src/sampler/mod.rs | 7 ++++++- src/texture.rs | 17 +++++++++-------- 10 files changed, 58 insertions(+), 48 deletions(-) diff --git a/examples/spinning_cube.rs b/examples/spinning_cube.rs index ad5acdb..dba48b4 100644 --- a/examples/spinning_cube.rs +++ b/examples/spinning_cube.rs @@ -7,17 +7,17 @@ struct Cube { impl Pipeline for Cube { type Vertex = (Vec4, Rgba); - type VertexAttr = Rgba; + type VertexData = Rgba; type Primitives = TriangleList; type Fragment = u32; #[inline(always)] - fn vertex_shader(&self, (pos, color): &Self::Vertex) -> ([f32; 4], Self::VertexAttr) { + fn vertex_shader(&self, (pos, color): &Self::Vertex) -> ([f32; 4], Self::VertexData) { ((self.mvp * *pos).into_array(), *color) } #[inline(always)] - fn fragment_shader(&self, color: Self::VertexAttr) -> Self::Fragment { + fn fragment_shader(&self, color: Self::VertexData) -> Self::Fragment { u32::from_le_bytes((color * 255.0).as_().into_array()) } } diff --git a/examples/teapot.rs b/examples/teapot.rs index 33c01d7..684342e 100644 --- a/examples/teapot.rs +++ b/examples/teapot.rs @@ -11,31 +11,31 @@ struct Teapot<'a> { } #[derive(Add, Mul, Clone)] -struct VertexAttr { +struct VertexData { wpos: Vec3, wnorm: Vec3, } impl<'a> Pipeline for Teapot<'a> { type Vertex = wavefront::Vertex<'a>; - type VertexAttr = VertexAttr; + type VertexData = VertexData; type Primitives = TriangleList; type Fragment = u32; fn depth_mode(&self) -> DepthMode { DepthMode::LESS_WRITE } #[inline(always)] - fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexAttr) { + fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { let wpos = self.m * Vec4::from_point(Vec3::from(vertex.position())); let wnorm = self.m * Vec4::from_direction(-Vec3::from(vertex.normal().unwrap())); ( (self.p * self.v * wpos).into_array(), - VertexAttr { wpos: wpos.xyz(), wnorm: wnorm.xyz() }, + VertexData { wpos: wpos.xyz(), wnorm: wnorm.xyz() }, ) } #[inline(always)] - fn fragment_shader(&self, VertexAttr { wpos, wnorm }: Self::VertexAttr) -> Self::Fragment { + fn fragment_shader(&self, VertexData { wpos, wnorm }: Self::VertexData) -> Self::Fragment { let wnorm = wnorm.normalized(); let light_dir = Vec3::::new(1.0, 1.0, 1.0).normalized(); let cam_pos = Vec3::zero(); diff --git a/examples/texture_mapping.rs b/examples/texture_mapping.rs index 562dbe0..dec6e58 100644 --- a/examples/texture_mapping.rs +++ b/examples/texture_mapping.rs @@ -11,12 +11,12 @@ struct Cube<'a> { impl<'a> Pipeline for Cube<'a> { type Vertex = usize; - type VertexAttr = Vec2; + type VertexData = Vec2; type Primitives = TriangleList; type Fragment = u32; #[inline] - fn vertex_shader(&self, v_index: &Self::Vertex) -> ([f32; 4], Self::VertexAttr) { + fn vertex_shader(&self, v_index: &Self::Vertex) -> ([f32; 4], Self::VertexData) { ( (self.mvp * self.positions[*v_index]).into_array(), self.uvs[*v_index], @@ -24,7 +24,7 @@ impl<'a> Pipeline for Cube<'a> { } #[inline] - fn fragment_shader(&self, v_uv: Self::VertexAttr) -> Self::Fragment { + fn fragment_shader(&self, v_uv: Self::VertexData) -> Self::Fragment { u32::from_le_bytes(self.sampler.sample(v_uv.into_array()).0) } } diff --git a/examples/triangle.rs b/examples/triangle.rs index d0b233d..7eb47e8 100644 --- a/examples/triangle.rs +++ b/examples/triangle.rs @@ -11,21 +11,21 @@ struct Triangle; impl Pipeline for Triangle { type Vertex = [f32; 4]; - type VertexAttr = Vec2; + type VertexData = Vec2; type Primitives = TriangleList; type Fragment = u32; // Vertex shader // - Returns the 3D vertex location, and the VsOut value to be passed to the fragment shader #[inline(always)] - fn vertex_shader(&self, pos: &[f32; 4]) -> ([f32; 4], Self::VertexAttr) { + fn vertex_shader(&self, pos: &[f32; 4]) -> ([f32; 4], Self::VertexData) { (*pos, Vec2::new(pos[0], pos[1])) } // Fragment shader // - Returns (in this case) a u32 #[inline(always)] - fn fragment_shader(&self, xy: Self::VertexAttr) -> Self::Fragment { + fn fragment_shader(&self, xy: Self::VertexData) -> Self::Fragment { let bytes = [(xy.x * 255.0) as u8, (xy.y * 255.0) as u8, 0, 255]; // Red (bytes[0] as u32) << 0 diff --git a/src/buffer.rs b/src/buffer.rs index 10f9a2e..3df5f4c 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -80,18 +80,20 @@ impl Texture for Buffer { impl Target for Buffer { #[inline(always)] unsafe fn read_exclusive_unchecked(&self, index: [Self::Index; 2]) -> Self::Texel { - // TODO: This is *technically* UB since the `self` reference is still live in this context. - // Really, this cast is incorrect and the `items` `Vec` should just contain `UnsafeCell`. - let items = &*(self.items.as_slice() as *const _ as *const [UnsafeCell]); - (&*items.get_unchecked(self.linear_index(index)).get()).clone() + // This is safe to do (provided the caller has exclusive access to this buffer) because `Vec` internally uses + // a `RawVec`, which represents its internal buffer using raw pointers. Ergo, no other references to the items + // exist and so this does not break aliasing rules. + let item = self.items.get_unchecked(self.linear_index(index)) as *const _ as *const UnsafeCell; + (&*((&*item).get())).clone() } #[inline(always)] unsafe fn write_exclusive_unchecked(&self, index: [usize; 2], texel: Self::Texel) { - // TODO: This is *technically* UB since the `self` reference is still live in this context. - // Really, this cast is incorrect and the `items` `Vec` should just contain `UnsafeCell`. - let items = &*(self.items.as_slice() as *const _ as *const [UnsafeCell]); - *items.get_unchecked(self.linear_index(index)).get() = texel; + // This is safe to do (provided the caller has exclusive access to this buffer) because `Vec` internally uses + // a `RawVec`, which represents its internal buffer using raw pointers. Ergo, no other references to the items + // exist and so this does not break aliasing rules. + let item = self.items.get_unchecked(self.linear_index(index)) as *const _ as *const UnsafeCell; + *(&*item).get() = texel; } diff --git a/src/lib.rs b/src/lib.rs index f49d699..c5097eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,16 +9,16 @@ //! //! impl Pipeline for Example { //! type Vertex = [f32; 2]; -//! type VsOut = (); -//! type Pixel = [u8; 4]; +//! type VertexData = (); +//! type Fragment = [u8; 4]; //! //! // Vertex shader -//! fn vert(&self, pos: &Self::Vertex) -> ([f32; 3], Self::VsOut) { +//! fn vert(&self, pos: &Self::Vertex) -> ([f32; 3], Self::VertexData) { //! ([pos[0], pos[1], 0.0], ()) //! } //! //! // Fragment shader -//! fn frag(&self, _: &Self::VsOut) -> Self::Pixel { +//! fn frag(&self, _: Self::VertexData) -> Self::Fragment { //! [255, 0, 0, 255] // Red //! } //! } @@ -40,7 +40,7 @@ #![no_std] -#![feature(min_const_generics, alloc_prelude, array_map, type_alias_impl_trait)] +#![feature(min_const_generics, array_map, type_alias_impl_trait)] extern crate alloc; diff --git a/src/pipeline.rs b/src/pipeline.rs index 9497709..0e711fa 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -122,36 +122,39 @@ impl Default for CoordinateMode { /// the behaviour of the pipeline even further. pub trait Pipeline: Sized { type Vertex; - type VertexAttr: Clone + Mul + Add + Send + Sync; - type Primitives: PrimitiveKind; + type VertexData: Clone + Mul + Add + Send + Sync; + type Primitives: PrimitiveKind; type Fragment; /// Returns the [`DepthMode`] of this pipeline. + #[inline(always)] fn depth_mode(&self) -> DepthMode { DepthMode::NONE } /// Returns the [`CoordinateMode`] of this pipeline. + #[inline(always)] fn coordinate_mode(&self) -> CoordinateMode { CoordinateMode::default() } /// Transforms a [`Pipeline::Vertex`] into homogeneous NDCs (Normalised Device Coordinates) for the vertex and a - /// [`Pipeline::VertexAttr`] to be interpolated and passed to the fragment shader. + /// [`Pipeline::VertexData`] to be interpolated and passed to the fragment shader. /// /// This stage is executed at the beginning of pipeline execution. - fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexAttr); + fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData); /// Turn a primitive into many primitives. /// /// This stage sits between the vertex shader and the fragment shader. - fn geometry_shader(&self, primitive: >::Primitive, mut output: O) + #[inline(always)] + fn geometry_shader(&self, primitive: >::Primitive, mut output: O) where - O: FnMut(>::Primitive), + O: FnMut(>::Primitive), { output(primitive); } - /// Transforms a [`Pipeline::VertexAttr`] into a fragment to be rendered to a pixel target. + /// Transforms a [`Pipeline::VertexData`] into a fragment to be rendered to a pixel target. /// /// This stage is executed for every fragment generated by the rasterizer. - fn fragment_shader(&self, vs_out: Self::VertexAttr) -> Self::Fragment; + fn fragment_shader(&self, vs_out: Self::VertexData) -> Self::Fragment; /// Blend an old fragment with a new fragment. /// @@ -168,7 +171,7 @@ pub trait Pipeline: Sized { fn render<'a, S, V, P, D>( &'a self, vertices: S, - rasterizer_config: <>::Rasterizer as Rasterizer>::Config, + rasterizer_config: <>::Rasterizer as Rasterizer>::Config, pixels: &mut P, depth: &mut D, ) @@ -219,7 +222,7 @@ pub trait Pipeline: Sized { } }; - let emit_fragment = move |pos, vs_out_lerped: Self::VertexAttr, z: f32| { + let emit_fragment = move |pos, vs_out_lerped: Self::VertexData, z: f32| { let frag = self.fragment_shader(vs_out_lerped); let old_px = unsafe { pixels.read_exclusive_unchecked(pos) }; let blended_px = self.blend_shader(old_px, frag); @@ -231,7 +234,7 @@ pub trait Pipeline: Sized { }; unsafe { - >::Rasterizer::default().rasterize( + >::Rasterizer::default().rasterize( core::iter::from_fn(fetch_vertex), target_size, principal_x, diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index 9682442..7bc39ca 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -1,7 +1,6 @@ use super::*; use crate::{CoordinateMode, YAxisDirection}; use core::ops::{Mul, Add}; -use alloc::vec::Vec; use vek::*; /// A rasterizer that produces filled triangles. diff --git a/src/sampler/mod.rs b/src/sampler/mod.rs index c4fe841..65098a5 100644 --- a/src/sampler/mod.rs +++ b/src/sampler/mod.rs @@ -11,6 +11,10 @@ use core::{ /// /// Samplers use normalised coordinates (between 0 and 1) to sample textures. Often, samplers will combine this with /// a sampling algorithm such as filtering or domain warping. +/// +/// Please note that texture coordinate axes are, where possible, consistent with the underlying texture implementation +/// (i.e: +x and +y in sampler space correspond to the same directions as +x and +y in texture space). This behaviour +/// is equivalent to that of Vulkan's texture access API. pub trait Sampler where Self::Index: Denormalize<>::Index>, @@ -32,7 +36,8 @@ where /// # Panics /// /// The behaviour of this function is *unspecified* (but not *undefined*) when the index is out of bounds. The - /// implementation is free to panic, or return any proper value. + /// implementation is free to panic, or return any proper value. Alternatively, some implementers may use out of + /// bounds access to implement special behaviours such as border colours or texture tiling. fn sample(&self, index: [Self::Index; N]) -> Self::Sample; /// Sample the texture at the given assumed-valid index. diff --git a/src/texture.rs b/src/texture.rs index 2242ea8..f8a6bb0 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -64,10 +64,10 @@ impl<'a, T: Texture, const N: usize> Texture for &'a mut T { /// /// Targets necessarily require additional invariants to be upheld than textures for safe use. Because access to them /// may be parallelised, it is essential that there is a 1:1 mapping between each index and a unique memory location. -/// If this is not upheld, Rust's 1 writer / many readers aliasing model may be broken. The `read_exclusive_unchecked` -/// and `write_exclusive_unchecked` methods may only be called by callers that have already ensured that nothing else -/// can access the target at the same time. In addition, the target must guarantee that no reads or writes escape -/// either method. This can be done by having each texel be an `UnsafeCell`. +/// If this is not upheld, Rust's one writer / many readers aliasing model may be broken. The +/// `read_exclusive_unchecked` and `write_exclusive_unchecked` methods may only be invoked by callers that have already +/// ensured that nothing else can access the target at the same time. In addition, the target must guarantee that no +/// reads or writes escape either method. This can be done by having each texel be accessed through an `UnsafeCell`. pub trait Target: Texture<2, Index = usize> { /// Read a texel at the given assumed-valid index. /// @@ -76,7 +76,8 @@ pub trait Target: Texture<2, Index = usize> { /// If the index is invalid, undefined behaviour can be assumed to occur. Ensure that the index is valid before /// use. Access to this index *must* be exclusive to avoid undefined behaviour (i.e: nothing else may be reading or /// writing to this index during the duration of this call). The caller must enforce this through a lock or some - /// other such mechanism with mutual exclusion properties. + /// other such mechanism with mutual exclusion properties. A sure-fire way to ensure that access is exclusive is to + /// first obtain an owned buffer or a mutable reference to one since both guarantee exclusivity. unsafe fn read_exclusive_unchecked(&self, index: [Self::Index; 2]) -> Self::Texel; /// Write a texel at the given assumed-valid index. @@ -86,7 +87,8 @@ pub trait Target: Texture<2, Index = usize> { /// If the index is invalid, undefined behaviour can be assumed to occur. Ensure that the index is valid before /// use. Access to this index *must* be exclusive to avoid undefined behaviour (i.e: nothing else may be reading or /// writing to this index during the duration of this call). The caller must enforce this through a lock or some - /// other such mechanism with mutual exclusion properties. + /// other such mechanism with mutual exclusion properties. A sure-fire way to ensure that access is exclusive is to + /// first obtain an owned buffer or a mutable reference to one since both guarantee exclusivity. unsafe fn write_exclusive_unchecked(&self, index: [usize; 2], texel: Self::Texel); /// Write a texel at the given assumed-valid index. @@ -94,8 +96,7 @@ pub trait Target: Texture<2, Index = usize> { /// # Safety /// /// If the index is invalid, undefined behaviour can be assumed to occur. Ensure that the index is valid before - /// use. Access to this index *must* be exclusive to avoid undefined behaviour (i.e: nothing else may be reading or - /// writing to this index during the duration of this call). + /// use. unsafe fn write_unchecked(&mut self, index: [usize; 2], texel: Self::Texel) { self.write_exclusive_unchecked(index, texel); } From df5282531c4d8477a5fd81bc948ad83290af5ae9 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 22 Dec 2020 22:36:12 +0000 Subject: [PATCH 07/37] Shadow-mapping in teapot example --- examples/teapot.rs | 72 ++++++++++++++++++++++++++++++++----- examples/texture_mapping.rs | 2 +- src/pipeline.rs | 51 +++++++++++++++++--------- src/rasterizer/triangles.rs | 25 +++++++------ src/sampler/mod.rs | 2 +- src/texture.rs | 10 +++--- 6 files changed, 119 insertions(+), 43 deletions(-) diff --git a/examples/teapot.rs b/examples/teapot.rs index 684342e..b20f974 100644 --- a/examples/teapot.rs +++ b/examples/teapot.rs @@ -1,19 +1,45 @@ use vek::*; use derive_more::{Add, Mul}; -use euc::{Pipeline, Buffer2d, Target, DepthMode, TriangleList, CullMode}; +use euc::{Pipeline, Buffer2d, Target, DepthMode, TriangleList, CullMode, Empty, Nearest, Texture, Sampler, CoordinateMode}; use std::marker::PhantomData; +struct TeapotShadow<'a> { + mvp: Mat4, + phantom: PhantomData<&'a ()>, +} + +impl<'a> Pipeline for TeapotShadow<'a> { + type Vertex = wavefront::Vertex<'a>; + type VertexData = f32; + type Primitives = TriangleList; + type Fragment = (); + + fn pixel_mode(&self) -> bool { false } + fn depth_mode(&self) -> DepthMode { DepthMode::LESS_WRITE } + + #[inline(always)] + fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { + ((self.mvp * Vec4::from_point(Vec3::from(vertex.position()))).into_array(), 0.0) + } + + #[inline(always)] + fn fragment_shader(&self, d: Self::VertexData) -> Self::Fragment {} +} + struct Teapot<'a> { m: Mat4, v: Mat4, p: Mat4, - phantom: PhantomData<&'a ()>, + light_pos: Vec3, + shadow: Nearest<&'a Buffer2d>, + light_vp: Mat4, } #[derive(Add, Mul, Clone)] struct VertexData { wpos: Vec3, wnorm: Vec3, + screen: Vec2, } impl<'a> Pipeline for Teapot<'a> { @@ -30,16 +56,16 @@ impl<'a> Pipeline for Teapot<'a> { let wnorm = self.m * Vec4::from_direction(-Vec3::from(vertex.normal().unwrap())); ( (self.p * self.v * wpos).into_array(), - VertexData { wpos: wpos.xyz(), wnorm: wnorm.xyz() }, + VertexData { wpos: wpos.xyz(), wnorm: wnorm.xyz(), screen: (self.p * self.v * wpos).xy()}, ) } #[inline(always)] - fn fragment_shader(&self, VertexData { wpos, wnorm }: Self::VertexData) -> Self::Fragment { + fn fragment_shader(&self, VertexData { wpos, wnorm, screen }: Self::VertexData) -> Self::Fragment { let wnorm = wnorm.normalized(); - let light_dir = Vec3::::new(1.0, 1.0, 1.0).normalized(); let cam_pos = Vec3::zero(); let cam_dir = (wpos - cam_pos).normalized(); + let light_dir = (wpos - self.light_pos).normalized(); let surf_color = Rgba::new(0.8, 1.0, 0.7, 1.0); // Phong reflection model @@ -47,7 +73,17 @@ impl<'a> Pipeline for Teapot<'a> { let diffuse = wnorm.dot(-light_dir).max(0.0) * 0.5; let specular = light_dir.reflected(wnorm).dot(-cam_dir).max(0.0).powf(30.0) * 3.0; - let color = surf_color * (ambient + diffuse + specular); + // Shadow-mapping + let light_view_pos = self.light_vp * Vec4::from_point(wpos); + let light_view_pos = light_view_pos.xyz() / light_view_pos.w; + let light_depth = self.shadow.sample((light_view_pos.xy() * Vec2::new(1.0, -1.0) * 0.5 + 0.5).into_array()) + 0.001; + let depth = light_view_pos.z; + let in_light = depth < light_depth; + + let light = ambient + if in_light { diffuse + specular } else { 0.0 }; + let color = surf_color * light; + + //let color = Rgba::zero() + self.shadow.sample(((screen + 1.0) * 0.5).into_array()); u32::from_le_bytes(color.map(|e| e.clamped(0.0, 1.0) * 255.0).as_().into_array()) } } @@ -57,6 +93,7 @@ fn main() { let mut color = Buffer2d::fill([w, h], 0x0); let mut depth = Buffer2d::fill([w, h], 1.0); + let mut shadow = Buffer2d::fill([1024; 2], 1.0); let model = wavefront::Obj::from_file("examples/data/teapot.obj").unwrap(); @@ -64,17 +101,34 @@ fn main() { let mut i = 0; win.glutin_handle_basic_input(|win, input| { + let teapot_pos = Vec3::new(0.0, 0.0, -6.0); + let light_pos = Vec3::::new(-6.0, 0.0, 3.0); + + let light_p = Mat4::perspective_fov_lh_zo(1.5, shadow.size()[0] as f32, shadow.size()[1] as f32, 0.1, 100.0); + let light_v = Mat4::look_at_lh(light_pos, -teapot_pos, Vec3::unit_y()); + let light_vp = light_p * light_v; + let p = Mat4::perspective_fov_lh_zo(1.3, w as f32, h as f32, 0.01, 100.0); - let v = Mat4::identity(); - let m = Mat4::::translation_3d(Vec3::new(0.0, 0.0, 6.0)) + let v = Mat4::::identity(); + let m = Mat4::::translation_3d(-teapot_pos) * Mat4::rotation_x((i as f32 * 0.03).sin() * 0.4) * Mat4::rotation_y((i as f32 * 0.005) * 4.0) * Mat4::rotation_z((i as f32 * 0.04).cos() * 0.4); color.clear(0x0); depth.clear(1.0); + shadow.clear(1.0); + + // Shadow pass + TeapotShadow { mvp: light_vp * m, phantom: PhantomData }.render( + model.vertices(), + CullMode::Back, + &mut Empty::default(), + &mut shadow, + ); - Teapot { m, v, p, phantom: PhantomData }.render( + // Colour pass + Teapot { m, v, p, light_pos, shadow: Nearest::new(&shadow), light_vp: light_vp }.render( model.vertices(), CullMode::Back, &mut color, diff --git a/examples/texture_mapping.rs b/examples/texture_mapping.rs index dec6e58..a4d69c8 100644 --- a/examples/texture_mapping.rs +++ b/examples/texture_mapping.rs @@ -6,7 +6,7 @@ struct Cube<'a> { mvp: Mat4, positions: &'a [Vec4], uvs: &'a [Vec2], - sampler: &'a Nearest, + sampler: &'a Nearest, } impl<'a> Pipeline for Cube<'a> { diff --git a/src/pipeline.rs b/src/pipeline.rs index 0e711fa..261ce94 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -75,7 +75,7 @@ pub enum YAxisDirection { pub struct CoordinateMode { pub handedness: Handedness, pub y_axis_direction: YAxisDirection, - pub z_clip_range: Range, + pub z_clip_range: Option>, } impl CoordinateMode { @@ -83,29 +83,36 @@ impl CoordinateMode { pub const OPENGL: Self = Self { handedness: Handedness::Right, y_axis_direction: YAxisDirection::Up, - z_clip_range: -1.0..1.0, + z_clip_range: Some(-1.0..1.0), }; /// Vulkan-like coordinates (left-handed, y = down, 0 to 1 z clip range). pub const VULKAN: Self = Self { handedness: Handedness::Left, y_axis_direction: YAxisDirection::Down, - z_clip_range: 0.0..1.0, + z_clip_range: Some(0.0..1.0), }; /// Metal-like coordinates (right-handed, y = down, 0 to 1 z clip range). pub const METAL: Self = Self { handedness: Handedness::Right, y_axis_direction: YAxisDirection::Down, - z_clip_range: 0.0..1.0, + z_clip_range: Some(0.0..1.0), }; /// DirectX-like coordinates (left-handed, y = up, 0 to 1 z clip range). pub const DIRECTX: Self = Self { handedness: Handedness::Left, y_axis_direction: YAxisDirection::Up, - z_clip_range: 0.0..1.0, + z_clip_range: Some(0.0..1.0), }; + + pub fn without_z_clip(self) -> Self { + Self { + z_clip_range: None, + ..self + } + } } impl Default for CoordinateMode { @@ -126,6 +133,10 @@ pub trait Pipeline: Sized { type Primitives: PrimitiveKind; type Fragment; + /// Returns whether the pixel buffer should be written to by this pipeline.. + #[inline(always)] + fn pixel_mode(&self) -> bool { true } + /// Returns the [`DepthMode`] of this pipeline. #[inline(always)] fn depth_mode(&self) -> DepthMode { DepthMode::NONE } @@ -182,15 +193,19 @@ pub trait Pipeline: Sized { P: Target + Send + Sync + 'a, D: Target + Send + Sync + 'a, { + let pixel_write = self.pixel_mode(); let depth_mode = self.depth_mode(); - let target_size = pixels.size(); let principal_x = depth.principal_axis() == 0; - - // Ensure that the pixel target and depth target are compatible (but only if we need to actually use the depth - // target). - if depth_mode.uses_depth() { - assert_eq!(target_size, depth.size(), "Depth target size is compatible with the size of other target(s)"); - } + let target_size = if pixel_write { + // Ensure that the pixel target and depth target are compatible (but only if we need to actually use the + // depth target). + if depth_mode.uses_depth() { + assert_eq!(pixels.size(), depth.size(), "Pixel target size is compatible with depth target size"); + } + pixels.size() + } else { + depth.size() + }; let mut vert_outs = vertices.into_iter().map(|v| self.vertex_shader(v.borrow())).peekable(); let mut vert_out_queue = VecDeque::new(); @@ -223,14 +238,16 @@ pub trait Pipeline: Sized { }; let emit_fragment = move |pos, vs_out_lerped: Self::VertexData, z: f32| { - let frag = self.fragment_shader(vs_out_lerped); - let old_px = unsafe { pixels.read_exclusive_unchecked(pos) }; - let blended_px = self.blend_shader(old_px, frag); - unsafe { pixels.write_exclusive_unchecked(pos, blended_px); } - if depth_mode.write { unsafe { depth.write_exclusive_unchecked(pos, z); } } + + if pixel_write { + let frag = self.fragment_shader(vs_out_lerped); + let old_px = unsafe { pixels.read_exclusive_unchecked(pos) }; + let blended_px = self.blend_shader(old_px, frag); + unsafe { pixels.write_exclusive_unchecked(pos, blended_px); } + } }; unsafe { diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index 7bc39ca..15ca169 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -97,15 +97,20 @@ impl Rasterizer for Triangles { let verts_screen = verts_euc.map(|euc| size * (euc.xy() * Vec2::new(0.5, -0.5) + 0.5)); // Calculate the triangle bounds as a bounding box - let tri_bounds = Aabr:: { - min: Vec2::max(verts_screen.reduce(|a, b| Vec2::partial_min(a, b)).as_(), Vec2::zero()), - max: Vec2::min(verts_screen.reduce(|a, b| Vec2::partial_max(a, b)).as_() + 1, Vec2::from(target_size) - 1), + let tri_bounds = Aabr:: { + min: verts_screen.reduce(|a, b| Vec2::partial_min(a, b)), + max: verts_screen.reduce(|a, b| Vec2::partial_max(a, b)) + 1.0, + }; + let screen_max = Vec2::new(target_size[0] as f32, target_size[1] as f32); + let tri_bounds_clamped = Aabr:: { + min: tri_bounds.min.clamped(Vec2::zero(), screen_max).as_(), + max: tri_bounds.max.clamped(Vec2::zero(), screen_max).as_(), }; // Iterate over fragment candidates within the triangle's bounding box - (tri_bounds.min.y..tri_bounds.max.y).for_each(|y| { + (tri_bounds_clamped.min.y..tri_bounds_clamped.max.y).for_each(|y| { // Only perform this optimisation if it looks to be worth it. - let row_range = if tri_bounds.max.x - tri_bounds.min.x > 8 { + let row_range = if tri_bounds_clamped.max.x - tri_bounds_clamped.min.x > 8 { // Calculate a precise for which the pixels of the triangle appear on this row let edges = verts_screen.zip(Vec3::new(verts_screen.y, verts_screen.z, verts_screen.x)); let row_range = edges @@ -114,18 +119,18 @@ impl Rasterizer for Triangles { let x2 = Lerp::lerp(a.x, b.x, (y as f32 + 1.0 - a.y) / (b.y - a.y)); let (x_min, x_max) = (x.min(x2), x.max(x2)); Vec2::new( - if x < tri_bounds.min.x as f32 { tri_bounds.max.x as f32 } else { x_min }, - if x > tri_bounds.max.x as f32 { tri_bounds.min.x as f32 } else { x_max }, + if x < tri_bounds.min.x { tri_bounds.max.x } else { x_min }, + if x > tri_bounds.max.x { tri_bounds.min.x } else { x_max }, ) }) .reduce(|a, b| Vec2::new(a.x.min(b.x), a.y.max(b.y))) - .map(|e| e as usize); + .map(|e| e.max(0.0) as usize); Vec2::new( row_range.x.saturating_sub(1), (row_range.y + 1).min(target_size[0]), ) } else { - Vec2::new(tri_bounds.min.x, tri_bounds.max.x) + Vec2::new(tri_bounds_clamped.min.x, tri_bounds_clamped.max.x) }; for x in row_range.x..row_range.y { @@ -146,7 +151,7 @@ impl Rasterizer for Triangles { let z: f32 = verts_hom.map2(w, |v, w| v.z * w).sum() * w_hom.z; // Don't use `.contains(&z)`, it isn't inclusive - if z >= coordinate_mode.z_clip_range.start && z <= coordinate_mode.z_clip_range.end { + if coordinate_mode.z_clip_range.clone().map_or(true, |clip_range| z >= clip_range.start && z <= clip_range.end) || true{ if test_depth([x, y], z) { let vert_out_lerped = verts_out.clone().map2(w, |vo, w| vo * w).sum(); diff --git a/src/sampler/mod.rs b/src/sampler/mod.rs index 65098a5..ef95638 100644 --- a/src/sampler/mod.rs +++ b/src/sampler/mod.rs @@ -52,7 +52,7 @@ where } /// A sampler that uses nearest-neighbor sampling. -pub struct Nearest(T, PhantomData); +pub struct Nearest(T, PhantomData); impl Nearest { /// Create a new diff --git a/src/texture.rs b/src/texture.rs index f8a6bb0..b7e72bb 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -140,16 +140,16 @@ impl Default for Empty { } } -impl Texture for Empty { +impl Texture for Empty { type Index = usize; - type Texel = J; + type Texel = T; fn size(&self) -> [Self::Index; N] { [0; N] } fn read(&self, _: [Self::Index; N]) -> Self::Texel { panic!("Cannot read from an empty texture"); } } -impl Target for Empty { - unsafe fn read_exclusive_unchecked(&self, _: [Self::Index; 2]) -> Self::Texel { panic!("Cannot read from an empty texture"); } - unsafe fn write_exclusive_unchecked(&self, _: [usize; 2], _: Self::Texel) { panic!("Cannot write to an empty texture"); } +impl Target for Empty { + unsafe fn read_exclusive_unchecked(&self, _: [Self::Index; 2]) -> Self::Texel { T::default() } + unsafe fn write_exclusive_unchecked(&self, _: [usize; 2], _: Self::Texel) {} } #[cfg(feature = "image")] From c4398f15efa2802dfc60daa9c30c67bcc3d56962 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 23 Dec 2020 01:50:56 +0000 Subject: [PATCH 08/37] Parallelism support --- Cargo.toml | 5 +- examples/spinning_cube.rs | 4 +- examples/teapot.rs | 16 +-- examples/texture_mapping.rs | 4 +- examples/triangle.rs | 23 +--- src/lib.rs | 5 +- src/pipeline.rs | 235 +++++++++++++++++++++++++++--------- src/rasterizer/mod.rs | 3 +- src/rasterizer/triangles.rs | 18 +-- 9 files changed, 214 insertions(+), 99 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index af3ba27..28ff8d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,8 @@ exclude = [ [dependencies] vek = { version = "0.12", default-features = false, features = [] } image_ = { package = "image", version = "0.23", optional = true } -rayon = { version = "1.5", optional = true } +num_cpus = { version = "1.13", optional = true } +crossbeam-utils = { version = "0.8", optional = true } [features] default = ["std", "simd", "image", "par"] @@ -25,7 +26,7 @@ libm = ["vek/libm"] nightly = [] simd = ["vek/repr_simd"] image = ["image_"] -par = ["rayon"] +par = ["std", "num_cpus", "crossbeam-utils"] [dev-dependencies] mini_gl_fb = "0.7.0" diff --git a/examples/spinning_cube.rs b/examples/spinning_cube.rs index dba48b4..b719dd2 100644 --- a/examples/spinning_cube.rs +++ b/examples/spinning_cube.rs @@ -9,7 +9,7 @@ impl Pipeline for Cube { type Vertex = (Vec4, Rgba); type VertexData = Rgba; type Primitives = TriangleList; - type Fragment = u32; + type Pixel = u32; #[inline(always)] fn vertex_shader(&self, (pos, color): &Self::Vertex) -> ([f32; 4], Self::VertexData) { @@ -17,7 +17,7 @@ impl Pipeline for Cube { } #[inline(always)] - fn fragment_shader(&self, color: Self::VertexData) -> Self::Fragment { + fn fragment_shader(&self, color: Self::VertexData) -> Self::Pixel { u32::from_le_bytes((color * 255.0).as_().into_array()) } } diff --git a/examples/teapot.rs b/examples/teapot.rs index b20f974..6ea6b7c 100644 --- a/examples/teapot.rs +++ b/examples/teapot.rs @@ -1,6 +1,6 @@ use vek::*; use derive_more::{Add, Mul}; -use euc::{Pipeline, Buffer2d, Target, DepthMode, TriangleList, CullMode, Empty, Nearest, Texture, Sampler, CoordinateMode}; +use euc::{Pipeline, Buffer2d, Target, PixelMode, DepthMode, TriangleList, CullMode, Empty, Nearest, Texture, Sampler}; use std::marker::PhantomData; struct TeapotShadow<'a> { @@ -12,9 +12,9 @@ impl<'a> Pipeline for TeapotShadow<'a> { type Vertex = wavefront::Vertex<'a>; type VertexData = f32; type Primitives = TriangleList; - type Fragment = (); + type Pixel = (); - fn pixel_mode(&self) -> bool { false } + fn pixel_mode(&self) -> PixelMode { PixelMode::PASS } fn depth_mode(&self) -> DepthMode { DepthMode::LESS_WRITE } #[inline(always)] @@ -23,7 +23,7 @@ impl<'a> Pipeline for TeapotShadow<'a> { } #[inline(always)] - fn fragment_shader(&self, d: Self::VertexData) -> Self::Fragment {} + fn fragment_shader(&self, d: Self::VertexData) -> Self::Pixel {} } struct Teapot<'a> { @@ -46,7 +46,7 @@ impl<'a> Pipeline for Teapot<'a> { type Vertex = wavefront::Vertex<'a>; type VertexData = VertexData; type Primitives = TriangleList; - type Fragment = u32; + type Pixel = u32; fn depth_mode(&self) -> DepthMode { DepthMode::LESS_WRITE } @@ -61,7 +61,7 @@ impl<'a> Pipeline for Teapot<'a> { } #[inline(always)] - fn fragment_shader(&self, VertexData { wpos, wnorm, screen }: Self::VertexData) -> Self::Fragment { + fn fragment_shader(&self, VertexData { wpos, wnorm, screen }: Self::VertexData) -> Self::Pixel { let wnorm = wnorm.normalized(); let cam_pos = Vec3::zero(); let cam_dir = (wpos - cam_pos).normalized(); @@ -89,7 +89,7 @@ impl<'a> Pipeline for Teapot<'a> { } fn main() { - let [w, h] = [800, 600]; + let [w, h] = [1280, 960]; let mut color = Buffer2d::fill([w, h], 0x0); let mut depth = Buffer2d::fill([w, h], 1.0); @@ -101,7 +101,7 @@ fn main() { let mut i = 0; win.glutin_handle_basic_input(|win, input| { - let teapot_pos = Vec3::new(0.0, 0.0, -6.0); + let teapot_pos = Vec3::new(0.0, 0.0, -5.0); let light_pos = Vec3::::new(-6.0, 0.0, 3.0); let light_p = Mat4::perspective_fov_lh_zo(1.5, shadow.size()[0] as f32, shadow.size()[1] as f32, 0.1, 100.0); diff --git a/examples/texture_mapping.rs b/examples/texture_mapping.rs index a4d69c8..8310652 100644 --- a/examples/texture_mapping.rs +++ b/examples/texture_mapping.rs @@ -13,7 +13,7 @@ impl<'a> Pipeline for Cube<'a> { type Vertex = usize; type VertexData = Vec2; type Primitives = TriangleList; - type Fragment = u32; + type Pixel = u32; #[inline] fn vertex_shader(&self, v_index: &Self::Vertex) -> ([f32; 4], Self::VertexData) { @@ -24,7 +24,7 @@ impl<'a> Pipeline for Cube<'a> { } #[inline] - fn fragment_shader(&self, v_uv: Self::VertexData) -> Self::Fragment { + fn fragment_shader(&self, v_uv: Self::VertexData) -> Self::Pixel { u32::from_le_bytes(self.sampler.sample(v_uv.into_array()).0) } } diff --git a/examples/triangle.rs b/examples/triangle.rs index 7eb47e8..881c1b0 100644 --- a/examples/triangle.rs +++ b/examples/triangle.rs @@ -1,10 +1,4 @@ -use euc::{ - Buffer2d, - Pipeline, - TriangleList, - CullMode, - Empty, -}; +use euc::{Buffer2d, Pipeline, TriangleList, CullMode, Empty}; use vek::*; struct Triangle; @@ -13,25 +7,16 @@ impl Pipeline for Triangle { type Vertex = [f32; 4]; type VertexData = Vec2; type Primitives = TriangleList; - type Fragment = u32; + type Pixel = u32; - // Vertex shader - // - Returns the 3D vertex location, and the VsOut value to be passed to the fragment shader #[inline(always)] fn vertex_shader(&self, pos: &[f32; 4]) -> ([f32; 4], Self::VertexData) { (*pos, Vec2::new(pos[0], pos[1])) } - // Fragment shader - // - Returns (in this case) a u32 #[inline(always)] - fn fragment_shader(&self, xy: Self::VertexData) -> Self::Fragment { - let bytes = [(xy.x * 255.0) as u8, (xy.y * 255.0) as u8, 0, 255]; // Red - - (bytes[0] as u32) << 0 - | (bytes[1] as u32) << 8 - | (bytes[2] as u32) << 16 - | (bytes[3] as u32) << 24 + fn fragment_shader(&self, xy: Self::VertexData) -> Self::Pixel { + u32::from_le_bytes([(xy.x * 255.0) as u8, (xy.y * 255.0) as u8, 0, 255]) // Red } } diff --git a/src/lib.rs b/src/lib.rs index c5097eb..a0cad87 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,6 +44,9 @@ extern crate alloc; +#[cfg(feature = "std")] +extern crate std; + /// N-dimensional buffers that may be used as textures and render targets. pub mod buffer; /// Index buffer features. @@ -64,7 +67,7 @@ pub mod texture; // Reexports pub use crate::{ buffer::{Buffer, Buffer1d, Buffer2d, Buffer3d, Buffer4d}, - pipeline::{Pipeline, DepthMode, CoordinateMode, Handedness, YAxisDirection}, + pipeline::{Pipeline, DepthMode, PixelMode, CoordinateMode, Handedness, YAxisDirection}, primitives::TriangleList, texture::{Texture, Target, Empty}, rasterizer::CullMode, diff --git a/src/pipeline.rs b/src/pipeline.rs index 261ce94..9bdfdeb 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -3,7 +3,7 @@ use crate::{ rasterizer::Rasterizer, primitives::PrimitiveKind, }; -use alloc::collections::VecDeque; +use alloc::{vec::Vec, collections::VecDeque}; use core::{ cmp::Ordering, ops::{Add, Mul, Range}, @@ -53,6 +53,29 @@ impl DepthMode { } } +/// Defines how a [`Pipeline`] will interact with the pixel target. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct PixelMode { + /// Whether the fragment's pixel should be written to the pixel target. + pub write: bool, +} + +impl PixelMode { + pub const WRITE: Self = Self { + write: true, + }; + + pub const PASS: Self = Self { + write: false, + }; +} + +impl Default for PixelMode { + fn default() -> Self { + Self::WRITE + } +} + /// The handedness of the coordinate space used by a pipeline. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum Handedness { @@ -131,11 +154,11 @@ pub trait Pipeline: Sized { type Vertex; type VertexData: Clone + Mul + Add + Send + Sync; type Primitives: PrimitiveKind; - type Fragment; + type Pixel; - /// Returns whether the pixel buffer should be written to by this pipeline.. + /// Returns the [`PixelMode`] of this pipeline. #[inline(always)] - fn pixel_mode(&self) -> bool { true } + fn pixel_mode(&self) -> PixelMode { PixelMode::default() } /// Returns the [`DepthMode`] of this pipeline. #[inline(always)] @@ -165,7 +188,7 @@ pub trait Pipeline: Sized { /// Transforms a [`Pipeline::VertexData`] into a fragment to be rendered to a pixel target. /// /// This stage is executed for every fragment generated by the rasterizer. - fn fragment_shader(&self, vs_out: Self::VertexData) -> Self::Fragment; + fn fragment_shader(&self, vs_out: Self::VertexData) -> Self::Pixel; /// Blend an old fragment with a new fragment. /// @@ -174,42 +197,41 @@ pub trait Pipeline: Sized { /// /// The default implementation simply returns the new fragment and ignores the old one. However, this may be used /// to implement techniques such as alpha blending. - fn blend_shader(&self, a: Self::Fragment, b: Self::Fragment) -> Self::Fragment { b } + fn blend_shader(&self, a: Self::Pixel, b: Self::Pixel) -> Self::Pixel { b } /// Render a stream of vertices to given provided pixel target and depth target using the rasterizer. /// /// **Do not implement this method** - fn render<'a, S, V, P, D>( - &'a self, + fn render( + &self, vertices: S, rasterizer_config: <>::Rasterizer as Rasterizer>::Config, - pixels: &mut P, + pixel: &mut P, depth: &mut D, ) where Self: Send + Sync, S: IntoIterator, V: Borrow, - P: Target + Send + Sync + 'a, - D: Target + Send + Sync + 'a, + P: Target + Send + Sync, + D: Target + Send + Sync, { - let pixel_write = self.pixel_mode(); - let depth_mode = self.depth_mode(); - let principal_x = depth.principal_axis() == 0; - let target_size = if pixel_write { - // Ensure that the pixel target and depth target are compatible (but only if we need to actually use the - // depth target). - if depth_mode.uses_depth() { - assert_eq!(pixels.size(), depth.size(), "Pixel target size is compatible with depth target size"); - } - pixels.size() - } else { - depth.size() + let target_size = match (self.pixel_mode().write, self.depth_mode().uses_depth()) { + (false, false) => return, // No targets actually get written to, don't bother doing anything + (true, false) => pixel.size(), + (false, true) => depth.size(), + (true, true) => { + // Ensure that the pixel target and depth target are compatible + assert_eq!(pixel.size(), depth.size(), "Pixel target size is compatible with depth target size"); + // Prefer + pixel.size() + }, }; + // Produce an iterator over vertices (using the vertex shader and geometry shader to product them) let mut vert_outs = vertices.into_iter().map(|v| self.vertex_shader(v.borrow())).peekable(); let mut vert_out_queue = VecDeque::new(); - let fetch_vertex = move || { + let fetch_vertex = core::iter::from_fn(move || { loop { match vert_out_queue.pop_front() { Some(v) => break Some(v), @@ -223,43 +245,144 @@ pub trait Pipeline: Sized { }, } } - }; + }); - let pixels = &*pixels; - let depth = &*depth; + #[cfg(not(feature = "par"))] + let r = render_seq(self, fetch_vertex, rasterizer_config, target_size, pixel, depth); + #[cfg(feature = "par")] + let r = render_par(self, fetch_vertex, rasterizer_config, target_size, pixel, depth); + r + } +} - let test_depth = move |pos, z: f32| { - if let Some(test) = depth_mode.test { - let old_z = unsafe { depth.read_exclusive_unchecked(pos) }; - z.partial_cmp(&old_z) == Some(test) - } else { - true - } - }; +#[cfg(feature = "par")] +fn render_par( + pipeline: &Pipe, + mut fetch_vertex: S, + rasterizer_config: <>::Rasterizer as Rasterizer>::Config, + tgt_size: [usize; 2], + pixel: &mut P, + depth: &mut D, +) +where + Pipe: Pipeline + Send + Sync, + S: Iterator, + P: Target + Send + Sync, + D: Target + Send + Sync, +{ + use std::thread; - let emit_fragment = move |pos, vs_out_lerped: Self::VertexData, z: f32| { - if depth_mode.write { - unsafe { depth.write_exclusive_unchecked(pos, z); } - } + // TODO: Don't pull all vertices at once + let vertices = fetch_vertex.collect::>(); + let threads = num_cpus::get(); + assert!(tgt_size[1] >= threads); // TODO: Remove this limitation + let rows_each = tgt_size[1] / threads; - if pixel_write { - let frag = self.fragment_shader(vs_out_lerped); - let old_px = unsafe { pixels.read_exclusive_unchecked(pos) }; - let blended_px = self.blend_shader(old_px, frag); - unsafe { pixels.write_exclusive_unchecked(pos, blended_px); } - } - }; + let vertices = &vertices; + let rasterizer_config = &rasterizer_config; + let pixel = &*pixel; + let depth = &*depth; - unsafe { - >::Rasterizer::default().rasterize( - core::iter::from_fn(fetch_vertex), - target_size, - principal_x, - self.coordinate_mode(), - rasterizer_config, - test_depth, - emit_fragment, - ); + crossbeam_utils::thread::scope(|s| { + for i in 0..threads { + // TODO: Respawning them each time is dumb + s.spawn(move |_| { + let (row_start, rows) = if i == threads - 1 { + (0, tgt_size[1] - (threads - 1) * rows_each) + } else { + (tgt_size[1] - (i + 1) * rows_each, rows_each) + }; + let tgt_min = [0, row_start]; + let tgt_max = [tgt_size[0], row_start + rows]; + // Safety: we have exclusive access to our specific regions of `pixel` and `depth` + unsafe { render_inner(pipeline, vertices.iter().cloned(), rasterizer_config.clone(), (tgt_min, tgt_max), tgt_size, pixel, depth) } + }); + } + }).unwrap(); +} + +fn render_seq( + pipeline: &Pipe, + fetch_vertex: S, + rasterizer_config: <>::Rasterizer as Rasterizer>::Config, + tgt_size: [usize; 2], + pixel: &mut P, + depth: &mut D, +) +where + Pipe: Pipeline + Send + Sync, + S: Iterator, + P: Target + Send + Sync, + D: Target + Send + Sync, +{ + // Safety: we have exclusive access to `pixel` and `depth` + unsafe { render_inner(pipeline, fetch_vertex, rasterizer_config, ([0; 2], tgt_size), tgt_size, pixel, depth) } +} + +unsafe fn render_inner( + pipeline: &Pipe, + fetch_vertex: S, + rasterizer_config: <>::Rasterizer as Rasterizer>::Config, + (tgt_min, tgt_max): ([usize; 2], [usize; 2]), + tgt_size: [usize; 2], + pixel: &P, + depth: &D, +) +where + Pipe: Pipeline + Send + Sync, + S: Iterator, + P: Target + Send + Sync, + D: Target + Send + Sync, +{ + let pixel_write = pipeline.pixel_mode().write; + let depth_mode = pipeline.depth_mode(); + for i in 0..2 { + // Safety check + if pixel_write { + assert!(tgt_min[i] <= pixel.size()[i]); + assert!(tgt_max[i] <= pixel.size()[i]); + } + if depth_mode.uses_depth() { + assert!(tgt_min[i] <= depth.size()[i]); + assert!(tgt_max[i] <= depth.size()[i]); } } + + let principal_x = depth.principal_axis() == 0; + + let pixel = &*pixel; + let depth = &*depth; + + let test_depth = move |pos, z: f32| { + if let Some(test) = depth_mode.test { + let old_z = depth.read_exclusive_unchecked(pos); + z.partial_cmp(&old_z) == Some(test) + } else { + true + } + }; + + let emit_fragment = move |pos, vs_out_lerped: Pipe::VertexData, z: f32| { + if depth_mode.write { + depth.write_exclusive_unchecked(pos, z); + } + + if pixel_write { + let frag = pipeline.fragment_shader(vs_out_lerped); + let old_px = pixel.read_exclusive_unchecked(pos); + let blended_px = pipeline.blend_shader(old_px, frag); + pixel.write_exclusive_unchecked(pos, blended_px); + } + }; + + >::Rasterizer::default().rasterize( + fetch_vertex, + (tgt_min, tgt_max), + tgt_size, + principal_x, + pipeline.coordinate_mode(), + rasterizer_config, + test_depth, + emit_fragment, + ); } diff --git a/src/rasterizer/mod.rs b/src/rasterizer/mod.rs index 4ce9fe2..1135a55 100644 --- a/src/rasterizer/mod.rs +++ b/src/rasterizer/mod.rs @@ -27,7 +27,7 @@ impl Default for CullMode { /// Rasterizers take an iterator of vertices and emit fragment positions. They do not, by themselves, perform shader /// execution, depth testing, etc. pub trait Rasterizer: Default + Send + Sync { - type Config; + type Config: Clone + Send + Sync; /// Rasterize the given vertices into fragments. /// @@ -45,6 +45,7 @@ pub trait Rasterizer: Default + Send + Sync { unsafe fn rasterize( &self, vertices: I, + target_area: ([usize; 2], [usize; 2]), target_size: [usize; 2], principal_x: bool, coordinate_mode: CoordinateMode, diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index 15ca169..1bd3fa8 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -13,7 +13,8 @@ impl Rasterizer for Triangles { unsafe fn rasterize( &self, mut vertices: I, - target_size: [usize; 2], + (tgt_min, tgt_max): ([usize; 2], [usize; 2]), + tgt_size: [usize; 2], principal_x: bool, coordinate_mode: CoordinateMode, cull_mode: CullMode, @@ -37,7 +38,7 @@ impl Rasterizer for Triangles { YAxisDirection::Up => Vec2::new(1.0, -1.0), }; - let size = Vec2::from(target_size).map(|e: usize| e as f32); + let size = Vec2::::from(tgt_size).map(|e| e as f32); let to_ndc = Mat3::from_row_arrays([ [2.0 / size.x, 0.0, -1.0], @@ -101,10 +102,11 @@ impl Rasterizer for Triangles { min: verts_screen.reduce(|a, b| Vec2::partial_min(a, b)), max: verts_screen.reduce(|a, b| Vec2::partial_max(a, b)) + 1.0, }; - let screen_max = Vec2::new(target_size[0] as f32, target_size[1] as f32); + let screen_min = Vec2::::from(tgt_min).map(|e| e as f32); + let screen_max = Vec2::::from(tgt_max).map(|e| e as f32); let tri_bounds_clamped = Aabr:: { - min: tri_bounds.min.clamped(Vec2::zero(), screen_max).as_(), - max: tri_bounds.max.clamped(Vec2::zero(), screen_max).as_(), + min: tri_bounds.min.clamped(screen_min, screen_max).as_(), + max: tri_bounds.max.clamped(screen_min, screen_max).as_(), }; // Iterate over fragment candidates within the triangle's bounding box @@ -126,8 +128,8 @@ impl Rasterizer for Triangles { .reduce(|a, b| Vec2::new(a.x.min(b.x), a.y.max(b.y))) .map(|e| e.max(0.0) as usize); Vec2::new( - row_range.x.saturating_sub(1), - (row_range.y + 1).min(target_size[0]), + row_range.x.saturating_sub(1).max(tri_bounds_clamped.min.x), + (row_range.y + 1).min(tri_bounds_clamped.max.x), ) } else { Vec2::new(tri_bounds_clamped.min.x, tri_bounds_clamped.max.x) @@ -151,7 +153,7 @@ impl Rasterizer for Triangles { let z: f32 = verts_hom.map2(w, |v, w| v.z * w).sum() * w_hom.z; // Don't use `.contains(&z)`, it isn't inclusive - if coordinate_mode.z_clip_range.clone().map_or(true, |clip_range| z >= clip_range.start && z <= clip_range.end) || true{ + if coordinate_mode.z_clip_range.clone().map_or(true, |clip_range| z >= clip_range.start && z <= clip_range.end) { if test_depth([x, y], z) { let vert_out_lerped = verts_out.clone().map2(w, |vo, w| vo * w).sum(); From a13bd8c87c20572cbc640d92bb44ffbcf18b24a3 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 23 Dec 2020 18:35:53 +0000 Subject: [PATCH 09/37] Experimental MSAA --- Cargo.toml | 3 +- examples/teapot.rs | 13 +++--- src/math.rs | 65 +++------------------------- src/pipeline.rs | 28 +++++++++--- src/rasterizer/mod.rs | 10 ++--- src/rasterizer/triangles.rs | 85 +++++++++++++++++++++++-------------- 6 files changed, 94 insertions(+), 110 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 28ff8d3..40ca95c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ vek = { version = "0.12", default-features = false, features = [] } image_ = { package = "image", version = "0.23", optional = true } num_cpus = { version = "1.13", optional = true } crossbeam-utils = { version = "0.8", optional = true } +fxhash = { version = "0.2", optional = true } [features] default = ["std", "simd", "image", "par"] @@ -26,7 +27,7 @@ libm = ["vek/libm"] nightly = [] simd = ["vek/repr_simd"] image = ["image_"] -par = ["std", "num_cpus", "crossbeam-utils"] +par = ["std", "num_cpus", "crossbeam-utils", "fxhash"] [dev-dependencies] mini_gl_fb = "0.7.0" diff --git a/examples/teapot.rs b/examples/teapot.rs index 6ea6b7c..dac6627 100644 --- a/examples/teapot.rs +++ b/examples/teapot.rs @@ -39,7 +39,7 @@ struct Teapot<'a> { struct VertexData { wpos: Vec3, wnorm: Vec3, - screen: Vec2, + light_view_pos: Vec3, } impl<'a> Pipeline for Teapot<'a> { @@ -54,14 +54,17 @@ impl<'a> Pipeline for Teapot<'a> { fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { let wpos = self.m * Vec4::from_point(Vec3::from(vertex.position())); let wnorm = self.m * Vec4::from_direction(-Vec3::from(vertex.normal().unwrap())); + + let light_view_pos = self.light_vp * Vec4::from_point(wpos); + let light_view_pos = light_view_pos.xyz() / light_view_pos.w; ( (self.p * self.v * wpos).into_array(), - VertexData { wpos: wpos.xyz(), wnorm: wnorm.xyz(), screen: (self.p * self.v * wpos).xy()}, + VertexData { wpos: wpos.xyz(), wnorm: wnorm.xyz(), light_view_pos }, ) } #[inline(always)] - fn fragment_shader(&self, VertexData { wpos, wnorm, screen }: Self::VertexData) -> Self::Pixel { + fn fragment_shader(&self, VertexData { wpos, wnorm, light_view_pos }: Self::VertexData) -> Self::Pixel { let wnorm = wnorm.normalized(); let cam_pos = Vec3::zero(); let cam_dir = (wpos - cam_pos).normalized(); @@ -74,8 +77,6 @@ impl<'a> Pipeline for Teapot<'a> { let specular = light_dir.reflected(wnorm).dot(-cam_dir).max(0.0).powf(30.0) * 3.0; // Shadow-mapping - let light_view_pos = self.light_vp * Vec4::from_point(wpos); - let light_view_pos = light_view_pos.xyz() / light_view_pos.w; let light_depth = self.shadow.sample((light_view_pos.xy() * Vec2::new(1.0, -1.0) * 0.5 + 0.5).into_array()) + 0.001; let depth = light_view_pos.z; let in_light = depth < light_depth; @@ -101,7 +102,7 @@ fn main() { let mut i = 0; win.glutin_handle_basic_input(|win, input| { - let teapot_pos = Vec3::new(0.0, 0.0, -5.0); + let teapot_pos = Vec3::new(0.0, 0.0, -4.0); let light_pos = Vec3::::new(-6.0, 0.0, 3.0); let light_p = Mat4::perspective_fov_lh_zo(1.5, shadow.size()[0] as f32, shadow.size()[1] as f32, 0.1, 100.0); diff --git a/src/math.rs b/src/math.rs index e17484a..42c1e22 100644 --- a/src/math.rs +++ b/src/math.rs @@ -1,68 +1,15 @@ -pub trait Lerp { - fn lerp_unchecked(a: &Self, b: &Self, factor: &F) -> Self; -} +use core::ops::{Mul, Add}; -impl Lerp for f32 { - #[inline(always)] - fn lerp_unchecked(a: &Self, b: &Self, factor: &f32) -> Self { factor.mul_add(*b - *a, *a) } -} -impl Lerp for f64 { - #[inline(always)] - fn lerp_unchecked(a: &Self, b: &Self, factor: &f64) -> Self { factor.mul_add(*b - *a, *a) } +pub trait WeightedSum: Sized { + fn weighted_sum(values: &[Self], weights: &[f32]) -> Self; } -impl Lerp for [T; N] - where T: Lerp + Copy -{ - #[inline(always)] - fn lerp_unchecked(a: &Self, b: &Self, factor: &F) -> Self { - let mut out = *a; - (0..N).for_each(|i| out[i] = Lerp::lerp_unchecked(&out[i], &b[i], factor)); - out +impl + Add> WeightedSum for T { + fn weighted_sum(values: &[Self], weights: &[f32]) -> Self { + values[1..].iter().zip(weights[1..].iter()).fold(values[0].clone() * weights[0], |a, (x, w)| a + x.clone() * *w) } } -pub trait Clamp { - fn clamp(&self, min: &Self, max: &Self) -> Self; -} - -impl Clamp for f32 { - #[inline(always)] - fn clamp(&self, min: &f32, max: &f32) -> Self { self.max(*min).min(*max) } -} - -impl Clamp for f64 { - #[inline(always)] - fn clamp(&self, min: &f64, max: &f64) -> Self { self.max(*min).min(*max) } -} - -impl Clamp for [T; N] - where T: Clamp + Copy -{ - #[inline(always)] - fn clamp(&self, min: &Self, max: &Self) -> Self { - let mut out = *self; - (0..N).for_each(|i| out[i] = out[i].clamp(&min[i], &max[i])); - out - } -} - -/// Truncation of positive values to integers -pub trait Truncate { - fn truncate(self) -> T; - fn detruncate(x: T) -> Self; -} - -impl Truncate for f32 { fn truncate(self) -> u16 { self as u16 } fn detruncate(x: u16) -> Self { x as f32 } } -impl Truncate for f32 { fn truncate(self) -> u32 { self as u32 } fn detruncate(x: u32) -> Self { x as f32 } } -impl Truncate for f32 { fn truncate(self) -> u64 { self as u64 } fn detruncate(x: u64) -> Self { x as f32 } } -impl Truncate for f32 { fn truncate(self) -> usize { self as usize } fn detruncate(x: usize) -> Self { x as f32 } } - -impl Truncate for f64 { fn truncate(self) -> u16 { self as u16 } fn detruncate(x: u16) -> Self { x as f64 } } -impl Truncate for f64 { fn truncate(self) -> u32 { self as u32 } fn detruncate(x: u32) -> Self { x as f64 } } -impl Truncate for f64 { fn truncate(self) -> u64 { self as u64 } fn detruncate(x: u64) -> Self { x as f64 } } -impl Truncate for f64 { fn truncate(self) -> usize { self as usize } fn detruncate(x: usize) -> Self { x as f64 } } - pub trait Denormalize: Sized { fn denormalize_to(self, scale: T) -> T; fn denormalize_array(this: [Self; N], other: [T; N]) -> [T; N]; diff --git a/src/pipeline.rs b/src/pipeline.rs index 9bdfdeb..8840e57 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -2,12 +2,14 @@ use crate::{ texture::Target, rasterizer::Rasterizer, primitives::PrimitiveKind, + math::WeightedSum, }; use alloc::{vec::Vec, collections::VecDeque}; use core::{ cmp::Ordering, ops::{Add, Mul, Range}, borrow::Borrow, + marker::PhantomData, }; /// Defines how a [`Pipeline`] will interact with the depth target. @@ -152,9 +154,9 @@ impl Default for CoordinateMode { /// the behaviour of the pipeline even further. pub trait Pipeline: Sized { type Vertex; - type VertexData: Clone + Mul + Add + Send + Sync; + type VertexData: Clone + WeightedSum + Send + Sync; type Primitives: PrimitiveKind; - type Pixel; + type Pixel: Clone; /// Returns the [`PixelMode`] of this pipeline. #[inline(always)] @@ -258,7 +260,7 @@ pub trait Pipeline: Sized { #[cfg(feature = "par")] fn render_par( pipeline: &Pipe, - mut fetch_vertex: S, + fetch_vertex: S, rasterizer_config: <>::Rasterizer as Rasterizer>::Config, tgt_size: [usize; 2], pixel: &mut P, @@ -321,7 +323,7 @@ where unsafe fn render_inner( pipeline: &Pipe, - fetch_vertex: S, + mut fetch_vertex: S, rasterizer_config: <>::Rasterizer as Rasterizer>::Config, (tgt_min, tgt_max): ([usize; 2], [usize; 2]), tgt_size: [usize; 2], @@ -362,13 +364,24 @@ where } }; + let msaa_level = 1; + let mut near = core::cell::RefCell::new(fxhash::FxHashMap::default()); + let near = &near; let emit_fragment = move |pos, vs_out_lerped: Pipe::VertexData, z: f32| { if depth_mode.write { depth.write_exclusive_unchecked(pos, z); } if pixel_write { - let frag = pipeline.fragment_shader(vs_out_lerped); + let frag = if msaa_level == 1 { + pipeline.fragment_shader(vs_out_lerped) + } else { + near + .borrow_mut() + .entry([pos[0] / msaa_level, pos[1] / msaa_level]) + .or_insert_with(|| pipeline.fragment_shader(vs_out_lerped)) + .clone() + }; let old_px = pixel.read_exclusive_unchecked(pos); let blended_px = pipeline.blend_shader(old_px, frag); pixel.write_exclusive_unchecked(pos, blended_px); @@ -376,7 +389,10 @@ where }; >::Rasterizer::default().rasterize( - fetch_vertex, + core::iter::from_fn(move || { + near.borrow_mut().clear(); + fetch_vertex.next() + }), (tgt_min, tgt_max), tgt_size, principal_x, diff --git a/src/rasterizer/mod.rs b/src/rasterizer/mod.rs index 1135a55..9ea65de 100644 --- a/src/rasterizer/mod.rs +++ b/src/rasterizer/mod.rs @@ -2,7 +2,7 @@ pub mod triangles; pub use self::triangles::Triangles; -use crate::CoordinateMode; +use crate::{CoordinateMode, math::WeightedSum}; use core::ops::{Mul, Add}; /// The face culling strategy used during rendering. @@ -26,7 +26,7 @@ impl Default for CullMode { /// /// Rasterizers take an iterator of vertices and emit fragment positions. They do not, by themselves, perform shader /// execution, depth testing, etc. -pub trait Rasterizer: Default + Send + Sync { +pub trait Rasterizer: Default { type Config: Clone + Send + Sync; /// Rasterize the given vertices into fragments. @@ -54,8 +54,8 @@ pub trait Rasterizer: Default + Send + Sync { emit_fragment: G, ) where - V: Clone + Mul + Add + Send + Sync, + V: Clone + WeightedSum, I: Iterator, - F: Fn([usize; 2], f32) -> bool + Send + Sync, - G: Fn([usize; 2], V, f32) + Send + Sync; + F: FnMut([usize; 2], f32) -> bool, + G: FnMut([usize; 2], V, f32); } diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index 1bd3fa8..a578a46 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -18,14 +18,14 @@ impl Rasterizer for Triangles { principal_x: bool, coordinate_mode: CoordinateMode, cull_mode: CullMode, - test_depth: F, - emit_fragment: G, + mut test_depth: F, + mut emit_fragment: G, ) where - V: Clone + Mul + Add + Send + Sync, + V: Clone + WeightedSum, I: Iterator, - F: Fn([usize; 2], f32) -> bool + Send + Sync, - G: Fn([usize; 2], V, f32) + Send + Sync, + F: FnMut([usize; 2], f32) -> bool, + G: FnMut([usize; 2], V, f32), { let cull_dir = match cull_mode { CullMode::None => None, @@ -98,43 +98,62 @@ impl Rasterizer for Triangles { let verts_screen = verts_euc.map(|euc| size * (euc.xy() * Vec2::new(0.5, -0.5) + 0.5)); // Calculate the triangle bounds as a bounding box - let tri_bounds = Aabr:: { - min: verts_screen.reduce(|a, b| Vec2::partial_min(a, b)), - max: verts_screen.reduce(|a, b| Vec2::partial_max(a, b)) + 1.0, - }; let screen_min = Vec2::::from(tgt_min).map(|e| e as f32); let screen_max = Vec2::::from(tgt_max).map(|e| e as f32); let tri_bounds_clamped = Aabr:: { - min: tri_bounds.min.clamped(screen_min, screen_max).as_(), - max: tri_bounds.max.clamped(screen_min, screen_max).as_(), + min: (verts_screen.reduce(|a, b| Vec2::partial_min(a, b)) + 0.0).clamped(screen_min, screen_max).as_(), + max: (verts_screen.reduce(|a, b| Vec2::partial_max(a, b)) + 1.0).clamped(screen_min, screen_max).as_(), }; // Iterate over fragment candidates within the triangle's bounding box (tri_bounds_clamped.min.y..tri_bounds_clamped.max.y).for_each(|y| { - // Only perform this optimisation if it looks to be worth it. - let row_range = if tri_bounds_clamped.max.x - tri_bounds_clamped.min.x > 8 { - // Calculate a precise for which the pixels of the triangle appear on this row - let edges = verts_screen.zip(Vec3::new(verts_screen.y, verts_screen.z, verts_screen.x)); - let row_range = edges - .map(|(a, b)| { - let x = Lerp::lerp(a.x, b.x, (y as f32 - a.y) / (b.y - a.y)); - let x2 = Lerp::lerp(a.x, b.x, (y as f32 + 1.0 - a.y) / (b.y - a.y)); - let (x_min, x_max) = (x.min(x2), x.max(x2)); - Vec2::new( - if x < tri_bounds.min.x { tri_bounds.max.x } else { x_min }, - if x > tri_bounds.max.x { tri_bounds.min.x } else { x_max }, - ) - }) - .reduce(|a, b| Vec2::new(a.x.min(b.x), a.y.max(b.y))) - .map(|e| e.max(0.0) as usize); - Vec2::new( - row_range.x.saturating_sub(1).max(tri_bounds_clamped.min.x), - (row_range.y + 1).min(tri_bounds_clamped.max.x), - ) + // More precisely find the required draw bounds for this row with a little maths + // First, order vertices by height + let verts_by_y = if verts_screen.x.y < verts_screen.y.y.min(verts_screen.z.y) { + if verts_screen.y.y < verts_screen.z.y { + Vec3::new(verts_screen.x, verts_screen.y, verts_screen.z) + } else { + Vec3::new(verts_screen.x, verts_screen.z, verts_screen.y) + } + } else if verts_screen.y.y < verts_screen.x.y.min(verts_screen.z.y) { + if verts_screen.x.y < verts_screen.z.y { + Vec3::new(verts_screen.y, verts_screen.x, verts_screen.z) + } else { + Vec3::new(verts_screen.y, verts_screen.z, verts_screen.x) + } + } else { + if verts_screen.x.y < verts_screen.y.y { + Vec3::new(verts_screen.z, verts_screen.x, verts_screen.y) + } else { + Vec3::new(verts_screen.z, verts_screen.y, verts_screen.x) + } + }; + + // Then, depending on the half of the triangle we're in, we need to check different lines + let edge_lines = if (y as f32) < verts_by_y.y.y { + Vec2::new((verts_by_y.x, verts_by_y.y), (verts_by_y.x, verts_by_y.z)) } else { - Vec2::new(tri_bounds_clamped.min.x, tri_bounds_clamped.max.x) + Vec2::new((verts_by_y.y, verts_by_y.z), (verts_by_y.x, verts_by_y.z)) }; + // Finally, for each of the lines, calculate the point at which our row intersects it + let row_bounds = edge_lines + .map(|(a, b)| { + // Could be more efficient + let x = Lerp::lerp(a.x, b.x, (y as f32 - a.y) / (b.y - a.y)); + let x2 = Lerp::lerp(a.x, b.x, (y as f32 + 1.0 - a.y) / (b.y - a.y)); + let (x_min, x_max) = (x.min(x2), x.max(x2)); + Vec2::new(x_min, x_max) + }) + .reduce(|a, b| Vec2::new(a.x.min(b.x), a.y.max(b.y))) + .map(|e| e.max(0.0)); + + // Now we have screen-space bounds for the row. Clean it up and clamp it to the screen bounds + let row_range = Vec2::new( + (row_bounds.x as usize).max(tri_bounds_clamped.min.x), + (row_bounds.y.ceil() as usize).min(tri_bounds_clamped.max.x), + ); + for x in row_range.x..row_range.y { // Calculate fragment center let p = Vec3::new(x as f32 + 0.5, y as f32 + 0.5, 1.0); @@ -155,7 +174,7 @@ impl Rasterizer for Triangles { // Don't use `.contains(&z)`, it isn't inclusive if coordinate_mode.z_clip_range.clone().map_or(true, |clip_range| z >= clip_range.start && z <= clip_range.end) { if test_depth([x, y], z) { - let vert_out_lerped = verts_out.clone().map2(w, |vo, w| vo * w).sum(); + let vert_out_lerped = V::weighted_sum(verts_out.as_slice(), w.as_slice()); emit_fragment([x, y], vert_out_lerped, z); } From 5371cff8f97305c4f906ff371ae9579a50f6520d Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 24 Dec 2020 00:14:56 +0000 Subject: [PATCH 10/37] Performance improvements, new logo, better README info --- Cargo.toml | 2 +- README.md | 150 ++++++++++++++++++++++++------------ examples/teapot.rs | 23 ++++-- examples/triangle.rs | 23 ++---- misc/banner.png | Bin 54303 -> 33959 bytes misc/example.png | Bin 52086 -> 49812 bytes misc/icon.png | Bin 8307 -> 9023 bytes src/math.rs | 1 + src/pipeline.rs | 45 +++++++---- src/rasterizer/triangles.rs | 44 ++++++----- 10 files changed, 178 insertions(+), 110 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 40ca95c..672d99f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ crossbeam-utils = { version = "0.8", optional = true } fxhash = { version = "0.2", optional = true } [features] -default = ["std", "simd", "image", "par"] +default = ["std", "image", "par"] std = ["vek/std"] libm = ["vek/libm"] nightly = [] diff --git a/README.md b/README.md index d475b0b..e18c28d 100644 --- a/README.md +++ b/README.md @@ -2,85 +2,132 @@ [![crates.io](https://img.shields.io/crates/v/euc.svg)](https://crates.io/crates/euc) [![crates.io](https://docs.rs/euc/badge.svg)](https://docs.rs/euc) +[![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](https://github.com/zesterer/euc) +![actions-badge](https://github.com/zesterer/euc/workflows/Rust/badge.svg?branch=master) -# Utah teapot, rendered with Euc +# Utah teapot, rendered with shadow-mapping and phong shading at 60 fps ## Triangle Example ```rust -struct Example; +struct Triangle; -impl Pipeline for Example { +impl Pipeline for Triangle { type Vertex = [f32; 2]; - type VsOut = (); + type VertexData = (); type Pixel = [u8; 4]; // Vertex shader - fn vert(&self, pos: &Self::Vertex) -> ([f32; 3], Self::VsOut) { - ([pos[0], pos[1], 0.0], ()) + fn vert(&self, pos: &Self::Vertex) -> ([f32; 4], Self::VertexData) { + ([pos[0], pos[1], 0.0, 1.0], ()) } // Fragment shader - fn frag(&self, _: &Self::VsOut) -> Self::Pixel { + fn frag(&self, _: Self::VertexData) -> Self::Pixel { [255, 0, 0, 255] // Red } } -fn main() { - let mut color = Buffer2d::new([640, 480], [0; 4]); - let mut depth = Buffer2d::new([640, 480], 1.0); - - Example.draw::, _>( - &[ - [-1.0, -1.0], - [ 1.0, -1.0], - [ 0.0, 1.0], - ], - &mut color, - &mut depth, - ); -} -``` +let mut color = Buffer2d::new([640, 480], [0; 4]); -See `examples/` for more code examples. +Triangle.render( + &[[-1.0, -1.0], [ 1.0, -1.0], [ 0.0, 1.0]], + CullMode::Back, + &mut color, + &mut Empty::default(), +); +``` ## What is `euc`? -`euc` is a versatile, simple to use crate that allows 3D rendering on the CPU. -It has a portable, compact design that makes it perfect for prototyping ideas, -unit testing, or even simple realtime applications. `euc` is currently under -active development. +`euc` is a [software-rendering](https://en.wikipedia.org/wiki/Software_rendering) +crate for rendering 3D scenes on the CPU. The API is designed to be clean, +powerful, and heavily leans on Rust's type system to ensure correct usage. + +`euc` is fast enough to render simple scenes in real-time and supports +thread-based parallelism to accelerate rendering. + +## Features + +- Write shaders in Rust (vertex, geometry, fragment and blend shaders) +- Multithreading support for parallel rendering acceleration +- Many supported primitives (triangles, lines, points, etc.) +- Textures and texture samplers +- Customisable coordinate space +- Built-in support for index buffers ## Why? -- Modern graphics APIs are complex, verbose beasts. Rendering with the CPU means - less complexity, less boilerplate and less verbosity: perfect for testing - ideas. +Below are a few circumstances in which you might want to use `euc`. + +### Learning and experimentation + +Modern graphics APIs are complex, verbose beasts. The code required to set them +up properly requires a lot of beaurocratic mumbo jumbo. This problem has only +become worse with the latest iteration of graphics APIs. Vulkan's canonical +['Hello Triangle' example](https://vulkan-tutorial.com/code/16_swap_chain_recreation.cpp) +is, when shader code is included, 994 lines of code. Compare that to `euc`'s 36. +This is obviously not without tradeoff: Vulkan is a powerful modern graphics API +that's designed for high-performance GPGPU on a vast array of hardware while +`euc` is simply a humble software-renderer. + +However, for someone new to 3D rendering that wants to explore new techniques +without all of the beaurocratic cruft, `euc` might well serve a useful purpose. -- Modern CPUs are fast enough to make simple 3D programs run at reasonable - speeds (although they are of course no match for GPUs). It's possible to write - surprisingly complex realtime 3D software with the CPU only. +### Static images, pre-renders, and UI -- Not requiring a GPU interface means that `euc` is incredibly portable. As a - result, `euc` is `no_std` (if you have a nightly compiler). +`euc` may not be well-suited to high-performance real-time graphics, but there +are many applications that don't require this. For example, +[Veloren](https://veloren.net/) currently uses `euc` to pre-render 3D item +models as icons to be displayed in-game later. -- `euc` has consistent cross-platform behaviour and doesn't require a GPU to - run. This makes it perfect for use as a unit testing tool. +`euc` is also more than fast enough for soft real-time applications such as UI +rendering, particularly for state-driven UIs that only update when events occur. +In addition, tricky rendering problems like font rasterization become much +simpler to solver on the CPU. -- Running on the CPU allows a more dynamic approach to data access. For - applications in which performance is less of a concern, `euc` lowers the - barrier of low-level 3D development and allows for more novel approaches to - graphics rendering to be realised. +### Embedded -- Unusual data types can be used when rendering. Want to create a command-line - 3D game? Now you use can use `char` as your pixel format directly! +`euc` doesn't require `std` to run. Are you writing a toy OS? Do you want a +pretty UI with a 3D graphics API but you don't want to write your own GPU +drivers? `euc` has you covered, even for simple real-time graphics. + +### Testing + +`euc` doesn't need a GPU to run. Most servers and CI machines don't have access +to one either. If you want to check the consistency of some rendered output, +`euc` is perfect for the job. + +### Unconventional environments + +Access to render surfaces for most modern graphics APIs is done through a window +manager or similar such component that manages access to framebuffers. Don't +have access to that because you're writing a 3D command-line game? `euc` might +be what you're looking for. In addition, `euc` uses Rust's type system to allow +rendering to unconventional framebuffer formats. Now you can render to a `char` +buffer! + +### Relaxed data access patterns + +GPUs are brilliantly fast, but there's a reason that we don't use them for +everything: they're also *heavily* optimised around very specific kinds of data +manipulation. CPUs are more general-purpose and as a result, `euc` allows much +more dynamic access to rendering resources. ## Coordinate System By default, `euc` uses a left-handed coordinate system with 0-1 z clipping (like -Vulkan). However, both of these properties can be changes independently and +Vulkan). However, both of these properties can be changed independently and `euc` provides coordinate system constants that correspond to those of common -graphics APIs such as `CoordinateMode::VULKAN` and `CoordinateMode::OPENGL`. +graphics APIs such as: + +- `CoordinateMode::VULKAN` +- `CoordinateMode::OPENGL` +- `CoordinateMode::METAL` +- `CoordinateMode::DIRECTX` + +Note that using these constants do not change the coordinates of things like +texture samplers, yet. ## Release Mode @@ -103,19 +150,20 @@ euc = { version = "x.y.z", default-features = false, features = ["libm"] } ## Goals -- Support programmable shaders written in Rust +- Support programmable shaders written in Rust. -- Support common pipeline features such as texture samplers, multiple rendering passes, uniform data, etc. +- Support common pipeline features such as texture samplers, multiple rendering + passes, uniform data, etc. -- Simple, elegant interface that scales well +- Simple, elegant API that scales well and doesn't get in the way. -- Correctness +- Correctness, where doing so doesn't significantly compromise performance. ## Non-Goals -- Extreme optimisation (although obvious low-hanging fruit will be picked) +- Extreme optimisation (although obvious low-hanging fruit will be picked). -- Compliance/compatibility with an existing API (i.e: OpenGL) +- Compliance/compatibility with an existing API (i.e: OpenGL). ## License diff --git a/examples/teapot.rs b/examples/teapot.rs index dac6627..907abbd 100644 --- a/examples/teapot.rs +++ b/examples/teapot.rs @@ -69,7 +69,7 @@ impl<'a> Pipeline for Teapot<'a> { let cam_pos = Vec3::zero(); let cam_dir = (wpos - cam_pos).normalized(); let light_dir = (wpos - self.light_pos).normalized(); - let surf_color = Rgba::new(0.8, 1.0, 0.7, 1.0); + let surf_color = Rgba::new(1.0, 0.8, 0.7, 1.0); // Phong reflection model let ambient = 0.1; @@ -102,7 +102,7 @@ fn main() { let mut i = 0; win.glutin_handle_basic_input(|win, input| { - let teapot_pos = Vec3::new(0.0, 0.0, -4.0); + let teapot_pos = Vec3::new(0.0, 0.0, -6.0); let light_pos = Vec3::::new(-6.0, 0.0, 3.0); let light_p = Mat4::perspective_fov_lh_zo(1.5, shadow.size()[0] as f32, shadow.size()[1] as f32, 0.1, 100.0); @@ -111,11 +111,15 @@ fn main() { let p = Mat4::perspective_fov_lh_zo(1.3, w as f32, h as f32, 0.01, 100.0); let v = Mat4::::identity(); - let m = Mat4::::translation_3d(-teapot_pos) - * Mat4::rotation_x((i as f32 * 0.03).sin() * 0.4) - * Mat4::rotation_y((i as f32 * 0.005) * 4.0) - * Mat4::rotation_z((i as f32 * 0.04).cos() * 0.4); - + let m = { + //let i = 100; + Mat4::::translation_3d(-teapot_pos) + * Mat4::rotation_x((i as f32 * 0.03).sin() * 0.4) + * Mat4::rotation_y((i as f32 * 0.005) * 4.0) + * Mat4::rotation_z((i as f32 * 0.04).cos() * 0.4) + }; + + let start_time = std::time::Instant::now(); color.clear(0x0); depth.clear(1.0); shadow.clear(1.0); @@ -136,6 +140,11 @@ fn main() { &mut depth, ); + if i % 60 == 0 { + let elapsed = start_time.elapsed(); + println!("Time = {:?}, FPS = {}", elapsed, 1.0 / elapsed.as_secs_f32()); + } + win.update_buffer(color.raw()); win.redraw(); diff --git a/examples/triangle.rs b/examples/triangle.rs index 881c1b0..97bcbca 100644 --- a/examples/triangle.rs +++ b/examples/triangle.rs @@ -4,14 +4,14 @@ use vek::*; struct Triangle; impl Pipeline for Triangle { - type Vertex = [f32; 4]; + type Vertex = [f32; 2]; type VertexData = Vec2; type Primitives = TriangleList; type Pixel = u32; #[inline(always)] - fn vertex_shader(&self, pos: &[f32; 4]) -> ([f32; 4], Self::VertexData) { - (*pos, Vec2::new(pos[0], pos[1])) + fn vertex_shader(&self, pos: &[f32; 2]) -> ([f32; 4], Self::VertexData) { + ([pos[0], pos[1], 0.0, 1.0], Vec2::new(pos[0], pos[1])) } #[inline(always)] @@ -19,25 +19,18 @@ impl Pipeline for Triangle { u32::from_le_bytes([(xy.x * 255.0) as u8, (xy.y * 255.0) as u8, 0, 255]) // Red } } - -const W: usize = 640; -const H: usize = 480; - fn main() { - let mut color = Buffer2d::fill([W, H], 0); + let [w, h] = [640, 480]; + let mut color = Buffer2d::fill([w, h], 0); + let mut win = mini_gl_fb::gotta_go_fast("Triangle", w as f64, h as f64); Triangle.render( - &[ - [-1.0, -1.0, 0.0, 1.0], - [1.0, -1.0, 0.0, 1.0], - [0.0, 1.0, 0.0, 1.0], - ], + &[[-1.0, -1.0], [1.0, -1.0], [0.0, 1.0]], CullMode::None, &mut color, - Empty::default(), + &mut Empty::default(), ); - let mut win = mini_gl_fb::gotta_go_fast("Triangle", W as f64, H as f64); win.update_buffer(color.raw()); win.persist(); } diff --git a/misc/banner.png b/misc/banner.png index 0817a5a51ef0a978449a85cb306841e567e1cd67..d0d5d6fbda4fd4ee750bde898098083de3f99d9e 100644 GIT binary patch literal 33959 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 literal 54303 zcmc$Gc|6o@7x!;cXc;P^EK$Z%vSeh*+9JjpDWycEu`gLem^LDzQTC8E3CR{?YeN}( zk!4Uqg9h2kH0C|yuAcjO-oM|^JD<b^9_ngN?#MjqX*1^@u^TIjzi?VJWc1bjK0R$06aEA|=UrwH+1jJi;doyQRd+qR> zIoG0goY^9O`9*xRD%~*TR@|=i^fSDZX<1qmSzEqcs5XDI+wAZf-crZomiNQwb4GL@ z9$zoDbJfh|5Ze|_o>d3F@Ja1n$N6Pd0>*P;iLn>^PW;fQM@>L#U<-X--47WfTS}{t zlP62N7Nt=@=>NChGdn+iL{#vSkep(%Sf`?uBYr*Ro4CW_4l!C#ZRX*Q$Cmj%x&=2^ z{X?KVrvRe8{`T-?Eu7okIVD`E@%PRkaq6IWpfrw1e`(H=5Bx+GS;H)!*}hFKC@ARc zXNX{$Q}FRF#G2hyG6tulSg`+}fyk*e6jBne5=j>iJk_CzUiUKqH)Txx(!u_Q?Vl0R zC|K(DR~l_VEj)cze4a!9l1^gw)K&q3C6}}xzgH1>bk7%s;AJC+gRu0gnJEN^6eqk} zpIa?sd(Bi0ok;NeJx~!5M6|D7l%LnyRQ8+g1c?TA^r63E45+l5yXf^EN;x8H=uH_PoIl&^;F?!*9!4&0S*CXMY{}$5dVK$^uTl&9* zRG~O{>~Px6lJWl%2?rVxjrNIOO6BbPy`J`R`0U|LQn!v6F>1f+Gj^K}{u27W9_&ZaWcS(1#A|I5f2Z8ju6`=ANK+A?OquSn4> zPBVZwx4@6vCanA)37`d#Iz`@vCrr9hgyxT0=WjgBMT=_f${}8ALXQiZyYlZtZsbEF zHtQ_Uk24?Y`u>{nEAIKDq?VlUCqzrve+y9tB=M9-M&sWsehEiWzf+d*Z_b?q>G-6{ z&)Q}>61N4F{>$liIai+_)66vdA83LIl%|Bh*UZ;zz8 z?42JJ{m-B<7{qmqiTaN+2n!(g(uNF<`aeszDZ(@v`T5ixe>FQBSSb^g%S`?|?Mpa2 zV##-mX+-#Qr$v<&kv3xdlAFTc-4ZfTgxHtwgLy9ex#W$!XryFA_VW5`f2Z*%B1bP9 zGWa+CRVR1&Vh>-0{(g~%FM5WI@{NBf5(OIFVKdXeo1x*}+wMoX40_d;YHd*&ea!h3yUgvP1?HAUh&A zxJ2mhpldKlpEc;OxMgmzBN8`Z5c=<+oiHc|2Aw|tM@(yU*$|%d<039meu|>Qy`k7e zReM?Z4xY!m;$P8fbcp21O*lbuiX`=gS?*ljQM1_+Wo%NXfUhnd}-fCygtYz7r`Ys;8FPy~&6lZG87UZDJa9UoAa_=2Won~bv+!T>#I&5h_Utg-q zJ0kLfnU!hr*n9huiEUD5p19R+Dji33ZmigjAcWWhY-wxu&T}hn1IdI?X*A7#vZf8* z&3q=CAvqBu6~27Q-F;fpkDtgdfb^uJ+SDsaq(!;=`Z<;KL7e}Qf%z}CCp$(=c>}8- z)N+G*DXv2k_+Gu&YI_ZrK0BX8qOURKeIQAFUgh+*?Sn&NjT!f&x_--g>L#R(+t3WIPJtJXvr z%t2caBlf|ZeH*Nb)DlA6B8dx96AHUABa1YCpS5;`|6m^mA!cJB6ZYCkMEx~ z7huiVenu)rILTLL%`72PZUZ-WaK(efbXX6&X~YW2E+mlxrc@&3?s|pcr>9p!xwqo{ z)XbQ(ag!aG*OOB}y|kqNw%Cu|d#+usTRiSeW?apjICgDuXhNV& z_@7xgiD*Iem&RP%sS%H#v$UY<%q)=Hjh(()E}hv%-NHdXK5=}p+Caz+8~)L%2Lx&c>Sb;igo3sl$b_kCGn)H%qOrX9b;VY#&rvA+OAi%y@fOrI#6`$!AT$V=JWG1yT?oMG3r8@9j62 zt`v~0F%GT+Z+P2;e3?`FS>Lm()}yA)@9SUeX>8Q{Op|LS#h&;?8?>tM7|E`h079fT z3m`_3xxs1ZA~Zos-e=S38=@u=^u)qrF?g(2HLc=a)5tJ|VToJ+y&&uUJ^d_Z(my9S z#FmDG3kac2$>TS-`KO954c9wi9wht9$m~=o4!+2P*mJ=tgz*a+I8T`Sre94(<+96w z4(!T{MiV4nzthHq~3hKbna(J9lL$ z54nU62gA(ltI9b>J6CcZCwA`tNU!2v5>N_52@ZR)435myz4z( ziot|oE_NEp5L6;Xh`e7y!$~4%^FCNE-#MRU_H(^2vn>7>_eF0arrM7mF3i!x!hZa1 zG?tNzO#gbNdFZ)_gV#7S0;@@+T+-_J`ko0VdoCjCG~9=zYT*3ow=*{g1nG(}JY=l= zpSNf%sxv0-v{GH;XIu!wor-}CZEbCCf~Uk1Ev&7r8%CRxx!?nD_-ut*R@OyI|0wc1%u8xdSGCnZo$c-^C~$4gtGR|&e?QDquSi_ z8oz=KGn!ihD2#_v;YyYCTbV}0GMv9Tk?fp8itDd7=%C@tv@`exr<51EPi^}^&yV;H zQY#TsLOfX7vJGOUHAiLRY8~-YjTI3j3S%>t?n=buZMnP@(4MYi1y095D}iw{Q!DQM z7KOppHHnzFgQL;D^&gQ&K0%~Yd7*T_l*3?^Q$n2tE-V3ENw?wEDNpLD&a)_7er3qE zxVRWctDGpf-aP3?TVCa>MJ7i^d&LXT$H@6vW>mtF_Zz^WMcN}@2%vuGeQrM4eF$-G z=sI_cm!M7m7H77SGO#m=34sy~n+sy*vM@N=W5#j13kwU|PZoA-4bj}<6eXMbY0LGN z`3tG1Mx9r$cqe%cR*ZD9ugpcu!js};7B_bcOHWC#@|}LcG3Zatit8%gWApR-NPsI0 zsmSC{>S?E)?b<*~!4^-Q$otT(TyPM4;=(~T(F@?rIYV3!#e|fUlsHhvJ02>0ugF2> zu)DgVqSuA43P}YeWO^IU?S#+Ni|zWIUYFVvomc#>jX!vb?taGms)^`(mGtALyo@-K zbu8xXbwB>O%a??vMrd-%#TEwO@F_Xpf8rN<9ZUUB#gBuCEJe_S2yjcHJkDSizrWOZ@SyY9+UuQ?j-~obv)db_3Ho;({S-#-i{R)Rrmfr7@6ihQ=uKF z`x#6hFRv>>^Wz`NN6s{5&dtv^4?Mp#45sBgzCk~+I>y9Ly8{J*0JpU;m}94OR5U7k z8D$&kf_!QEtWtBZ!%2aBD$c8=zrs_if))C0m^E%n*SYPJq!F-CzHdKT@JxFogv!w} z0?4sq+qc9u4<0;Fc9`4TudXgZ^>j!Ky2y%MlO5xWE;hw#h#7U(`YhJo_nKZUM>iv4 zrkbDbVFe(E*hEEP-!I{?Z>Cx9uLq=H)mQg?lX{rBC-S7R<*22qSe3ure5_2!$TWOpo^JnXLXR4;|EZ8;Nqv3R^%8E0xslAVSHy&ZxR$K%AZWFo1 zi1@`N-(#E$B7{!?WSG`xj4jS$O<_Lfu6(lcXZKQT+6aeRenGy zKaO}sYl?*|TB3B3eTW@{O^TPsu8qL`*-cf#ao9%FsxUasgv3mF zHQXRzEg-1kBg6>L-fYrV>!whugTXu*^zM6AB7!4+>D)F7h>!OA^K3RD5IY=Iab^=m zu=Ie7)6;|Gngq;S-GgIqaS%{;9m7zz$?NJSW{Yrc6|u*G=~BE%pJv9W{{sGLgGR(l zw7>$F`_Tk;h@A)eC|bPxAKi?fba+ zyxcDSdmfIMoN!K)Wtrs#kj^@=%&{+&5CPHfbS$R%E0c_S?%nm|l$+^Cj$xyRC-Nww z)L(>M_^5~o+n@40QYDbtY2rK;^cSl!xPkkovaE#wU_G|75TvEqyCR1fn47LIosB!^ z=A|tP`gv7MI^*H?>WZ&XW3%q)qr)9R4YSESEHC#>A=%AqFT3L0jteU82TvFcSWOJe zTf{cNQs*WonLSrKe^J1n$z1hRynkQX!qsCj+E-Y8qD5`$ic#GUZG(?BfpkIfZz%^J z-&Hg+!4Yi_u~z_5ZAa{p$<=V-db#?L_)J!i^NBXKA$jyM23NC1MfaHHbN5egh(X(S z*CbT$GIvfp9^cca@XoUf_R{8CS>dcbqK*bDQ6f`@l3#0OWfd&x;9AF zjW`*6>{8CCsFFPdS_c4Nd!?#EudDggwbebHT5v5VT;;X89n~OyaVP%@rC7@gZZX_g+`Gu*mx$jco*# zBPi*xv5+-D>zvt!E|Dh-*&3KXgnj#q_Sr_J-%9^FDDJ|&sgDvxo0^ilc?->Z%EZJ( z=Uh@U5!?s|+ZdccJ1GOjGM6?TAK3|+aN`3p!h-T|6UANMeEC%}mftSnNm;}d9$0*;usLN?M{hyt}l6d_J`$$~7&4I6N&j+cMKh7HbZ zx7J37goJE&Up#*GtH4&}mmX(h#R{A^fM(O(_aozY z$2&eM8g&-3hg)67`y5Ye6UKK5gTz$uKDI-=edF^>I#;2DgdoJ;%$2=uVQOq#Hr0tO zLen73kgT_9{tK*=k3(>#1G@nI<2;upd!K&b3_rk?HS9N;1`}9 zooSx@e6;BFfyC@QOxBL`NvFQfwp=}UYxuGpu62FFE6R1NaaLNQeyT}nWpXl1_MlbG zwqy|xKG>4lHQ~KIT;onr*rz_Wn|5qSn@ALM?ayqjeZ@^|^!NEg8Vq!v~-IVuZ|pgYmg1N)N<5t`D*NG9;vzYd@`2rx4@yd(zbI%1|K3~7H^V4 z#EAOYrhN!RA*b*QY>-?a%^A=85CEOH-;I245IAmew<6#vJ0XFAF`qm^%%%!oJ?QK0 z?Jdj)OJU?TY*P>7$W6^WE0&i%|}VwQ@k0Xxe92w!?Uz6G9El^rsC6 zc@I3jy%&;F3G+!lp2w&(sc^oVz)!t4CRc}|Fy}TASmyGxSO)|$AS{Iu*vYuUT1D&) zh5bjLN@T0OxsgsMpfPFqr&i@WQLdCq@8fA7W!_Mx)@|t{b8XlnD0Qu-rlyC;$8Puu zBGXDu8y2QCw97Ib1wm|-axpe%wgTccZLV+<+Wkb9nV*r^#O`qa>KcsAkBRR5)8#TY zzhc|l+lw^Koqe@L!NPC?e19cA)2xoSoNL1dbzXUv6`tn3>4wZLEVN*Y3mpY&`B_xj zz3F4W)z1(#h?9LV__Z?e0ZC-L{1q9S$=Hte_8FT;9|4D^^=o|9*h*`e(Sk)9&wLU@ z#Kgo>@0BpkHA6vV?RUZ&AImc`GKA&UoE6ypl*D~AoZ`sN&R(Y1yLo3>0-&v@Jr}PZ zs*i&7I(i1=M-blS#b)!CD83$39k@GncBwn{k#u1d09=T@lBx^auj$Y@D%$O9YR6aw zT`j=he*tZNqS*hyqdLg**KQ%{-0WSIh9Z>lEp6Z6TPtJB;+Ku?{_nCj68qKEn8qEl zF@E0)NVLapL9>FE&!?m7j%&0Oio^~C-JQ!g{j6y%qN7xUp03{4u_i~4gSJV1WU{B& zyVpUa4JY{^v%F@$aQ@?kD5LRCxMgaXKy`P&X3Tr;HvhzIgJBgezXt8RwI6o$S!BH0 z;gI%2yEp*fI5hA7V5NqkVDS|USB&Irx~k{s*Y8cWg#ayv6pWe33Plev2RlMUPcI>D zy+K}0Uq|1MS<6+s_(|;ImsLM(ab`wq|MHJ+YP+s)ka<$VgLEYa#LL%rO2)xBZCV^l zQaS2uXk;hU5FmluJeh(@dwbIvaZc3=4ti=(#o}og?*eBU#Ln@VV@9K;{AVv8fFLZG z@pNu{Hmi1-i<^nEkx+8p#yGn)KC4?>j<*@8s;HcPxn6?)eeA^U`#6Ts?n<-Vd!Lqs zzVqs|XDUfw<(q#(D4IG`bpDNA9jkg-$F-#lB}%>Zw(9GnjHMFyPsOiJD9~PBZ4Wyh z`3ps;-!{np++8%rYdyc_Xl<+tR?dW4g_B%oCif0Q@ zjjy%`Om3R%*ydZS^^fRx-gH5p)0boXD8(sTek7~`7Zm*$YXxfFD1> z`LHRJ3tSvn=yKgIsA@v#mmenTC2A$`gLMdbyWFSCvWV*HL4rf&QLAx#ds2l>d5y;H zOxJTVY&IBO6C<=0?BBotT9B4GX@Bu>_kv(No&;^ z1NX(U_*kW4yDwI3&rA!u`#w7fTn5nY`gYX4>|HB>eWT7@LF8xnPHILWk+yWhOTJYH z5yf)Qc2Jh~Qj>?XHfz;C8=@T$iFoMu6h-hEp*>Wt+`d6br0MyQ44{6c7XXg&aYsrr z`TdU8Xqt+_{)vxO(kMdLV*7o5kun4j2Ib(`KMh#2!Q1 zvJDBB{<>JJ^rZk%uoH^$6-SP$ z+rb#yj}WhnUkgf=zON7EHwehq3m`l0@1({w7%Zm9$n>jCIU;F4h2^a)N676{4-6T~ zJWy3DsZ2u?z~s5EUopJ#oF*2}+=sSzxY+62rAEW*ZB@ovM=Hl+I?mC*SglQY%qq5T zJlO_9varzjeHKV#H|uohTC-{!>ODW+GgV>rqPtb=5kfS1&m5b7q#4kyEql3xxDU=e zWD>t5w+`yOP7{qJLRCdT8+NHvaYSk#Q2pf^yY_Z~g4<{?1^_2*CntJnCtgWNAt*7s z__vO9EOcsC2n@3Sc>keeTil2h8nGRoyO3!j)O4MBeyyM1$XGw)Ght@$8i&Mz1YdOH znuboj1A<7F8B`eI=W&2d)D76@c2;7ZQS3Fw=4Y`QIVS|1S<(<%JZ6~0Rv+}}M4mW= zx|an2QxOI@HC;@wtbLcS8(n5Si^}WD^ybfX8kyD)c0KTR9=MNa4+q{WUS98dq3)w6 zTRr@nAu~Pzx2fm^a-!ZfXtOX3VSqDNSJg^zBJ!;H-L$!!2AF`_{rHesED)HEjF!un7Q9+hceqLz75a zl5k#=Rtk3uoc9xLhZ?!~qYp%;+S~0{qS}=L^*7qW(W`)Y!M-1NZP#bwzTPJMJPV`~ z$n6Lxt9mU11Qa}C7T%oS60!;0X9cOJr$8v0LN;7+B+YmwYC=- za&{ICxS-qNHEN|>3@O4u-L4c7m4Ct&7z#+9_6`^-xwkkPO;BY49(!!A46R*STC!Kc zJ?@?e9kV(V^YeJ)5WZ~B?>GRkB35$@)UUOCY?@+al=TJHZW*LRac&X3{LCmx$$JEt ze=GeKd)@H!fHv^f;sBn{t{P*}mqK9I4q;vA_wh>&ohQp(DfpWj>|Y-dimpmN>Yh+U zFctKUDkpmh9I;=TT142T-&gKtz5RE5rHFVy@rsZQ^&yED6btqZOb;BNyyiOF%a>KY z3TCo+5OJPuD6F6Cx;CMXd{#(d<)F4O51@HMHlz^QkR4C3g;=+BcD{P0r$_7$3~2lT zz?0{pdbw-=ncHb+3q{1sTzxQJY(tGBO>tA(R9IR!t*eM6vMRv}Z(w_e508)6#+69e zge$X0+*#FA<>v{uGuPaauB=5{w)5+5ix{8x->jlD)|G!c;U#FgHXbyD)uFVy;k_1~ z_LxwVdQ3KMm@s3yaiS}~PhuAunBNz~rhTTW`j9Cv<0^*Cc?t^A*6-t_Bkk04@tWT_!_4rhzJs2x9lr$4X& zWX*^oB%bzZj-~rWsmiEhRbd<#w(>Zy>!YL!?)KRP^@wgz6s+fB>KfzrEjsOC|C!9P z@T>|wr`Y|uLDcY67X-dtK)ui+M+768P;`6fOVxUNJfNJ4UR>A!x35CLG{RxFw2i=a z`HO}OF;OfDV|&FnnE8FA$#-o6TXQQpNKpQl=dcQG!`4~{u!xfzaT;&{a(`e}Ci-fp z(Aiw`vzu194|O^z);haj9kvgS^ZvNnb@89;T>8qYrERY89%^*`ENjXugNAiJOw7lK zpBnI1EG`N_f`s-*k%HyDSh*bN>Nh`r)77-)R~tI2(sOchWCTrNW+NAxz`BlgJkX*c1YB~%^=_AcgH4xO$|20O|GK7x z^fSbF3@c~oXL(eygkZWFs=RU1lZz@ih-GR;l@wr^jT>PZR-?i(W@8f+~8SKSi8Qcamnj%FVuX9M*MOFK(VbFI9s?-#s(oOQ+Z0f|J zdN+l!Q7i3ssF=wzGwQ6bUs$+5*Rz3DQ%FP4gi`t7a<+YjJ`2yK!|bYOw=~Q&U>mQG ze#ZNFHm=4mg!nvnhJp%xgw}G&@>mHAy(KP-Wq7&C#WkI2gGvA)Bm>oU4Ww)deHiv; zIO4CDltQxThi#AvTW$MtfalU;8XCcQrc3!73<>$&PM6v*H?=eT9fi3s?+o522>Jw? zp1ot4*#%G+9cY^=f!-8T1n(2VTQls8x84J64sE%f{cOM2Dr`V2fY_A)@fip;OtFex zx~3l-OG}ACDW;VzibhP&o@W^+r8wP6*_tTVEOk;6YK{JOE*0GX&b z-SKT(=6~Ia14eeT%u+3G|b!{R$0~?@P^G<1yCi3QrixL z_D$%CSL<|SY#UWK?l1xYJ>TG|ln*Y!?~+h0Hgfni`7tf%ai-O5fs@3MBS#8|CA%`b z1|OO4Zc=!+<)Bx$m2cpo4r{3QulK;x(-fXh|H6^Uni%RBw9}GRd})#D-y17fmDo!{ zgOA#q;;oZ>jerClDB!X>yuPsdlhQCanqo1xZL*IwmZTmz%>4T8n}+;wf3xT~mFjmY z%dEoP*7i#|arwBJ>QuVG#c#c(QHedl7w@@3$@!FQT%7H_!gH~EPCXNi9F`WZR-Ll6 z{4g{$G*4W%Hl_xMQ-m63a!!A%374Dzgb-wK;j_we%k=d0R6iq+xNdO}12Qj8Lm?mz zb+A}`C-P6!N>I~<%u*Eq=gR{lbs|qoG$s=?Bt<2X2>>zEsMG5vsrGnBa@EAT7v!4z z=PEUtoppp78u0O+M|R)Wx3;qz6TlUcq2gU0NMERpn>%VuwKG`az?Hwb^s7bZHlO#1 zx$diZDEYBQ3pH3*O#rm8uUL9tFtQp~R*6S7w7+2ELlnR>q=o9OSryAd_tFWs!c(C$n9HAAPu& z-M$7=V4j)kc?G%CcQJ2Mo25)|Tb9_jyTS!LD^6XbdjIKxj@rYfb6HSb9UL(~l)-{g zp>e9Vi)atBDH}&$XC$^blK&iM9bDev&Se%eRB?&B@}j zlAC&V_-a+1$jfggMIPB5*W|XZc>Qd_p^hm`pawvXXf&FkG<81P5s(_s%F4>KvQ%$c zP*vvJ{;_AfaYP)003mVRqsXfD+Mba6t1O?I>e#eCGVWr#*xzL5W-G^+k(FF0spp>p zv)_c(u*-pMa^3Iw<>Y5Fpw zGyR}$(8{nK+WxXn6rK${4Rxz9hz(Afo#po1 zkEyPwSaVQ5RDvfF?HHXtE0&bhDb+Z%jYUO?Ak zq*5x>{Zh5m)qfqpwL&#?ws}$)w8;r?{rE&)oRgQDs3!~Vhjy>qnaULbArpz0pFs}1 zYy>&2PDkc307x!-!EW!xo_{5KcB~SdGB`WsxjaeK{#j#oTdK`*l3ydim!~+QZ;S z<8)2E9(;FlH?C*L?StyOIFaVD7!ZT``2hcM>sRPxx;)peyC|@2n`#@GPk(7x4y$Hp zb}N)2gaVHsucL_gq*DZuVaQbDcV;hp>awl$rW|XY%yXnIZtoMPe4;`1m4f$yK1KK! z4G2kR0qxT$0gcS{>PlCMT)bP7N)p+q)4oirsXfw4v&K+=62DizW~Cr3dkZ+!#QoMH zGc}Le1S`p0Jib)Xs2hX(KIFAZ#Rm#*s0%Ebc^oANfy zsEIS2GI&Zn*tCh@qLTU{rfo;39pQ7SeMt|Y)akf{2| ziJm6%0vm|3ST)73p)IrH+_o~A%8|nj81QSFIBq@v*JO30PF*sY5@^h;V@0b8CAEg9+t-jp zFjQ>~+Rni}G=KSG&%6)g(HI@m5+x%~)t*Ofk3Wo;532+fWMaP;Tnw+o2=Ac|H3Yi} zFzLd9wT_*1T{zJ?=((1-0|$a6T;#GP)JLJ(>ZVY<-h6UVbhcsn-Kj2BJ>r+!;G$Y7 zfqny}hBg+sRzbQrHLN+aLHYW3=M4!x1X3z?xfKd%%4lwGQn>Ql6cPrvyf<>#3KJ+g zWeM95B%R8NFPkEOr?4$rOSFy~7eOyeu}PzZi2U&!5i!c_TSJlMKVXF4kEnyJ0y&K) zY4C3HNnlAb)MR`e=NcspTc;-Gmr^Bh-y4EMS;fc9b~;)f4K+^*>frYR7VdQ!I-TQV zVRe8f(zf?3(4C2qZfyMlWhL%Y&K|4OJnODdW3gGDk*$-BsT}3p6D55osDN2`eBoXP z%>jWX9nzSKb8M^g@sox>O(2jv;DRky<^rqi!y)3-Q@n&7g6(!wtDpl4w%ZO2#6#v= zvJZN_99T7GXc*FaJYuyz+=13#kt$#8^t~X%rk}i=ZJA#XKQyd};OpUDJx!=SZKko( zi--W=B*1Fb5{4y0wh9SVS#%g+0>!2*kE% zcf>>cDf~y`M=I?8L< z)m?Huv0xpzY0#&^{hierfqZ)?Hnpm4dwn^|)&FbG{+~S}9lr`T zY<~>eS>>1TezCRV0r!p8u+M5XHD2=p3W3wip@nk~%9oF9|HY$%;_2un9;J6(v~df1eX)rG}1(#T!o{LS{(}sX`Z|QvhqwEO_-hc z4|L((E5_ULaj|sR-`_tHYWOk4NQj%SeH^E0HIq0&X6c_O8VV5pCc;w;?M?v!0qBVK zs7kmm9%#_fRpsP(J}k2nk|ik|Q46HQU*7bJ*^u|a(L&GC89#W>yp@#F*3WnbkRhl~ zt35k^OC{a1=u%7=tML_sdv>NH55w3w<)8SG0?mxsR4nwoXhCNu1ext82i6!`2sL!$ z#`}6E9)A$T-y37(omJ0hZFmh4of`{m3}VRlA(wY&FQ4u}R~II=k6G-F5okQ~Pt3L{ zI}4f)QX#rHCSHBKV+ZKO>Mq<;tABOSOyWK#*H%(I9$*sgm^mC)4YwwUsdiNm69|ee zqP*Kis9{mh|Ag523i_u~%R3itN}O}ou~{Ze(?I(;WN&OxSTJDBo+u zs&zvqvEc4p#SzUPBK!?ABaUsCP6OTv&%~-R&{n5x5bh}}X;6zTmKJApgH{A}L?#&k zjXT^q5RW$$q!A4Q&6~#H3Szt{taKIMoak-Nt0P%SFJdK6&)Iur9rQ99Zk*ZYOY2X~ zXY~k{tj!dRK_zAw9>XKrtCDXk??P<0zA zliozEQ7^0Z_zWs`a?SRk-m6B$pxsnSypK_-rIlo4D!`leD)5G59pJ{cm1y}OWq`rH zK}|DF{EpG>W&6oEC?}u8FRb$ou1FBx;c%c}jd3%B7IZzqYKqp~nz%1-|4J8ky5*bR zQm3i7IaORLARfB$Jx-WvGcz5tUEQBkr0#oV9I4G_!ka@&K3wx;18q6^$ZL;9^qpIs z1@*EUma{TFS-sE@*>1u6l#oQk9hk{YN#UDt$_4^ni}1cdQN^w$ImzP5$-PA1bHK#> zf~Pzf2+t}Z#CgGjd{tI)nn2Wi&{yjvxl&x_?%Y8fedJ6R`yI`G60W0C(a4Q9K3?g^ zLiSoK3d%~Hd!E@vL_`QdAMJgw=SOOdNAV|LK21PZ&>eki2gn7&N$eac=b!9olga({ z+!|d$I>&xQu`<`=cIIhuiX*;ua&odNOac-gZB16^3z~2RUUmsY<}%9;u^}-tj50L@*;=uK`lKG%KM;vvSJ9{;ESzce9aiHV+h1z8seC?&hdAv_Ksc;G?46v~o zdU#|xsLlO^Y#jd)(~|cT?wFF=1YuE!+M>d!K*jkxS3@xjmZW`}QLP)fzXq$5*S4%yp$>a7xT!zgx(-0HZfhuj3VQd5BgEU9V?xOeZ`O zx~ExIb82KV2rSL$%T3z$`q1YT0>#I=PSz_15yhpnpn2o70y1Z-E-yx!dUh&)^H@|J z7BTGfx&WbcAY>?~!JbdDnxGCRU!<&qOp3CD8o5@}^)|m;N^PGj3EHU}e<>O@Q&QbG zc_(HI?(+|QCAQLp;q}7a?Si0K?Z=)8KDo114L`cSEqv=UFy(C)WS-15e{9gJvJlmm zA=UfT@x$XgYLO7z67Z0X7(s+251FU5u1- zzE1Iu8`~N836A;3vWUpT+o3kQ=ehs zcM$8*Tz(Nc8vFN)4Se|qU)cVB8L~%^3m@4VS>2c`KWz#45(P_Zg9QKil#q^so~=Fv zUH^xoXjOPdx%wl!{eLLxZ-b|e*IWn=e<^C*0)X%4kL>ZQvX^I=J)~Kc@^}8hL#b0S zWV2pl{@!PY9M+%R+H2h6sc=Zeatc#+tH_%%KX{xcf>d_2+% z;WXWmfF8PQ%KXpxgYU(u$j0*<7N|Xl!npO{^$*^vln_P^n!nIg7Suk%SpT1uOcyiR zv~TV1k;Jq8_YMLftQ{`K_$U$no1}*0^)qzZM1FfBG8dhXFF;PB%&m&Y!qY=LNb zr5Aw3uJn4==4)e9(C+o0L>gvhTac@cwVj9TP$q#}kevN%>fcruYD7sZ=ihbL`WNFP z*nsirT!v(4WL14eG3vj$A111b+;}9$LljJ_$8`PsOgxA1z%{!CGW%r)DmmcG*xq8l zfx{NLJCk&MhWnD(r@wZ^NmlOu*G`i$w(NwB-s55UxjjhW=@Z6Y$KUzTW9_DEN*8g% z|FyOrYwd=Re&OdAr`1l*_6DN&{*Q1>wOEUx(>DFDG{@sa)W0V66?v4c-kVv732`6E-uI(#LN0P5=wD*q zm=(iX>8WH{7M|@%?=MOD%V!q+rkq-b1D&Q~>iHU;h~YtCqKMF}Ps?e~LdE5OU5xfLmLlLB8!3B7 zT_)Xmjz0?fmnxp$$+`vqk_zYEwYdLhU)D%gu3Fja{NUzwH%E788Kb885I%w`f@h0g zXCqB`fTXZLjl8uyT1jIANM6p+Jn-hm^u#?)OISj7M0(B1JquGj$5V0Focn_0u&G>W zVF^=K&{m6vtkhky^87(g)2V|G+g===%Ofl7tbBDT*-7HDae5`NV8w<++=jN?R!2w2 zC9X|YtJAXT&KFk&hptk&nxK_^uNQI|4HAUw_EqD%Z)_|*cvx}=T}VaU)H`91b6?HX z1UchciF8k8*Z#|V1m9IdVR;D~55CvRde4iv&2Mpwzg2mwH<}A!*`x@;zOq)k(O1}} zduF~mJMUf%dmOV>#B$;ls-JD>(Yh10)&fjWp)bXkXFi`IU%6YGd~0AkS7_WjjgXB8 zZv{R8`vA+FKLd?l<@LiesjK2sPPLF5P|w}ZJBrsI0x(E^dlVD(Mv!UE5v{$O_h^^K zZY;AR>G8MJ*>X^}?GqMmZq8D01|)r}&X)NTiI`3Nk&y~cc2X}KMH zQA7K%+d}pHq7}(UM>5mwprP@)x03gwBCB5W`p*Aq#m^?5MB2& zBAvOa&0QFAOqUh~Dc*W(;zU_J6mf7$@WMK8u3Pmvh}X||A@|Kv$=yG!Wd`>8+UJ*n zO-T>h*iJ&OaMv9}lu*w#@9I@$ZPImEXIl93)AWvyeLv*tyMKih)YUx7$&o~crcL>s z5wI~V3GZtlOKxg^5|QqL;4`gZ6#zcv?oaQDiRxN{GHT$Pc0J5@GnvJ_d;7^-#TF|; zlHBe>c7lY~%S@OZZ730Cu3x{&Bw7q^7Cjv;s3i3Go@3%yfAj0VJXki$)Kfp#Q^6WL zYosS0Xw(-x*7ATdY`JT%?5eBU#seIPpkwu*t->yD{grEZJpCRqATLZTm9d(0^{SXr z&$xf&@!lLt3xtH$?^_1Bhpv)WT_CG&j4|GMw^4ara6tZ|Am{4QZhw)LCFtvRCMw=} zh}P4jueFIt&}#R7#J8e?I}RIZFy=ZXk@z^%*KcjV??4uR@@WWqxIHVB-#2)5@Aote z%Q~BillLs1;IV;6e#Oz7_8*T(efyp&e#vy@Fvt8h|5_*nfqLrdHaxhhEn56byXjeM z!}+4xI~V5bZ+7`>AqW|NDL)#414isqxladVKKX0u;^@dk-B z)o-5re!zK0y37k3`QGmCMUT?u$RnAhnw)iO4|_>rm9ev22-9ZtWNPM6(LQs zH-#(f{R^@>Po3;e|2JEUbMy0M_HVB^TLO3SiWKBDI(*$Ey-FipP& z#)j=_dUnrDn5-U}fF@Yn`pPVG>Er8xzNG2Z-EL2$gt$bUB2@Y6ULckUejJ-zPUve0 zgdI-FU*Lj=lu?EfF%2k{MzvO#9qG+Tmulp!Vd^mgp)bWbuH zp8_c?(GQDX=L&uGt$u6LEiN1U&UA}-KX-e3sb@iqCE@$d#nsh;mY$x)TR2y99T{KJ zaFvojm2LmP1~)$eT&V5tHgSgkb&BRk`KL=y~EpWcJn3|xEqw!7DeNU zNl9uFm0Sr`6IbGIaj~_9zn}W1g9JtnSDpOE@arz;!fURoGt7E#h*u(4S?!wNjOBj+ zIT=dY6oIIQsN4k~$#q6vYj$Uq)`(~?+BfNBL`6DiF?)Sq#&_|b-)TjeZ;S8Js+o#9 z{y1#o@Kn2O@au%{9~Wk__H<3Cw4AOl$#@%8($$KnC}vCwMh;(DgOqfcpj37bS33zc z{0tsG^}@2K78wNCF8}&&4#a36c^LBw``XGQLztZ|!@cHw0$d#gqi>WA`KWr@SRw?4bLnI;auU zkTZ7+X9}Q!tfCZpEZCdjX?xYfYx4_F_e==wTsn26{R5~0mT?|lABV2K{({Ldw2D_V z6;*eEe8v0p={}4e<5tU|@hJ_ypRI!lN~?a{HydB_*v|%C&5>sM50D}E zo%>hk286w;X12U5UMJ@5c07D9W4lb#{*YUmYlnnvTlXvk@YRXj*=rF|dMH5sRIGQ1 za_=wsOqx??Dt!l%IQ!(8VBOJOefZHru+Ad9r;j*QZ>$lrBL8AQ3|BZ{09R{c-9@P& z+BTcfDc~8yXV2LFre}Pc+Wp8^BsO{5%#A(bkN+9G>S~=y)3*K+==3@Sag8y%`&O*T zXLN*KFvrcvVq&Ufwn=J`yPKOEZtz(C+YKwx*ZYWzr>q)Xe{m4*Q<>*R)oy+L9@4)aHOC;2TD@%C`{=B<|@!P8N{=H_PR=Y2>^(=XX0n`}pU!oI%l>gsB} zaquDc!s24#u8hQEcWd{GrY2ZUFgmbYa1;6JB#)dLLv}97Z(;=OQhVy6_L|&{^(+eQ zP<%sHLImq1UwxX^1y{_Lk#EiocJH`C!k&3gj!oaT<@faT_+?I1a_?MBPA*-Cl@m+P zK_Yrl&{N36`rkn0B>Ze5pLsH)>cUR&O!%o`IgzwE?DhX)>b>Ks{@?%c=OI)!h3ruo zDILciQJoMk_SYftN}7c*9&R2llp!4%HRrGu=-ZJ#IyEgNd@o5a~`k7B~$u{*}v zvOx%m9~&}%1}j6v5g6uY${1-rO{Cgb3999W**rz!%sH1KUoqei^QwzyJ-UcKsOa5! z46NH`VAeO~xP8TG7o!4~QP0B>l`@?ur>psfTM6odD;L2xaz!;A+;OGJHi}vf68J_1 z?L*c>8?}T0nn?M3w_zSh26KMTIKG?7!ztCswPLa1xI3-aAS?Oi;O5H3c5oIubxg&% z7}DCwW4s}4s>ibrU^x>=3vs9~I4yo*a)kSI~PEDc>~tR|gO+MorL~wWX5tsiS2R=5dc~G=p9rrqWPL8XY znVBsuFDwj`9(G_IT zIvkZco9Uu+-;J`jP1$w6n=7Hjq4h1&dOH1edGnzvVH*i;f?D!~pF(`VJ0S2fl(g3uTt16z8&cg>`XW(A&B1V%mF?989kvP{;s9*h!mu z&}s;>IErM>-w6dnmP0putXwRPvEgAq&&yOreUE(^~wQeJ5tjo-5th?KAsYck| z1kjHTnhA6fZxfmib=`sGVZLNq`etg^u+RsKJK(XL#5^;D?w2@q=gfCC>bpB%1(3)Q z%!4Lu^N!6A#j)=CgF0houyah}PM)TQR`?gVGgt*RkA82_`~g*U*3g+oGqj}+vKZ!n zI*-s(hWm5p4R>&|^>RHB!8IFv#GO$WBi--mN+96S{4=P-~+Mfe`JD$YiJs);<*1NO;g>Y6MSrh z%k4`vxUMC8jF)=c4mxn!>c%-{_wF!e%hXVR2*dGPPg4^DF0t|*ABW!HbE%&z3$Nhr zh{+@qN#hp;&}dEZ^%wA*7~CL?_t=7>)v4dIww6zZ!&S~_YQly)$(;zwCE=IUf(+w} ztxQorKas1zzV3CD88rVicEu(~mk1n>jYUp~po63nJndz73DHSucoNWh<&YN!fl0yP z_H3lnL^FNJDhqC+PkV6V6X1u$@va3hcypN<8BaS$2gl%+PPNns_Mx|Ou@G3!oURS0=xSM(<0`R6Gav8237w%6@~O+VTHa=q zrm$w*`Ewk7;BLpP^bp1X`QveBbxb`pkUH`LeN69tQfSDJ(gI4IOW%Rk?=oWQ_Ta8b zo8%*}i)-j@3!;9KU?v?*0Y+-Eq;Bc+;Qt_+4R#2-|MZ1I^}oofuMK|=|b3+%{Ch5>efb5V7$!ZNUM=Xvei$sVn1Go zCPz$A{mLg5)#KK_X;tay=~_qyX8kVNYF_NkwX@stm7$Ixk0^@O`&s@ezMHAf#~tar zRp|iRgXMX5`o3Ax9L4p>_8#u4l;-8l%4?1I;lFyOJnh}#h7MezLEE}e&Yww z(M~iTEUeGsUQWbUxT72TOAA_1B?OH5uH^p$U*K3Up~#=e(%C(3#Nwc!pm?+SMCi)% zOB5x>EAm$=3NH2eNU1MrE&L=0>q~M_?iMUHhi7A~n#;>Dmf7FDl z{vL`8*OcGM%Hc%pvO^8e7WAVta&mI!p*m83Xnj~#W{f7-0Qa(c0b=It~VC1Wxg?%`BbPtyoMn zE|mNDG2`f@&Z{mV5?BEdW#3g-;T<*un-7s*yt&Lbl;+BNiv?>@%Z|c3VjFU7Ozd6q zz+q$F-Px&hC<2$VC<4>ZWhf&PWp649`@mftbGQko{KiifT)b1I2o+JSe2o4DA;ykU zk=gHz3j+)LLq%V3YHmzlTOO(m5kia+^dCOu#Hf0IS>T3}mGSXE+dcq4Di6HGvTSpm zP5D#;P<$X=doX!;8C!ggEkc0fkILQ)@NKMJQgI_TKW`wds8_OWcgf+nhDY@=4Zx&< z#krS(&+0TwTDKA2VJ+31xqU9P6WZ#dnz!F!o=P97Fc4oap^C6n1D^D5#2-;DS!9Ql zjaoneqpiH~reY|TsAejbzKI?m*XtiD z`5O-Ws{ud`NRzsgtElztUHeP;C$cJ7)cH{d<(C2?Ro;`Q(Fe$k&U2H>hvg=H-P-+n z7OP&#d;HrxhbGdLL^n6LcPGE_m;hJIAH8HVjiL2GrP+J=_pMoR1df#!@YL^unl(hH z-dVP>)nC)pT0Y--PzS0UQTE>yz!6lEA?h9=5V733DYzo^bgXdV_@)DLZC>lF^c~mK zJ7N!yv0BWN=%`jw0sb)*`79t>BWvv1T-s%+LLY{_j$h>_d@W}T70Re~i4!B#TL7c9 zn{G#y^q-un)?1pLtqukn7y`nXF3(>My!0iy4f);3>QXbQE&BOO;daK}-Q(e(-Q}9s zb|OhB#Mn0+gzdWMxI<+qc+@i`U9i(Z;N&y>9qkbY>+0pgc-Nt&OFr1681E7ov$8?o ztQBk5H(()IPo0>Y`mMPmhh=t1V(;QSVwhep*{uP=hZ=N{`+e7J{_uu``wxDweY!8) z`lTB8A4Du0Cwj#t7;sECx06xRL%2WGXKv8D4Kdw)Ay~>(H?kQhiuG%@Ez#FBQrlbk z?DwQET&^B>oi;zp$|L*%sR{6W{1FTWQsi6ysEcF|M=s$ zKD6o!ITn&0*Jok652h75nCV^BL?6w4|9+>dBMXEBN0g;Q))CI;s%EYuApB1E8&-{s)?Xv+m&E4g}gDa$aGdX>C{wlVYRmi zFMvJ7vz@eVn~AAUSkw@XyE;@k)=u6;W_7(lGXm& z`y&kQh$~?TP)g&^e&9<4VBgKZUOBR%E`?(P476EUjOe!hO%6z)%Zzk#oN~tf8yK}J z6l(U>_q=i&>>sGw>~G5<$@_B{m2@iN_zLkB6k2OSebE11OLrAg|IiI>b5OZ{IWVcv{by zDcVgBPDD`sy;OdndiNpuds)F{B$)U=F?Ag>C_sRi@TxS-e29A8PF}twV^D4M*MD!a zHLV_k{J{MaVIZ&swzOJ|k$-`%faTG}7ThlXe$9at={cZ8BlXuk!z_xpKUvwn{F3+Hk847ESrs!*h5I%odzXOS-=9RT2_-zY zmgbHbWwb80`#>{Lx&HvefEJF#_im+&b3|DrI=tB*03inDLZJ!F?aU#jS{~(68sNLL zHk+LJbQz$I>mtatyO?T&xm(*wok--tij?)VULspFEtJ{yhjFyvRhZq-1`kx%V%0$w zkgzw_I^W`1x$Tg0w@b!<(FM6ieRK*BOaLA=3Sh8mo6owyBJnotx(F*}o2}=c2Dk=n zi%{%OkM&Dp%V&h@?B)-?OVI-nlM8Ke=dgOGUH0_M0e|lSO1I%mgJ@D2qQ%oVX>cpu z35?N-`nha7nagmqWzRqEUp(F@)!wwFQ70j}nOybTyqCu(u(NZj;q-+0sNwj;MWxynb>Ji@w6ClNWhVr$tj$Y_Pxvky8{Y$SFO1pdFyGjQZf- zP91Cf`MsxSO>+xxF1*@#?=3g_;4V0~-jYKrFSfVY2}=yLPXw-?NfkUw8re)=2yR07 zE{~>m_sg8%LXpp*QcL+`TROv%O$4tGAfEBInBGL{Tm5Z%yRiN6mJBzatdr-5s z5pU?sHXe2jId(A@@$J?Aguuf1^2)bgvQN{-FNAu5%+R@KB{%7hQCX=!YZ*bJrgfex z2TgQZ_TvNB)n=4*AC;m&DI;)HS7WkO1b(XE^ z*Ml7%6dt&~Ez=gIc-F3NVli>xQqOnu8_R3;a?J$ooEk8nC8W!pe$}0b<}GCiFY!J_ z?X1ysP%ejmdph*NaVx!K^;agk>Eh;#-@TuJQ%I8TjbUJ7O2etde7C&!tdynekc#VX zVxp`;;nm@uu`v?5fTt}wV9^GA)S|7j&h7&dZJzYYJudTqT!6d2lc!h<)lX!-583V5 zmaLn~3veprgcOEN>g++lc%gm39az8!+#cU(7Vu!`pYxb(OW`NKd>u)W-#za_dkoPx zMcDMZ&~tX`>oEj4jswX-OPpALde%Ku&RS&bAAJrPHZ8y!uPfc9{fZi?S z4ehn;;4%x*lZah9-<}|0N1E}FhSC{M$$KkNUh?IE>vJZsEeDm0;pI<~nz+3AzADEe z5HnM_leAAJy%t#G)HF2;mz;R;yj%YO8A?{4ZnAVeY5GI}vB=N_4=|+8A509ma-r_~JRPC1c4VrGnbMP9PC$28r*0mP?hy2eXT(62d!cgEA` z1J(@LRM>dH&&@!Mn2oa1JDFs!%aozjgwwY)EO3(wwa^xi{usf1KqDui=0U-bBY+su z(^XrMyFk(8p!mfi-21iyOn6n^D>f1*(PsEUO2+)-WE$>TK%|NBiwh#1KDI{9gnT{< zh`SCt8_EE;Z*Uvd7gW+GnkAd~G*^Aelk<3THRq@U4ytIOUa=hZHrJ$3Ti zGj}<0EGM|RmX8_bo-&6e{DjLel@=|N7do@=n1GUzxFPG^SMV>3o9Xjt=R$j=|EvNR z!-0GCg>5<{3~P{YEgF=#z3eowV1Js;rw5d{zRRhoSa+Pd9e?SiN(Gm@%$8xpFO>85%RPVg_#jF}?=2$D z=rX9`J{)P#zc~W3$fg;u>iSGQ7h~YE%aHZ7(AfOTvr%U=hu)R*VepSFfJ1uVA$<-t zof~q4{(IvI1f^dZbFi1v<^OvSG(5oHc%b!;PGZDTki}m~@9!sR`On}daZkhdw=1rC zysQMCBcfM=NwCTL%3r}OC@MwAs(NqYfWk*H^jjVcvCba4@LGbj83-)*uu%3_#U+3V zTNd1E9)msauMhIOvCDLfD)w&aDAGW?9#%c`>!%oE?3==lj-t@V<_L`3H4CeuXt?5x z61r&XesZx8_irnQ4ST&Hg!tFQc$X){{RwTC$2jgeFqf;BMUV2r`Cb-%V;-H&1pP_G zh3p5YX>weZ&c$}KQ&CNFMpa=IWq|zL=Qb3oj2MgR={YEeu*`5uQ>9z+F%KUZ?nS=& z13~~R>vntd&T|seK-TwHz-MEHgwtz#ALyBiw^YkfOZ~nl$!8q zw5wy+CM8Exlr3U6zg!%n$?Xmlf~At~4j_3jqcqUfLLYOQo1F7^m$@|bmL_VJc=l#^ zAP2LgM1*$rsb)e-Sf{Ox|4IL@b`e8E?YwI804odbZDvQc%1o?2YZp)Ot{-u`Svxl1 zjZ#k8S+f7zJnjcyd3T)iyvw!8XKHP2O_>QhkL^v<3+zzj z6z^Mk<4R>x@5C6+Aw(&|Qbq-USpQaC@H@;Jj(q|nYdG6slqU9!-mGTO5URWI+nyqT zp>>3ZN(d{pY+`F~iTkAWHVxqj0|MPV$h8NMi7{9;kZswm=mqHjdZ=Z0)R-Jhi$#?8 z!i8&`j|`>636Y65Vy`yx)rDbWvxfJy-hf3AgcOdODev`s!(R%6nh9CYM;cf0WFNvd ztT)PUXDU%OCx$lvSS*k&n_C6=# z34;uCZy#klTP#aFk;to1g~B>KnFK&61_P2TVe9jK%VG{{+Ie{g;V2ZD zkhT&bwltPw;2ijRq4P(xUi5 z2{NDhD;IC0$gwyvm5(udO?3t_`hP6z7G8$KeJrZ7dw01s6Te7w#w`aMx}AP{{O$H1 zjNa`~sL!We$tt&^yxdjb%o&>m&ZI_xz`VI=_+8a$khjS98vZGQ()|{zjM9BYipEKd zf;(aPd9Tu=p0_JujbuYg!cR9TT7=0y!K%`AU;!!Yc^ZLX*;PNj{P#l#OP5_uL!a4= zzUq>dr3})Eadv2`HedKZi0G$`tZb^qx2*}Jol7CJAMXs&+0DDoKPFXeerf68H=a+q z!(m!VQ2yfuh!-|aS6EnR1(5Ja00&#BhMj@k(T)+9yM&l2Sgt7=M(p@UU=hmD#=})t zaMr&e%SB5>5Ndz;n5CQw(?5;A`N#(s5}V#IFg%k+8aBW|{_3jZNHmOWB(gBf}z9ZIlyM!V{&q!sq08 z0|*MS*6OG&F^-iBu z5Y;nE-8hY0GfM+JhA*}y%IDu|PlgezX4fRB&ArxY(g zpJk&2ty(zT?@I->Meltc=bvrX_x7fA#tm(CBG>335_&pFqrDSEJ+&|`I(2?V9~d~< zLdgA-AX^{D?UXPpj=tow*1&0OuhBg3m-R>)k|sFH>;yriD!1BKM71YIDgKAiC+HhT zfQ=AMSZR!>G{Dh^B}OlB8=c*)@|Qd-c}}pZrfn;3d)-S(sFp;G#s59rWbe{MSf)o) z@j+bYwqF{|X|}rUkrd;!?kn*}=!+hw_GfIYi z57$wOHBakYnA8~r>7`=(aWAr7Ac$Lsaw;~$Bfp-YH_8V}wbm;9QZj}Gmsq=cRcx3p z958UV0fUqc?LE115KA089n~bEO&&bH`nwuXpms$%_lpSkfz{?~G;R2moMh+b+ieZ)ycP?27xdbMx`gF25MtNV z*e(OMGcGhv_k0krL()7xx2dpzW>nFbymyJ^_^Fhm1I(*lRlN0}#q$y}Kd#OW*<;qR z>N9@M&JU8jHb*HKI(9rB+&T%Otdm#ZC+pQ$e}0A?`S=FT531)`;?yd)Bn#J5q+dLZ zYDPVOxg9q!P{xwyamPM)hQ>6Tlco7herZJ-dqE||O!=rh-o z$DCZthi>>ra)KO8?w2U_j*T7qwRmEO%cdn$Lz*XW3Q@5LGg_|wZCN6AFaamBL&5mGv><`NBp^r zeFVj+HC1)h-l+F@s0Yow?%6}F_fARn+w5Xd5{Kkv~=7Bw!t zC~RiAq(pQ6r?a&fI_n-H+OQ?V-n9#MI`@bR?ubjZO@n(lcD;}Vw+|I3=v;O*7fRz}ZW^NH#F z5b_K`&duy@?CxLTe?2#@GV9C!wAcbyp8$>3WV?SM8&0dg5%K$vVsU$ny5&G~gIfDu z=b@=AP!2YFx%XA=H!pc%EHtb2W^@brC;x2PPp##erTcHZ6zrBL4Jz~YM>$JQY;{9v zCLahSm#6Z?t`r%7$7+@W(r0nSTULXB+e*18l^;%AUOQcJKE5sXw}n63Q8_&=bl{=v zo;O~D6tnS7wlX(2m+3r^0tHdSqF$F5-bKT=mBxc4hVN(3-#SZeT~S;BpoSFO*J#c@ zA8%Su7X5*~Er6rDGNPTfmkZ4<@I8DHo7~fn8TK2jwD4`2JsU4I;GOktRMN4p$l|Wz zbiIpLMZD!q@J7+R+?0dVe^Y`$Z^V8dOVvGps1$Zn#S^yvr0xj480?^XCcz zsl7f!`?bA+8V%3rPlSI4HEiK#frZ)Gzk2~*Cxvs=Nh_5%4v;sUI8pcXEip?{V0n2N z^O5uelq*rU;sNsObja15M*{NK<%KWo2lH z@LZYHO^~@0tfKtZNwUASaUTl99kiJA9vi3pcKuShNP&1wmpbatX!h+!=AMgh6G*JR z*rfol=Ov`X%*~wm@?Oz|GQL6|6UHLUr0|$E8~We}=u5L-Hh!M{aGkH2PPdDgh=_ouL9Sc zfL?jJ+9?C37SA-mv)GNCk*34|p5RA=jpB+lRCG0K1KkaDgD{kVe6vxvM#+U%#C9Th*aFt+NYa$!F6|z zaxLSz+M$+(&2;*Vc!MM_Q*sXrxD9YblKJext5>gzuStk6@FO3l*_)c!GQScn1OBRl&gyj$#=81w&n?*u&PHjOu4+WHyuN!5y_~;WrAp?j&?|MzNlBm zQANuaLV0<49d$z&hjw1%r^7dj_%gX(! z0!$YfY%RoVHhW-FXtvJH8ZvBk8=2m1Hg<+7T<5m-@DazecgN6d-lv%Wj>rQi&bNj~ zJhO;lQ)uzx#fuoe#o)27E>9zA;sVVb{ZDg&mx0fhh--+SDV$6+cn2I`fpg_9M*>MT zZbQpmW@DDi6LuguK+{lnha6mFZ^{{4S=1X?(!1jhIkS!Gi6^piyugh<0B`v21JC(P zW*FCMB8gi4EZBU*B|v}}-d(v!54wc1b?t!3+h8bA`lw}by_BBl{@rYMd5njZx|Lhz zGr1kmn3}~>p`$3$ZRmiVH6)MYG(GK;?KIJ(FOz>=cnaBHXGmN)0$g^o5|qLf=Nnr2 z&YY>vI{B)BaR5R8vi?4`{9T)&iXX?uC54q-0Q2+4vXJB_;PrKzSAlfS8#tO9(P^U1 z*GZm1lwpuR-ovMNvzN*N9n`+PbW{`R2laPk?s3{DdidyQF8>&2a>@X9_B>m}sPMb7*o75k(9yCgK%|h~ zi`OBmw}w;QPvfr;Sp_iAVwwqnsd)w+nw^*Dlmnk;5e_U%)^*p*Vnt};KyK!R?Jfnx zg%3umy=U(C`7mE~-uUzg3HVN(ceVM*v=R!9~N^pIEbbS0qpO2MXbKRXK*FvB( zqriXi*=Tk`Aj?3;J2ZmQ32X<(EXhZJb)=o#3F~J&e(V>7{!mm)GH^bD2#3`XiSw0) z@&bSfVW?eCqmKTuWJ=z1C|tJygcuZ=8%2Hx7|@2q03k#=fT?KuUs;DBu*opVOMZtS zStoBcpWVu+`Y<0p4KG|rK?mAM{XQRg_O2v#!Pg+pxrGdeeJ2M8OB2iWn_IODeGH|E zDPUHZArCNZG#-~(zfp>;6frtFI_)QiD%0(!lOz7npvdizrQI~A1DQ}Vz%yc5dQK-v z7f|lJA@aFQMQGaJrtin0S}edig38K!52JcK!KHY?QXxFK?hs=SdZU>e7u=g*&N7iV z250xB+fXu`5=r(#SHcPaHO>!O0x^ajpe+=FEvIC_x06e)Adt7qCU{!nPR5Rqjq?&{ z8MHSF;*+7cka}wDHhIsba2)_{p%6T~=N(xEi8Lex&|(1-XA)@3*FfbE#A+cscm-%> zQ$2a|gz}@M#=w~$OC1BJW9w3Z-2WD5CG5Djm&!?GZ=^U7w|`q--r57;f?Tl`X;jUn zP*Aw$5fMYMK&n-_?e3HJ%+aCJfMt}fd*1xX$MO5om z&>qJCpGpfiYN^*u?Mcu2zL4;sYxF=t0=KV)U!}nu^kxEObAcsT>2>gHwvs1uv)KnCzyX5Ch<>&v1V(#A!{%MZ66XCv4LSuZDvY+g^HG zG}DU8f7oYa=JChZtN1)X>`B$LonBj)g^l;U{iTZqVuS#}!yFrDj^8QqR|(B@8I( zGw6JFW22tBu~sCmRXu}-8*m!-Fh%nXta-GVx-G0-o5VBZly%}8lBh8E`m)tgvaoQy zyI=W5XkhaYwdLFNYd*W?(nMX`<$eF@n%EiBbKwSaUMTWsNILgRl{DoX@?pdwp`DVt z36S7!z@#n`&U~Q^>Jkn)Vp~Ym)}1qn##0|gnH#2ZXMewFJ6qn{W_|esM%<=YnlRg) zi}7f|^8vn{!}F2%*>$F;C&(eNyw1;7%NiLKp#eZkJI%nkbJOhcWF5wo>JbF~fQq?G z+%cABw^f1S-QiM`(4nfACTHhr&JwAXOtBmR(r#n6LmP_ZU^)mh)8s@B=4KTF!R$^b z8z@;KZcCys)q>h+z<+q$8;HPOcpg$jSGW9Lvjf|;F4q5;Vq4S6_4u&N+B{BOT z%hv=dFYndpX4?9wjw)dcd3O8DCL5vsh6;jmzZ`b9Ej)DGnS4qv+s^#yFM5jUt;^w9^7I{_jgm{-kY4-5G* z-Iq12V5b3h#kW|*C6*jituFZ7NA<5KahnPqsw;l?=N5q zWGB~EzVta0)wVnT58Cr>5tJhsLEXVksFR2By`XT3msPRhS9e_swNE%Mzbn5Y3U_5r zp+h#YfHy|N-F6zl0y&@dK`CWm{G>mkybWZw>}0t2d0A5#8`C*6 z*icJ#1#rxh<)?1lo+)4>BRR3@FfkVyKmIU^sA7RKxCm_AIn=9X2HcQ6>=8(Q^=(qM z?t((cPc7M}SKx!6&h<+JB@x@cVza$baiVS#f~%xg#D6~YsfQ%Tt{hfRSYA?Fydh5O z?1QV+atFD&fUhwtDe&ep!`j%j&ET5vG%$Ow!6;zn>Ecm zCqRz?8~IG@w{LfR0L$&6g^7r=t8F&$AbgE%l-a|GYFCjY!k@sNlZf;d@}|-IIb&!! zqRE!z;^sE;RN5f{pL674G!qPADkk3+@$&z{!%cOp+Xtoj51~{2#x6QKQJChd7Wcro zE<(2*rp;ll7rfW|KuL{u9wt&FON~!Q6)?*}07wiB$Bcd;_0M?d;HX{Lh8@>-x;Uts z0o5YBtox$aK5dIwqK=!jn2ed4JJq;B(wU1ej~u}GNj8{Mc$1@&%w{=$Q+NkEso-u7 z!rph#>xBG3>W}_W8d+7ad#z+nk;JjrDd8keXwN|0CvsB3_m+gh%|cM2mpiE-aN-!1 zCVLkqBYKR>AeA-*YT#m^jm3etEx38krBPXr|1;U@y3@?=E}(hj`lWp0n(WND0~k&G zfLu5-e=PWT9*b0VAc!*SJm|xFypQviBu^@+8EIFD$Wn3=TzWJh2XLn(0uo+Nh-i!B z(N(L2r`&}AtCmizm@));5VlvRr>F0^Ip4DYJuyc>OS7IPveh*S*M7%J*oUt^Ba^_- zJr04^nmaNEn4r5r-f@h6{GNNxHN}`Yd%w{mjjBb3j+@N{d8#cD?1+6q1A8NINHPE# zaR-!G6%6n9vA1}#fqj==?}3j^Dv`))=5zQtuFz2gW|12k3-hmede@iw1Ak))esYgh z>s!Z-xrU;AU=#ZUW!smu6a0z|<-i*%yih5vB@j+`>4@mlBraq6U#u+qnC>78M(d@~GzM zkBdW94zl@xc<;$)uL{D2(~UDgovGong2IPWjVCObo|+ost-Z=>NR&S@)$?+QD zrMQ6!y$Y#}2bnd}|m$j`bc)9fmg$%uUD4!Ae!iJG5!0Z%Cn=5f?x%>h`! zGt$j<;*{Xee%nP?z&29=3?nC@peh&m#lO?ceP?R=%3H<7;w2X}V>Yoq4S(2&}| z@Nhvri#Yv{O=B(D22ym-{!frh)&eIe|HnQ!bj4I28$kcb4B$aZ^}JW;VEtpY$=*c8 z+sMwKU2{^<=8~3TH7EBCKt|$gTm-yUb+ly%*+z=_tE={JpTMTwO<~q1a2@p2a$a%- zhOQG-l-cubj+R{;09n*di@tB#W-9hy6AVZ};9TiJ_c;Nk0(c`{**)+&d57=qTEFAW zUg@4+Zrk*OQ^Z$3p^Tlv#7OlQ%?S;(x{V?+PPG19ib9n;``|Mm%cysx|-w;fJ!L6rUm{{L! z@8gLrm9bLxC6+CoUN5jGjwAz)>q~*AHL;+Axb281E*_`#=7k3jFbdq!{~Cv*&j3-8 zH!s@Ws6|MCO+t8U34oHR{}9rn|NV04u-q{@nLl^%2-7gr@y6+u+(W60I^9V(u6K8+ zA{eu&;%=<43LsAi|5_5+<3DyP@y1n_ici#!RUX%_et@+5?O3lSo$UG1F?@57tyk`% zmeZpj7qf506M_2*m$&zFctL(iyxFka_fCp{*c|nK0h%T*n>JVpg^A1ise@~)?lT6w z%v%O&=!lefRG@P0I9&G(3cr5J4G=Vg3eU!{f`6H^gZ?Rh|K}67Tt|)3&I&7y816~g z5ssSyJNP>u_D?0>KfeJd-90?)`a3AvU*!M=WyOQ~%}2qp02j#gYK0mF^9x@Z$X^8y zB{@rC@?QP>#YoHEVyigJFq}$84}AXFN@5M{_tKAT z%(eEN`L2dVqJjJk@b9$vg4D=%w3L_+;@_8OrANfltk7il(3s7OVZ&qKI)Jb75^zyT zi4W3g=s6Y~OnW1sh7;B{FK-o@0e)keu$(#gMZ`E5_G2J?*>7E02=a`%r$a z4tcLYsk?AYNs_>ffKjYrZUiKJ6w86^}yO>aRu({ACDJg-_*)#fg6Q2U;S~2v2s_}Hj z9Y;Bk{%*X64i&&&hv`xle$W7k4>4?FGa(!x<|5Yz9l6!x-jNTeUtX&(Kvj1%~ z69wKS=sRq@s_i!r-r}$Fp1ExW>}K-WO`uQ|ce{xR;zErIa;uDAKl|&HJ@ndSG9h;S z{UFQIeauD6;f74dbuc{;e%PJ6!J-N>e5b)#k*4*f?P0fkwv%(u4vGs0hCYU3;W+~IGo z2tDOGaWuRK?g~Y3g)lEhLN@E~)%sd!4DAFrB;I&{|KE`lLUu?1(B3nJ6IMrPtCS!0 zHpF@4J+|DvvFo1q(6ExL$yPKFk*dd3>a?(gcDDIkm&4ek|AzC>6)Uc!{%Dib)4b2RnUqcg)SBJ~OK^AdS9ooYxhw2p>;~ zU-9jm_phC=$m|nEN}YjN5~o@R^S0V1qF8v37u!_`AjXEl zO(k;z{l6DJF63p@1;SO2@kex*Kz%4jC^jlLn~iKcTDw_JlP8dV`3*eQUMl%p|F>kBVqr~S(On12XY)W#!WgEYl)P^*i{-)tTb+<~GG%>i zz6CrKd9C8+|9hxE>>GHf?r$@X-iHe?{EeguEOazNfuj+=F#z36FGCGL65%(E>&yRp zFJu?vWH`=O1S$TdyK&`Y(szXatu@oZ+Yy!$rai=SZhWe)Uay;iXsZk-G_Lu|Kd zGvFle+1AsWr{A8leWP6JD{<*nJ=C4w%wU=ttGc0(Bw5V?XeQ*D%kBW90I-e7Gv_`J z^w0)|wX4``AQ^W%7Afq+P&W^o#MXl@v-mk^ecCc_dfeP%jwZle1VKt z{91oyt1-^ah##h2Z1$zKIx48-Jafb#vw)a%{$n%o(hxCB)6R`mL)Uu?kYIo zGm7|lieM>NH+>1@DeLJoqh4sHuxlCApj?@x({<@+uXXkGOw9vgsp2>DN#eFYgyEi( zW8^fOXJ+YgknVp-hw!87XBw~0&(FVmB;IZ|HGLwk%tjgd-tL(KklFV};y(1)>HGMu zTJG^S(d1vN?NXmXI_Bi?SEKrOFk`n6s*Cf*Elc+-Q%%9r*)^MmreN-<-cD6=Y0WtU zIBhgKN%y02(*l*ou2*oOI=t!PZX^Mqc`W$Km^e}R zLR64$>X7TyAh1WT;Z9Zp=h6(iJ3A_pIeUFLWn$U`eV|>b3=H3OmwGVKU&xq;ybgwN zZc$`W*ggoQj*O`7+tz}^IJYCAY?v#rt8(FbH56*O$B`JWF6g1cYB9KLCcds5@k;Wk z7za8uFMTF~U~ekL@op4k7Xbm~(88b#0S;48Q$BD+H1a{0*@mo&Q7Y@wO;urMsjKnG zPb%mJjz&M=&x4Y{tVspc><32KKq^BM6!?a0vTNt5ftvp@rt(-VSxIkbt;I8iqwUB8 zcqY$lyRS1t6644=!r^ZJ*>{M}yWsQcDa&UQkcFZ^KNzJinU>hzfG3|8ND3}A)RsL} z0v3{Mw&TajZ(7NcdLy;>Ob6fHxn`ppe-(bM29ou(V)g^WbKI;K zpcie$S=|vR>xJd=((`7NA ziMlxw(X6MbTHgWsv)d6D#e?%H$z|FjVH4I!W@zojFyN}jorzeNLiz$dhl>zhVhtZSgRZ4i z!0Gy;RSBAu%fnJmH1dSCrKw>K$ZRrB9|#t6vuo`7rh z7MHAZ&&_56DAX;2qg7Z7+AFl8q;J$8>Za7600K z6@W@RZLsCuZI_F3G4R_&D@Ni*nXS(wmgyRqUx(u=5foCypVQC<-WDfn>(xB>xBZnN zp!zH;(256Dsf1_$HbXFTAsNi|PnF)2tgNr7kez>B1ANLraC|Pa(%>995V5FfE-W8% z?Ia=`loyJ;Gbs+0Pp*`ck@1-Odggm$hVC-6;C6Zlo zf6$l#sB9z}thd<%vQS{@_co@A-WcsX&zm>+COkff1ON@UF$Ys)4a7EJ;`@PjF&y<8 zs+7fo8ulw#ZZs(IkP{9}!|^kQe{P4pNxk)^f*+(QB`E36>3k3B{ycVccFrFK89EqQ z<+}J?I&0_EU9yU7w@WH~`c_r-u zjO)_$RDX-H948BAGPCKcBKlanj-iV*ekmPZLb&@DD;I)?kD`rL)$LxV=$qRM1C=AM z6O@QV&dZcj!mF@;+HqOoz?wT_WjEKq-PL|ZFr_9iz2*o})8mPOv!X-KkAeQrIXUOY zYL^>o`?PAeumM^HzJ<2as>t%w z>I9-dj7FB!)FA%k=2S-^Q5+Xv0wIuvsZ!7` zrUJw-Jf*6}219co$ouuMlO6IE^i0J(Bbsy&5xE!BcM*l_T<9zP^`zqgV-C|bl1oy( z%J{NNeU%KNAhHS@(tjA%TXUxIqSh_YRAlrg1X{3Oxvjit#B!Q6?|SfM@{W8F3|wlO zyOj=+mgy|E{{Oi%)~%oRp$OAMaHG9)|BzkWo0u>PuPAwmEISm_F~V>rT%o|lV*Y%a15?QNb@t?(GCICIKBp5c#uyd zR{OrCh7bY{(c87TX)chR3iP_-0AdXRmR(5LV967{eU873o7TL&uDGjF@h)H=w0$SnP3@^C>P8sVHO?B}ZjW z=IMMsO=KRz$(SL@ROXpdq>fo6V;Lh(91_C2e5&tzy??;#y85AgojpB!KhIw4zVCIf z$qnVDx-#)u;e7XfSSub#&Rd?O0LHwNkZM=uZ)cZH$4bk7n*&flRhsYTkkKQ`qAJG1 z!qT#~v24zbPUra}ac~ZMLox3#&+!Je&|&M_-R3rr5F(cAfKs|DShZ6v!*e9Fo}3-bU^c52Cb>6zU1DEb z(Dd?&)E4#i-C>fR0+IcLB+izQhhfmx4rUx`0&As&gHS> zWXC@#BGK;^6%@*#ax=}=H1O?JH643@UThyn=RewmHs54BHfY(BuZPNG8~Mi8QC;Ha zvc^Yuph3krB%`5*?6X#ez$kO&FQ3!N5oPnIBHsPH09cRY_+lKq(5J zRKKykWp3%FG6DROu3q&>wLFKjDiNQp1Pb6`B32{wvZ>KK@mIhcUlW*3fWAI7LU=ZE zMyyUe>DayiSx)G|&eJ4C8L3I$+DI528nQIP_6}0kIMJ@iF`68z3^+`<XFM1!b=q!csD;;H#^<9u?UC8hnh}a3MnFzW)7y?1Wy~Qq>{hAl-+xPOwS%jJx~^? zj$=7^ZYoe!B{S9x?4PvPYqR&698J9VkZ4lja@o?|z4Z3$%xMR5xiBOSp^}024i2R8 zfXDqCmlUYj=d-U}Mb@m*`_vdDyR`EHFMQo2VIj98IIDawf4OgRCvy5=7MPL7v=2-z ztsh8Ea#yg}dsoyCs%k;VXDWn0HK&Q28uw8u*POOV`SOq`Zq;W-W*HSjsjV;84&&=? zg3aj)XR+Ip=g$u3v)$I{{Cwhy8P5J8% zUcu-Sva;r8vtX}zERvx*`Y4r40Lar)-R2l21Qu8QRxol$+s7PuR0DoBdVi}2tw6x; zVD7B`VX3+Z;@0rTj|r}M7OCy!ylNbavyJ>{i=`pI{{VzbDbpT)^QoG(R^UEHuhuK@ zDZ0PR{}9{IZIa?+kO2Pq|>O<%=|rE!ag) zN47WLcDi!)0-X@e`FqN7y!^f>JK%I7&4Ez47sc z;2L}pcBt`RH>Yy3JkI4Oz#ja|F-;+Ox{#DqM*V=8T~&N8MwyEUN_wfr&q_Lk zSH<$_;f61=)bly0!h9FPf0oJgUO$cB1P#^^2ms9?3H2FmCF`$RuuAr5%@i4qW*ACx@W@%B)gqyXV99sl-#bZld>t zb<@=OCgm*}!uW(6{Xm&=U-Hn8VV2jw^n~b+$YQuOamz?06z4FszHV;^27X7v!oo_M z5?YWjBxt7*+zw9g(bZ5Q4#yGYg%`Uz^`m<_kgk{|hqr4V=GT!%8`Z>fnxF>t4DlWM z;X~a(MoJ)D=rfNi-PLeaa3`d)Lg`ye4uJ{;cO=pJWK(||nXsO`^&%n_^IcZZhe#}! zawFfi%GZAuPMpHX(KYx9Dbxq1>ZPyE=RD(Y$wm5QOYWw+eY_PdSjJrQu9M3#e8gf zqM)SWaI5*i;M&&ZP*Yi-rD;#d&BuY5ER0>NsMeA6-m}>Wh($fk@#zPZ(EDu52RNh^ zZks2fSx;abucIfdohO8h&LlHfTUof_RiMa~E}?(j+lRS96UkI~3xL&WFq$E*3NW%C z0Fm)nkf`PK>x(46V}!j&-}{Ihh;VirknB~S8Yzrbctq4hek8vU@OCO^{^1rMr5URq z6Is!whx_8G$`(AkA%st17^TS!DBIZzwX(mQ1=nuq_lF>-gXto{*ErbbU!HpzJJFJ_jGZn;6q*9@mFP&)?Ln@@~)NBG4zSx{@h(^lv!0Gqu#qQzb#e!8e51{mP3@HT?Sx9@Mj1xmZDf8sjpr%=b9F9>A3%O$ z*}N6EJTY>R;VVXd%7MW0FjDO^vtqQ~G3;wCfUg4=sQr>1qxwSZUu9G39S52*UP_gr zL~_unj5AW}!BXWQ@pT2@e)#lGz{YlfDxqzhtIW|}a1Cc^Ig}Bl-RnQC*)|th$=py! zGy>^OcN}D)vWS=DA)eirwHv zasTbb6UuYepWB*Jr!<0t_s8JZ4}t>R_^;*bsbj#Dw0TuMkX+f9szQx$C4%h1#%h$`ab(wtp2J22_HB%#e_X&!&+mr;prz=B?rm$D?!*N-DKS z+zV<82mJ49QGM?kzw8OkQ<8n_jU?HzTFPu2ENgDvj0uiZtT}~+TVJ1E$e3SZ{ulET zcbrQ}oISWE_H;71S)6)b1O}3zig<#FHgl$aj%kQ7o8@Ec^h{XQ(OWOF?5|Ic-s7v@vK9K0W0g2rOkpM%qrr(E#G&vy|L|@w_*G>Y-Fwo> zJ|}A5lf$NiT*W1vVJ${bKhA^=>@ui2AG|ke_cGA@ zdz}u=yYHVGAnrG>(V{~){{zn&?~>N`HU{^uQKPz1vEetQbDh_ExUYHwGb?R2+c}*m zdgGb?43*HAjkr=}-yh=q=_;RXf~IHADsEvKHk9SVu^$U_mE^*Up>^Wc!Sq6r-GMsx z8?7gJ1vhl|Zr&&I?RR)$Ey`(T9lGFw1M{||E`VqKB5#C^$;pB$Sl4rRW8M4XY)4Ju zt7f@d#=uq7OL2VN5(A0DUMaVCx3l$UVqN5tJd6||?xiS@zpEBdVm9j=x!fG!1H135 z%q_*i1LEgv8^aT`KPx!PV^hJjH&FTGCPW)i=S7!XZAb^bj?si(naqtcp}jaCtC$x1 zZT`QjcTMVxe1esy??wb7IR$EXOlyM+x}hRr`zle^W6Mt8=!$x3}!^ zix&%{4jMWY$+5=Q^H-={E_a#J3$iu)HT1jwqc*ysjWtgp9&_C;lXfgj=cwb&Mk2TJ^-JYBXK8 zha}~GH`6WQ(xcrERfoqXj?jPoMQ-#rsu1g*dqW72>#%k?*6eCFsB2BW5gBnMfdv6( zL#Hpo`72jH#k4t;yBmhQZRam_ncyVe=HOT77$n}^A4hMawgYuTg>6XbF}acBegd&R zR()zq1$>~^RrD3P+jH(ze8Zg>GRKFu{KVJlLzHEeMkc*_fm5$2;KVN$ zY#?vi3e;l`30S|^KMVJB`Y2tDudc(A)|Xst5f$T zru9Q;AhkkEq|#$GRnIjDlUxEfbNE}TZP)6^k^Ri7_i&6q{S#KX2Flzqk4y}q z<7rHkLh!LM^8$O5;uLPL?Z+lglAnyeA_pJXV{z-94Z;bw3%Ezx{-|=F)om@c^ZoeB(VD+U_T=1u^48-wE(eCF(F<`DT_+#xW-NI zD?*Xb`c^)?8)yNoncL<~KE}}G=Rbx)Glnk%*wZQIN*)`(lPwq_V0>+ks!HUW6ZWyF z-gv9AxlN$$wOl%C;q%pVk!f}N81a#+ug=&jvjmS5T2fr%lkFS;B9jzcY-3OCfg2?lOuTh%x_rpA^5(>Qv>9d5MCupLp%hH}g#^)Al+|p1X-MCO$YI;+A?5eb zqQHtOO^=?3Eji@!ugIibS+SljZ9KA0NwQ_KFag%j$av{jX?XHKZZoh_rw9K)pJvzI z-HQnaPs8%E8|zEoG#m`o;VqwC#FEQ(#GK#WE-D7Jp7;CfGa~8KXL|M@r?3>6Izrgz z&Z6|#I%xX&zp~;?V9j&fE%o(6?P+`ZS0uYEu{tW#@#~OQukd$zKav(y7%}L{kF7eM zxQ0msuwoDLe>KN?;YHgjJ>W=B3C#oZ-j+pf0e99<3kwwcRNi(U?zm8DHhZ}b^Wo!1 zFNn5%b#m-5-=@6R%b>V0;@0}ZgMvI!?HmXHVMLsSZGzNrC%~`wj>vv}QvNN_P2LD> zsjI&43vrY0JulUic}s$|Q+7Kkj7wM63=RsI6jOM`v_o1nzciJ&erHP{eSqHg?GT`E zFVGrE#cr}b59D;t!pp)SpcrU?;)Vw-j5JhV*>^>lfKux#>X=%AKuzZWVLL3t^^7<; z)w+Dva29v&>FqtRv;9Um#9MDMgrhG^#51@X1#2N4f8)~Fwsd7uwWPGvTalg~jJHFy zg|>#8xeR`QqI9jxj&=t&75KTq01vSzwE#1|lUrhq)E*DPIc#1X{$PAk6G*6&G2Kqn zHvscPr%yXsIs-Z;GT_HXB7MF2icu7jP&!s^iG%6%APK6kR9!0uoH8F*LNf3(ENgDL zqyMbqbZPC1tO&qy=_Qs+?Oc?;{T`Hp!P~Z`nm>rgI0e^Yd)FR~2Ld*uVeg;ayOsq( z>tS{QT&{BUrVWsyRubwlZlGN@ND)a`o!v&Ev#v#m?&n6L{M+INQDdoDD@M)94bS+ zV_Gmd0y4uA;{+_5^5{J^wo1ySDGYz^#Y-&-m3maBf?ZBE%QsgDQ|?S`N1@auo5fYp zXfh%D^?Ib@0nPGHIqf7P`~eq&(t(IX^O^kZ0 zHLlFU-O!%|54@2bj>tS`BOPl!miT{7u5gAWw?sM1NkLqLDy$bnfn>I zufe-(;h2;f?!>W50?~MRX{m>lC|g+XPgl&{5SrPCT^o3w#APF4sH4otU&(O<@#mG+ zHFLRX$}D_6auUigz_87Vy|eCh9LcY$cWpL&%z^uw6<(#$pyV(Z=`tCKu<_1VzNiET zm?WD_DYp}?8!6=mWl*{B`I^K6a5$(tdy#EAHlddC)V(q z2>K51mK+MG53Eh(lG{6M-zb{ZBP3L?WebBzoz>HfzNH!}JtUfgi$7Jx#!eQQ_3hnt&P6pQQx=8R9{ZWQtk>BVLex?aE)K#ocP$?miLw zey36tx8UNv(f15kGVg=BJSoT}W@aETnbUb(^pa%drg>RdRS%-0hh>fw%@U>yn`lRFAvNRIx@p3J6)t=i-p1d&vW2k(#7Gj=~Yx6dzIX}N&Tq;z>lNnZVd zY8!9&;T-}x*YYNsSLr{D^$0Mo6x+VvBW`-rvF>vK;dR~?MQAYMEr8J<`5G$5r34k= zo)55i&flh6;p2IFudMVzQqufq1xLSiwt{wYFszE` zhLckn&{@%Ttxoy5xz)vB_=2xUwGq@IUJU7>X^?mKSZXG(*eS0YXMS&bPzaubiSV9x zIVHhIQw3wIf;v8ZS_5BZIZ$=V>BnRo*fH7$!0Ix1o!)mvX-EtIpbaZ@;HB zJY=@4Qk4h92!V83G!vEJC(B0o;CqQH=BY%pt~M;K$0=tj_dkUI|3h2bw@uEkbdQ3s zT`ZXLt2*A;l<@EHNT#W6Wcjv0EVonzQU&c==9*MupnH`bEs#a!;-{?7)ZEfZ1MvG&K=CT`_>HWIE% zYbFyz0@{vb?euBeXY=4@J_v0M^zlazg^CBVE7sKAZ5sQv>H4Y$+l z;801;sMu$bygN_s4LfBFb_G`^^{7mS+VO5d(|Dl+arJ#29mm}n5=}U-M`QS)VO2QU z*%$w#pzGH<6op9DcFwp(9ZI+4p6_#ZLtYUAJNlHqEL3>nH5<$u2ifOCV6*1`lw){e?2NHJ@CMn1+O<2v!I0=f%Mc)iXw(sq$d;+zBB-cO990nl$LjX$#&tLe0J=+DYN>6@8@95pg)mM+qz#} zy1P-{rP{S2;KJ+lpJ5stQCF+N0qRkpS`Q7Bk&7Iwzq!d>3>pG0h#0p`0B!v6a2%l@ugFx(FKGe9s_(S=g>_2U+e0&y9dB+0tHPQi}5W z6CKNU`+{+C*TIZ2VFW^cdHM#MVaK8Gs(chqJI>AK580aW#9a%|r<6}U*05!KawTO4 zOi?Sy(`CGz1v$bFDEsUoObzz=)1jbg*OkC{ALeBNB#Ror*4?fFo0buKcpMR4XGw$} zSYkT)Jcekzr|4e`JI{E15F_kq5Ci__A4vdoRl<9M11^$&(c(92de>wjJ^xZxR`y`c z6o9V1#8qQ$RsMSFhGO&yQ*#Oj24Tr@w(d<=Bz4!SFKOdaTS|2Cx~t!KY!+7o&U)5M zv634u>vg5!)sIu2XdEa6&S<(%QMToKf0?7zNnAYDPsX)N`;e^R4uambe6pKnNZo~R zWlYAn6NW1XcfWB>vI7Sv0QWug7uFM2?NPyzi%=nScmGH1yxW>3Olt5w#B?O!t4||| zmGzCi*YgXLAU6(sxTYj&ago%z=_0Vr+us#_vH3=Oin3bPH0Dn}MqK?2Z-P_sBSR-S z_TWL5(M`~w{3|(Wj&yN1t|dvrD%{z*@EADi6tkR`rG!kCU{{T2`NCQYF41C1>f z=BIE$|3Y>V57hxDyDGJCBaRLZH8xQ0MSJFXG86uQ`JI+mP}RhTz61I!H(ZqG3^pCw zTS~6bSiAW?6wqyUGX)C2BiuoUG257(C@18%G(fu;L~k6S}J%=DiSZISOXj-!33?*K|l7~9-*Bfb&_W>I}S3`~NCT$$s+3cGiKkLT> z6OgvqwJtCA5iPi-cDehsQ+12_74yOCv zEWYXsT~H_)F`6mCG|jG;pu}e{V6e9%8N44lJ=wwmr~v^*6RWHqqZ@qjomo3sl-@`~ zjdSy%>#Rud@QlADpd({WMPsYlc`4`0tG>R5yk)~mazLQyAT>H9wEccoMw@JD)Bu*5 z{9Q>1OIbhDlk|WU7GJH5PVnP)=VIGXZWXq>oRj?%;hcdTz znPGoAFxmHd&?EuE0^LtxSZuqw8g5wO!J$--_*Wq*ee?+Y5B0t%y7#Bb}j>&j5tK^jJ!mb}O69)Ap(LLj97X`<(8t z1bRJ)hjOB$neDzEg|{R(=&yvH{;F-HGZAait883*`37C_OY`IK3|1!ex3oI|tr>!L zgVj}4GeOfgGw)d?K8vlY;B@3@97s|24WMe+=d% zZG%-US<++E&5jQ z&eSsobl|~~`Fc#c$xD-g>D@+>Vb9qcf{Pvw86`bCI9qeBtpb>6vo*$>6Z zc}NuDC8kRyEWc4eBJt*Ko{gMYnS-g_37%R8HJxS=wuc+;0U$1*d~WG{N!kh#-$v@V z#w_Qs{P^%E@=a|0y}Qs06Nk-F^Q@lKn{C))B|nz*ZT$yuMXV_=1C9GrDd~$P(0h@i zv#(}CCns?z$$+>xJYef@MUQYNGC3XtIV}V3%LZt0*8kY`qLTs-TZ;`3MOm2M z^zv<9pa5M-55;VCBfvQ*UI3IkI*Ui}ZAXm!9&g7oVUojH@6~4VrXPHUba%LaxFwhm z`WdwrpOI+^^o>}lD2Hx!#5vdK%<+2=K8m?76^S1iI_Z70>>;SDAB2eTMl`Q8f^@bA zmdkLe^QnfvqGf+L$?d-sRwr3{8ypw*uq|`L(7r`#aS#OQ7r5gLJM4J@#&7{uPDZ3V zqnqqwXRi}!QU#RmTG}|<{C7Y2xARt9?1!^GZEbDR-|xITm>v^luBy}9_vu2$sSS=4 zW_5g=n~LVV+x-=vOFO1&o~l7`13oU3(F%Sr;9_7t@%_J1>VpYlGs%Llw!&~B+Q1a; z74f@D6sIslDRlnSf1*xRsadO_e_%?lW4xSM>p{60>-ehn-v`ZO)m@zNmK6^nrVq>( zYZGa6ZQ-_YTHs6$f31Jx%A`SzEa>#a2F5^f7(cpWwHMm|gX50iQJg+`?}x!VtB zsa#Kd&%pfAgXy&Wz2|UZ)GQO>#Pwgp5A_Y2BKQJn*6#Ib%X(c267@bcR{?SNN@FqA zt0Hl3^fr1-&NuSY1=9{z8dz0wF?p08SFoXAXFo*kl;lSif1y*+mQC`JYXO}AaICZwpcBPXlPyufG+QAX%bFr*~ zb8g>KSxQC{ByBEf%%t_Xn$3ptl|V!Q^@4ZL|9}xLqyE^l{)i~Teu3Y6A9-h_ke*N3 zE=)p#VF`9Wv#(<4>rpPkijU{_S9f=@9ChK9^?oNVxMq_jRDAT%im%<1>k`ZXDkkjq?>og9#!{vQQ3a7YehG1IeV~~%O=2};4<_3NZD`QpvjE8g?(`M zZQ_{RY(jtHuX5o%Aw!WZo1`Qui~wLA#`(;XUE<}(xCC`%Tf_><+_HN^WKVPno(@V; z1{B8NzZ#M4fdhGilox)8wNoClt-*dz({8gsP%m6Aa0J8QM2uGqgD!M8RF>_t`JrX+ zA2rYtC2d18uC*m4xd+|7Sp;f5E`pWM162Tf^p<07AO4ahj*z#G62c)7U3(5i&{w?} zwh23FG$&?dZeW15u8sr2Sa8pNOa^gvfM_`Q@uOS{(OGLkg+-{4DuGaUIyP*CthZw? zb><|-dXjcJk>v~RIIFn@LS1jp)Tc-82K$-~_J%VlSLRmM`2cN`pD23n=v&aM(B1BE z8aM=auzVWKsRQt2j-l_&v9TNOm&7K{tuYBH^kBa*-`eDD2&n97CwXE%PKr5^mK4a7 zsjHVI+`%M;y^=5GpEHPlePgp3WDF+p?qdHAvK^- zu~L=)fL$&bD8_=)OVp#+5noHTC459!cQup(7`KdS^||Xf zVAV4IG5~sZj+OMvVt&R>Qx|~ebFyf zFFX+CZ{VuuS1D7zb_4SqJMZGbSf6>6l2RTDojZ?OR+SuSb@FHJoV)(64tmC2NxLX) zHroUv!MNt}$e2@4a5NZ2bGVgNKE~=po0X}k6Z^u5`yxxI)Y#F~p^4(xByj34-1kiZ z#4mP5K3VY50eeEGcI#sV;jA8>fwFp;j;R7;QO-vNr7Fhu8**&5G^4Y(=M0z!!;`g2 zdU+vj#dl!_VI8R)krl(XJkpAU)Qe;UHH+wZ_P5)R8VryOWZ< zri#1cTQ42I%Dbg@rFSd(P~YUh0;r=YnPY)h!LFSWd`*V!oW_^x8&d38dl9xypy9{k zvZ|v_XeVjaZpo}{s$%PNVttMrh$t_G@&yGGAbacLf+Xgr=C}ypVPY%jv#Y@aUm%!@4RblEV;eGm1W z=lYAMUVzWk{E77}`z=>+)z!Ap6WM1Yj7fmx;1$UrvSMDBF0#OlY$;Tb?_9M7dK+i9 z&O}@ZZNq@?k8j>pwYAEYq#?|QzCPoN#>T1(XYzXR5r+(<+Sfa^lRWA5JjluSk+hwEka`L?HiCY4lK~;U6OB zh6HV14s{(|f6%#IyuL;4-(EZwY2v!@HM3~DxCgHv$j@q$8M~%P*c6t^qkjI%v2hPx z26y5gq8-(9%zkJho5T$;^x^>q?OUQ!l+JB=%2}bd$6wJ8&#l0~%!s+#y$$9_rm8P4 zY<^`#Sg64FJ#X{ry}e@j0d-d8R1DRs6y}=v!794oNL8(a3#Kb(wEW|fjiUC@7uY&9 z$3SO!SZ}3rxOzl6CaUTgz4kArvWhLx+Yg{JC|&d*)0eYeun+vq0+wCTK{eRUda_)b zXYqzSOo*2hA~joU2u^T#cc`tV{e~^7*OjoD#BMhXEmCTW(Y6STVe$#gFWr>yD!e&^ zaWQj0h!BV2X`H&@cN#Wj`%v%1AWrtwlly{wtH=NOkqcTO|J2Gla6d?+rZJoSFs-P5 zwE?X3NHAl>y9U=;J10O5G`0JDEu_=8Y)D76{hDeoQGXXD#3y)*u=izc4EYv$Yb%lc zlz767e!YI;$0VmO_%7uC0@FzTki)Dc)A#Y1Gp?%>@t47ejn>Kb8Azu8S1T)CsC!D_ zUe@1A+55)_+u_sx{Ax?->b-Rgd+}y_jEky)hHvr zu5C!hyI$R+kxNM9!neen_oVd#XW%ojQ9KgjM+JTE5dBXVuJ9hCwiya6UK^;dsxmux zPA>K3cBTX-ve<|EXQDodN#}Utt;y|4upLPHP7yyoN3$B?3Zxe$ucROrsFb7TNz!%2=fV!`hWsgA)7X!3kReVs* zWu<3BBU^QThp)Gl=YvTP!60b5dsr6Rxh<-A`7f>d`=WWONC~Cp{m3nPwr#4$Sm)^R zrtz-JGCPCYR;as&pd(WbT1;kBVoOoaWOSf$!5_M{39@o4JzTT*);;ckHeZTAiRO6y z3Rj9f`6O}@H}pMewwHgw^3fpK$6J)M$}w%w0Fv~*p6>3vETgRL<^c#=8lS$MM85(F zx^n)qq&6?%Ex0RjY?R(5lIgyy(mv(;?BeAH5GoaRi~BTnb-``q$1OTsJm?}IKsX!< zf`ltSqX3KGOc|4_7fC;b0^gwGo!I9)8Z_@-1EIlAwNAVtmhwr77Z6QoP#aiA8% z3mKV=&sZX{&n55Ju!UMT7Rh^(rnfeS1rS#N#_?(P$(u7w-P1_?gcsJgZv5dRI&NhxK_{c8BiyKkB()c-bO>+`THU07s~P8QPjyaIt*&)HuZAx z+P=ty&Xfo(DDQw3D_&0{C>`uTnAl^^M~6u_F($Bjfd^bN$P?tyk>dOqVqm3BbGV+s z!nb0DfelMF#Ul>lZec zC1hI-q3cMSYf=Qa%_OE`N3ibb%Tu-7!M1B#;Hw9C$&Y_PZWep4b_3kS0-=spK9pEU z)$!n^qN@kB3CKjI&nWPOr98k%_1mL7wMkyic1a4@jWvS9_C@eKT)idn@Cccj3;klF z)%^h$MH!%gyU*S^1&Fawoc)Rj-6-frHNfHaF*qwfvaf}#n%U;8GTR2tZB?#PEQJN2 z0Z@M_F$yX4K|9!J6%bNBT#mh7PFa-pD&W8^-5KcYJ66syM@z0l!zLhzAW+NUdLH8I zYqmhX;y`ye#3T9S7qi4ZWDQT$_~J!g%r*)yCZs@3$4=eRL^Abk%eSapOx}j-CGmR* zN08EOlEN-4`tS7k&9ji{Zh}ppz(Ca*0nn8IqKk(P654m_T5gRxbFd9v2{oOTYct)L zcsm^-i9e>_{xDR-$Xn8_%J;7Ne?c=(SiqhJSLk9&w;4V_J1Qf%6QY$u;R!?2`*l-3 zMvn`63oNT;VGJX1(6Qn8_q0u6WK3l;e#tE@)H}3sDL2-qw!<+8SEO=H}t433& z`JD#rNXG_R2*o6<20;`S6$E*S9De@lq^J0MvwA>Zjp=5U#ADxt?2Q~)yn2aolok3yJPMQNT5a?6|lw50N)%%zF@nI z(>ggjni4!8>MKGuQ!Z3COaOf7!|}}M@NNbgW}4&1V$xwm`=Dz|ShAsxd)15j)LoF1 zpBd`VfF$oSK0o%+8xlv*w51Ix5w}D~kk(|IIcY!VIdGgKIBCzlpfZ4yrhvcdk9Uv;!8S4`NG6O|v8*)Vh^KF=2y}@(bhB#NJLPy&&83SM zKU{h+6%2Zp7XkIEYPjw}yBtYU4Q<1ote#U)Zma()fQc5!K4D18V*PGXFPW80zZH1T z)N$q}HD?5%I(z`t-(l6%v)>R|I#=_vWK6C(_L=gZ5_(BPA)1ck+jNR(Ap5KpgTTFE z=%n|9+Qsaq>Er0FodW;W{NpMqc)dNloq$bm*GpB|*n)yY!WtAZcX!{;P7R=ISMFoZ zr+%7~BX(_HH@9urKgwTe&hlY*6@I(bVYApuIfzCxsp}H@HtYLEy`y(t2I^fFFFpF| z_SEC1MV8aa=4B2qC*rMwM>c;LeH%*bo632^MTxq0f=9w|0P2rLTcLT^e#Cd~@TWP- zY8_}{PgVB+sjz4CWv9Bv6$53Y8t7rl>our`?YjP(Kfi=rwDlL9Vd@s@|F4e!UUVMf zY66TfayQC42#5`9`chLUx0?*%m`J=N#dzENCI@$J+n@?=JxJit5p;^dllRrW(h-Pvc{7>*Lbp4r_rT-Iw3~ zE#-U}@>+92Sc9c3twcwcset~n<6&*FAe~wD?>wxZeDc%{J|1#t;=dNPvq{ILZ>Zuv zyZ*VW&6@vuob&MXm~8dk+VV|h`R4eeeyjVy@XESBwc1y4#PgOXOVo z_2x(Fx88MibziJb_YXTzvzwvtF^ z%LnH8PQH%UlDG5tah24b8l0gOvFBFt(1z2PUsCnr?TQ6*)Bzuvf06Ys=Cfy;+*p)& ztY-XbN&=sP&3DLNt6u>Y72kh6)k+>19Q@TZ@jkvUq++jHf7Q_ZI@>~L(9gc))b+B} z=@G#%2DO*SW~QFt^Pv3?O8cCLnK_4!UozW5AJ)#J=P@5Vs{Lcg$R#LsWbJ^^S^uPpa$(cOqVeFn9{4deH?cY zl*ZbNuCt@T#NS)a@uRfkz33|&a>)99fiVn7G)Y=bfd*~;xruHD#I?(waDoAqY{Jl> zYZ4e_W6N|t{|P<%eeM%PM0in5_f*$_8vVXvR&-v_p0Fq_iC~OjAHo0)ihakEW^r!k zpQBhG!JvoF=|r^*sQ&Czk%$QIOsGSxhW;MqV+w3Br4H3g{@)fjlMwtyi}&vqu24kC zG@<6qb;1yS-xH%QQoEUld0-Ha)Moj%@B0UADayl~GQR(J%e{k$|9UUVXp#SWft?KK z?Z46uE;pI}Uf?Y%RCJl?xo0KqpW8eSU9$UM&~aJl{yi1v8Q?bZFmy4RDDA)DLt_Gn z-@X?qo>BgL-~~1msccV}%i=-5pLoO19q1{OJ)t^_>(3T7c^cI7K)S(5?0=_%F)qEz zTbe(Ii4=yz7_CS*$o(B+Z4k+)*Vz+X|6X0B3I+{c zXqHy%{ClQxO@y|xb?pg)e|LHtg3$b93E1-Y`M&MOeb`Ij%r7VyHZe7o_x$$pg{R<` z;2^o`m6rXx7!DQDeI)*VDdF)U z+9JA&Kl9G;;8u1>WI}o`a&s$|R1FkXNY+)CJ<|8-lbN`9QrMd}Sr;O@exFGIPJH%J z4c(uKG&4cLu}SrmKvq}q-{PQsA0eHNJJNsN{>($Q*+J(G+%2hpzin!dXwdm0g?!AP zJGG=jPu1q_|I9hHa?HqSS?DTEdn5yu2E$vhqd@IP?!a2x`Z@9*@)-3LPli?enZ} zcBAb1E~@R9I_o(i@XUHtuV}qCKKSKpkk{1)t96^)4{N6IZ*_5TaptGf_L7T2V-Vs= zAD9bB9|3=zAgAh~l;OcaC3dz>r}C{AU}SiJtu{C~IBQ~JLe<#UV0)MU4}S!U4f{Se1qS(W8@op}DXEdT<3^?TmEb#h z%OTrC!=+TuZr_w&cx0C;yd%Cd`o+a-d_ z{s-OBojQcvbp5?w6eU%j7zjakBXYaBR(S@E>$;4OmiXc+_Lo^JMxMytB{mF~r{nzk zIh4zp<=vP{)yL20MM)vQ57d@)e(7C%+AS&kNyg{=Psm@SNvSCRHmgF)tBJV9z20EW_`Z+s?Vp~H8dGVZf)Q2^ ze5DIrdvBb>Uk!bmFmYliCViFOs5(bL81+Lrrg|c97K52Wx7r(@pA+zHdBg(|{0?l( z9XDkoPXYqor&oX1d|}c)z=wohDjEueOZTX$nb_|Cd~*zZ^vFxW&`ZwM#l;rnMIh&4 zYw2Zc!w7cpddH}wqVd|`!+mN30!9KAd6_r9(_77cwKfLc#fTw<$U=hHJNbuUuipQD zA8z;Y_xq0@ZxUJd-R3v!VvLcweeV|yEwbsGX`dPW^Dgqk4UZ!aKIDXlo;UTfhqpJ< zIZUIcUzYLrGgxX#?%n`DWi_-xUj+Uy0{_1v@Z(Ox4_*R-_ZofW1{Pe(L`>1qBbSpGT0e7h>*wMvUE(drr)jKf z`46ACq%h?BSu*%=5)iyXUL*K(bD_edf?QUaDV&jeoLNdE>m9kaPiS=Xy&C?0=XlG2 z>F@kq;&6UESh(xmskMLB+4T~reQVGe0^_R)EOnZMfS_BLp>ffU>8_R8bLWSLP(vHn zzQZ>ft&N+ z7$iumcZ{`}O?AloT>SG#CKU)vj1{?2l@DbT5r37Zo&Sf>ICgj&BEvu-Hz4d!l$LZ9 z`-A`f{ksZ&hA^0mA8W;m9&_TJ>5p+hlaH6xG}QOlBLDoZ(4*d%0o{?jWOc_I9}0?% zxzifony$_+n+FrEOZ(XM4KDK({Rg!wmLk zQYuT}kNG6h3!ksu(tsKJcDDCqZmAb0X!mlIm-~TF!is&i+eUyYN*f(z20Ui?%qa!@!jvzmb(EPRlw12eMom64GC>d04g(*iM(Lyor07l=1V7T|`r5FO zPcaLPYuM20qJb7^W=7kyu@|#VV}jqW0$?qClK}}le7wIB<2f;&#mQ+q?H)ctprSuE z*vfxLM7&-8S#(xrjLP@#tPX9VSrI(Gw9F0$2CCK40;~ptxvr5cZ9iG|U>WlLKO@!s zkC+dfdhf0*iHfk=qxq<0k6YN&B=<%khXXtdF+e~L!W=~i2+EQ#msfFldAWb3hW3is z%LCN1a({iBPUWj)5m|>)LL)w%y>O5*ezP$yZ#oq!3vOGA+6QfoVO?~srr+ccfvJof z3*(2o-l1)N(^&VXub1$Q=yC+yAow9JQ%4KDOS|iA2)OSkXb1uX2NMA?^60>-%WA%p z@SMvge%-R6O=M@+9POsbTLT?sBbo(P7L4-Qkba55?^pZ{}6iU?YRI$8JtRefEdgfZX#LW2xVmCf*lh6#^N$+x-Rr&p$Q-8#A_J0n{ z)6P>@R*vOG&R%JK~b1&7a6{6&NLW(8%=Dlhk5JsgA-COH5ebzI5$OiLA@O%(J z-}zgGiWPLyKwu@3_x3+A=FnT@euF?E{lD7;?h_FF1`!a5q5}7eXB&h1;`O$FnwL^@ zc|WMNbYi2*3XGfiRPy>SRhBwAe~pq2;jhyc5uxGP7gBXDK6ab^`~9Qx$T8*ujzr`+ zWBejxETPOKar-P|rp5L%Z~*X;)w238735u=>|Hi1JC}H2aRwpREwS9x=D6xa9bl{f zfr7H=(Y_5DqC=D%Q}$L^y?PDPKJ8lqws?3zR)Yr)TM59+~XN)avEa5pt|< zy=&0G6kqUI=jw=92DyfaGv?(ql6w-dI)ypS3k)6-;Mc0;^OtvY-*geIo z_wL&>d)QAcaXNF_qK}G;*@naqmbiI+$LeV-*!-t{mrFo2*VVVAzSc1nu&^$uf3mTI z=H)1;ikf`N5)|M!W|va8f?31vnkQYoZW*Z{*-e7?RlaLG_7yG1fym$&-djz4;PgVf zVlkBtQ5Ds3jfXjb*KEDhCH!>MHEZp^uxVvO3(EN%W^1oW9bxV1&$cI8FBci4YCI<{ zNLAxH$%FGED|3 z{I89fe%ZSY@z~!yyVoEryZ12#zO;jq(5B8il*J9?50q3R+-Hc}<>LC1u#?^^C3F5g z>%~oLwGan1RZ7Z9SR1PM0?{cIMFnK-6Jt|z!*w73a~YZl_p(%d<5KxX7l!Tr8Y8Fj z+5=}wZWm(oIyl1q5dpz{^6OVf(zPmpySyVoIGgyZN{!wOtqDV-UVr~ID=%19`U$y$ znvFj(#(`>+sDRR1m2H{xUNa0%C{A3U-RR+{9#wuD2`k>Q_Vj zeLNbi(5jnT;V4B;N^ZK(O+al?BK!9k@4$r$G=QXjKBkGOS)RxPY@-hN9(Kgcyx94muYvt%sujg9~qK{tnWbyGEed)OUlQcG1wUV7`2R z_ab?{z>RvB9YhOJZzHty&7`N!1wj3(tba%Np4J)AbjUj>VbzgXgzcOiavx=0JT-96<#HK&U0_eRUzH&uIE1WjpsOv>Z{H>#e`K9Xjy-pI?YL2Pc<(Osr&{g~MCqLA)nKA{{^<`b z9&dj&sClvU@AYp+*8TEGq2XP0xju3kZo(t&4Vv4NxUrjhU;j@$q-;+kaEK?58uWT_R5rKFk=KH&uuG(XLZA`nhx2S&%c<B0nO~wu2 znD$Jx92IWz1(ef$vTijb(!5TPD*>l;RaNTgKxiqy3^;-&#~uywN$u`?ayJLB!}Io| ze~#Yl@j574?TPVmax9P<25Hm=aTcY9Mz@?`l=S_>3`;l?(}XQ4?14P^S88^M1+>NG z*rSw*7Em_65;YHjXzk)jnBo4tiJtaeFPA5@KG)eDDs9F6Nz^~t zD&>WEg(F8vfug)dXemw9y0@kaQyWy*1VN#*;sQbvQe&jDGWa)dkdZnL7`mH% zm|2}-gMgb+tJLoYZv0E$9m(Uc?h3@Qq_*y!ofVU^Xa~og!JqfqTncrAK08@me>3x7s>B)U_ z(dS1CBU*J%i=rfM0?X1y>n~%2Zp_;#Maoj{WVcAa?AsoQ~k7R$u~Z0>z7XT!0et~swyXJBotn45yrT2|9Y73 z9fEG6Usuh=ylug9l%IQ!uiER zYi|3jm-8#qme*-dPcnm_lp2;OmvHDeGjbg8TqAI{dwoS1I{F6Bqs)U>))CI+$!^rz zcf}XXZff6^+6~+hI6BF*kDKvxkyv-=@OcEI1i7Ses`b*8YdDj$sdB@t%XRVNVT@10 zALLmOBr>u5+XK+Wa#Yf3-o!4XDs;+ChSTPSo)SF*Y%MyKyefig4JcT)Z+pnhyx^>< zBQDT`0lXw=rq3E8hB6woLh`uVe(LR0l%yymq<^>l0D!+1~@6oz9jIeQd zGx!Cck~6%+=l$9}Ul$na_gj*d5UC76*QKd!f+m_0<~j6zW3P#>ymG$1~X59Tusv1tbrh^C=J;}CdpOOhD39PA#d&X0x?_LA! z&{fqSSlc9(p5B$bLOmG!NC}70puF!x*S>B_i{sI#Kt7!L$4B6CBnJB|>xm$IE}%b%-tE+yBk$T6FXfA~IsnfF zatu(|T(^@<=`R|vbRY#UQc@Gk_$U@Y)7E$w)Fq~DUl_)}TEccBD3X2!pZ+1G@6dI| z51isar@_KRQIES)s3bC8BfM4ISz(rml&YA2eL6w>f<{KGJ`vNcJItIvO)3$xs+Kja zG86}497KiB6E82&qyl|k&bIx17x2`)!DD@#V<2V;c7AC~a^4=e4c*(>(?M)#hFMhg zLenRSxj^q=(6?P-p0LT=L>EA`hCUo^q1Q4G)@Cxy#(+^TVdG4CO+d z39UXIa0g*VJ8r^2|FQTpPYjGYU6#p+RFvD4jmPHjwxw02EYLNxp|MyKyZS-(ZdE#C zQ?c6Gvf9`*-;rf%g{s)JM7>?9*fCw?p>zLf3odT-i&?1;kEzW#v=O{>7Fr%xkW3wt zvvr>-e4w0sH4-o28UVrY2rJAFh7OBABH0_m(L)?B(D}!RbR2|iP>F~o{I6tEX59n% z9(N+`k_8P@F7F&kn}^&H4uAinH=?W4YP{cxr(ej}Sl=TT;Clw1`S{+l;^gc3#SXYt zD%wg$P;5AO$Dz1sU!l0=FpUrGkE*|MN*Z+8WB-)$==KMw;d(6cd}m8QlXTJd^2|Rj zo0;2oH9J=VjBm{r*ks5K_~y9LQ~0)ilNh5{lt-bX$`nq~^Wi zR$rC7975i8)&URse#e4Gm)wO@;c#XuXat0JnS;wbc);DENW}H5dMWU2p#Hp%EZgn> zFV1e~FzRfpb3LH{&NDF{?&5&e@gpk7W519OyA6>!NE2)I_6-Is=Tx-D4kDx1yqNum z@mj6lL?-KSultT7h&*rg33F@vi(`virZXd!2eTRe$R$a8?vz^aD!&4y zWRkl$N~DL}tEbfBL6v{g1SR>zOzQ7H+796V*t@_Vr;hE0x`hh|9 z+Vkf+Vb58ZVRekj!j$TfojHPLDF%%rxMUO)o9=(zpLHTFZ$LRu30vE37pw9#6wnx^ zjaQ+%GTqq}V^wb(r!JrdjUtUy{WcEET~GcN$FS|0I5hjCx8ShUP!1lea6*V^lSO0RF5oC z8%0fG+Wi7UKnj0{pS3{-_15!L1}*mi{m=q-!d&HoJLcXkExwRb!nunbh{O?{={ia3 zxPGsTqp4G2?aNC_zB|^-(Z2g4muJ|gc$6djtajAp2F9uyBfdBa8I(M*u~sh@G2Bpv zX^?fYaF8&wj-3Ze!8ns5k`^0$qB;L3-QViE0kAPu{VrYWyMOcPAJa0g6&EKq-7!Tv zu=5N}O;BGy5)^FRhKPw?z%OgkbXz)auIGw|xEnX5hTf|p(}^w@7^y(k*&CO0BCWU(JfBI6w1ANN;iKZYU`!hP?5r|_0Y!d!oPT-90A|QFP-V^6 zrYWrBk4zh1Z9ZewE(_(j-_8+Tz!=}Mt2HOCUMXV#QnT8C&G;ftOL{rQb4UzZR8Gu| zxv{6Fx_QC)QTF3VV^K?#%EpZ9CK2{Ooc{48Ws5_9ylTt;CtbjO4j8GD!AIlm#%1yD z4o{f<%AWbf!=b$je=LIBV9uyz`hsC=vL?h5K^~rr5{D+YgOcLZRTN^`ZINTKn|8r;6e6J&PS3JAKvspp|v%rh~3t9y zt)5zwyOt=N&!8=phz0HCF@_<15V70tY0{EW=3aIm{Kz9xj_SUBwdLJ_>=+~aZt}zd zff0G+ay@17mx6|eELH&*XCABgX2Pq@b;g_b?V))>`uaz?2mOahGAz8h#xjP}A@7bf z#Sd(<12rrcM+H2Aq`zTkY z)?^*+>R4X-a7!9G^P%8Nlewi>L2B#Y@(SH6XV8P(n!&$8d`?=HnXawdy#_6NEeiuU zzb)zW0fW|~UwZ+hW&5`+b&3pBi%Z4o?k`T-4b-V@d{%YJu^yk62ij9okv}A>36&D9 z`$@l!pJEZ2M)M0l=saYfsjg?0+?ikMCg$9$eVxZ@SftMTM{Ve+IUOL56T9aCL-DVc zk`Su1_1~CQM$p}nBYgvxB6XT zOzgfGxA39Zt?e9lfiJ$ym3aATRF&^QG6bgtoW}KHi%GDXWyuJ`P3^K&9b=`c#5Yf1 zCfJT%!8Bj)l+k~{PzP$gE!}K zQWyPsH}){WWW@*@-6wfg&L!wrHe#8nB$d&h%GT;~U1xv)@<|50(A*DQD=`ZolxY|wQy`Zh94^J&))dT+==FD6ZTsrIo1;#x~Qamc6NIaYF4_ocY>QQ;f z;*#NL#jFFEPtG#_==7`_O(}dW5EM;~K|b&yz44?7{5gEoX!{`adF-2)x7bvYiXe~6 zvmGGzK^Ue)@qvOjv{q#`a(#9yB-BuY-U#+?F?9e-vx;+BHh3g061euXh_@bUF~Rnw zv{WsntN+6GCB+1U{02H#Ex@?dnShWB*GqWovFqhK6Y|5b?-E28C%BdiiTtxafKvVq zqt2o^Egmp|6&ZGN<#ayTP4baC#zfJF97f!TN*8Yi;sC=!-btfWQkJ!yxff(8j&8RB zbli;m9*{g&=2xK~pl|^X7NPMvmAQ0_tdVv*)p(Oy(*>3T*NY(_13d#|$Zt^Zh0J$w zn}vlQ?^?@pZ?@ySD@O+qM@DTV^uLXe>NlQ)LVgpsKaX|E`CGf&GyM*hFs}H>C1_g1 z8$EiL+~meA{xAv}2ZWizGCsqA@c|l5@z9 zmdT0we`f)9;y^8z9rAo!&db}bQYQ&f^nNG!W=~@7=9ZvCtP-;VHZfGbWl^)Wb#z^i zfe+xE?*NT(bPpn8j=_>{74KcC-~}D-e9}jWIVI0`Y}e5F98DzYny^p}m?@zc_N$BE zsJIAB4^%ExY)NU7Ph}HVk`nFgDYvQT!*5nrH?ERv;#4|p%=&rm*;Wok)0C0VyVzT} zc3-;UU6-M)eM0s>rQ5N47J!~Tp~MzPv3T4|Y;Lykbi2{lm^woz_8Ijy5CRno|2%f~ z>GHk7;2sHvhNQKvv%FCDkR#95=I^K~*1rI*o;2gcjs}w`M55NKLXKC!!NmUA7b)e(4H7U92o=erV_&hcpd7fs@0POn}%T{y)w!UJY5OeVf&ER#?7Fn$H z(zkmc3nl8K>jkxg*B8&g+fD1B$?@CrUC*#QFy+o?h=Lx+;h*w05uy()n4*=P=(tbQ zh`=u@yQ#kS4Qi-sG9YXsHS^?#1|-sOXZ))U5LsAAu-~$Luu$G*Y(`_bEUyGEE$swe zqo_M={cTApEAkkdv8pn(XZMAz0KuYFH_ICNrJua$OQfw5UhYa<7EiY zR2L4)5n5K3%p@pkaWnm{pY0A(EN?N_-CCu%g+>y#NCvm=nl|J3f zXYm(%a9-JZvI9N6?{gB?sd-ca4UoRrhU1_nYlnpHFOx=p0&Yy~8-xRxtaDF|drY3{ zzg_zizBnjifx!|2>8kN5n*_Um^jTtd!B6L-n-IDjO(l^W%~lEuChTX2AnDX1--#H^ zzEZmdwo?sd%}wwwu4OFO;~LKrN0+jctEJ3-NcDSB9LL$`BJm5M?w?ls$83*cNr?cq zH{z;=n~tNdS?<@>0@~Twg}$MXG!v}mCAsE~MADCZi+EAph;6qyi{mY5o=#suM@4ewjgmbcQ=W zfoTS1*78q}H$s0B8thIXJc|v68hs}cd(?|^Japmk5eiRNL7W#g&|*-Vqj`%#bB`4_ zs`mz~c6nxc!3bt^=eyehdmu`jG%1PmiKr%%XyIbT787uqWt$7 z-Q0WHoHG%Ny;F3Q*Vzv%9E%z~_S=_j0{uPZlO)fdnE&NP+u5g%8<_sxsGa5^<H? zpf%JJJBLk|o&wdG09;)|qjt^=xVum^4GmX+ zL$$SiYkmfGx2yfel>w8>(XuB!t3spE!i-L}-iYuF!LcdO~{T}SiGBIr{d-C5Ff#}PneF)qvXa_Itzk+9wMvm~x1Gw9AP{__F zahV1h*#=3A*4CP0uIegqihj7apMsE_;4-iSVWKZg@-lM490V+IaTjnf;Rbf78DLF8%WV7kh2*Hkg@4Hy|J-a?e|f- zo13j|TFpmLdDwF~jVvGaRr4e_5zRP;J>gzk{DKShW}9iesfe!tH%;Lr z5d$2Qt=!kJ+Q5>9#z@(@Y{xipe+;+SAl!K1WSrf^qxY#VPjumdTlUI%i@6i$Q}8*< z;$Hh*=!%wn>9STm+GoJF?+8%#Bx?fZW)^loM8JUI1RsXXoV;A18v!8kYRaTr7~G2O z0@CqwXIdDsnx!p$wEQ|OK*CMF>FAz2 z_Aw3ic7~mbqD&vN6Uvl;J_^D5!TMFIS3C-qhcvYY)HVEd9$N!#2feB3taNL`(Vw$fM8pPG4tY&f zai@xyK=hg)u6zOziG$C!2E*d|HeFlKvyLvqxeKQnc2h#=6<6sU(UkKb&GNsmhbrpE z{O7-XVf1A}L5qKM`+pCO9udnbsBjgVsHL@MPL`xd4ytKYZl97ZvP?VUZ9shaM`hyG!VM2m);umOOy@13*f3FQguJWf|seG0%~@DFlqIxaxo!^i)Hjyp~F)X!dX zvOiejE7bcl`G+)kr-N@xdVej^4A{k{@0YLNWC=BMq1g)NPWPp=C1(R@d5#4-W4Zl_7KJ~bz zHDQvt)h{al6XE;qyQe7~1HI+r*4B9-`qqS=E!$(2pR5?@X`xoDPoYasI{M3|fVoi1 zLXtbo4nWx*5|pC@*3y!`$>9RA{8nbjC@4O+ONCD{t(efxIU9^uM_A$v{x`Ds2PBhn{cxL;7w z1oXI-^AAnESux1;z@P%J&o^PJ5vuDaY;&G4PKk)=9c;e;^KbL`zSDfRdo`Vv-UalD zNkf~v!94kt-9urI6gsk^yR*mmYj^l(I=eZdlB!H98~@InFp_5oLbKKZ1(`FDrrr9b zi0s{-_Ky_msEgC--Se7?^L5#LgDo##!D60VN7j=Mmx3vBU;}~PwN^Fu92AN((iyg_{_{LUD?};UFdNJ z$K1Zf&Na~los8jlNtr;}y0oBSxnsp+F4(H`^}@}m$vQ(ef47UWd@7xolNG}BP~F4d z!wON#dzz(nKG(YECo|sMM;aExHXU5%MWc|%Zg3I?xG`DTsXCfknwlE7Wuvo7dUWBg zB!C|Lw5j}>+tz|5HG!MyE_?pz8mv<6N~iUq%S0n^e;hY$PFJXq=bzA|(Q823z3e9M zO@0VD;3@ISUwn|=BPZ__okddP@;H5%Ubi8JYtNc%<>iYW4cPtV#c+Ga!zurOrr6jb z`%U(8F?8jqcLW*UJ0*_N7?R4Hg8ZVKrc)%sl7d#-?v#o{oepT~3>dVvV=B9pxP%?y zV>1NB1LnIwHFrJ@pKIH6v#QG%)m|ipTuRRY$#~9qZ_&^JFcA796RN7nff?p1o$=|2 zPx^pM%a=ITA{Pg3`^0b)7teyu8j_P z1U?PZy8}nsZIXuUblj5jie!EPv9uD7)TlQ7KE#!nDpp)ht^AK7>bCV$sV)`LHp3;U3N% z_tMyImyERc7{P7yq6Mp)G{)|N@kbFZP8=TOl^uwzNZXlG9-_@esdw?GT#Yu?Z4yVu z+BKTV8~sR~LwY_>*|ch~b_+2(9eVK0LE2~cJ z1|8{T)Tb;pRarZ3da7|-QnW5~+V6+C!6x9aZH~mJu=qXkR|Ef~us`p1X&Hhp0igvu zEe*Fmx|q&FS>F|Ta6Zfz#4<7VrlFgoFOkMuw=n*Io6~8m{t4UX8T|AVcM+I}MVK$fe7S3@r{HuPef|$MrZJHRTb3BK$0)M8gl{aT{RT)yy zyQbEuH%_!qg_Ac#P{7CJF`IC|4jcGyK*&K_;5ol;)mTu9#GL|Vr)CuGPt_^ z;Y|h(puuVU1OX;wZiMV2l6cF!sM94pwXhiF$GED~5av&5 ztL2jw$eCujNnzH|8vS60Q*tWPIq}?0|K#s_=7aDoVGdVaN1Hs?SM}76yn2D(*X{Ug zAm6y8u35V{H@-`$k%EFRdCN_dace)$bjUi@prA6Z)Hld9O9{D`tAPB_zV;u@d)k{3 zg15G$K-&=|>x60ZbVZ)&o&#|AZrsK{^;Uw<&VhWRlDz0R*NkAJ0%Li-8Y5-LjNa-? z=hM%@Yg&t*y7kx;7E-6X<|okdAolZHX33N+8}~<3D(E$K2MS842fB2=Z(Al+p2d~~ z61DqnUI>*tbbprCy~ll(m$| zQ$R#aq_Q31Fz`ieFceiyq+h_+jxS{C^)@IcF+=ibM@RD2QCLSQb(trLtJieCCaiS2 zY%iQPf1z!wVC#9-n`tx5LcPb7Tbxu~?O(5%RcOcq4yZy^>1dZR!?s-BxH8;v{a3u7 zuFOcgUJi?N(O6rR7l_g?xXI<7_O+f>x!?SHsKq9(KG-ZyLTA!|p?bD?-*ZHin1hxi z0JD=aKWJ8SO?H%{fo5v85LNRr7G>7>S~%6c$Yg@SKq#-KK0`@Pn!KvFoC`Qzd+gpB zf^7Nh7AY&OCpe46v$V`*WLEU7-}z=h*pGUiszJKW-owK#3HpD$clhT;jZ6G+q&2KV zxx5Y0jmVg>piOXIgyt9^Vmcy_jZ%dLW2NS=#0reXC7G)$e0zKcHO9UxBjVl;4@F50 z--;>cpX#bU`aVHZxoazSwrk}LZ;rjx1{C-spA*qM+M?xUoK*`I^d<+tC_F3ngo z5Z*&5cfQ$x+rMc>_&mNZ#=6b6YL2Ne?362)h*FXO2R$$lEaHh2-8vR~0NSFLD~#=3 zlpgL#vU!|D9?R$HBV9ee>$CH>Fm&2(mMz8$jeDAyGxGKbUD3R-ZWgebu7BGywVwZx z*+!(_4Y#uQtZ4w-3&dwg+flpl+m}C7FM4BGjOl=$4h1h?@UyF?2UDnM#nnDisIu&g z6gx6p5GvRZv1_}5rs%BjNxJ8GIkg%Rd{2f`&`ncyT6U6cpa+qA z;O0YzeJ*zN*uXRqydzZCYu*?B$({ao<@yuja?gqKehMe4+Z+Z5-FN~U>u_X6hSg$U zwo@3(pm(G+61Ej$$;!(f=Cb=1JfPpSn!c$ES;@%vJ^{L8Oz_K_Eh0p>EsJXPr#Z^8 zua4(GMg6DuShlUVr_0OM0o{&enH8+dXZw`+NBbipVR^dsRww!oYJYg&ypDgGraVE)GUYZK$r$Jhv4!7ZQl8%h&?N$4WNl0N zeOnF1XU$6Q>i;i-N*|<;J4xdL&#hjh$PqEY`p0z>eL(gk%U?m%4*j15oq#zfXp+@P zOQ;LKB%Y^gWW+UGRK#I$-e%vpYq%4WT%l4cLwCc0WG96wCUGYMIe1~v%Ag24BW-n~ z-iF3$@FBcxdCi*Hn$4!&yJ*sg;DfbDhul*@2$P|GdQ}$#g2U>o!|w=4kZ3Xfl6U!mzQEb*k_*&Eh*S)~Xk<$xi@d9av(x zwsibS*pKu^@5h`y)vW!7DtE4|B)5GIZ{2jAll8g9k$P9;L3ZC|w@7Pi(zwpP>XJCb zK}PtMl^SO7M1_6cjKi1xX?MxTJ*wO?lnLaOph4Tj>Qf;-!KFdRj5i}c4%6mX~Q2mj9u`UWlkYC#(lvOAnbWU{~I zOTEV(*unnifVjW}_dNABXe?cc-G&|uP=1M)VBc6NB|3V{EG4citV3kkdgq~#oZ(|y z*Lxy=Z{uk@&Qw{4LOnh8h$nRBU^@n>6+QzCWPL@A{k|OqJqLOj+=_?~X&3yk{7pwp zVJ})?p0_@%-xeB&Hm2ShJTRuUjga8;a5Br_!T;5o^*PKcKI-_*3*9;l(*#WHP(!1! zan)#!K8)xsWV?sd^m(SIh0*7j(^Lf{U(^+rHA{ls84^sE=yk^FJf7)}JapA?PaJkXoIf8nK$j->vhI*@xO%SZEO)2D zG`_Wg>@>M&P*J!P=a&^d4g;wWa-d{VkKlLFCEBmmcNwNHL1SJ|_M8c>*mBO&+rjKZ%I`w1P91>5avk9$j{O}jA zrM)R6E--kmZIMu6P<9h`p zHq0NJ_)`mTIaEH>HGP%!)zc;OR0?j+z_x*Q>^x>yg-F~RJxjI>7R@7jouJ%I}6}9xzV%nF;%ZTPqM5e z<@WlMnidm3?f3`M^Wv0qpS&R*M`wV_5ji8Apq?T~*HN^|HT7JerVulDT~!GwU>%Gr z7x@W{N&)vIw95)a>*qQQBeb2R(6`%aZlxY7K0l?qQv2SL@0^m(I2o{epafVTH?^zS zs6r(`5QDi^%fqwRc(5HJx11y+4z8<(si>vTSog zA8^i6cG=W$jSexVYrNlXXpFb`d-U7UMM>zr&`bkA!96LTKIn-1#_8-hNxrB#Ki_Nk zYiy$4U;<(@_Rs>nZiY#d!TonUhL7T64l&C*@p|L@epk>w+1FDG5x9q-V;_7D&mS=88NE$qyqNX05F0 z;v>yP&c|cp@($iA+d{Qnv{R);pJ|W2b0}ELLdUgH6?!g@QPfRLJp$A9-CtiN?fFIt zu{>&_6_XC^W04Tk7IrNp$)I{Y>)q!)UT@HN4qRcVZ>J-)q>y*t*gTweChT+qKTbq7 zJ|vvu_$YAgZOw)5eLzAvM%_4yEhF*S!BucvC=5O(L&w?c?r#y!TyAJxY6VnD<7brm zbo3-%<-{E>eARHTP(EpE+H)ElGRHdBqq@&2jTTUyu?EjTlH~;of3#K7?g=P3M!)(^ zlXj|C;^Q(<_ig$O{a=KPU(!x5zEt2iGNO3WCXjAo7>)>U7(a5Czf=r}1A z|BX2t&GX)E>8#Lb8fku?zdXadnlh zQ6C!NezbkDQ)~vMCHHt!Up|)l?rb8g#snkMf{my8q_YD(cqXNa7PQK+e);@7Co z1_R6^w`NbO|Hso+hc(@QZ55P~E~Ny4(IPn{1?iNO?uL!tXazw+x?5@KX21ZYyPItw z-9s8g;GNI&{N9U;fA{_D^F8N2=iKK$XLDtX%q@X)49?Rc7yTMcgJJSI@8%h9JNn-S zxRl$m(HNf~pr}8+`VNI&#Zuv9GmU1NB)yolZ=tfqt8hA-dg$O`c(pIt2XY$B%;^07 z-w7NRF0zwR)*gNsdp1SGWU`SGnYx87e*`7Vu&SC~RP-)giQ9ckA;(p0^-mmDBmO^> z)jVa#TWdB=zQL&(7D~C6&qlO%#e(Phq?G`uA4 zmt8%hIpe^N{=%47!?~g40B2v&J$a!6C;WcRGq>39g@KL8E)k+Uk;lwjNOcf=p#oF_ zM_0zq!g27I?m2T}SUmHSXLJPM(tLp2;dH(L9~tWm=dRHzx2lXHhukhsw;(RF6W&x} zqL;5Bx$U>j-0Z;M(5IqVXV>as18i3A^G|(t^hWB2Y~}V^z3^X=2h@&oPQU!g_MMN3 z|CNU*9a&+tR)2-R9pa68Uzc^iEkl-uQI)kFTQubqnrFw#OMPqX+lhJjahS@^ng)=q z4FXvUt{o%zHjm880Qsf40aNSW@rBy84hMi11Kaj&>bl^yj&rO^NXH8km~nA^gk=Nr zVBv|9))G-ZBj&4C#>QI%ZQQ%6@PSM1|ID+;DB*{W3f{!sXtJ5+b>t2@&4!ThJN5Hm z^DXFFCLTaE=fg(8JE z`O#Hxb5Sd}S1(S*`V$R3NY(4O>ARs4jcX#hG*?x19o zIm)rRz*mk&tbA+$hlX`Pc;r4f5YwRkoE({KxPO|*PE^a9ph*{^`J@l|gt^23V$hO5 za#9qe9(>QaR+I)4pH46XJ#T^?-&AgV@2#}@{JX9MEzi$P=9T!ad{VXEkaCD@20^m~ zuedZayg3%@8ylRXZ*EVVof-D?9D~=uuZ57@(PF7JuM#xf^b7-tQ)YZ1(WNe~)dd-x zwT789jjyKAo;|LpJf*H-X!})$KLdR7uQclcEsBVICB`|S+u*jpi^>eJ$2?^=u^9!% z^e5-Vzb;}Xj-&*s9wwFuZ54PMSt(aCrf$F=r24DHRR`fRSr(>Wc03b@tZh)+T(F0S z#Bm>=HfmQ@HWzbKXhDCzl{zifmwKU_cyc|^#Vz#ErF6hKF53PrpGW7_*I{cyw&d9W zDs?L1J`P-E0wx)#iA-wb0(6N$YgTgc|kt2BZhKxfG80lpk(;0v1{N zIhmVueWb7v?rglZ`H1|PkS6=xNL(ey8!INk*8UM6rp#kYh;J}I0^*f*b-q_we^Z^z zr|eL8mRm?!C@?S^uQl78uivB0aZt(1u!wPoOaAZgF@xp0tc38tTUZVQeltwG1hIHL z;Ttt`)V%wg@Yy4GxzVT#z!buM_Lx_m5z`%a-I=;-g(x;)dC<&FMsGXT?ta4~1j+Dw zQmj^+a_OLb=8^<*7nR<~cF`0+o|_dCH>Ht477I>f%d1o56+Jz+Vr@?JAOK`L0$)j* zlsekU+|B|`b59~-8w)CGBZLif#Uc~Qn?>P3$#&3I$AQ&Yqb3Gv68}!FXa25ks(I#N za9^OV(jz$IBt)$WH-4;ry@00?qXcip>FP~^XN#PB1zW_^h{KH zdAw6U{{eR(t_n|H^(t;b-q07mp>X7Oj}=1}_fFATZR^Yd3eUeW5?kB7BITT`$do}0 zW8^W-!lE1wP7>eiS4V5zz9W1anbD)9=DD3}XEW%vo<;#o!tz&y`y{MI%U*g&Yv*Zl zup$|AdiIv4HB&?gMGi^KJ%cMofW;)d=0uXrpaCyYP%nFWB$Pxp_}dh1lccbigu&9S zvf>Y+ieQ#ksWpUez2})5M(`u(S;q)&X4fKik2!h(OkRBM(&Cjtb2;y*v7q#j)|&JD zRaSAm1V74>gil^ybj-AQL-iwr_ATc~w}jr3GdCY(3^mL`l3Gk~UVU%1ES{hCZNy|) zk{sb)=bZeZ)bxaV!r_1q_dY~d7Tv!m%>4bLgqU(pd)8BT&5`RM_kC5}BQ}FjL}03x zGKV;3YYb>!shR+wN-aTbKRa8JT?Ut9|3dbA*!sYj&(YIaKjhQE1(bsyronP3 zLE8j!%xJJd=sub5nxXWIR#WdKcsAF7(V&crze{lR0IWfa8T@44qUz~sT0Gr9hQKoz zY1;qP&@eZj5vCdrG_rz{pFrB|sJj+)>Yex-N=?Lj1$qdwa2umByov(--{@M8(~}3O zu|9Y%R*b_)7$GVWy6kUaxr7_4ggbF8fsw+$x3{VnUQ8<(HTiO%-9O3YF+$@a@vU<0 zzSjH)Zs+8=F6a#Nz2Jl>Qn1|b4`-lD>2 zyIq=h6T0gf^cK0xJ?q95LB8E-d0eqt2aC>1U9Z-`-2bdP5m~X<`l8iZkd&&fT79mE zZz7)HkWj){)Z<8o5e_3jT&a2yA+A-Xl|-U5+x4P3kAJCriej(8>;VcbCIf$7g<#$8 z&3#{E;1mD#`9hOI8}sM(gcvjLmM{I}k*53N=w73hTBo9%TNY2Twjq$Br)+_eZ3tb| zl=wuMM$e*HELT4>EqRV~au?7UrM*-MX*LaYjjc)W8m>fy@>p-uRy8#>nNK{i&5u6Y zpKlxo|Jql^wT><|QOtb1^G@yA)BE>+SbQtSv~{cTt~!e)*Jt1E8QRUqZVvFsh!tg5 z)lm?>Ka+D@)WYW|JXBgf@n3ejzWpnsbHqphG0Ny+Zp={kskLe2Xpy*WT`g$ll@Xe1 zD|x5&LpEixKuK*2Zzu`>j9c;Z7qaa)U#@PX7;GR}$HHEuUB76`@X`gni`%(}7qjeAY2{J|PWO?WMWaCwe8@?mf_V5~Q{vKC!7O zZp4YoUfU}1M;K}5tsY*~Ty)0Qz zBR_r73GBc#fxR&srBkj`e#VHKufMQZ$4%8!5qgK_^`BIWgx9$F?NTp6`&N}*nh`3> zKWifKFY>Gk_nkZU=YQaRvvxR^CFfEy8?;?uOHUH~ln`6O>nY(dm`YtbUH{u#X+-cT z&7yFDH_4A^1-G*U`oGde<@XYaLW=^u+?Aen8n539$R+BurA-x8pNmlbvuxu-#*E*_x!YM=%0L9!6M&`{dOF2mJy(yZKe6+qweisC zV2NJB~vRftC&QDGdhwtD2-{UQjmtq-j};i_+Xd#8}REb z<34dqn>6(0_W4Isl2gpOf+%NSmZ&xM7{8A%G1pPj>f~*BH#+z;uZjDV;QbY4afL;0 zkN3dDnABFGUf)y=hl49J(p!%a2sS~A;JNsO{KE77r#&10IE!??JF5E z3z+bk>5n^ewDNW<%Yfh>fTDQ+P&X8p7!T0N2$a#GolxH^q1@VxY<$P|$>=^%we#iY@-F9}1uLeeQ=JwP3i@TI)^i;PJgB~AIb zdsvEC(Ui&VX$rh8c?qNTKYfHz1TBg~o!N5ildB2@lWhls>6oGP*~+=e3ORw2IcLw;~-@emnRI71Br$D2`Qsc)9vnOr)qIXcxY*J9)H zdbOO_Lp-h)ICU8Svj$SOHoa4`#7@W@ z!dYp%46Q+z2?^iSUKT?c!?Bg5St8ag{E!LT6pq<4b|l%-on* zz8}ZTR(=KI{+6C6?yRp-qL3FJtFSD5UWi}uXo@v?LG-&$Udc;o_6zr7_~-o1PR-*} zWv_r*C&O@&neCRMXF5qei)Xbxx*s<7Wx+M04g$*MCRnC3z9Wxn7<^HSMo+8)Fww3* zqv;&)yRp0Kf0>894y7^@_wNy}84z=8SXRG1$Ba+_IiQ!O#dl*feS}&QQzAj8u#HHDe7;b=XsqgnM4y z*tj){7O~89E4X557EiGwbKyB=FXNG!=2a~~dPmBHY=4I7Zri?-V} z=VjkfD1Y6qt*u0CCHS3O$1s#mbm0n9&@FEiJH@Ds?HzrSe+Ot(It9 zfFC-5dgw!K(x=SMA(!)wJJldXqt0JEooG@}ZAv;LM@>7vs6Kwu{PoU1U$}Ww zvc*H*mp;!IpN@|<51^ZaJE36*CiHpZMTW9C<+LVVDP<;XBi;XC zxkqkO&@+kFDr`ZO(4hVOrADzg%*3R zK8KuaWVX8e-6=y4O_#t|$k;FWmuD*k)==L`P&e|h?$5=9lOckS_O6NLn1uxUKAZ_R zBdqZi9d)ChioNhASZ_zCaOt-lN6(b_GqobsES2;D|BS6E?o8D>fyAANqY!y3QL((b zH+ceHmuq@lTzA$_HxiDsl-Z01IXjJ)`Z$wJ&dE}O;*0X$N*%29hIlEXqs|j5*&q5? zNkjI``l$~o@9doNSab5NlePxaL?F~i|^kVSReMT<)fQg ziW}XZ7(nr#xe*95EwU~aq9kad_oNb!tVJ>&_l3ex!^SZZnZAScqKoT=NnWuE3+5>2 zFpCPt@exM_om$~Ohnss z&wZviFm^|abBxJs&%@8{p^APzUo?g34jwD`bsa5vw*omIak1rFR@au&PrsX0sauzQ z11hYDaw8}hG5?q;kc-ZaZ~J{Ix+o)VR-ajCWW@>Mpw2+d6J;BEw;iM`DF1nLUz((v zHu&omX3fl}G{kad>j2$wg>JeId^F;rQ;tnTb@fi>P@!_+@QF45NaI5_4aL6RU}(nx;SQPAW@brT%Cm8=ydIQguLvVJksbvP^uu^guSs2K2hIV+mW?k&f`Aq ziY>8B^*W^#8z0FL>$XHK<#et>-mofQmKfYnd?s*x@A^cpaLa!7GoGq~-Jp^9&d)i} zeVYuvWc0?_WrWhb3(JRHw80-xB%a=9DpEJ5%+VEX9qSKL!Z$+KTs zDKnLmv^X^*3w8K4`E{F<(p4wLHOS(_ZEc2aQwrg4T2iz`zpg3lT=IzqM#Vg1b&J|& zpU})>wiSXzx#uZeh4txJ5B8DMsM(8eK9|?hUtDtlIXkrlEfj43syt>S(6LN+6se}M*yNg8;B9FYDJ5+2t0b^!>@vv$v?76& z#MYdT5{{!F7wk%rxpJblN(#ab9*M$0dykKgu#`Wy9rY|`x7=n#P{gyaa*R1-Xf9Al z7Anb4aj4amJFZpMEpDV_@z-Z-)Hx??7|c?5Io0zN3hskSDxBB#eiDIdcI4VLe=JJo z&byGagsR%yB=fUXX**k}41>lsT;6Iv_}4@N3PI-rz(gq_? zrunANsh*@9DK4LPEDMNWPlJEEGcU5F&C_v=EBYv|G$p7eJAjlGq$3sJVx=TMF^|*A z(8>@rHeL4VDWKr!Y3BBQT%2qoF%l`9>~BaYJDa*42uT?-p6zWceQTtwJ0*;$Kj4Q- z02g$(CybdQ(;Rv?V%jX)W|)gB`@OiT>JGh{fAN#woIbka-Y<(22>Oel^zt{WI7@5U zan@4t2nnM7qeityY?;$?+BRC|G2g1R9>EPbSd)}>HXv+;eH9=F?)xT6C6633ij*=~ z&SvVlTqnpARCM2kvu|3?wWI^K+(WH5wCsF3#(i>zECE`XQAs|Pr>BbX(y7XND(uP5 z=WkQ|Tz7F{rKxd=?aX|^jeakxa^>`=>t5|i`-ySFP`r3=j)vCZU~zBSe?!tWfC;&d zE5-yC77_18{tl*3ZQkP|=um6VVcW!x&WI#4;+uWj{>9Rwr=U;rSN9Jj;{dbL-ClB6)~N6Jf@+C0%vL%U9;c-c{+;{ zAIQMCMADG-;9K1@9~n4wC&qNPe3=1-Roy}YBJ5?UFoK~aIQZu6qzR~h_U!UhG zxQr^wbJ>l(bbNX{IW4g%(wF|}uqOYW*)X!j>v?&0M@K( zGQ$Ud7TYgB*e_y5gz_yGrOAQ33G}p-m_m|5Lo19>#>tO2Yok&rrcEq?0s@rX#v)VA zoF_~M8$R#eZ}n*9i zjdqa=8g-|7e&sADVu(=xuDhyrOCBN$=|8u6u`XTCSF$`o&4Xrr@?%!VSroSxH3 zd{|FIQnC6y-kS|+6iORc+8S@i{{;LSt+%TOEWu6^G56K!he3XE44bq4@Td&I{vK8z z0!t?SxC9y8+z<*X5lXy79bHMNgh~nOG;gb*8|_$HoAJq6o@Izqn9^fQxscP^?F#=`9Vq5EwAFhx=>?DT{g3^F+DwiDMP9aRfRt;GV>DOCG93jH9Kke z7HUZjci zt{w0aW&Ki?nRFxjFdHj`jjraTXY9JD>$~LlK0r@6`zg^M-ZQEsLcMNnj)H=wH>SvGs6rHFwialK+|a-740h+xsna zuZr>hb2B;MH5-3`F^eLJz`Hc=(k94(bsbp`Stfo##VM}95Q|cxEdGQifmXv+EvH<` zqi2~6WC2Fj4aFw#?nQg;E+}qOrp3VT??NAG(?HlOh_K5nGvWDRi77{UM`#HL8W112 z__`x>XP;>?#;M^8-R_*DM%r7!K4yzrI+;=If1(;Y%RXJB1cAXD5hx!lQHSrcw}`jM z7bs57M__47ReEXNBNR!Xr3Nl|Yq;u?z|N7O2%~z*QDux-gt~?*+^Q%w5vKgSM;Y|c zPNf3p?Tihp?k6LmdNsr0?jsYca`5Lg9=ZnDN8eu)w{o(Kz8%Pp@4rE~tmhF~OPP)E zpVD^U1=_+xls@GAeT`8uh)(dUA;^oAoW=V=IC)!)YRVOuV2;9JRyfB@O=&$Sa{I#(!Icm}YMjy$24Ip{!@{jum z$cO3wPYaN-XZ9zlF>8{1=e%2G|L(#X9H3D+4l~5JwAE#VxFxSj;Uzl0u+6J40IR2w zvxbmFpgjqs4WB}Eh4bd_gqRbwYNnM{230uA;OO<)BNH~4<8IX`2gQT?1&U>2u8~%z z`{mdhFMriigN@o8i3Omns!h>N7kn10t+x-%g012({{J`Y9KTi4p>n@BG*7Hq_Y!tS zjc5D3BUpB$j376bSkHl6PDw_YvR6bvfGy%@h+Lk2(h-^C?K>37)B!kUahg2EYrlY@ z=@jj5C(PLr&grW4Q-mtfsL$Tp>>2_U)C^QLl>NhwO>nBJ-b;X~b?FsO4`A z$uHep-q1C8Z1H{cr2F#H;?N;<>-5n~5Es+B>OYs@RQCQ>TWU~JZ}#FxCYMU*uv)ex z498Pe4Vl;6_*_|$WM-X5ktOS`RwvAyiNfPK=lTnBl&=Yt{s15m2YR~n`w^YN2Fylo z;deXKZ^4hAABF-$6Wk$XaMr{Fn-PYtB#9y>qi@+C@yORY&9lwMLLz)5E!w19I4+`a??ZULr^3eN5(?-TuTOzEI3qA zznXn^E4;w9nQOoCW%*;Mwu!jbQ9T+mcMH>+#P#6f>P2zqIPVgSctJ;D!|Wz z#imEs34HBeKYgXT(0fb~j{x`ipKy2C{#n8!k6Arq7~+8M(EVo@mfbWSVHlr#Dwd8& z6k(EVX?8%X6#KgP)=(9{{uSAw!v3y1m|e2~8&8^>om58?D!pXgm^$xY=wd(j*o9{c zMUgz6$|aGM7x3cgtq;ni^Y?Eh`a+XBvEE%sM04{rZC#FC8h>eJrZ}uaoK}CU)Y}-4?R*JcSSdPzNOsc8`kB)M zauQYd>h5FW&fmgSl+lNiU-iN9exaaberx@&#`K}#`iKSjrTmEWC+Ud^l+aIItH13u zvcE>QHwU>;A*WL=$pC~lPjh_J9W?c@paw>(#3`3)MqLM-R;y&WHeMe6yXTBnKi;07 zJrZpQxaLL^$4d=d@rHA0vQ~ZbO^?~&V^)YYDX6Vh)qA(L*1YwD9CyVd2tinO> z`K<9TCnK?L&+CP$(3KGxigh)e8qFY)*^sCsWT?BlT zu|B3fAtl;D(9T<>@jtGj(49lo&o z2-|u{86uCYC|^MsljE`|Nmtgo#*|4ts@^Rj>su0z`n8+y8)@YtxI`bLPABGwZB7%z zc1V=TuR5xOVo7X94J)0cOg;jbB*I^8Ha|h$Gu#E z$Zimwo-J|8bYho!?OxWPR-VLSVe=TfVsa5G#kjZ5R)#-zg-g|xT{87KdsGLP62Y&K z1q%zZG}cadiM>3t?i`KUx5LSM!~VG ztW!nUNB^Yr`fj6)HjGG?*%eU@%;}p5NYkZd_6Hlv8qx1 zsHjo`PL6@ox;yQ&zpGhg4iDFp7SAfzMqCbje#g+X=l^uimaMbx&t!cHJsh^tbpLzUzUWwL8_N zX!G0FL12Gih40pI(1CXZB!!HV&{t7SMr#~f-x&lYjf%e(c?5yHa8dxS9U=kK&5@je+E+uukp3J<)m~sEyDshja z6~DtgSBVDAkzB-X4zFF5p>H}or=Z*?M}nIsqhLQqJJWcSzLj=@Rs`{~@YUNPp8ma8 z&=U{HwdaO3i)#Pa(J0%47N&n|+Ar?5Ea_?X%xVPt+<6E>M=mpTy0`_|$L|LmN`3=Y z-!e7E0^l97ypmV@C(oMtN_lG{d$PS@*}^p%Oqe9z`A~HcLkhmhRjYfnCs3M9fuirok(T2uP z6scxdKO2)eI{lpE_x%fj0k0U7SrE*lEHBMy8QY~#MU+ak(EtUBlYBz7Ge>JbB^A2! z1#UibcZ91I4cJ46C~e8Pv5~Ey8SJk@vhe=7RUXX#*|bN=u!^Kw&T|?+MEM%>+LU3i@U2E`h*2ZL(+Cwr z`KsIqMY`v(Nnf=8=I%b{#i!|4ANniIGUHxik_N*x?7zV9?JKaTST{%o5ORm%UmSOZJ0Qb(l8vPiLq?Mgo$%k=%d$}kIbNz*D%N>Coahx5cv zMOuh`Y8yGq_@Y)60P>qz7c6YD-#=hDdQ{tnb5z;CQ)gE!M7Me$%G>jzTy#A3(Ml6y zyX^yWOhgDC_CIt9k!*6C+HZNKs+U3DZ(fQKlR= zX$1+?BnSUWzGoQZ;=V{+u}dcQ_@LS`b!LPQ`w90xfj-JqmFRK@Sb$9NAFv-^!- z?HE;Qo2l`^>v@v3Y6V-r9}reO21Fqc-O(q6ROs2xq||4qjFxx0lhMxF&>xXaLkloyJO^?XCP zU}CWQqzt|snRU7JWBxeqJS5_RJfGWRYtvU|9w+m!&Hm-iD3lPgzvTzm{SYG|^U3qs z=Z(B6pahs5PisVT%L3?O6Qz*_<^fPHKX~g~(IfEkDKn0tnxcxAUm8O2%bS9sb``4x zCiPMT%V{8a`qFWSxr_NT<8v#>-R=)`NW`6o|1hoo5QDU<~I=a zCXQ6o<-Dlgh>d?B=%&d=f~j*x>Pf)!iDO%m+O6%EJJ?&}jVj&$;V6XyBBl*Arp!6C z1U3r+GdCUUUd~XmTg$<+9pw0j_vi1ybn502!sb5Z=og!iW9ofwce`Sa7N-Wa@)@-w zJ?J6jE96e9k+W0Hb=DBamP(rIPbX2zpY2x9|A#OOXG72Y&8%z_BNpk;8*#ExLV|;y z?Yj2BXzHqgZ>?y`pJ*Ew>N&iXe6ff2qw)zo$ludB#d(OQ5>cM1BgCHisHHSz9V4?Q z|8a%3+mGGCAn4MQTBTQ>&sW4M!e=_k0d5MRDT@8+um7^j=kG$>OekIY77zz``y_Vc zJ~arz2v8KjJ@a-SCAf4C8r4>j2({md7HSQT6vINF;)R{+6gc~a?Al5S@FbXm6n#{!yVgQXve57t# zLC}6&8&7hA)}Z}j<(Y&_K!Bjf9_H5(dIP-&tWw(6< zx!IiBEH9=0b#m}W)Jk7u9_NpsB`%Kt%BhpmMp$;WLTejp=Fzue1@?JLMYnb@>kk<2 z=>;R)*JuRm&GL;}6>&yu#{I4&!dhmqd;C1j%>e3W$(cqX38X!s)~ z%RA|C-TQB?im8)CjqBB9!ISwtOMsCjz4mR0HSJeNVWo3y@YERu3pwfH0`$iCY9w{n zYs^I5oX@%PZbzDH;l(QV%1v!c$ufVxO{;_umsms&<8!}2*lR%qUriPn=Ces^QR6EQ z!5H;S8$G;EzCle(Pm|nfzUG-L)L0D$i=O@dsG(4YGxa795s%Lg8!#VUUx<7Hn*-?6 zd_zSBD0^idum>UD2rMz|woBY9V1WI`bUNJnr-(;S!$*I@%}&>KBa;&PGgy9$Jt@?yq6+{<-EUF~?re2t)+gBfK7_H&^n?aoAcSO`QMqp1rqo*hY z1sJenJipYkgEFe9NUU!zmbizU!70c_KpwDc(AIjVUNO4%jUiJihI-#~knp-aOvw-UjO+av^FVEyRt z4dV9Ls3%I9Z77uLR38_? zHmTMDb|FMBvJs^&>=p%Vzf5n_4$Zrk-#4P1u#mawT1_i9_UbtJ@5{Zp860b^Qo^jX zyl)-SR&jRhwvHayoT9uiWZ1z1uQ_hOmECNtRPOxO{UttpPPqRSFd%Gz8#B-P6qyl7 zP!3*p`*R`bs}gfzLL`-!yH+2NKK?+B&G;FV_|>sAu0O+nymke#>-%2;ss#tm7Ym5N z`HyRMV1eD02e9LYRCXSMJfNcj*a=oy;iDECV3b-=j!6P2 zYqweJOGA>Re=IH9xo`D~9KZ*R_%nAT&SXNc(tM{)_i+2i{uy~=_nW^cv;8h`v#)$R zJd;T?cV=(m$H5+UIZ129{utAaFmvk_H&=c3ob-Xavequ(;(nM?=iAK)&plc?s|v7T z!3;{*e6sygqO~-^s1m3W7V#|g`k9^l5=iaChg?juxk-Vs>%l!U;U8*6H5Ezj`=IX) z&d`tD_h1a0=m%g8xUdl`ve(=uo-H2W>U8#glXmDl%Ktjk6x0QsJNG8kX%0|XmGrct zeTVGJl&zr$2;R9XAA9CD(F3OQYs8S@t5tlt=Z+NiUJqfk!M0RLuzgPuyp+)G9fG}( zqE4$^|7o&6kt;3M=k{EsjogFVJ!essrgTbLF@(ngw&HQ`){7BD2y|-u!KJGS5Ti{?S*#u+*Q(kJ2O^Pl3;ak zNq421bnQiu(M4@Ek+YPC7y&q{0RQ?oHrA|==GQW#SeHE?G8f7S;-*xf1{n$B>(*8E z5rf#CVN*|J42GY)^CcG2&?*dI^4})qL(fUFspi3Ne}`HB4xoAcFZEYJ;+m*=W!KYG z*0wz&3LR3l0eLlU4qx$riY?-XqhBq(jOcslV&JppjhN)*oLJU3u(t?vdzG}?_>%8l z(+xr~GeheLr=~p?)ZMnZ*{-=nv_c?Qtx+9ks(PMR` zCYWq!YUo0uHSU6w2Y!umg;&McPBh{nTdqOAh>i@3=fZPJqO3@OYt`EvmVP_B*NR}A z4J-_B(=HE$9g=<#nySbPH=^w7KwbenG{SS@(}NbAxVtf>qc9VKkVnjw8u7HuTz&#i zA1upDlHS5!ZrfTg;61I&W~@udX@?a9Lv-W2UK8AJfF`@7T7yrqW9cy=MUP7W#bQmD#keM{R93MJWu~( zuJF&m@5$dPwVpk41u7Arup~q%;%eS}qn1e$VGJ2nrtzT8buG;D8GN12XmB5$QZhIh z%lp_F76cE3aj&F&_I-R;=Hf@dchh?qaGD~OiS(KXt^hAnN6$a1dS7UeY6H;;+IQ;g z4vgCLk8Cl-5tDk1skZLJb2nzI?*3eJ4Fe>!v=VF9MIUHcHZs~uce5zQF@ggFm=&w- zNz`@D1vn1UYQbR?@jMRLzJrETrOP>TIE*e{1P_mmd{TDP%ZJ=@FsqSj^F1D(=LoW%F9UJ#NKkzOOZArfg6- zF+sKG+91!E)8stu=9J;Ye>f&wZ$w zfSZTkg2k!k;wVp>%(f4dAt=&o=W7#+3r2w^3AZfi=**W_&;%EWgUls(glx#F!+|c|>&4`i& ztiL$~+Oam=gUv3pVG~Z)tAbM18yR`zwSK-nt7PPdHj~d0csg=drW@%>wvv-7+?*!xQ{1}B%Rv_3A0 zCx0Lv+}2)jfK${AY9ufqe;(5Y)9H3{;ZU{Nx z0KSo*YW(TYYOFA+Ef~d<8a(ii3657_p^2p`UEbFd)N%Rwd0169^C`+6eaCMa`p(T@ z(c}9G?wX|XbJQbQDgE6rk4>Atg3`&}EC;nj>gppE{GFZn!Qt6rmS~*;83x!hpDnBP zmIS`>-ZCdIv11Kicj<&q{w%rz^Vp?XZ=DzY0wmZ9e@ya!D{gFGOw5Q{d)HDKD1^*!F@D2g%~8# zAJ%ar^5Q-Yk(|dtgMp$6%)`ZQDEvag$4!HW{ruBJa4_ntJ;puEf%OzWD)g0Y4$OIj zQrSsY`+IvMtQ2CONhAQ25?CCbCj>W<$8QIL!ImtcUq&Kry2eAhcJMzFBl4w{<&sZu(lj)kU8*_@fy8jS<=Yqr9rJm%4bEdSW|;|00Y5 z=8o_J;o>Pzoo>fRPqD>(5e?*4OBCd|V^P|H?4>98D%XMul#=>we9Sp)QnnJgzo^%7 zj}Z*Ls=Azqz|Zav#*B!ty3Pq9d>YhiPn)@Znof&eK)>&0^88w7wDMaSj;)jWbq>7S zeCF@GWBQC#57?JmaX*7#AUtuaQIm^&ERpn(T+iVZ&dvv`m>=fA`4QAYTpHHL#(&N0 zcH1oA(yeE)D3fzhlN-K(oELH1?*|oYh38i&UHH%uLE(%0?skSrJN7u3RHqVDZC;STGz& z)8{bbmqIHpCEt6?l3lRSMAV|1-=!op)YqHbHw0xYwZ$?^;@<~{Fh znulf8WfKe(3)AY1)&FW1V~VDB1c%(yxA+q=+LUtluhHhv0pucsvdxQbz7L4BC}&G# zMq3;E&)bPZfA|O&1EfY_%Kz!(ytnRF4*FZMx0~87erH2}E_<5>oO*B+D+a*-8Fw!11WxDOYi6D(krrNcRcHBHhq`8R0w3|8o3D zv~&hQNhVkkS2@AGjYWVjQAcY~H6<}#Hc3TJw&!$RFmX*um`>xo7b61{9XaM6D)tc` z;oKLd#Tse(;dsB0xG8zHb^AUyK{L-!us`}L46uBST3$_wuBP5g%W`N%zi6erY-qP- zgR!zwId~mTy|mGZZSs1*_$8<3A>7D}5;Yck;^2V|PraqY`xlKPo1@hdLkC906%(h} zG{pUvOuzb z1D=1IH_-}-1J)U8nESPv%7d|t)fww`CD{&Nc&i?&uIBvDTx##pDS7k2WV&5cB}H+ z=)}hqI zo-!3S5`b3|!S6OqKM;SgbWrOG>>H8%{mxg%<9}KJ_Vk6Za83BemPykTX^*L_3GC2b zqFFp))Q5D;b|2>+YVU<7t_-GBTvYJ+$U2-=w6l=N#(-h!U3`a}b{=R$x7o&Z2|7u${}xBF2jOdrFQi341-p*=@fRSVb0%9Ff0FeVC}nw zfkecvAPO=0^GEWoE8Ja42h9{Q1L0$o`#Cn6L=D*Eq!`)gUtAa}h!yD|D@?k5B3Se5_i1$>D7u7cRa;M z@b@b#&`4V_YMlrRqo6BE#@h8rJh=y5x%g9ear5>1vmX&w%9rK^!o^yti{Zes%oJk3 zv~tP)#$QnZhg+P}2$;oS`fkg|XPIpC5_TMsTW7N}qqrD0_HPP}WsWA60N;mMs!y#q zYO_5HSb0-*bq<@PuZon^SN4pWWFpie3yug&9Ed(ZWv|!^rt7^2Q2M0Kn_MCvx^CGB z!Jah@iNL6vqF-Kgen1dBQJ?!{;RWLd`aSKh{bZDho%-49_ViHlM6Jr-fki}q*tlg5 zw;STG5Q)2f%24E4Ir}-m4rjK#kUd-fK&nO>!v_2nB+y5l>WZ8#DLfa2Sf4asAdf&U z-ZMwQ_wVoWi)3Y${{K6$I zne6Aj+AX&UlXjq+ASpMv5QW9<|J_#eSv;|nDb+{z!n8uI;VhPcW^Fdpqr5P)6NzQz zVpGwnAl8y7%-huXw59|&v0S+J=11`?s`O4Bc^al8y{Zc$-26Vk%UAWZN{!1_E4Kg_ zcufe#p5tbYw+m{ye9E@OZrVvRZAs$-bu%bmm@UU0fR`Pvp}J@F)FErR)EwFuAwM*? zQ%UN4_526uA4nI(@_4^}qxQ_K1J#WQJPb|Oe_-E_X4u+~a5hCSieveD;393zU*yg~ zM+5xwF`8p>I*mvv8%=1FI=q|2eJMk)oK8`(+gCD8=D7Vd9S6=bt#>JG5Ig-jUZ zU&FsjyM7bzW-E_O;L<>7rOTl)VzIfg!6U$FtM?o| zi*|*Ia8hZh^Jh1!e1X$9jXZ|cxVW1I!Lw~>HfNV3ZNXu07W}ODx_XwEuN#4XW}n?{ z@VC{TckP<`1G>Au3xG$Wq*3V~Q8l04H}7F&RR3l-fxB~3r-24&U0$c=hY^W=F#%m4 zFTUTH#7=lD30}Nwk~ey{y&GNO1bkuI=63aagbUb+IU4u??J%5eX8;aq&ZIxI_&Mv_ zMEs=bZtvpHYl3wH^Z6d6wM>y0D?YlbJjI5mI}UG-lf;unkF*542(=68@okKOYhL5PuMa{FFGZ0Tf&XIE zn<}%r@#``o=RyjV!tm|w&CQP&OIH#ue#~vVCZ8{#1MRP$`u^@rw&RrwxZ3S01LA-2 z^K)quqlv|Yf5FTY04A3@gXGkf$-32bqV|8yS|L?2=gOw*6&gzK^`iCgV@9p0?Nkie zrzpNZA9(ML`O>-O``3Q9$I)E6ehu$vIxp#$`g`r%&4=6Q!1?LxKB z@L}NI-rcg)RT-;abLc8nkPa+1A(Qz3%KOT&sJgIS6-5vUQ9wmfQW^>ARvPIBr6qZ{9CNqYpuQFeq!Cvvk#Pw+R5xY zpRS0G`W~1x80`*yNV4A1OU#f~gmrFW9kxXIJOZe@AEgp569f|8}zcS~F9`vB6-2DZHU6jm7!8NX)* zA}ny&+~uhy5_1r6`m+xDv~M_ow{a)1FA(womKJi}wT=Osl(gU8Yon_RhtwYQ^Q zGIHU=vZ`7l(ir$Uu=X3p7qucuDZgiGpVdK4-hPm(4SN;ShxwxJGJAOHK3?&`jcY-6 zsq^9j?C7=EA%25UAEjP^ed|aI;kIe#W`wM~MjjHe|Dp1ea=8qk{X^K#@=vu`zxF$7 zQBD-0;^>9I>qkGiJXQ&34vBPN zZiy^5`J}>{EdY+(oieKD(3f9Y;Im-=+Bm?zN3Ex^SXU_kPf`C(T4r4_Z)kbOqC zmC!rkZ>Y~ow;1bvv1R3QZPsgHd0gVY+W_~4(+KK!{CgrY9fd*70U)3HsK}{sKhA&y z(uVXZ&rZo;r^4QXISM&VM$<_^Og5Ygx6>^ zg!3h9LhA4$I%y`5_ZOaXqRlhjQsDho&`~Zz(lVKG}Rk4B*D-F_p0XO|O5*@p=iXpv`-j64z;ZHiYvPe%`-# zRy#m|bWPzjQCBlQnyyUBsCV5TsixF*1kE4|G#K|+EjYo95%{WXzm2&eh}60C zC6^kS^-~Hl3$vD1eZs+k&uO$yhJ7gB*K1!zymwdxj=2&p<~1|D7kV2%Aq~Z9$1&8S ztMnQnz=CYU&fr=!L!4+lx%j8uU}V zrCkFKKtt7(u9)8)z_1!ztMx=Z&GU>tzsyj^XyJRCA%n(^gP>_^3h+{kn(*d;BhO~p zRte1~J$@cHSelUo)l;csRT>LY6z<)76qy%ilqLs+cg@{IxF0J9v=6WzY)F1V*v}{y zg9YOO#%`z;UYJuV!z%AT0)gLoZ|3nYm#r@v;o~v=z*jCs{ZC>j4n)5&)JsjN)c=wb ztjPtWBO2N0w0{y*Bv^|4Q~wJN(@wxiY))VC#5n01aw|=jUh1%GYieb6C|P8%692S@-Ig}+#nRDKZF&d^>BQp5485!)qVrX)TpgBL{ZKOYy<+ZA*FA3r7S;q=0 zPo<-=_WJr-AqqyVfij0;izU{cjXXAf7V@m4Ju95J994n%L;sZ7<%=mAw)ruQU$NnV zuiVc501T>-fs8N_$=6jXwh!heu2*&T>vRsO2tnyBfS!OeK7U zehhJrfp}GfCiOMkUAf`ErSDYTK^4r{OJ^GnT+NL*gy`7cSX#<-I_*{Szu4#URcG)` zCC2H0@1{BX5negXVNW$3J9@wtnqM}s>z*7~vlzH#w*C9}yXd83T9pWbQHF#4V8hlq*IDxL=-a31lG*FBVq>8>8dh@O3w=SXNz;oH>hhsG zO%|F}H;vEf~F?)J%mQz1@^-oOy z!W#iDK%||bLs#jCd9vnfe}}U$r)miNm}AF54avMJiej}+};#lJn0~Ac%zogT}i^1VFduZhcMj5e8b(UFmX(I z*C%ypVa=5x zZy{y19M#TRHahSlpe#-f>p78ZNxZw}5r=7}0yyC}l9w)ozfEHT&@SiW){TGbt?~~g zi?Pi=S1LTcE6i&spGY33gs{S*Vem@?RqlNT<@I-v)LdZPpZ}9uJG`~*51BMxFv>4# zkbR1tvvFBF=}&`sceuH*R*Gg?9b2SE=1qABQvv5sK_FF?88F4K{^KM{tyWPV0_$;N z%c9t?acPM^_!GnKgvDwGwjnXvDC*@JC_-i&dqCxqrkwZC|8OITL_co+)u$Lph0A| znH5uxR;8atk5Qo)*}#ibB&Q4b6S5H`SzDJ)SXVMly7Q(U#>A?NJv>*}Po>_2(tg7< z#9*CQXmS}2^|7DVLM!ny$EixORwd2Zp-FGGsq~@+ABgT9@;EfTsF0=s~(* zTfDNebZBJD>{yujifX#3yDK<#iOju7G%2O8vSD$HJ;`&)cJi%Bo??{%b{1)Y(xdMW zZ=PPsI7V~efG_umeQQ~oQtVG=Nw{EqkGhd3A)5p}c2L|}(8R?+=Y^G!X?~e+Hvyva z^;EIt`xWP315y7Xl*O?zF~=@lEYkyjcf_&Z8H7AT(QbOn zNA@nOg6c%v$;FhXYIJxD-*X@3%ldp2;~F#SF;i0YRW$9>ENw3$0LUzfY#lP?zfWi` z)`%zLjou0-JXY>7Z7)uCVeO1tv*G1vMUEEVcLJ#1q^WG5tt+ENx{z`vqHJK+rOXeU zl}0`KDj2sLiTuFl{&c)VxIT9I(tO3JJ+|5oSqmrhE1j;d0i|1*q0q{@#cPm8IVr4%lB=IR!g#-nWbh*pG z+IXmM;l-u<=^Yl%IT)KyJK<)P1I$@3MP z_lnj196llzsY!DNIi!`Gro)+xq{H81>pT?L1UNA`Prc&jY57ZWVOv@IHwIo)RW1mH zRWJU$_po4tZhe!mc&3P%)sAeX&#g7vOZz`ss(A6q7R_a6HFLj~wNkQTomGc&ak4xs zfB&v%68e*TWDQ$cL$*^|`F<840efG!*4N9HsFb%aCRO~ZyO&(vD|Y@7MgvZ}zw*;( z>-C9c5Jt|?XS0$%54@u4bN@G-N{m9`pe`M&ixr;v+c0frLa*8?%Yx;b2RsMgHSnoF zyIF#?6Hds6`t8*PDNsy7gXxyMaZ6v!6MIFua$oFAEET2~R%K^zj!=~0FfTJ!JnM}2 zyff3Faf>LZcq*2%QxQga7>0dRitr49K2ykixtJ}?1>AH?7Sx8q42$hrf8gIhv~u}pA-(G z9fBa#HRfjXkcMq-rSqL7Xfbwxt|M9k0R=PD7m2|b)zk(2*`^MKUw#my!h?Giyx<-# zqkSt&a4nLmPPY8cX!IY(-|>!19MS2MyCth5YtQE-cv&(t@ezjaQ{?qB$}Ox|17)Cl z=*3%mhk3K>2Zq}zskmd&93&|GOxwen;z#kvrSsa%OP7`Plpvk-y{V%=V7Y;_+EKXG zGcL`}39p0nrQ0BBPRl~=yX1G6mRABAu3b4H8WrEZ7dyj#Yi^W*%=`XZ5FQAgj-EV#fD^ zt*S`}V(<8_PvB?(p)g!K`nAisvx+MZYb{bC7#*4|lrXm$FL&j)$v1plU?g~6yLHO7 zq2g^>lWm1ftu2ZVMR%9fDX>)TzaHlA3Qj00`Mgp%oZXN;1%18}=~@ZqtJ?Fz$Ch7Edl3eagxPGsl5@8MIi zeD~NO`W-i5zr6i6>t2>{G3D()bU&$lpx;eAo4cf19$#tlj}_)bSQ7|?)OeA^tJ}e_ zB>bk{ZJl!v+;i9`Q9b1_tco*Q^W#Yil7|CDp7Vt5&q>|IKN13)*Zgci`|vRX(|C)i>hsp3&+~?B7OmhgA-)gM)hdfi z>cQ`S+C_W~Lo zF{U1E%Udzcr|;%ODS~#Jt+7)sz9q$3kk-5O3K}oxIBOq+9XA7|pVx&auYPmD-p#q; zWXSTQ4e6)q@|&f=a%CV^qB!%YRd+>2X%ObcG`)_MK0X=^a5vj>>v^#I#e0j)+fQ@; zQ8M>EAT01o3wZ)?BACQXvYyWBC^~;|p?naNnr=KhQ39GkZVRUXshZX&bXtn^pQG50 z%5_Ovg;hnO9YzF%23`p{FqQOC3BB&7TQ&((#xge@X(w44*2R4=DS|z(k=mw|@wQZ- z_g44%Vq7ER?UccHCUiZ?sc&p}^#wO4o)ooXbMKY&*7Ub41oc}XXk%3VE?pK(-zeLo z+Ht#cMEB$;LqwP!p89MOiC8nkBj|Qe@ zxudq?wkokqC*B?|c@hPFTpO7FAB&ax0ekMhJ45KzOu(er5)dXq7``)TU zu5j^fr211E`p}fX?@x>@4oZdI--Y`~VH4M56Q^NhW>}lVNT}Mb>$|(w8s_2f^H4aA z8|v3WS^jz5EptztM5Sq$s(JOlTH?~wV?H^+9vGqlE260MTF^3 zk0MzA0=}FINZXAq|!Xm(Za?6{q zN;j>sZ&!ZzxN#1WA@CMoTd~S*)4X)@o+qNbJN3-B|L-pK6r~NSS(ZWWyE5LdWkeaU zc@6+B-=t!~CzO_}rY3DyMd5ke(;y|w%@XzpqGObLy5CuHcxsCe7hXNo-55kN>hYv8 zeguJQSH4>RP~8>!)rTsoXYf1k(OBDR+T+Ji2k}(%4^Rsah7J06s)-%Y;C5@GnTdj+7$<;AeCefO0_?T1ym~%y&j8w6pwiBqUf z;JsJ3@u`<-kYkvA;$7L2CAg0U#Q;$T)ogsY&t%30hHz)Cc}g6lCY4RFA^j(K0wyV& z=$U;SlEMvd2-IV;gQj3*Bx2v!YU_1ZjP8G?wdn|q>2ZtGuyl5X=Zu^mql>jHP*j4! zOlevk{CfI4-s6w;>_6%%Du&frLw~77C%4I2Np#kJjPVaKEA!=KJL~gI!snoUff0R9 zmB+)Oixvfqc!H6YJ5uKl;@7L)5|Jb$>mH0G>8fdsg5}GEx9L(UyMns>ZbCmHxe#wZCI8^zOxg<%mC)B#NIVl!ZuR ze!=WLKt&>Ne@~(W9-cnx)10JIOm=O;MHGX4VE^Xs^lo0|EW8R%D3A|pNTVq&>BEo+ z`rQ-xZdB$~tGSKz4WExML2zrOa~vllc>Q6WEWWOF8fq)nO(zz#?_@3_>RBe_ORBPb z9!ITg5`MEwb_u?hbl~teLrd^a@|@WJZ~+<@09|UCyaKhs)DaE<`~mo(bNcUM=aiC6 zn1#9-8`$n+)EmP$LgXqd0ZK3XD#_D4bcu0a)C2tmq#5b#OnnJ+9VFpeW8n^yNcnMa z-z#+XRMgaWx(8)mBSw@SPzd1hTeX&})zLW-=wVtcd1{^3y=YyS_aclw8RmJnaKGui zQ=+)_mHS1mE>ETS-1^M3L1!`)-SRT+Q@_z7lX`=8F(snFm3#K>-}%;|Aq|J*HtT!)h!4j{$~g* z{jF->#4~HV1|i#Sez2UjtH_s*U7cl)xQjVEwHmtjTr0`v)!gYJ`xypq5Vymw?~w7> zqt^2bleA_Y!~)~60wv%x!u#WE^9zRND^OnNBy=Q1rusk@hF^0Fwrmgu2)+!u#rSNy zvoYhKk(@fuD#cRC_&Y3eq|K0L6w+Hb=!+@n@=!R_e`9akQMdDDJN8MYF1Bof`~vaBVRAiu4^{K-Zq$Aj+*LQ}n)?+kv*8QKi0@DUWUAXh9n$d< zlXl*SlTWrw%1eD@67KW5H2$VhrS z_AV<4f?yF?hu~E?ZpTi9w~?QU?$LUvvTd4vBq{!PSM%sM_G#Tze+rAX>pg=53c(go zp$zj=Fk2)P%t{e~I{YS4Oa1li@CJlB8PJOAbRB4uLa<$LrEvD z!5fRalRHb%_>5WH#7z9br^@<^mq{g)UqW32pg%w*{f_K(t2Y3J;^cj8q4!U+yNFNw z9NUChYoOZS$PP;V3=#IbcRuFjt2*Y(?cIJ%qjCqL6Wiff=`?3+u5&5u5N;5)kgK!l zn5~n#wF*DTp!{njhuV&de-Lx^76A96+FY2<`|$G)y7NI*}Tbq7Z8%q!{6`CiUasmqm5W*=PSUNg!zLIr=;xwIMOfnVLO zly#JRf}EY`q+3qK#DFAU-If~q92DI>{l3;rp9!ixHCMt6k5F=h3?)|<|M<4&wjIFN zfkXMz|1|S58>?$k8OrbtB|t0UqhcNr1@f&Jx0inQtSdrAr#QLsXy5t_k5VApyR7_2 zmu70?b}a1)MAU9B3$)S;Rth4ZM-6=gBH6E0S%b?pbAQdUPM+5N0N-&LH)z8`AT5E{`DgqhwwQs~NdzbU=JSb^&dMj{jo375-|h97!?-~w z8kuR!{-~&!(*E_5ZPGG?s5we;^)~$<9kidaboM`|fB%oqm6p)_5iNe}55@Uo-VFt7 z6va3!Va*#1I+q6$m-uUjqEOFo{c~NjG!TazCB!DH;CqPCeKrfO zkT~Yw5ZyQ4epF459`(3R3&q)LGR7n# z3Kp2uiE~l(@{#Hw#LWA+v?)-iIs5>D2Ct>`8PWN!6<7=5m1A1I%JW8T;!Lf0D0r*a zhVA_5ykQ;>9L;8ckD(Zs?Ot8fr;z@)be>6;ToX8v<7JKlt3tZEyp9xdf%V6czzRp?^nrUahUX3$qt35lDG^mI}Q zzRC?PXZ%P6gq~gLg)i3+FV^E2@-wpeWYFx;r}blZcdMu!eG`_XF1Sr7+hB9-=b!9b znfg#qwqgSshCx=|_-e3jj9b-|b@FaF*X^yD$BzY*U-e4&ptoa5Xt)GS%rfa{X9@cm z7_PIZ@ITEi&`{Lz?+b*GZA~Bqx@wC^CB9hS21S`uar@4Ht}Y+P+bzuvxOCQZ*Eoxk)K7 zsSFa6(}>OB>O%zwuwgvkw7*!!H-S)1L5y5T{1QF7i{((VCa2G~No@pL*00i6TG&PB z0@-fX+TshtAVs!`8e7{F$2i^CsqV5cj7@YA=8kqM6;{dJ`z<2JA#&VI1@ycR#hu&j zP;^!`8GLBwecs?h{mNyt%iQOz{tfkSq1*?-QvV$dOvuHmqj)uxXGj2EMDIRg(U>su zQNZtnq`cUD5(B`~UNTg;8_#^8ftCTknio^#<6XSiDKe7F^d!^xFA z+_o6izE$gXI6)?p>(t7%%%wE=IKnq<2wcBGs>R;u%TZ{v%un2bO+L5Yp=>%Fwi4Ce zeX4IK!<7;9*z-f$R0$i83dox>Cn%ZRGPt&wez;FneyJ9cBPX|D8OF_FluNXxbz`; zL=N4mN0xTWAvJ2#COZCFMZ%y8$$XD24N%z)`%IrywwDbzUqTT9jPA*XWv_}QR;mGx zPPq7ysrP|~{a<0p-#4Q(n|S_yTwu(2_256*RK;~Ns^$YETp)k9Dv?ypD^ikws&krd zCyBniIK2F_$DirO|MM4bqf6rkB%pV``!wnf(koVkUXL_ea+%TN0sG=2^NSIxH@|-* zWJ*c8H!Vm%|N2N_7b{jdNFyA9*Mb~+_77*@hTsP9nc0>d-yG=OHuog#f*Bl7CHLC@ z&W{{VP&r~STIjcMscuSW6@XIiRDEXT*k0N>pKS2ZCfs7qfLC>KVrm%wE{bkiw0Jf! ztEl(SQ=xq%G_NSZz!8oZ$@JZ!3FnofsMx3J#B=8|0!A;Pli9_`RJ+_fF>;G}nO^aw za~5Lu>GYd4q}7dAn`>Ud4|LT5X3LaP4m_AD68vBtc>*=vCS3>r84yeQw`Zog2uA|H z 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 xkcbHT8~-2w5svz=0k1EhAS%jU_m1Yw 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 b5B%phU8H89v*EEH0}yz+`njxgN@xNAY*1eK diff --git a/misc/icon.png b/misc/icon.png index 76996b2838cf60bdd30c82ae981a0798bc369280..1b256d88aa9e1863ba63b82d9948e41924084892 100644 GIT binary patch literal 9023 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 OfuO9Nkd?>(y8S^##P;SM3%IZW?Js3 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 + Add> WeightedSum for T { + #[inline(always)] fn weighted_sum(values: &[Self], weights: &[f32]) -> Self { values[1..].iter().zip(weights[1..].iter()).fold(values[0].clone() * weights[0], |a, (x, w)| a + x.clone() * *w) } diff --git a/src/pipeline.rs b/src/pipeline.rs index 8840e57..ca13e52 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -273,15 +273,19 @@ where D: Target + Send + Sync, { use std::thread; + use core::sync::atomic::{AtomicUsize, Ordering}; // TODO: Don't pull all vertices at once let vertices = fetch_vertex.collect::>(); let threads = num_cpus::get(); assert!(tgt_size[1] >= threads); // TODO: Remove this limitation - let rows_each = tgt_size[1] / threads; + let groups = threads * 8; + let rows_each = tgt_size[1] / groups; + let group_index = AtomicUsize::new(0); let vertices = &vertices; let rasterizer_config = &rasterizer_config; + let group_index = &group_index; let pixel = &*pixel; let depth = &*depth; @@ -289,15 +293,22 @@ where for i in 0..threads { // TODO: Respawning them each time is dumb s.spawn(move |_| { - let (row_start, rows) = if i == threads - 1 { - (0, tgt_size[1] - (threads - 1) * rows_each) - } else { - (tgt_size[1] - (i + 1) * rows_each, rows_each) - }; - let tgt_min = [0, row_start]; - let tgt_max = [tgt_size[0], row_start + rows]; - // Safety: we have exclusive access to our specific regions of `pixel` and `depth` - unsafe { render_inner(pipeline, vertices.iter().cloned(), rasterizer_config.clone(), (tgt_min, tgt_max), tgt_size, pixel, depth) } + loop { + let i = group_index.fetch_add(1, Ordering::Relaxed); + if i >= groups { + break; + } + + let (row_start, rows) = if i == groups - 1 { + (i * rows_each, tgt_size[1] - (groups - 1) * rows_each) + } else { + (i * rows_each, rows_each) + }; + let tgt_min = [0, row_start]; + let tgt_max = [tgt_size[0], row_start + rows]; + // Safety: we have exclusive access to our specific regions of `pixel` and `depth` + unsafe { render_inner(pipeline, vertices.iter().cloned(), rasterizer_config.clone(), (tgt_min, tgt_max), tgt_size, pixel, depth) } + } }); } }).unwrap(); @@ -341,12 +352,12 @@ where for i in 0..2 { // Safety check if pixel_write { - assert!(tgt_min[i] <= pixel.size()[i]); - assert!(tgt_max[i] <= pixel.size()[i]); + assert!(tgt_min[i] <= pixel.size()[i], "{}, {}, {}", i, tgt_min[i], pixel.size()[i]); + assert!(tgt_max[i] <= pixel.size()[i], "{}, {}, {}", i, tgt_min[i], pixel.size()[i]); } if depth_mode.uses_depth() { - assert!(tgt_min[i] <= depth.size()[i]); - assert!(tgt_max[i] <= depth.size()[i]); + assert!(tgt_min[i] <= depth.size()[i], "{}, {}, {}", i, tgt_min[i], depth.size()[i]); + assert!(tgt_max[i] <= depth.size()[i], "{}, {}, {}", i, tgt_min[i], depth.size()[i]); } } @@ -364,7 +375,7 @@ where } }; - let msaa_level = 1; + let msaa_level = 0; let mut near = core::cell::RefCell::new(fxhash::FxHashMap::default()); let near = &near; let emit_fragment = move |pos, vs_out_lerped: Pipe::VertexData, z: f32| { @@ -373,12 +384,12 @@ where } if pixel_write { - let frag = if msaa_level == 1 { + let frag = if msaa_level == 0 { pipeline.fragment_shader(vs_out_lerped) } else { near .borrow_mut() - .entry([pos[0] / msaa_level, pos[1] / msaa_level]) + .entry([pos[0] >> msaa_level, pos[1] >> msaa_level]) .or_insert_with(|| pipeline.fragment_shader(vs_out_lerped)) .clone() }; diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index a578a46..da1e2d7 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -1,6 +1,5 @@ use super::*; use crate::{CoordinateMode, YAxisDirection}; -use core::ops::{Mul, Add}; use vek::*; /// A rasterizer that produces filled triangles. @@ -105,6 +104,12 @@ impl Rasterizer for Triangles { max: (verts_screen.reduce(|a, b| Vec2::partial_max(a, b)) + 1.0).clamped(screen_min, screen_max).as_(), }; + // Calculate change in vertex weights for each pixel + let weights_at = |p: Vec2| coords_to_weights * Vec3::new(p.x, p.y, 1.0); + let w_hom_origin = weights_at(Vec2::zero()); + let w_hom_dx = (weights_at(Vec2::unit_x() * 1000.0) - w_hom_origin) / 1000.0; + let w_hom_dy = (weights_at(Vec2::unit_y() * 1000.0) - w_hom_origin) / 1000.0; + // Iterate over fragment candidates within the triangle's bounding box (tri_bounds_clamped.min.y..tri_bounds_clamped.max.y).for_each(|y| { // More precisely find the required draw bounds for this row with a little maths @@ -154,31 +159,32 @@ impl Rasterizer for Triangles { (row_bounds.y.ceil() as usize).min(tri_bounds_clamped.max.x), ); - for x in row_range.x..row_range.y { - // Calculate fragment center - let p = Vec3::new(x as f32 + 0.5, y as f32 + 0.5, 1.0); + // Stupid version + //let row_range = Vec2::new(tri_bounds_clamped.min.x, tri_bounds_clamped.max.x); - // Calculate vertex weights to determine vs_out lerping and intersection - let w_hom = coords_to_weights * p; - let w = Vec2::new(w_hom.x / w_hom.z, w_hom.y / w_hom.z); - let w = Vec3::new(w.x, w.y, 1.0 - w.x - w.y); + // Find the barycentric weights for the start of this row + let mut w_hom = w_hom_origin + w_hom_dy * y as f32 + w_hom_dx * row_range.x as f32; - // Test the weights to determine whether the fragment is outside the triangle - if w.map(|e| e < 0.0).reduce_or() { - continue; - } + for x in row_range.x..row_range.y { + // Calculate vertex weights to determine vs_out lerping and intersection + let w_unbalanced = Vec3::new(w_hom.x, w_hom.y, w_hom.z - w_hom.x - w_hom.y); + let w = w_unbalanced / w_hom.z; - // Calculate the interpolated z coordinate for the depth target - let z: f32 = verts_hom.map2(w, |v, w| v.z * w).sum() * w_hom.z; + // Test the weights to determine whether the fragment is inside the triangle + if w.map(|e| e >= 0.0).reduce_and() { + // Calculate the interpolated z coordinate for the depth target + let z: f32 = verts_hom.map(|v| v.z).dot(w_unbalanced); - // Don't use `.contains(&z)`, it isn't inclusive - if coordinate_mode.z_clip_range.clone().map_or(true, |clip_range| z >= clip_range.start && z <= clip_range.end) { if test_depth([x, y], z) { - let vert_out_lerped = V::weighted_sum(verts_out.as_slice(), w.as_slice()); - - emit_fragment([x, y], vert_out_lerped, z); + // Don't use `.contains(&z)`, it isn't inclusive + if coordinate_mode.z_clip_range.clone().map_or(true, |clip_range| z >= clip_range.start && z <= clip_range.end) { + emit_fragment([x, y], V::weighted_sum(verts_out.as_slice(), w.as_slice()), z); + } } } + + // Update barycentric weight ready for the next fragment + w_hom += w_hom_dx; } }); }); From 1cf838b4d1059efc0a659c2cd2f343788bb1dee4 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 24 Dec 2020 00:30:42 +0000 Subject: [PATCH 11/37] README fixes --- README.md | 10 +++++----- examples/triangle.rs | 2 -- src/rasterizer/triangles.rs | 4 ++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index e18c28d..af7bc49 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ # Utah teapot, rendered with shadow-mapping and phong shading at 60 fps -## Triangle Example +## Example ```rust struct Triangle; @@ -31,10 +31,10 @@ impl Pipeline for Triangle { let mut color = Buffer2d::new([640, 480], [0; 4]); Triangle.render( - &[[-1.0, -1.0], [ 1.0, -1.0], [ 0.0, 1.0]], + &[[-1.0, -1.0], [ 1.0, -1.0], [ 0.0, 1.0]], CullMode::Back, - &mut color, - &mut Empty::default(), + &mut color, + &mut Empty::default(), ); ``` @@ -66,7 +66,7 @@ Modern graphics APIs are complex, verbose beasts. The code required to set them up properly requires a lot of beaurocratic mumbo jumbo. This problem has only become worse with the latest iteration of graphics APIs. Vulkan's canonical ['Hello Triangle' example](https://vulkan-tutorial.com/code/16_swap_chain_recreation.cpp) -is, when shader code is included, 994 lines of code. Compare that to `euc`'s 36. +is, when shader code is included, 994 lines of code. Compare that to `euc`'s 34. This is obviously not without tradeoff: Vulkan is a powerful modern graphics API that's designed for high-performance GPGPU on a vast array of hardware while `euc` is simply a humble software-renderer. diff --git a/examples/triangle.rs b/examples/triangle.rs index 97bcbca..4835632 100644 --- a/examples/triangle.rs +++ b/examples/triangle.rs @@ -9,12 +9,10 @@ impl Pipeline for Triangle { type Primitives = TriangleList; type Pixel = u32; - #[inline(always)] fn vertex_shader(&self, pos: &[f32; 2]) -> ([f32; 4], Self::VertexData) { ([pos[0], pos[1], 0.0, 1.0], Vec2::new(pos[0], pos[1])) } - #[inline(always)] fn fragment_shader(&self, xy: Self::VertexData) -> Self::Pixel { u32::from_le_bytes([(xy.x * 255.0) as u8, (xy.y * 255.0) as u8, 0, 255]) // Red } diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index da1e2d7..2f8b3f5 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -45,11 +45,11 @@ impl Rasterizer for Triangles { [0.0, 0.0, 1.0], ]); - let verts_hom_outs = core::iter::from_fn(move || { + let verts_hom_out = core::iter::from_fn(move || { Some(Vec3::new(vertices.next()?, vertices.next()?, vertices.next()?)) }); - verts_hom_outs.for_each(|verts_hom_out: Vec3<([f32; 4], V)>| { + verts_hom_out.for_each(|verts_hom_out: Vec3<([f32; 4], V)>| { // Calculate vertex shader outputs and vertex homogeneous coordinates let verts_hom = Vec3::new(verts_hom_out.x.0, verts_hom_out.y.0, verts_hom_out.z.0).map(Vec4::::from); let verts_out = Vec3::new(verts_hom_out.x.1, verts_hom_out.y.1, verts_hom_out.z.1); From ce3b830c1d6c9a65fb8e5a81758eb0e354f52fe0 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 24 Dec 2020 16:35:29 +0000 Subject: [PATCH 12/37] Experimental MSAA support --- examples/teapot.rs | 3 +- src/buffer.rs | 20 +++++ src/lib.rs | 2 +- src/pipeline.rs | 153 ++++++++++++++++++++++++++---------- src/rasterizer/mod.rs | 36 +++++++-- src/rasterizer/triangles.rs | 20 ++--- 6 files changed, 175 insertions(+), 59 deletions(-) diff --git a/examples/teapot.rs b/examples/teapot.rs index 907abbd..a86180d 100644 --- a/examples/teapot.rs +++ b/examples/teapot.rs @@ -1,6 +1,6 @@ use vek::*; use derive_more::{Add, Mul}; -use euc::{Pipeline, Buffer2d, Target, PixelMode, DepthMode, TriangleList, CullMode, Empty, Nearest, Texture, Sampler}; +use euc::{Pipeline, Buffer2d, Target, PixelMode, DepthMode, TriangleList, CullMode, Empty, Nearest, Texture, Sampler, AaMode}; use std::marker::PhantomData; struct TeapotShadow<'a> { @@ -49,6 +49,7 @@ impl<'a> Pipeline for Teapot<'a> { type Pixel = u32; fn depth_mode(&self) -> DepthMode { DepthMode::LESS_WRITE } + fn aa_mode(&self) -> AaMode { AaMode::Msaa { level: 2 } } #[inline(always)] fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { diff --git a/src/buffer.rs b/src/buffer.rs index 3df5f4c..da4663b 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -56,6 +56,26 @@ impl Buffer { /// View this buffer as a linear mutable slice of elements. pub fn raw_mut(&mut self) -> &mut [T] { &mut self.items } + + /// Get a mutable reference to the item at the given index. + /// + /// # Panics + /// + /// This function will panic if the index is not within bounds. + pub fn get_mut(&mut self, index: [usize; N]) -> &mut T { + let idx = self.linear_index(index); + self.items.get_mut(idx).unwrap() + } + + /// Get a mutable reference to the item at the given assumed-valid index. + /// + /// # Safety + /// + /// Undefined behaviour will occur if the index is not within bounds. + pub unsafe fn get_unchecked_mut(&mut self, index: [usize; N]) -> &mut T { + let idx = self.linear_index(index); + self.items.get_unchecked_mut(idx) + } } impl Texture for Buffer { diff --git a/src/lib.rs b/src/lib.rs index a0cad87..9b495ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,7 +67,7 @@ pub mod texture; // Reexports pub use crate::{ buffer::{Buffer, Buffer1d, Buffer2d, Buffer3d, Buffer4d}, - pipeline::{Pipeline, DepthMode, PixelMode, CoordinateMode, Handedness, YAxisDirection}, + pipeline::{Pipeline, DepthMode, PixelMode, CoordinateMode, Handedness, YAxisDirection, AaMode}, primitives::TriangleList, texture::{Texture, Target, Empty}, rasterizer::CullMode, diff --git a/src/pipeline.rs b/src/pipeline.rs index ca13e52..5590d81 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -3,6 +3,7 @@ use crate::{ rasterizer::Rasterizer, primitives::PrimitiveKind, math::WeightedSum, + buffer::Buffer2d, }; use alloc::{vec::Vec, collections::VecDeque}; use core::{ @@ -103,6 +104,18 @@ pub struct CoordinateMode { pub z_clip_range: Option>, } +/// The anti-aliasing mode used by a pipeline. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum AaMode { + /// No anti-aliasing. + None, + /// Multi-sampling anti-aliasing. + /// + /// This form of anti-aliasing skips evaluating fragments in the middle of primitives while maintaining detail + /// along edges. The `level` should be within the range 1 to 6 (inclusive). + Msaa { level: u32 }, +} + impl CoordinateMode { /// OpenGL-like coordinates (right-handed, y = up, -1 to 1 z clip range). pub const OPENGL: Self = Self { @@ -170,6 +183,10 @@ pub trait Pipeline: Sized { #[inline(always)] fn coordinate_mode(&self) -> CoordinateMode { CoordinateMode::default() } + /// Returns the [`AaMode`] of this pipeline. + #[inline(always)] + fn aa_mode(&self) -> AaMode { AaMode::None } + /// Transforms a [`Pipeline::Vertex`] into homogeneous NDCs (Normalised Device Coordinates) for the vertex and a /// [`Pipeline::VertexData`] to be interpolated and passed to the fragment shader. /// @@ -290,7 +307,7 @@ where let depth = &*depth; crossbeam_utils::thread::scope(|s| { - for i in 0..threads { + for _ in 0..threads { // TODO: Respawning them each time is dumb s.spawn(move |_| { loop { @@ -334,7 +351,7 @@ where unsafe fn render_inner( pipeline: &Pipe, - mut fetch_vertex: S, + fetch_vertex: S, rasterizer_config: <>::Rasterizer as Rasterizer>::Config, (tgt_min, tgt_max): ([usize; 2], [usize; 2]), tgt_size: [usize; 2], @@ -347,11 +364,11 @@ where P: Target + Send + Sync, D: Target + Send + Sync, { - let pixel_write = pipeline.pixel_mode().write; + let write_pixels = pipeline.pixel_mode().write; let depth_mode = pipeline.depth_mode(); for i in 0..2 { // Safety check - if pixel_write { + if write_pixels { assert!(tgt_min[i] <= pixel.size()[i], "{}, {}, {}", i, tgt_min[i], pixel.size()[i]); assert!(tgt_max[i] <= pixel.size()[i], "{}, {}, {}", i, tgt_min[i], pixel.size()[i]); } @@ -363,53 +380,109 @@ where let principal_x = depth.principal_axis() == 0; - let pixel = &*pixel; - let depth = &*depth; + use crate::rasterizer::Blitter; - let test_depth = move |pos, z: f32| { - if let Some(test) = depth_mode.test { - let old_z = depth.read_exclusive_unchecked(pos); - z.partial_cmp(&old_z) == Some(test) - } else { - true - } - }; + struct BlitterImpl<'a, Pipe: Pipeline, P, D> { + write_pixels: bool, + depth_mode: DepthMode, + + tgt_min: [usize; 2], + tgt_max: [usize; 2], + tgt_size: [usize; 2], + + pipeline: &'a Pipe, + pixel: &'a P, + depth: &'a D, + primitive_count: u64, + + msaa_level: usize, + msaa_buf: Buffer2d<(u64, Option)> + } - let msaa_level = 0; - let mut near = core::cell::RefCell::new(fxhash::FxHashMap::default()); - let near = &near; - let emit_fragment = move |pos, vs_out_lerped: Pipe::VertexData, z: f32| { - if depth_mode.write { - depth.write_exclusive_unchecked(pos, z); + impl<'a, Pipe, P, D> Blitter for BlitterImpl<'a, Pipe, P, D> + where + Pipe: Pipeline + Send + Sync, + P: Target + Send + Sync, + D: Target + Send + Sync, + { + fn target_size(&self) -> [usize; 2] { self.tgt_size } + fn target_min(&self) -> [usize; 2] { self.tgt_min } + fn target_max(&self) -> [usize; 2] { self.tgt_max } + + #[inline(always)] + fn begin_primitive(&mut self) { + self.primitive_count = self.primitive_count.wrapping_add(1); } - if pixel_write { - let frag = if msaa_level == 0 { - pipeline.fragment_shader(vs_out_lerped) + #[inline(always)] + unsafe fn test_fragment(&mut self, pos: [usize; 2], z: f32) -> bool { + if let Some(test) = self.depth_mode.test { + let old_z = self.depth.read_exclusive_unchecked(pos); + z.partial_cmp(&old_z) == Some(test) } else { - near - .borrow_mut() - .entry([pos[0] >> msaa_level, pos[1] >> msaa_level]) - .or_insert_with(|| pipeline.fragment_shader(vs_out_lerped)) - .clone() - }; - let old_px = pixel.read_exclusive_unchecked(pos); - let blended_px = pipeline.blend_shader(old_px, frag); - pixel.write_exclusive_unchecked(pos, blended_px); + true + } + } + + #[inline(always)] + unsafe fn emit_fragment(&mut self, pos: [usize; 2], v_data: Pipe::VertexData, z: f32) { + if self.depth_mode.write { + self.depth.write_exclusive_unchecked(pos, z); + } + + if self.write_pixels { + let frag = if self.msaa_level == 0 { + self.pipeline.fragment_shader(v_data) + } else { + let fetch_pixel = |pos: [usize; 2]| { + // Safety: MSAA buffer will always be large enough + let texel = self.msaa_buf + .get_unchecked_mut([(pos[0] - self.tgt_min[0]) >> self.msaa_level, (pos[1] - self.tgt_min[1]) >> self.msaa_level]); + if texel.0 != self.primitive_count { + texel.0 = self.primitive_count; + texel.1 = Some(self.pipeline.fragment_shader(v_data)); + } + // Safety: We know this entry will always be occupied due to the code above + texel.1.clone().unwrap_or_else(|| core::hint::unreachable_unchecked()) + }; + + fetch_pixel(pos) + }; + let old_px = self.pixel.read_exclusive_unchecked(pos); + let blended_px = self.pipeline.blend_shader(old_px, frag); + self.pixel.write_exclusive_unchecked(pos, blended_px); + } } + } + + let msaa_level = match pipeline.aa_mode() { + AaMode::None => 0, + AaMode::Msaa { level } => level.max(1).min(6) as usize, }; >::Rasterizer::default().rasterize( - core::iter::from_fn(move || { - near.borrow_mut().clear(); - fetch_vertex.next() - }), - (tgt_min, tgt_max), - tgt_size, + fetch_vertex, principal_x, pipeline.coordinate_mode(), rasterizer_config, - test_depth, - emit_fragment, + BlitterImpl { + write_pixels, + depth_mode, + + tgt_size, + tgt_min, + tgt_max, + + pipeline, + pixel, + depth, + primitive_count: 0, + + msaa_level, + msaa_buf: Buffer2d::fill_with( + [((tgt_max[0] - tgt_min[0]) >> msaa_level) + 1, ((tgt_max[1] - tgt_min[1]) >> msaa_level) + 1], + || (u64::MAX, None), + ), + }, ); } diff --git a/src/rasterizer/mod.rs b/src/rasterizer/mod.rs index 9ea65de..0cc2d04 100644 --- a/src/rasterizer/mod.rs +++ b/src/rasterizer/mod.rs @@ -2,7 +2,7 @@ pub mod triangles; pub use self::triangles::Triangles; -use crate::{CoordinateMode, math::WeightedSum}; +use crate::{CoordinateMode, Pipeline, math::WeightedSum}; use core::ops::{Mul, Add}; /// The face culling strategy used during rendering. @@ -22,6 +22,30 @@ impl Default for CullMode { } } +/// A trait for types that define an interface for blitting fragments to surfaces +pub trait Blitter: Sized { + fn target_size(&self) -> [usize; 2]; + fn target_min(&self) -> [usize; 2]; + fn target_max(&self) -> [usize; 2]; + + // Indicate to the blitter that a new primitive is now being rasterized. + fn begin_primitive(&mut self); + + /// Test whether a fragment should be emitted with the given attributes. + /// + /// # Safety + /// + /// This function *must* be called with a position that is valid for size and bounds that this type provides. + unsafe fn test_fragment(&mut self, pos: [usize; 2], z: f32) -> bool; + + /// Emit a fragment with the given attributes. + /// + /// # Safety + /// + /// This function *must* be called with a position that is valid for size and bounds that this type provides. + unsafe fn emit_fragment(&mut self, pos: [usize; 2], v_data: V, z: f32); +} + /// A trait that represents types that turn vertex streams into fragment coordinates. /// /// Rasterizers take an iterator of vertices and emit fragment positions. They do not, by themselves, perform shader @@ -42,20 +66,16 @@ pub trait Rasterizer: Default { /// /// `emit_fragment` must only be called with fragment positions that are valid for the `target_size` parameter /// provided. Undefined behaviour can be assumed to occur if this is not upheld. - unsafe fn rasterize( + unsafe fn rasterize( &self, vertices: I, - target_area: ([usize; 2], [usize; 2]), - target_size: [usize; 2], principal_x: bool, coordinate_mode: CoordinateMode, config: Self::Config, - test_depth: F, - emit_fragment: G, + blitter: B, ) where V: Clone + WeightedSum, I: Iterator, - F: FnMut([usize; 2], f32) -> bool, - G: FnMut([usize; 2], V, f32); + B: Blitter; } diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index 2f8b3f5..5c71a0a 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -9,23 +9,23 @@ pub struct Triangles; impl Rasterizer for Triangles { type Config = CullMode; - unsafe fn rasterize( + unsafe fn rasterize( &self, mut vertices: I, - (tgt_min, tgt_max): ([usize; 2], [usize; 2]), - tgt_size: [usize; 2], principal_x: bool, coordinate_mode: CoordinateMode, cull_mode: CullMode, - mut test_depth: F, - mut emit_fragment: G, + mut blitter: B, ) where V: Clone + WeightedSum, I: Iterator, - F: FnMut([usize; 2], f32) -> bool, - G: FnMut([usize; 2], V, f32), + B: Blitter, { + let tgt_size = blitter.target_size(); + let tgt_min = blitter.target_min(); + let tgt_max = blitter.target_max(); + let cull_dir = match cull_mode { CullMode::None => None, CullMode::Back => Some(1.0), @@ -50,6 +50,8 @@ impl Rasterizer for Triangles { }); verts_hom_out.for_each(|verts_hom_out: Vec3<([f32; 4], V)>| { + blitter.begin_primitive(); + // Calculate vertex shader outputs and vertex homogeneous coordinates let verts_hom = Vec3::new(verts_hom_out.x.0, verts_hom_out.y.0, verts_hom_out.z.0).map(Vec4::::from); let verts_out = Vec3::new(verts_hom_out.x.1, verts_hom_out.y.1, verts_hom_out.z.1); @@ -175,10 +177,10 @@ impl Rasterizer for Triangles { // Calculate the interpolated z coordinate for the depth target let z: f32 = verts_hom.map(|v| v.z).dot(w_unbalanced); - if test_depth([x, y], z) { + if blitter.test_fragment([x, y], z) { // Don't use `.contains(&z)`, it isn't inclusive if coordinate_mode.z_clip_range.clone().map_or(true, |clip_range| z >= clip_range.start && z <= clip_range.end) { - emit_fragment([x, y], V::weighted_sum(verts_out.as_slice(), w.as_slice()), z); + blitter.emit_fragment([x, y], V::weighted_sum(verts_out.as_slice(), w.as_slice()), z); } } } From 3945b85e7b82f289747e9da7a335c9fc0662c371 Mon Sep 17 00:00:00 2001 From: LoganDark Date: Fri, 22 Jan 2021 11:12:36 -0800 Subject: [PATCH 13/37] Updated teapot example to mini_gl_fb 0.8.0; fixes x11 compatibility --- Cargo.toml | 2 +- examples/teapot.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 672d99f..a33a0d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ image = ["image_"] par = ["std", "num_cpus", "crossbeam-utils", "fxhash"] [dev-dependencies] -mini_gl_fb = "0.7.0" +mini_gl_fb = { path = "../mini_gl_fb" } wavefront = "0.1.3" criterion = "0.3.3" image_ = { package = "image", version = "0.23" } diff --git a/examples/teapot.rs b/examples/teapot.rs index a86180d..0f4ad09 100644 --- a/examples/teapot.rs +++ b/examples/teapot.rs @@ -99,10 +99,10 @@ fn main() { let model = wavefront::Obj::from_file("examples/data/teapot.obj").unwrap(); - let mut win = mini_gl_fb::gotta_go_fast("Teapot", w as f64, h as f64); + let (mut event_loop, mut win) = mini_gl_fb::gotta_go_fast("Teapot", w as f64, h as f64); let mut i = 0; - win.glutin_handle_basic_input(|win, input| { + win.glutin_handle_basic_input(&mut event_loop, |win, input| { let teapot_pos = Vec3::new(0.0, 0.0, -6.0); let light_pos = Vec3::::new(-6.0, 0.0, 3.0); From 23bdd70f42a3f04fd291ba733a19a590e24cb357 Mon Sep 17 00:00:00 2001 From: LoganDark Date: Fri, 22 Jan 2021 11:26:10 -0800 Subject: [PATCH 14/37] At least switch to git rev --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a33a0d7..7a9d615 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ image = ["image_"] par = ["std", "num_cpus", "crossbeam-utils", "fxhash"] [dev-dependencies] -mini_gl_fb = { path = "../mini_gl_fb" } +mini_gl_fb = { git = "https://github.com/shivshank/mini_gl_fb", rev = "02b3b62520e5ee1990fd004348b89d12e294dc47" } wavefront = "0.1.3" criterion = "0.3.3" image_ = { package = "image", version = "0.23" } From 25c153fe5f687b7d176d152a8d4d16719c5ce46f Mon Sep 17 00:00:00 2001 From: LoganDark Date: Tue, 23 Feb 2021 18:14:36 -0800 Subject: [PATCH 15/37] mini_gl_fb 0.8.0 is on crates.io! --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7a9d615..74fde9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ image = ["image_"] par = ["std", "num_cpus", "crossbeam-utils", "fxhash"] [dev-dependencies] -mini_gl_fb = { git = "https://github.com/shivshank/mini_gl_fb", rev = "02b3b62520e5ee1990fd004348b89d12e294dc47" } +mini_gl_fb = "0.8.0" wavefront = "0.1.3" criterion = "0.3.3" image_ = { package = "image", version = "0.23" } From 9439ddc2462b9172f2e51d87758c5a070cb26dca Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 24 Feb 2021 11:20:10 +0000 Subject: [PATCH 16/37] MSAA experimental --- Cargo.toml | 3 ++ examples/teapot.rs | 33 ++++++++++------- src/buffer.rs | 10 ++++-- src/lib.rs | 4 +-- src/pipeline.rs | 71 ++++++++++++++++++++++++++++--------- src/rasterizer/mod.rs | 2 +- src/rasterizer/triangles.rs | 17 +++++++-- src/sampler/linear.rs | 69 +++++++++++++++++++++++++++++++++++ src/sampler/mod.rs | 52 +++++---------------------- src/sampler/nearest.rs | 40 +++++++++++++++++++++ src/texture.rs | 15 ++++++++ 11 files changed, 236 insertions(+), 80 deletions(-) create mode 100644 src/sampler/linear.rs create mode 100644 src/sampler/nearest.rs diff --git a/Cargo.toml b/Cargo.toml index 74fde9c..237590f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ image_ = { package = "image", version = "0.23", optional = true } num_cpus = { version = "1.13", optional = true } crossbeam-utils = { version = "0.8", optional = true } fxhash = { version = "0.2", optional = true } +micromath_ = { package = "micromath", version = "1.1", optional = true } [features] default = ["std", "image", "par"] @@ -28,6 +29,7 @@ nightly = [] simd = ["vek/repr_simd"] image = ["image_"] par = ["std", "num_cpus", "crossbeam-utils", "fxhash"] +micromath = ["micromath_"] [dev-dependencies] mini_gl_fb = "0.8.0" @@ -47,3 +49,4 @@ harness = false [profile.dev] # Optimize by default so we don't need to remember to always pass in --release opt-level = 3 +overflow-checks = false diff --git a/examples/teapot.rs b/examples/teapot.rs index 0f4ad09..c859980 100644 --- a/examples/teapot.rs +++ b/examples/teapot.rs @@ -1,6 +1,6 @@ use vek::*; use derive_more::{Add, Mul}; -use euc::{Pipeline, Buffer2d, Target, PixelMode, DepthMode, TriangleList, CullMode, Empty, Nearest, Texture, Sampler, AaMode}; +use euc::{Pipeline, Buffer2d, Target, PixelMode, DepthMode, TriangleList, CullMode, Empty, Linear, Texture, Sampler, AaMode}; use std::marker::PhantomData; struct TeapotShadow<'a> { @@ -12,6 +12,7 @@ impl<'a> Pipeline for TeapotShadow<'a> { type Vertex = wavefront::Vertex<'a>; type VertexData = f32; type Primitives = TriangleList; + type Fragment = f32; type Pixel = (); fn pixel_mode(&self) -> PixelMode { PixelMode::PASS } @@ -23,7 +24,10 @@ impl<'a> Pipeline for TeapotShadow<'a> { } #[inline(always)] - fn fragment_shader(&self, d: Self::VertexData) -> Self::Pixel {} + fn fragment_shader(&self, d: Self::VertexData) -> Self::Fragment { 0.0 } + + #[inline(always)] + fn blend_shader(&self, old: Self::Pixel, new: Self::Fragment) {} } struct Teapot<'a> { @@ -31,7 +35,7 @@ struct Teapot<'a> { v: Mat4, p: Mat4, light_pos: Vec3, - shadow: Nearest<&'a Buffer2d>, + shadow: Linear<&'a Buffer2d>, light_vp: Mat4, } @@ -46,10 +50,11 @@ impl<'a> Pipeline for Teapot<'a> { type Vertex = wavefront::Vertex<'a>; type VertexData = VertexData; type Primitives = TriangleList; + type Fragment = Rgba; type Pixel = u32; fn depth_mode(&self) -> DepthMode { DepthMode::LESS_WRITE } - fn aa_mode(&self) -> AaMode { AaMode::Msaa { level: 2 } } + fn aa_mode(&self) -> AaMode { AaMode::Msaa { level: 1 } } #[inline(always)] fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { @@ -65,7 +70,7 @@ impl<'a> Pipeline for Teapot<'a> { } #[inline(always)] - fn fragment_shader(&self, VertexData { wpos, wnorm, light_view_pos }: Self::VertexData) -> Self::Pixel { + fn fragment_shader(&self, VertexData { wpos, wnorm, light_view_pos }: Self::VertexData) -> Self::Fragment { let wnorm = wnorm.normalized(); let cam_pos = Vec3::zero(); let cam_dir = (wpos - cam_pos).normalized(); @@ -78,15 +83,17 @@ impl<'a> Pipeline for Teapot<'a> { let specular = light_dir.reflected(wnorm).dot(-cam_dir).max(0.0).powf(30.0) * 3.0; // Shadow-mapping - let light_depth = self.shadow.sample((light_view_pos.xy() * Vec2::new(1.0, -1.0) * 0.5 + 0.5).into_array()) + 0.001; - let depth = light_view_pos.z; - let in_light = depth < light_depth; + //let light_depth = self.shadow.sample((light_view_pos.xy() * Vec2::new(1.0, -1.0) * 0.5 + 0.5).into_array()) + 0.001; + //let depth = light_view_pos.z; + let in_light = true;//depth < light_depth; let light = ambient + if in_light { diffuse + specular } else { 0.0 }; - let color = surf_color * light; + surf_color * light + } - //let color = Rgba::zero() + self.shadow.sample(((screen + 1.0) * 0.5).into_array()); - u32::from_le_bytes(color.map(|e| e.clamped(0.0, 1.0) * 255.0).as_().into_array()) + #[inline(always)] + fn blend_shader(&self, _old: Self::Pixel, new: Self::Fragment) -> Self::Pixel { + u32::from_le_bytes(new.map(|e| e.clamped(0.0, 1.0) * 255.0).as_().into_array()) } } @@ -95,7 +102,7 @@ fn main() { let mut color = Buffer2d::fill([w, h], 0x0); let mut depth = Buffer2d::fill([w, h], 1.0); - let mut shadow = Buffer2d::fill([1024; 2], 1.0); + let mut shadow = Buffer2d::fill([512; 2], 1.0); let model = wavefront::Obj::from_file("examples/data/teapot.obj").unwrap(); @@ -134,7 +141,7 @@ fn main() { ); // Colour pass - Teapot { m, v, p, light_pos, shadow: Nearest::new(&shadow), light_vp: light_vp }.render( + Teapot { m, v, p, light_pos, shadow: Linear::new(&shadow), light_vp: light_vp }.render( model.vertices(), CullMode::Back, &mut color, diff --git a/src/buffer.rs b/src/buffer.rs index da4663b..0a075e4 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -64,7 +64,10 @@ impl Buffer { /// This function will panic if the index is not within bounds. pub fn get_mut(&mut self, index: [usize; N]) -> &mut T { let idx = self.linear_index(index); - self.items.get_mut(idx).unwrap() + match self.items.get_mut(idx) { + Some(item) => item, + None => panic!("Attempted to read buffer of size {:?} at out-of-bounds location {:?}", self.size, index), + } } /// Get a mutable reference to the item at the given assumed-valid index. @@ -88,7 +91,10 @@ impl Texture for Buffer { #[inline(always)] fn read(&self, index: [Self::Index; N]) -> Self::Texel { - self.items[self.linear_index(index)].clone() + self.items + .get(self.linear_index(index)) + .unwrap_or_else(|| panic!("Attempted to read buffer of size {:?} at out-of-bounds location {:?}", self.size(), index)) + .clone() } #[inline(always)] diff --git a/src/lib.rs b/src/lib.rs index 9b495ef..ca1e5b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,7 @@ #![no_std] -#![feature(min_const_generics, array_map, type_alias_impl_trait)] +#![feature(array_map, type_alias_impl_trait)] extern crate alloc; @@ -71,6 +71,6 @@ pub use crate::{ primitives::TriangleList, texture::{Texture, Target, Empty}, rasterizer::CullMode, - sampler::{Sampler, Nearest}, + sampler::{Sampler, Nearest, Linear}, index::IndexedVertices, }; diff --git a/src/pipeline.rs b/src/pipeline.rs index 5590d81..18ba76d 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -4,6 +4,7 @@ use crate::{ primitives::PrimitiveKind, math::WeightedSum, buffer::Buffer2d, + sampler::Linear, }; use alloc::{vec::Vec, collections::VecDeque}; use core::{ @@ -13,6 +14,9 @@ use core::{ marker::PhantomData, }; +#[cfg(feature = "micromath")] +use micromath_::F32Ext; + /// Defines how a [`Pipeline`] will interact with the depth target. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct DepthMode { @@ -169,6 +173,7 @@ pub trait Pipeline: Sized { type Vertex; type VertexData: Clone + WeightedSum + Send + Sync; type Primitives: PrimitiveKind; + type Fragment: Clone + WeightedSum; type Pixel: Clone; /// Returns the [`PixelMode`] of this pipeline. @@ -207,7 +212,7 @@ pub trait Pipeline: Sized { /// Transforms a [`Pipeline::VertexData`] into a fragment to be rendered to a pixel target. /// /// This stage is executed for every fragment generated by the rasterizer. - fn fragment_shader(&self, vs_out: Self::VertexData) -> Self::Pixel; + fn fragment_shader(&self, vs_out: Self::VertexData) -> Self::Fragment; /// Blend an old fragment with a new fragment. /// @@ -216,7 +221,7 @@ pub trait Pipeline: Sized { /// /// The default implementation simply returns the new fragment and ignores the old one. However, this may be used /// to implement techniques such as alpha blending. - fn blend_shader(&self, a: Self::Pixel, b: Self::Pixel) -> Self::Pixel { b } + fn blend_shader(&self, old: Self::Pixel, new: Self::Fragment) -> Self::Pixel; /// Render a stream of vertices to given provided pixel target and depth target using the rasterizer. /// @@ -396,7 +401,27 @@ where primitive_count: u64, msaa_level: usize, - msaa_buf: Buffer2d<(u64, Option)> + msaa_buf: Buffer2d<(u64, Option)> + } + + impl<'a, Pipe, P, D> BlitterImpl<'a, Pipe, P, D> + where + Pipe: Pipeline + Send + Sync, + P: Target + Send + Sync, + D: Target + Send + Sync, + { + #[inline(always)] + unsafe fn msaa_fragment Pipe::VertexData>(&mut self, pos: [usize; 2], mut get_v_data: F) -> Pipe::Fragment { + // Safety: MSAA buffer will always be large enough + let texel = self.msaa_buf + .get_mut([pos[0] + 1, pos[1] + 1]); + if texel.0 != self.primitive_count { + texel.0 = self.primitive_count; + texel.1 = Some(self.pipeline.fragment_shader(get_v_data(pos))); + } + // Safety: We know this entry will always be occupied due to the code above + texel.1.clone().unwrap_or_else(|| core::hint::unreachable_unchecked()) + } } impl<'a, Pipe, P, D> Blitter for BlitterImpl<'a, Pipe, P, D> @@ -425,28 +450,40 @@ where } #[inline(always)] - unsafe fn emit_fragment(&mut self, pos: [usize; 2], v_data: Pipe::VertexData, z: f32) { + unsafe fn emit_fragment Pipe::VertexData>(&mut self, pos: [usize; 2], mut get_v_data: F, z: f32) { if self.depth_mode.write { self.depth.write_exclusive_unchecked(pos, z); } if self.write_pixels { let frag = if self.msaa_level == 0 { - self.pipeline.fragment_shader(v_data) + self.pipeline.fragment_shader(get_v_data([pos[0] as f32, pos[1] as f32])) } else { - let fetch_pixel = |pos: [usize; 2]| { - // Safety: MSAA buffer will always be large enough - let texel = self.msaa_buf - .get_unchecked_mut([(pos[0] - self.tgt_min[0]) >> self.msaa_level, (pos[1] - self.tgt_min[1]) >> self.msaa_level]); - if texel.0 != self.primitive_count { - texel.0 = self.primitive_count; - texel.1 = Some(self.pipeline.fragment_shader(v_data)); - } - // Safety: We know this entry will always be occupied due to the code above - texel.1.clone().unwrap_or_else(|| core::hint::unreachable_unchecked()) + let fract = [(pos[0], self.tgt_min[0]), (pos[1], self.tgt_min[1])] + .map(|(e, tgt_min)| ((e - tgt_min) as f32 / (1 << self.msaa_level) as f32).fract()); + let posi = [(pos[0] - self.tgt_min[0]) >> self.msaa_level, (pos[1] - self.tgt_min[1]) >> self.msaa_level]; + + let tgt_min = self.tgt_min; + let msaa_level = self.msaa_level; + let mut get_v_data = |[x, y]: [usize; 2]| { + get_v_data([ + (tgt_min[0] + (x << msaa_level)) as f32, + (tgt_min[1] + (y << msaa_level)) as f32, + ]) }; - fetch_pixel(pos) + let t00 = self.msaa_fragment([posi[0] + 0, posi[1] + 0], &mut get_v_data); + let t10 = self.msaa_fragment([posi[0] + 1, posi[1] + 0], &mut get_v_data); + let t01 = self.msaa_fragment([posi[0] + 0, posi[1] + 1], &mut get_v_data); + let t11 = self.msaa_fragment([posi[0] + 1, posi[1] + 1], &mut get_v_data); + + let t0 = Pipe::Fragment::weighted_sum(&[t00, t01], &[1.0 - fract[1], fract[1]]); + let t1 = Pipe::Fragment::weighted_sum(&[t10, t11], &[1.0 - fract[1], fract[1]]); + + let t = Pipe::Fragment::weighted_sum(&[t0, t1], &[1.0 - fract[0], fract[0]]); + t + + //self.fetch_pixel([posi[0] + 0, posi[1] + 0], v_data.clone()) }; let old_px = self.pixel.read_exclusive_unchecked(pos); let blended_px = self.pipeline.blend_shader(old_px, frag); @@ -480,7 +517,7 @@ where msaa_level, msaa_buf: Buffer2d::fill_with( - [((tgt_max[0] - tgt_min[0]) >> msaa_level) + 1, ((tgt_max[1] - tgt_min[1]) >> msaa_level) + 1], + [((tgt_max[0] - tgt_min[0]) >> msaa_level) + 3, ((tgt_max[1] - tgt_min[1]) >> msaa_level) + 3], || (u64::MAX, None), ), }, diff --git a/src/rasterizer/mod.rs b/src/rasterizer/mod.rs index 0cc2d04..af91e97 100644 --- a/src/rasterizer/mod.rs +++ b/src/rasterizer/mod.rs @@ -43,7 +43,7 @@ pub trait Blitter: Sized { /// # Safety /// /// This function *must* be called with a position that is valid for size and bounds that this type provides. - unsafe fn emit_fragment(&mut self, pos: [usize; 2], v_data: V, z: f32); + unsafe fn emit_fragment V>(&mut self, pos: [usize; 2], get_v_data: F, z: f32); } /// A trait that represents types that turn vertex streams into fragment coordinates. diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index 5c71a0a..fdc6341 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -2,6 +2,9 @@ use super::*; use crate::{CoordinateMode, YAxisDirection}; use vek::*; +#[cfg(feature = "micromath")] +use micromath_::F32Ext; + /// A rasterizer that produces filled triangles. #[derive(Copy, Clone, Debug, Default)] pub struct Triangles; @@ -157,7 +160,7 @@ impl Rasterizer for Triangles { // Now we have screen-space bounds for the row. Clean it up and clamp it to the screen bounds let row_range = Vec2::new( - (row_bounds.x as usize).max(tri_bounds_clamped.min.x), + (row_bounds.x as usize).saturating_sub(1).max(tri_bounds_clamped.min.x), (row_bounds.y.ceil() as usize).min(tri_bounds_clamped.max.x), ); @@ -180,7 +183,17 @@ impl Rasterizer for Triangles { if blitter.test_fragment([x, y], z) { // Don't use `.contains(&z)`, it isn't inclusive if coordinate_mode.z_clip_range.clone().map_or(true, |clip_range| z >= clip_range.start && z <= clip_range.end) { - blitter.emit_fragment([x, y], V::weighted_sum(verts_out.as_slice(), w.as_slice()), z); + let get_v_data = |[x, y]: [f32; 2]| { + let w_hom = w_hom_origin + w_hom_dy * y + w_hom_dx * x; + + // Calculate vertex weights to determine vs_out lerping and intersection + let w_unbalanced = Vec3::new(w_hom.x, w_hom.y, w_hom.z - w_hom.x - w_hom.y); + let w = w_unbalanced / w_hom.z; + + V::weighted_sum(verts_out.as_slice(), w.as_slice()) + }; + + blitter.emit_fragment([x, y], get_v_data, z); } } } diff --git a/src/sampler/linear.rs b/src/sampler/linear.rs new file mode 100644 index 0000000..7ef0a35 --- /dev/null +++ b/src/sampler/linear.rs @@ -0,0 +1,69 @@ +use super::*; +use core::{ + ops::{Add, Mul}, + marker::PhantomData, +}; + +#[cfg(feature = "micromath")] +use micromath_::F32Ext; + +/// A sampler that uses nearest-neighbor sampling. +pub struct Linear(T, PhantomData); + +impl Linear { + /// Create a new + pub fn new(texture: T) -> Self { + Self(texture, PhantomData) + } +} + +impl<'a, T> Sampler<2> for Linear +where + T: Texture<2, Index = usize>, + T::Texel: Mul + Add, +{ + type Index = f32; + + type Sample = T::Texel; + + type Texture = T; + + #[inline(always)] + fn raw_texture(&self) -> &Self::Texture { &self.0 } + + #[inline(always)] + fn sample(&self, mut index: [Self::Index; 2]) -> Self::Sample { + assert!(index[0] <= 1.0, "{:?}", index); + assert!(index[1] <= 1.0, "{:?}", index); + + let size = self.raw_texture().size(); + let size_f32 = size.map(|e| e as f32); + // Index in texture coordinates + let index_tex = [index[0].fract() * size_f32[0], index[1].fract() * size_f32[1]]; + // Find texel sample coordinates + let posi = index_tex.map(|e| e.trunc() as usize); + // Find interpolation values + let fract = index_tex.map(|e| e.fract()); + + assert!(posi[0] < size[0], "pos: {:?}, sz: {:?}, idx: {:?}", posi, size, index); + assert!(posi[1] < size[1], "pos: {:?}, sz: {:?}, idx: {:?}", posi, size, index); + + let t00 = self.raw_texture().read([(posi[0] + 0).min(size[0] - 1), (posi[1] + 0).min(size[1] - 1)]); + let t10 = self.raw_texture().read([(posi[0] + 1).min(size[0] - 1), (posi[1] + 0).min(size[1] - 1)]); + let t01 = self.raw_texture().read([(posi[0] + 0).min(size[0] - 1), (posi[1] + 1).min(size[1] - 1)]); + let t11 = self.raw_texture().read([(posi[0] + 1).min(size[0] - 1), (posi[1] + 1).min(size[1] - 1)]); + + let t0 = t00 * (1.0 - fract[1]) + t01 * fract[1]; + let t1 = t10 * (1.0 - fract[1]) + t11 * fract[1]; + + let t = t0 * (1.0 - fract[0]) + t1 * fract[0]; + + t + } + + #[inline(always)] + unsafe fn sample_unchecked(&self, index: [Self::Index; 2]) -> Self::Sample { + // TODO: Not this + self.sample(index) + } +} diff --git a/src/sampler/mod.rs b/src/sampler/mod.rs index ef95638..27c436d 100644 --- a/src/sampler/mod.rs +++ b/src/sampler/mod.rs @@ -1,11 +1,15 @@ +pub mod nearest; +pub mod linear; + +pub use self::{ + nearest::Nearest, + linear::Linear, +}; + use crate::{ texture::Texture, math::*, }; -use core::{ - ops::Mul, - marker::PhantomData, -}; /// A trait that describes a sampler of a texture. /// @@ -15,10 +19,7 @@ use core::{ /// Please note that texture coordinate axes are, where possible, consistent with the underlying texture implementation /// (i.e: +x and +y in sampler space correspond to the same directions as +x and +y in texture space). This behaviour /// is equivalent to that of Vulkan's texture access API. -pub trait Sampler -where - Self::Index: Denormalize<>::Index>, -{ +pub trait Sampler { /// The type used to perform sampling. type Index: Clone; @@ -50,38 +51,3 @@ where self.sample(index) } } - -/// A sampler that uses nearest-neighbor sampling. -pub struct Nearest(T, PhantomData); - -impl Nearest { - /// Create a new - pub fn new(texture: T) -> Self { - Self(texture, PhantomData) - } -} - -impl<'a, T, I, const N: usize> Sampler for Nearest -where - T: Texture, - I: Clone + Mul + Denormalize, -{ - type Index = I; - - type Sample = T::Texel; - - type Texture = T; - - #[inline(always)] - fn raw_texture(&self) -> &Self::Texture { &self.0 } - - #[inline(always)] - fn sample(&self, index: [Self::Index; N]) -> Self::Sample { - unsafe { self.raw_texture().read_unchecked(I::denormalize_array(index, self.raw_texture().size())) } - } - - #[inline(always)] - unsafe fn sample_unchecked(&self, index: [Self::Index; N]) -> Self::Sample { - self.raw_texture().read_unchecked(I::denormalize_array(index, self.raw_texture().size())) - } -} diff --git a/src/sampler/nearest.rs b/src/sampler/nearest.rs new file mode 100644 index 0000000..83d19f5 --- /dev/null +++ b/src/sampler/nearest.rs @@ -0,0 +1,40 @@ +use super::*; +use core::{ + ops::Mul, + marker::PhantomData, +}; + +/// A sampler that uses nearest-neighbor sampling. +pub struct Nearest(T, PhantomData); + +impl Nearest { + /// Create a new + pub fn new(texture: T) -> Self { + Self(texture, PhantomData) + } +} + +impl<'a, T, I, const N: usize> Sampler for Nearest +where + T: Texture, + I: Clone + Mul + Denormalize, +{ + type Index = I; + + type Sample = T::Texel; + + type Texture = T; + + #[inline(always)] + fn raw_texture(&self) -> &Self::Texture { &self.0 } + + #[inline(always)] + fn sample(&self, index: [Self::Index; N]) -> Self::Sample { + unsafe { self.raw_texture().read_unchecked(I::denormalize_array(index, self.raw_texture().size())) } + } + + #[inline(always)] + unsafe fn sample_unchecked(&self, index: [Self::Index; N]) -> Self::Sample { + self.raw_texture().read_unchecked(I::denormalize_array(index, self.raw_texture().size())) + } +} diff --git a/src/texture.rs b/src/texture.rs index b7e72bb..e91ee25 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -1,3 +1,5 @@ +use core::marker::PhantomData; + /// A trait implemented by types that may be treated as textures. pub trait Texture { /// The type used to index into the texture. @@ -60,6 +62,19 @@ impl<'a, T: Texture, const N: usize> Texture for &'a mut T { unsafe fn read_unchecked(&self, index: [Self::Index; N]) -> Self::Texel { (**self).read_unchecked(index) } } +// impl<'a, T: Clone, F: Fn([usize; N]) -> T, const N: usize> Texture for (F, [usize; N], PhantomData) { +// type Index = usize; +// type Texel = T; +// fn size(&self) -> [Self::Index; N] { self.1 } +// fn read(&self, index: [Self::Index; N]) -> Self::Texel { +// for i in 0..N { +// assert!(index[i] < self.1[i]); +// } +// self.0(index) +// } +// unsafe fn read_unchecked(&self, index: [Self::Index; N]) -> Self::Texel { self.0(index) } +// } + /// A trait implemented by 2-dimensional textures that may be treated as render targets. /// /// Targets necessarily require additional invariants to be upheld than textures for safe use. Because access to them From 3b23507ce2cb0533bffee71665596bd56dc5207a Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 27 May 2021 17:54:29 +0100 Subject: [PATCH 17/37] Fixed incorrect culling winding flip --- src/lib.rs | 2 +- src/rasterizer/triangles.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ca1e5b5..bcffb75 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,7 @@ #![no_std] -#![feature(array_map, type_alias_impl_trait)] +#![feature(array_map, min_type_alias_impl_trait)] extern crate alloc; diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index fdc6341..531b40f 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -73,7 +73,7 @@ impl Rasterizer for Triangles { .unwrap_or(false) { return; // Cull the triangle - } else if winding < 0.0 { + } else if winding >= 0.0 { // Reverse vertex order (verts_hom.zyx(), verts_euc.zyx(), verts_out.zyx()) } else { From 9e13444f7f9c6bb8554f85bf0e1f582122d0710e Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 3 Feb 2022 16:51:52 +0000 Subject: [PATCH 18/37] Fixed examples, added clamped sampler --- Cargo.toml | 2 +- README.md | 2 +- examples/spinning_cube.rs | 24 ++++++---- examples/teapot.rs | 94 ++++++++++++++++++++++--------------- examples/texture_mapping.rs | 28 ++++++----- examples/triangle.rs | 15 ++++-- src/lib.rs | 5 +- src/math.rs | 7 +++ src/rasterizer/triangles.rs | 2 +- src/sampler/mod.rs | 26 ++++++++++ 10 files changed, 136 insertions(+), 69 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 237590f..2000d9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ par = ["std", "num_cpus", "crossbeam-utils", "fxhash"] micromath = ["micromath_"] [dev-dependencies] -mini_gl_fb = "0.8.0" +minifb = "0.20" wavefront = "0.1.3" criterion = "0.3.3" image_ = { package = "image", version = "0.23" } diff --git a/README.md b/README.md index af7bc49..3375829 100644 --- a/README.md +++ b/README.md @@ -173,4 +173,4 @@ euc = { version = "x.y.z", default-features = false, features = ["libm"] } - MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT) -at the disgression of the user. +at the discretion of the user. diff --git a/examples/spinning_cube.rs b/examples/spinning_cube.rs index b719dd2..232c73a 100644 --- a/examples/spinning_cube.rs +++ b/examples/spinning_cube.rs @@ -1,5 +1,6 @@ use vek::*; use euc::{Pipeline, Buffer2d, Target, TriangleList, CullMode, IndexedVertices}; +use minifb::{Key, Window, WindowOptions}; struct Cube { mvp: Mat4, @@ -10,6 +11,7 @@ impl Pipeline for Cube { type VertexData = Rgba; type Primitives = TriangleList; type Pixel = u32; + type Fragment = Rgba; #[inline(always)] fn vertex_shader(&self, (pos, color): &Self::Vertex) -> ([f32; 4], Self::VertexData) { @@ -17,7 +19,11 @@ impl Pipeline for Cube { } #[inline(always)] - fn fragment_shader(&self, color: Self::VertexData) -> Self::Pixel { + fn fragment_shader(&self, color: Self::VertexData) -> Self::Fragment { + color + } + + fn blend_shader(&self, _: Self::Pixel, color: Self::Fragment) -> Self::Pixel { u32::from_le_bytes((color * 255.0).as_().into_array()) } } @@ -53,15 +59,15 @@ fn main() { let mut color = Buffer2d::fill([w, h], 0); let mut depth = Buffer2d::fill([w, h], 1.0); - let mut win = mini_gl_fb::gotta_go_fast("Cube", w as f64, h as f64); + let mut win = Window::new("Cube", w, h, WindowOptions::default()).unwrap(); let mut i = 0; - win.glutin_handle_basic_input(|win, input| { + while win.is_open() && !win.is_key_down(Key::Escape) { let mvp = Mat4::perspective_fov_lh_zo(1.3, w as f32, h as f32, 0.01, 100.0) * Mat4::translation_3d(Vec3::new(0.0, 0.0, 3.0)) - * Mat4::rotation_x((i as f32 * 0.002).sin() * 8.0) - * Mat4::rotation_y((i as f32 * 0.004).cos() * 4.0) - * Mat4::rotation_z((i as f32 * 0.008).sin() * 2.0) + * Mat4::rotation_x((i as f32 * 0.0002).sin() * 8.0) + * Mat4::rotation_y((i as f32 * 0.0004).cos() * 4.0) + * Mat4::rotation_z((i as f32 * 0.0008).sin() * 2.0) * Mat4::scaling_3d(Vec3::new(1.0, -1.0, 1.0)); color.clear(0); @@ -74,10 +80,8 @@ fn main() { &mut depth, ); - win.update_buffer(color.raw()); - win.redraw(); + win.update_with_buffer(color.raw(), w, h).unwrap(); i += 1; - true - }); + } } diff --git a/examples/teapot.rs b/examples/teapot.rs index c859980..d1ee390 100644 --- a/examples/teapot.rs +++ b/examples/teapot.rs @@ -1,6 +1,7 @@ use vek::*; use derive_more::{Add, Mul}; -use euc::{Pipeline, Buffer2d, Target, PixelMode, DepthMode, TriangleList, CullMode, Empty, Linear, Texture, Sampler, AaMode}; +use euc::{Pipeline, Buffer2d, Target, PixelMode, DepthMode, TriangleList, CullMode, Empty, Linear, Texture, Sampler, AaMode, Unit, Clamped}; +use minifb::{Key, MouseButton, MouseMode, Window, WindowOptions}; use std::marker::PhantomData; struct TeapotShadow<'a> { @@ -12,7 +13,7 @@ impl<'a> Pipeline for TeapotShadow<'a> { type Vertex = wavefront::Vertex<'a>; type VertexData = f32; type Primitives = TriangleList; - type Fragment = f32; + type Fragment = Unit; type Pixel = (); fn pixel_mode(&self) -> PixelMode { PixelMode::PASS } @@ -24,7 +25,7 @@ impl<'a> Pipeline for TeapotShadow<'a> { } #[inline(always)] - fn fragment_shader(&self, d: Self::VertexData) -> Self::Fragment { 0.0 } + fn fragment_shader(&self, _: Self::VertexData) -> Self::Fragment { Unit } #[inline(always)] fn blend_shader(&self, old: Self::Pixel, new: Self::Fragment) {} @@ -35,7 +36,7 @@ struct Teapot<'a> { v: Mat4, p: Mat4, light_pos: Vec3, - shadow: Linear<&'a Buffer2d>, + shadow: Clamped>>, light_vp: Mat4, } @@ -80,20 +81,23 @@ impl<'a> Pipeline for Teapot<'a> { // Phong reflection model let ambient = 0.1; let diffuse = wnorm.dot(-light_dir).max(0.0) * 0.5; - let specular = light_dir.reflected(wnorm).dot(-cam_dir).max(0.0).powf(30.0) * 3.0; + let specular = (-light_dir).reflected(wnorm).dot(-cam_dir).max(0.0).powf(30.0) * 3.0; // Shadow-mapping - //let light_depth = self.shadow.sample((light_view_pos.xy() * Vec2::new(1.0, -1.0) * 0.5 + 0.5).into_array()) + 0.001; - //let depth = light_view_pos.z; - let in_light = true;//depth < light_depth; + let light_depth = self.shadow.sample((light_view_pos.xy() * Vec2::new(1.0, -1.0) * 0.5 + 0.5).into_array()) + 0.0001; + let depth = light_view_pos.z; + let in_light = depth < light_depth; let light = ambient + if in_light { diffuse + specular } else { 0.0 }; surf_color * light } #[inline(always)] - fn blend_shader(&self, _old: Self::Pixel, new: Self::Fragment) -> Self::Pixel { - u32::from_le_bytes(new.map(|e| e.clamped(0.0, 1.0) * 255.0).as_().into_array()) + fn blend_shader(&self, _old: Self::Pixel, rgba: Self::Fragment) -> Self::Pixel { + let rgba = rgba.map(|e| e.clamped(0.0, 1.0) * 255.0).as_(); + // The window's framebuffer uses BGRA format + let bgra = Rgba::new(rgba.b, rgba.g, rgba.r, rgba.a); + u32::from_le_bytes(bgra.into_array()) } } @@ -106,57 +110,73 @@ fn main() { let model = wavefront::Obj::from_file("examples/data/teapot.obj").unwrap(); - let (mut event_loop, mut win) = mini_gl_fb::gotta_go_fast("Teapot", w as f64, h as f64); + let mut win = Window::new("Teapot", w, h, WindowOptions::default()).unwrap(); + + let mut ori = Vec2::new(0.0, 0.0); + let mut dist = 6.0; + let mut old_mouse_pos = (0.0, 0.0); let mut i = 0; - win.glutin_handle_basic_input(&mut event_loop, |win, input| { - let teapot_pos = Vec3::new(0.0, 0.0, -6.0); - let light_pos = Vec3::::new(-6.0, 0.0, 3.0); + while win.is_open() && !win.is_key_down(Key::Escape) { + let start_time = std::time::Instant::now(); + + // Clear the render targets ready for the next frame + color.clear(0x0); + depth.clear(1.0); + shadow.clear(1.0); + + // Update camera as the mouse moves + let mouse_pos = win.get_mouse_pos(MouseMode::Pass).unwrap_or_default(); + if win.get_mouse_down(MouseButton::Left) { + ori.x -= (mouse_pos.1 - old_mouse_pos.1) * 0.003; + ori.y += (mouse_pos.0 - old_mouse_pos.0) * 0.003; + } + if win.get_mouse_down(MouseButton::Right) { + dist = (dist + (mouse_pos.1 - old_mouse_pos.1) as f32 * 0.01).max(1.0).min(20.0); + } + old_mouse_pos = mouse_pos; + + // Position of objects in the scene + let teapot_pos = Vec3::new(0.0, 0.0, 0.0); + let light_pos = Vec3::::new(-8.0, 5.0, -5.0); - let light_p = Mat4::perspective_fov_lh_zo(1.5, shadow.size()[0] as f32, shadow.size()[1] as f32, 0.1, 100.0); + // Set up the light matrix + let light_p = Mat4::perspective_fov_lh_zo(0.75, shadow.size()[0] as f32, shadow.size()[1] as f32, 0.1, 100.0); let light_v = Mat4::look_at_lh(light_pos, -teapot_pos, Vec3::unit_y()); let light_vp = light_p * light_v; + // Set up the camera matrix let p = Mat4::perspective_fov_lh_zo(1.3, w as f32, h as f32, 0.01, 100.0); - let v = Mat4::::identity(); - let m = { - //let i = 100; - Mat4::::translation_3d(-teapot_pos) - * Mat4::rotation_x((i as f32 * 0.03).sin() * 0.4) - * Mat4::rotation_y((i as f32 * 0.005) * 4.0) - * Mat4::rotation_z((i as f32 * 0.04).cos() * 0.4) - }; - - let start_time = std::time::Instant::now(); - color.clear(0x0); - depth.clear(1.0); - shadow.clear(1.0); + let v = Mat4::::identity() + * Mat4::translation_3d(Vec3::new(0.0, 0.0, dist)); + // Set up the teapot matrix + let m = Mat4::::translation_3d(-teapot_pos) + * Mat4::rotation_x(core::f32::consts::PI) + * Mat4::rotation_x(ori.x) + * Mat4::rotation_y(ori.y); // Shadow pass TeapotShadow { mvp: light_vp * m, phantom: PhantomData }.render( model.vertices(), - CullMode::Back, + CullMode::None, &mut Empty::default(), &mut shadow, ); // Colour pass - Teapot { m, v, p, light_pos, shadow: Linear::new(&shadow), light_vp: light_vp }.render( + Teapot { m, v, p, light_pos, shadow: Clamped::new(Linear::new(&shadow)), light_vp: light_vp }.render( model.vertices(), CullMode::Back, &mut color, &mut depth, ); + win.update_with_buffer(color.raw(), w, h).unwrap(); + if i % 60 == 0 { let elapsed = start_time.elapsed(); - println!("Time = {:?}, FPS = {}", elapsed, 1.0 / elapsed.as_secs_f32()); + win.set_title(&format!("Teapot (Time = {:?}, FPS = {})", elapsed, 1.0 / elapsed.as_secs_f32())); } - - win.update_buffer(color.raw()); - win.redraw(); - i += 1; - true - }); + } } diff --git a/examples/texture_mapping.rs b/examples/texture_mapping.rs index 8310652..e8651bc 100644 --- a/examples/texture_mapping.rs +++ b/examples/texture_mapping.rs @@ -1,6 +1,7 @@ use euc::{Buffer2d, Pipeline, Target, TriangleList, CullMode, Sampler, Nearest}; use image_::RgbaImage; -use vek::{Mat4, Vec2, Vec3, Vec4}; +use vek::{Mat4, Vec2, Vec3, Vec4, Rgba}; +use minifb::{Key, Window, WindowOptions}; struct Cube<'a> { mvp: Mat4, @@ -13,6 +14,7 @@ impl<'a> Pipeline for Cube<'a> { type Vertex = usize; type VertexData = Vec2; type Primitives = TriangleList; + type Fragment = Rgba; type Pixel = u32; #[inline] @@ -24,8 +26,12 @@ impl<'a> Pipeline for Cube<'a> { } #[inline] - fn fragment_shader(&self, v_uv: Self::VertexData) -> Self::Pixel { - u32::from_le_bytes(self.sampler.sample(v_uv.into_array()).0) + fn fragment_shader(&self, v_uv: Self::VertexData) -> Self::Fragment { + Rgba::from(self.sampler.sample(v_uv.into_array()).0).map(|e: u8| e as f32) + } + + fn blend_shader(&self, _: Self::Pixel, color: Self::Fragment) -> Self::Pixel { + u32::from_le_bytes(color.map(|e| e as u8).into_array()) } } @@ -109,17 +115,17 @@ fn main() { }; let sampler = Nearest::new(texture); - let mut win = mini_gl_fb::gotta_go_fast("Cube", w as f64, h as f64); + let mut win = Window::new("Texture Mapping", w, h, WindowOptions::default()).unwrap(); let mut i = 0; - win.glutin_handle_basic_input(|win, input| { + while win.is_open() && !win.is_key_down(Key::Escape) { let p = Mat4::perspective_fov_rh_no(1.4, w as f32, h as f32, 0.01, 100.0); let v = Mat4::::translation_3d(Vec3::new(0.0, 0.0, -2.0)) * Mat4::::scaling_3d(0.6) * Mat4::rotation_x(0.6); - let m = Mat4::rotation_x((i as f32 * 0.04).sin() * 0.4) - * Mat4::rotation_y((i as f32 * 0.008) * 4.0) - * Mat4::rotation_z((i as f32 * 0.06).cos() * 0.4); + let m = Mat4::rotation_x((i as f32 * 0.004).sin() * 0.4) + * Mat4::rotation_y((i as f32 * 0.0008) * 4.0) + * Mat4::rotation_z((i as f32 * 0.006).cos() * 0.4); color.clear(180); depth.clear(1.0); @@ -144,10 +150,8 @@ fn main() { &mut depth, ); - win.update_buffer(color.raw()); - win.redraw(); + win.update_with_buffer(color.raw(), w, h).unwrap(); i += 1; - true - }); + } } diff --git a/examples/triangle.rs b/examples/triangle.rs index 4835632..0dc553c 100644 --- a/examples/triangle.rs +++ b/examples/triangle.rs @@ -1,4 +1,5 @@ use euc::{Buffer2d, Pipeline, TriangleList, CullMode, Empty}; +use minifb::{Key, Window, WindowOptions}; use vek::*; struct Triangle; @@ -7,20 +8,23 @@ impl Pipeline for Triangle { type Vertex = [f32; 2]; type VertexData = Vec2; type Primitives = TriangleList; + type Fragment = Vec2; type Pixel = u32; fn vertex_shader(&self, pos: &[f32; 2]) -> ([f32; 4], Self::VertexData) { ([pos[0], pos[1], 0.0, 1.0], Vec2::new(pos[0], pos[1])) } - fn fragment_shader(&self, xy: Self::VertexData) -> Self::Pixel { - u32::from_le_bytes([(xy.x * 255.0) as u8, (xy.y * 255.0) as u8, 0, 255]) // Red + fn fragment_shader(&self, xy: Self::VertexData) -> Self::Fragment { xy } + + fn blend_shader(&self, _: Self::Pixel, xy: Self::Fragment) -> Self::Pixel { + u32::from_le_bytes([(xy.x * 255.0) as u8, (xy.y * 255.0) as u8, 255, 255]) } } fn main() { let [w, h] = [640, 480]; let mut color = Buffer2d::fill([w, h], 0); - let mut win = mini_gl_fb::gotta_go_fast("Triangle", w as f64, h as f64); + let mut win = Window::new("Triangle", w, h, WindowOptions::default()).unwrap(); Triangle.render( &[[-1.0, -1.0], [1.0, -1.0], [0.0, 1.0]], @@ -29,6 +33,7 @@ fn main() { &mut Empty::default(), ); - win.update_buffer(color.raw()); - win.persist(); + while win.is_open() && !win.is_key_down(Key::Escape) { + win.update_with_buffer(color.raw(), w, h).unwrap(); + } } diff --git a/src/lib.rs b/src/lib.rs index bcffb75..008a6b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,7 @@ #![no_std] -#![feature(array_map, min_type_alias_impl_trait)] +#![feature(array_map, type_alias_impl_trait)] extern crate alloc; @@ -71,6 +71,7 @@ pub use crate::{ primitives::TriangleList, texture::{Texture, Target, Empty}, rasterizer::CullMode, - sampler::{Sampler, Nearest, Linear}, + sampler::{Sampler, Nearest, Linear, Clamped}, index::IndexedVertices, + math::Unit, }; diff --git a/src/math.rs b/src/math.rs index 6fda603..2629ccd 100644 --- a/src/math.rs +++ b/src/math.rs @@ -4,6 +4,13 @@ pub trait WeightedSum: Sized { fn weighted_sum(values: &[Self], weights: &[f32]) -> Self; } +#[derive(Copy, Clone)] +pub struct Unit; + +impl WeightedSum for Unit { + fn weighted_sum(_: &[Self], _: &[f32]) -> Self { Unit } +} + impl + Add> WeightedSum for T { #[inline(always)] fn weighted_sum(values: &[Self], weights: &[f32]) -> Self { diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index 531b40f..80a0c89 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -87,7 +87,7 @@ impl Rasterizer for Triangles { let cb = Vec3::new(verts_hom.y.x, verts_hom.y.y, verts_hom.y.w) - c; let n = ca.cross(cb); let rec_det = if n.magnitude_squared() > 0.0 { - 1.0 / n.dot(c) + 1.0 / n.dot(c).min(-core::f32::EPSILON) } else { 1.0 }; diff --git a/src/sampler/mod.rs b/src/sampler/mod.rs index 27c436d..f429afc 100644 --- a/src/sampler/mod.rs +++ b/src/sampler/mod.rs @@ -51,3 +51,29 @@ pub trait Sampler { self.sample(index) } } + +/// A sampler that clamps the index's components to the 0.0 <= x <= 1.0 range. +#[derive(Copy, Clone)] +pub struct Clamped(S); + +impl Clamped { + pub fn new(sampler: S) -> Self { + Self(sampler) + } +} + +impl, const N: usize> Sampler for Clamped { + type Index = S::Index; + type Sample = S::Sample; + type Texture = S::Texture; + + fn raw_texture(&self) -> &Self::Texture { self.0.raw_texture() } + fn sample(&self, index: [Self::Index; N]) -> Self::Sample { + let index = index.map(|e| e.max(0.0).min(1.0)); + self.0.sample(index) + } + unsafe fn sample_unchecked(&self, index: [Self::Index; N]) -> Self::Sample { + let index = index.map(|e| e.max(0.0).min(1.0)); + self.0.sample_unchecked(index) + } +} From 0f68e9be975d6f9c37a1d3b95336431cdb39cedc Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 17 Oct 2022 22:31:37 +0100 Subject: [PATCH 19/37] Better benchmark --- Cargo.toml | 5 +- README.md | 4 +- benches/teapot.rs | 212 ++++++++++++++++++++++++------------ src/buffer.rs | 5 + src/pipeline.rs | 7 +- src/rasterizer/triangles.rs | 5 +- 6 files changed, 157 insertions(+), 81 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2000d9f..801a5fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ exclude = [ vek = { version = "0.12", default-features = false, features = [] } image_ = { package = "image", version = "0.23", optional = true } num_cpus = { version = "1.13", optional = true } -crossbeam-utils = { version = "0.8", optional = true } fxhash = { version = "0.2", optional = true } micromath_ = { package = "micromath", version = "1.1", optional = true } @@ -28,12 +27,12 @@ libm = ["vek/libm"] nightly = [] simd = ["vek/repr_simd"] image = ["image_"] -par = ["std", "num_cpus", "crossbeam-utils", "fxhash"] +par = ["std", "num_cpus", "fxhash"] micromath = ["micromath_"] [dev-dependencies] minifb = "0.20" -wavefront = "0.1.3" +wavefront = "0.2" criterion = "0.3.3" image_ = { package = "image", version = "0.23" } vek = "0.12.1" diff --git a/README.md b/README.md index 3375829..cce5f85 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ Below are a few circumstances in which you might want to use `euc`. ### Learning and experimentation Modern graphics APIs are complex, verbose beasts. The code required to set them -up properly requires a lot of beaurocratic mumbo jumbo. This problem has only +up properly requires a lot of bureaucratic mumbo jumbo. This problem has only become worse with the latest iteration of graphics APIs. Vulkan's canonical ['Hello Triangle' example](https://vulkan-tutorial.com/code/16_swap_chain_recreation.cpp) is, when shader code is included, 994 lines of code. Compare that to `euc`'s 34. @@ -84,7 +84,7 @@ models as icons to be displayed in-game later. `euc` is also more than fast enough for soft real-time applications such as UI rendering, particularly for state-driven UIs that only update when events occur. In addition, tricky rendering problems like font rasterization become much -simpler to solver on the CPU. +simpler to solve on the CPU. ### Embedded diff --git a/benches/teapot.rs b/benches/teapot.rs index f966f85..1fb866f 100644 --- a/benches/teapot.rs +++ b/benches/teapot.rs @@ -1,92 +1,162 @@ -use criterion::{criterion_group, criterion_main, Bencher, Criterion}; -use euc::{buffer::Buffer2d, rasterizer, Pipeline}; -use std::{path::Path, time::Duration}; +use criterion::{criterion_group, criterion_main, Bencher, Criterion, black_box}; use vek::*; +use derive_more::{Add, Mul}; +use euc::{Pipeline, Buffer2d, Target, PixelMode, DepthMode, TriangleList, CullMode, Empty, Linear, Texture, Sampler, AaMode, Unit, Clamped}; +use minifb::{Key, MouseButton, MouseMode, Window, WindowOptions}; +use std::{marker::PhantomData, time::Duration}; -struct Teapot<'a> { +struct TeapotShadow<'a> { mvp: Mat4, - positions: &'a [Vec3], - normals: &'a [Vec3], - light_dir: Vec3, + phantom: PhantomData<&'a ()>, +} + +impl<'a> Pipeline for TeapotShadow<'a> { + type Vertex = wavefront::Vertex<'a>; + type VertexData = f32; + type Primitives = TriangleList; + type Fragment = Unit; + type Pixel = (); + + fn pixel_mode(&self) -> PixelMode { PixelMode::PASS } + fn depth_mode(&self) -> DepthMode { DepthMode::LESS_WRITE } + + #[inline(always)] + fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { + ((self.mvp * Vec4::from_point(Vec3::from(vertex.position()))).into_array(), 0.0) + } + + #[inline(always)] + fn fragment_shader(&self, _: Self::VertexData) -> Self::Fragment { Unit } + + #[inline(always)] + fn blend_shader(&self, old: Self::Pixel, new: Self::Fragment) {} +} + +struct Teapot<'a> { + m: Mat4, + v: Mat4, + p: Mat4, + light_pos: Vec3, + shadow: Clamped>>, + light_vp: Mat4, +} + +#[derive(Add, Mul, Clone)] +struct VertexData { + wpos: Vec3, + wnorm: Vec3, + light_view_pos: Vec3, } impl<'a> Pipeline for Teapot<'a> { - type Vertex = u32; // Vertex index - type VsOut = Vec3; // Normal - type Pixel = u32; // BGRA + type Vertex = wavefront::Vertex<'a>; + type VertexData = VertexData; + type Primitives = TriangleList; + type Fragment = Rgba; + type Pixel = u32; + + fn depth_mode(&self) -> DepthMode { DepthMode::LESS_WRITE } + fn aa_mode(&self) -> AaMode { AaMode::Msaa { level: 1 } } #[inline(always)] - fn vert(&self, v_index: &Self::Vertex) -> ([f32; 4], Self::VsOut) { - let v_index = *v_index as usize; - // Find vertex position - let v_pos = self.positions[v_index] + Vec3::new(0.0, -0.5, 0.0); // Offset to center the teapot + fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { + let wpos = self.m * Vec4::from_point(Vec3::from(vertex.position())); + let wnorm = self.m * Vec4::from_direction(-Vec3::from(vertex.normal().unwrap())); + + let light_view_pos = self.light_vp * Vec4::from_point(wpos); + let light_view_pos = light_view_pos.xyz() / light_view_pos.w; ( - // Calculate vertex position in camera space - (self.mvp * Vec4::from_point(v_pos)).into_array(), - // Find vertex normal - self.normals[v_index], + (self.p * self.v * wpos).into_array(), + VertexData { wpos: wpos.xyz(), wnorm: wnorm.xyz(), light_view_pos }, ) } #[inline(always)] - fn frag(&self, norm: &Self::VsOut) -> Self::Pixel { - /* - let ambient = 0.2; - let diffuse = norm.dot(self.light_dir).max(0.0) * 0.5; - let specular = self - .light_dir - .reflected(Vec3::from(self.mvp * Vec4::from(*norm)).normalized()) - .dot(-Vec3::unit_z()) - .powf(20.0); - - let light = ambient + diffuse + specular; - let color = (Rgba::new(1.0, 0.7, 0.1, 1.0) * light).clamped(Rgba::zero(), Rgba::one()); - */ - - let color = Rgba::broadcast(1.0); - - let bytes = (color * 255.0).map(|e| e as u8).into_array(); - (bytes[2] as u32) << 0 - | (bytes[1] as u32) << 8 - | (bytes[0] as u32) << 16 - | (bytes[3] as u32) << 24 + fn fragment_shader(&self, VertexData { wpos, wnorm, light_view_pos }: Self::VertexData) -> Self::Fragment { + let wnorm = wnorm.normalized(); + let cam_pos = Vec3::zero(); + let cam_dir = (wpos - cam_pos).normalized(); + let light_dir = (wpos - self.light_pos).normalized(); + let surf_color = Rgba::new(1.0, 0.8, 0.7, 1.0); + + // Phong reflection model + let ambient = 0.1; + let diffuse = wnorm.dot(-light_dir).max(0.0) * 0.5; + let specular = (-light_dir).reflected(wnorm).dot(-cam_dir).max(0.0).powf(30.0) * 3.0; + + // Shadow-mapping + let light_depth = self.shadow.sample((light_view_pos.xy() * Vec2::new(1.0, -1.0) * 0.5 + 0.5).into_array()) + 0.0001; + let depth = light_view_pos.z; + let in_light = depth < light_depth; + + let light = ambient + if in_light { diffuse + specular } else { 0.0 }; + surf_color * light + } + + #[inline(always)] + fn blend_shader(&self, _old: Self::Pixel, rgba: Self::Fragment) -> Self::Pixel { + let rgba = rgba.map(|e| e.clamped(0.0, 1.0) * 255.0).as_(); + // The window's framebuffer uses BGRA format + let bgra = Rgba::new(rgba.b, rgba.g, rgba.r, rgba.a); + u32::from_le_bytes(bgra.into_array()) } } fn teapot_benchmark(b: &mut Bencher, &[width, height]: &[usize; 2]) { - let mut color = Buffer2d::new([width, height], 0); - let mut depth = Buffer2d::new([width, height], 1.0); - - let obj = tobj::load_obj(&Path::new("examples/data/teapot.obj"), false).unwrap(); - let indices = &obj.0[0].mesh.indices; - let positions = obj.0[0] - .mesh - .positions - .chunks(3) - .map(|sl| Vec3::from_slice(sl)) - .collect::>(); - let normals = obj.0[0] - .mesh - .normals - .chunks(3) - .map(|sl| Vec3::from_slice(sl)) - .collect::>(); - - let mvp = Mat4::perspective_rh_no(1.3, (width as f32) / (height as f32), 0.01, 100.0) - * Mat4::::scaling_3d(0.8) - * Mat4::rotation_x(0.002f32.sin() * 8.0) - * Mat4::rotation_y(0.004f32.cos() * 4.0) - * Mat4::rotation_z(0.008f32.sin() * 2.0); - - let shader = Teapot { - mvp, - positions: &positions, - normals: &normals, - light_dir: Vec3::new(1.0, 1.0, 1.0).normalized(), - }; + let [w, h] = [width, height]; + + let mut color = Buffer2d::fill([w, h], 0x0); + let mut depth = Buffer2d::fill([w, h], 1.0); + let mut shadow = Buffer2d::fill([512; 2], 1.0); + + let model = wavefront::Obj::from_reader(&include_bytes!("../examples/data/teapot.obj")[..]).unwrap(); + + let mut ori = Vec2::new(0.0, 0.0); + let mut dist = 6.0; + + // Position of objects in the scene + let teapot_pos = Vec3::new(0.0, 0.0, 0.0); + let light_pos = Vec3::::new(-8.0, 5.0, -5.0); + + // Set up the light matrix + let light_p = Mat4::perspective_fov_lh_zo(0.75, shadow.size()[0] as f32, shadow.size()[1] as f32, 0.1, 100.0); + let light_v = Mat4::look_at_lh(light_pos, -teapot_pos, Vec3::unit_y()); + let light_vp = light_p * light_v; + + // Set up the camera matrix + let p = Mat4::perspective_fov_lh_zo(1.3, w as f32, h as f32, 0.01, 100.0); + let v = Mat4::::identity() + * Mat4::translation_3d(Vec3::new(0.0, 0.0, dist)); + // Set up the teapot matrix + let m = Mat4::::translation_3d(-teapot_pos) + * Mat4::rotation_x(core::f32::consts::PI) + * Mat4::rotation_x(ori.x) + * Mat4::rotation_y(ori.y); b.iter(|| { - shader.draw::, _>(indices, &mut color, Some(&mut depth)); + // Clear the render targets ready for the next frame + color.clear(0x0); + depth.clear(1.0); + shadow.clear(1.0); + + // Shadow pass + TeapotShadow { mvp: light_vp * m, phantom: PhantomData }.render( + model.vertices(), + CullMode::None, + &mut Empty::default(), + &mut shadow, + ); + + // Colour pass + Teapot { m, v, p, light_pos, shadow: Clamped::new(Linear::new(&shadow)), light_vp: light_vp }.render( + model.vertices(), + CullMode::Back, + &mut color, + &mut depth, + ); + + black_box(&mut color); + black_box(&mut depth); }); } @@ -94,7 +164,7 @@ fn criterion_benchmark(c: &mut Criterion) { c.bench_function_over_inputs( "teapot", |b, &size| teapot_benchmark(b, size), - &[[32, 32], [200, 200]], //, [640, 480], [800, 600], [1024, 800]], + &[[32, 32], [640, 480], [1024, 800], [2048, 1600], [4096, 3200]], ); } diff --git a/src/buffer.rs b/src/buffer.rs index 0a075e4..2131e20 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -122,6 +122,11 @@ impl Target for Buffer { *(&*item).get() = texel; } + #[inline(always)] + unsafe fn write_unchecked(&mut self, index: [usize; 2], texel: Self::Texel) { + let idx = self.linear_index(index); + *self.items.get_unchecked_mut(idx) = texel; + } #[inline(always)] fn write(&mut self, index: [usize; 2], texel: Self::Texel) { diff --git a/src/pipeline.rs b/src/pipeline.rs index 18ba76d..25b1421 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -311,10 +311,10 @@ where let pixel = &*pixel; let depth = &*depth; - crossbeam_utils::thread::scope(|s| { + thread::scope(|s| { for _ in 0..threads { // TODO: Respawning them each time is dumb - s.spawn(move |_| { + s.spawn(move || { loop { let i = group_index.fetch_add(1, Ordering::Relaxed); if i >= groups { @@ -329,11 +329,12 @@ where let tgt_min = [0, row_start]; let tgt_max = [tgt_size[0], row_start + rows]; // Safety: we have exclusive access to our specific regions of `pixel` and `depth` + // TODO: Actually, unchecked_exclusive is UB, fix this unsafe { render_inner(pipeline, vertices.iter().cloned(), rasterizer_config.clone(), (tgt_min, tgt_max), tgt_size, pixel, depth) } } }); } - }).unwrap(); + }); } fn render_seq( diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index 80a0c89..b718e94 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -173,7 +173,8 @@ impl Rasterizer for Triangles { for x in row_range.x..row_range.y { // Calculate vertex weights to determine vs_out lerping and intersection let w_unbalanced = Vec3::new(w_hom.x, w_hom.y, w_hom.z - w_hom.x - w_hom.y); - let w = w_unbalanced / w_hom.z; + let w_hom_z_inv = w_hom.z.recip(); + let w = w_unbalanced * w_hom_z_inv; // Test the weights to determine whether the fragment is inside the triangle if w.map(|e| e >= 0.0).reduce_and() { @@ -188,7 +189,7 @@ impl Rasterizer for Triangles { // Calculate vertex weights to determine vs_out lerping and intersection let w_unbalanced = Vec3::new(w_hom.x, w_hom.y, w_hom.z - w_hom.x - w_hom.y); - let w = w_unbalanced / w_hom.z; + let w = w_unbalanced * w_hom_z_inv; V::weighted_sum(verts_out.as_slice(), w.as_slice()) }; From 2381bcafa349605a32d24af0be91c8b790ffcaf2 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 18 Oct 2022 15:55:06 +0100 Subject: [PATCH 20/37] Improved parallelism --- benches/teapot.rs | 2 +- src/pipeline.rs | 25 ++++++++++--------------- src/rasterizer/triangles.rs | 5 ++--- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/benches/teapot.rs b/benches/teapot.rs index 1fb866f..6f4ec03 100644 --- a/benches/teapot.rs +++ b/benches/teapot.rs @@ -164,7 +164,7 @@ fn criterion_benchmark(c: &mut Criterion) { c.bench_function_over_inputs( "teapot", |b, &size| teapot_benchmark(b, size), - &[[32, 32], [640, 480], [1024, 800], [2048, 1600], [4096, 3200]], + &[[1, 1], [32, 32], [640, 480], [1024, 800], [2048, 1600], [4096, 3200]], ); } diff --git a/src/pipeline.rs b/src/pipeline.rs index 25b1421..a60c5fe 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -300,34 +300,29 @@ where // TODO: Don't pull all vertices at once let vertices = fetch_vertex.collect::>(); let threads = num_cpus::get(); - assert!(tgt_size[1] >= threads); // TODO: Remove this limitation - let groups = threads * 8; - let rows_each = tgt_size[1] / groups; - let group_index = AtomicUsize::new(0); + let row = AtomicUsize::new(0); + let group_rows = 32; + let needed_threads = (tgt_size[1] / group_rows).min(threads); let vertices = &vertices; let rasterizer_config = &rasterizer_config; - let group_index = &group_index; let pixel = &*pixel; let depth = &*depth; thread::scope(|s| { - for _ in 0..threads { + for _ in 0..needed_threads { // TODO: Respawning them each time is dumb - s.spawn(move || { + s.spawn(|| { loop { - let i = group_index.fetch_add(1, Ordering::Relaxed); - if i >= groups { + let row_start = row.fetch_add(group_rows, Ordering::Relaxed); + let row_end = if row_start >= tgt_size[1] { break; - } - - let (row_start, rows) = if i == groups - 1 { - (i * rows_each, tgt_size[1] - (groups - 1) * rows_each) } else { - (i * rows_each, rows_each) + (row_start + group_rows).min(tgt_size[1]) }; + let tgt_min = [0, row_start]; - let tgt_max = [tgt_size[0], row_start + rows]; + let tgt_max = [tgt_size[0], row_end]; // Safety: we have exclusive access to our specific regions of `pixel` and `depth` // TODO: Actually, unchecked_exclusive is UB, fix this unsafe { render_inner(pipeline, vertices.iter().cloned(), rasterizer_config.clone(), (tgt_min, tgt_max), tgt_size, pixel, depth) } diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index b718e94..b333f7b 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -173,8 +173,7 @@ impl Rasterizer for Triangles { for x in row_range.x..row_range.y { // Calculate vertex weights to determine vs_out lerping and intersection let w_unbalanced = Vec3::new(w_hom.x, w_hom.y, w_hom.z - w_hom.x - w_hom.y); - let w_hom_z_inv = w_hom.z.recip(); - let w = w_unbalanced * w_hom_z_inv; + let w = w_unbalanced * w_hom.z.recip(); // Test the weights to determine whether the fragment is inside the triangle if w.map(|e| e >= 0.0).reduce_and() { @@ -189,7 +188,7 @@ impl Rasterizer for Triangles { // Calculate vertex weights to determine vs_out lerping and intersection let w_unbalanced = Vec3::new(w_hom.x, w_hom.y, w_hom.z - w_hom.x - w_hom.y); - let w = w_unbalanced * w_hom_z_inv; + let w = w_unbalanced * w_hom.z.recip(); V::weighted_sum(verts_out.as_slice(), w.as_slice()) }; From 9fb73b070f9988607efed66ef55505703370d250 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 18 Oct 2022 16:37:44 +0100 Subject: [PATCH 21/37] Don't generate MSAA buffer if we don't need one --- src/pipeline.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/pipeline.rs b/src/pipeline.rs index a60c5fe..5950d73 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -252,7 +252,7 @@ pub trait Pipeline: Sized { }, }; - // Produce an iterator over vertices (using the vertex shader and geometry shader to product them) + // Produce an iterator over vertices (using the vertex shader and geometry shader to produce them) let mut vert_outs = vertices.into_iter().map(|v| self.vertex_shader(v.borrow())).peekable(); let mut vert_out_queue = VecDeque::new(); let fetch_vertex = core::iter::from_fn(move || { @@ -397,7 +397,7 @@ where primitive_count: u64, msaa_level: usize, - msaa_buf: Buffer2d<(u64, Option)> + msaa_buf: Option)>>, } impl<'a, Pipe, P, D> BlitterImpl<'a, Pipe, P, D> @@ -410,6 +410,8 @@ where unsafe fn msaa_fragment Pipe::VertexData>(&mut self, pos: [usize; 2], mut get_v_data: F) -> Pipe::Fragment { // Safety: MSAA buffer will always be large enough let texel = self.msaa_buf + .as_mut() + .unwrap() .get_mut([pos[0] + 1, pos[1] + 1]); if texel.0 != self.primitive_count { texel.0 = self.primitive_count; @@ -490,7 +492,7 @@ where let msaa_level = match pipeline.aa_mode() { AaMode::None => 0, - AaMode::Msaa { level } => level.max(1).min(6) as usize, + AaMode::Msaa { level } => level.max(0).min(6) as usize, }; >::Rasterizer::default().rasterize( @@ -512,10 +514,14 @@ where primitive_count: 0, msaa_level, - msaa_buf: Buffer2d::fill_with( - [((tgt_max[0] - tgt_min[0]) >> msaa_level) + 3, ((tgt_max[1] - tgt_min[1]) >> msaa_level) + 3], - || (u64::MAX, None), - ), + msaa_buf: if msaa_level > 0 { + Some(Buffer2d::fill_with( + [((tgt_max[0] - tgt_min[0]) >> msaa_level) + 3, ((tgt_max[1] - tgt_min[1]) >> msaa_level) + 3], + || (u64::MAX, None), + )) + } else { + None + }, }, ); } From fb8876e9f68501a2e15c6df3d088c34a9c5607e7 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 19 Oct 2022 10:41:31 +0100 Subject: [PATCH 22/37] Simplified rasterizer state --- Cargo.toml | 4 +- benches/teapot.rs | 2 - examples/spinning_cube.rs | 3 +- examples/teapot.rs | 4 +- examples/texture_mapping.rs | 3 +- examples/triangle.rs | 25 +++--- examples/wireframes.rs | 162 ++++++++++++++++++++++++++++++++---- src/pipeline.rs | 25 +++--- src/rasterizer/mod.rs | 2 +- src/rasterizer/triangles.rs | 2 +- 10 files changed, 179 insertions(+), 53 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 801a5fa..0a6693c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ exclude = [ ] [dependencies] -vek = { version = "0.12", default-features = false, features = [] } +vek = { version = "0.15", default-features = false, features = [] } image_ = { package = "image", version = "0.23", optional = true } num_cpus = { version = "1.13", optional = true } fxhash = { version = "0.2", optional = true } @@ -35,7 +35,7 @@ minifb = "0.20" wavefront = "0.2" criterion = "0.3.3" image_ = { package = "image", version = "0.23" } -vek = "0.12.1" +vek = "0.15" derive_more = "0.99" [lib] diff --git a/benches/teapot.rs b/benches/teapot.rs index 6f4ec03..adc9909 100644 --- a/benches/teapot.rs +++ b/benches/teapot.rs @@ -142,7 +142,6 @@ fn teapot_benchmark(b: &mut Bencher, &[width, height]: &[usize; 2]) { // Shadow pass TeapotShadow { mvp: light_vp * m, phantom: PhantomData }.render( model.vertices(), - CullMode::None, &mut Empty::default(), &mut shadow, ); @@ -150,7 +149,6 @@ fn teapot_benchmark(b: &mut Bencher, &[width, height]: &[usize; 2]) { // Colour pass Teapot { m, v, p, light_pos, shadow: Clamped::new(Linear::new(&shadow)), light_vp: light_vp }.render( model.vertices(), - CullMode::Back, &mut color, &mut depth, ); diff --git a/examples/spinning_cube.rs b/examples/spinning_cube.rs index 232c73a..e2195e6 100644 --- a/examples/spinning_cube.rs +++ b/examples/spinning_cube.rs @@ -1,5 +1,5 @@ use vek::*; -use euc::{Pipeline, Buffer2d, Target, TriangleList, CullMode, IndexedVertices}; +use euc::{Pipeline, Buffer2d, Target, TriangleList, IndexedVertices}; use minifb::{Key, Window, WindowOptions}; struct Cube { @@ -75,7 +75,6 @@ fn main() { Cube { mvp }.render( IndexedVertices::new(INDICES, VERTICES), - CullMode::Back, &mut color, &mut depth, ); diff --git a/examples/teapot.rs b/examples/teapot.rs index d1ee390..1a5d4be 100644 --- a/examples/teapot.rs +++ b/examples/teapot.rs @@ -1,6 +1,6 @@ use vek::*; use derive_more::{Add, Mul}; -use euc::{Pipeline, Buffer2d, Target, PixelMode, DepthMode, TriangleList, CullMode, Empty, Linear, Texture, Sampler, AaMode, Unit, Clamped}; +use euc::{Pipeline, Buffer2d, Target, PixelMode, DepthMode, TriangleList, Empty, Linear, Texture, Sampler, AaMode, Unit, Clamped}; use minifb::{Key, MouseButton, MouseMode, Window, WindowOptions}; use std::marker::PhantomData; @@ -158,7 +158,6 @@ fn main() { // Shadow pass TeapotShadow { mvp: light_vp * m, phantom: PhantomData }.render( model.vertices(), - CullMode::None, &mut Empty::default(), &mut shadow, ); @@ -166,7 +165,6 @@ fn main() { // Colour pass Teapot { m, v, p, light_pos, shadow: Clamped::new(Linear::new(&shadow)), light_vp: light_vp }.render( model.vertices(), - CullMode::Back, &mut color, &mut depth, ); diff --git a/examples/texture_mapping.rs b/examples/texture_mapping.rs index e8651bc..f97b9d0 100644 --- a/examples/texture_mapping.rs +++ b/examples/texture_mapping.rs @@ -1,4 +1,4 @@ -use euc::{Buffer2d, Pipeline, Target, TriangleList, CullMode, Sampler, Nearest}; +use euc::{Buffer2d, Pipeline, Target, TriangleList, Sampler, Nearest}; use image_::RgbaImage; use vek::{Mat4, Vec2, Vec3, Vec4, Rgba}; use minifb::{Key, Window, WindowOptions}; @@ -145,7 +145,6 @@ fn main() { 16, 17, 19, 17, 18, 19, 20, 23, 21, 21, 23, 22, ], - CullMode::Back, &mut color, &mut depth, ); diff --git a/examples/triangle.rs b/examples/triangle.rs index 0dc553c..b24332a 100644 --- a/examples/triangle.rs +++ b/examples/triangle.rs @@ -1,24 +1,24 @@ -use euc::{Buffer2d, Pipeline, TriangleList, CullMode, Empty}; +use euc::{Buffer2d, Pipeline, TriangleList, Empty}; use minifb::{Key, Window, WindowOptions}; use vek::*; struct Triangle; impl Pipeline for Triangle { - type Vertex = [f32; 2]; - type VertexData = Vec2; + type Vertex = ([f32; 2], Rgba); + type VertexData = Rgba; type Primitives = TriangleList; - type Fragment = Vec2; + type Fragment = Rgba; type Pixel = u32; - fn vertex_shader(&self, pos: &[f32; 2]) -> ([f32; 4], Self::VertexData) { - ([pos[0], pos[1], 0.0, 1.0], Vec2::new(pos[0], pos[1])) + fn vertex_shader(&self, (pos, col): &Self::Vertex) -> ([f32; 4], Self::VertexData) { + ([pos[0], pos[1], 0.0, 1.0], *col) } - fn fragment_shader(&self, xy: Self::VertexData) -> Self::Fragment { xy } + fn fragment_shader(&self, col: Self::VertexData) -> Self::Fragment { col } - fn blend_shader(&self, _: Self::Pixel, xy: Self::Fragment) -> Self::Pixel { - u32::from_le_bytes([(xy.x * 255.0) as u8, (xy.y * 255.0) as u8, 255, 255]) + fn blend_shader(&self, _: Self::Pixel, col: Self::Fragment) -> Self::Pixel { + u32::from_le_bytes(col.map(|e| (e * 255.0) as u8).into_array()) } } fn main() { @@ -27,8 +27,11 @@ fn main() { let mut win = Window::new("Triangle", w, h, WindowOptions::default()).unwrap(); Triangle.render( - &[[-1.0, -1.0], [1.0, -1.0], [0.0, 1.0]], - CullMode::None, + &[ + ([-1.0, -1.0], Rgba::red()), + ([1.0, -1.0], Rgba::green()), + ([0.0, 1.0], Rgba::blue()), + ], &mut color, &mut Empty::default(), ); diff --git a/examples/wireframes.rs b/examples/wireframes.rs index fa03dfb..663b97f 100644 --- a/examples/wireframes.rs +++ b/examples/wireframes.rs @@ -1,3 +1,128 @@ +use vek::*; +use derive_more::{Add, Mul}; +use euc::{Pipeline, Buffer2d, Target, PixelMode, DepthMode, TriangleList, Empty, Linear, Texture, Sampler, AaMode, Unit, Clamped}; +use minifb::{Key, MouseButton, MouseMode, Window, WindowOptions}; +use std::marker::PhantomData; + +struct Teapot<'a> { + m: Mat4, + v: Mat4, + p: Mat4, + light_pos: Vec3, + phantom: PhantomData<&'a ()>, +} + +#[derive(Add, Mul, Clone)] +struct VertexData { + wpos: Vec3, + wnorm: Vec3, +} + +impl<'a> Pipeline for Teapot<'a> { + type Vertex = wavefront::Vertex<'a>; + type VertexData = VertexData; + type Primitives = TriangleList;//Lines; + type Fragment = Rgba; + type Pixel = u32; + + fn depth_mode(&self) -> DepthMode { DepthMode::LESS_WRITE } + fn aa_mode(&self) -> AaMode { AaMode::Msaa { level: 1 } } + + #[inline(always)] + fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { + let wpos = self.m * Vec4::from_point(Vec3::from(vertex.position())); + let wnorm = self.m * Vec4::from_direction(-Vec3::from(vertex.normal().unwrap())); + + ( + (self.p * self.v * wpos).into_array(), + VertexData { wpos: wpos.xyz(), wnorm: wnorm.xyz() }, + ) + } + + #[inline(always)] + fn fragment_shader(&self, VertexData { wpos, wnorm }: Self::VertexData) -> Self::Fragment { + Rgba::red() + } + + #[inline(always)] + fn blend_shader(&self, _old: Self::Pixel, rgba: Self::Fragment) -> Self::Pixel { + let rgba = rgba.map(|e| e.clamped(0.0, 1.0) * 255.0).as_(); + // The window's framebuffer uses BGRA format + let bgra = Rgba::new(rgba.b, rgba.g, rgba.r, rgba.a); + u32::from_le_bytes(bgra.into_array()) + } +} + +fn main() { + let [w, h] = [1280, 960]; + + let mut color = Buffer2d::fill([w, h], 0x0); + let mut depth = Buffer2d::fill([w, h], 1.0); + + let model = wavefront::Obj::from_file("examples/data/teapot.obj").unwrap(); + + let mut win = Window::new("Teapot", w, h, WindowOptions::default()).unwrap(); + + let mut ori = Vec2::new(0.0, 0.0); + let mut dist = 6.0; + let mut old_mouse_pos = (0.0, 0.0); + + let mut i = 0; + while win.is_open() && !win.is_key_down(Key::Escape) { + let start_time = std::time::Instant::now(); + + // Clear the render targets ready for the next frame + color.clear(0x0); + depth.clear(1.0); + + // Update camera as the mouse moves + let mouse_pos = win.get_mouse_pos(MouseMode::Pass).unwrap_or_default(); + if win.get_mouse_down(MouseButton::Left) { + ori.x -= (mouse_pos.1 - old_mouse_pos.1) * 0.003; + ori.y += (mouse_pos.0 - old_mouse_pos.0) * 0.003; + } + if win.get_mouse_down(MouseButton::Right) { + dist = (dist + (mouse_pos.1 - old_mouse_pos.1) as f32 * 0.01).max(1.0).min(20.0); + } + old_mouse_pos = mouse_pos; + + // Position of objects in the scene + let teapot_pos = Vec3::new(0.0, 0.0, 0.0); + let light_pos = Vec3::::new(-8.0, 5.0, -5.0); + + // Set up the camera matrix + let p = Mat4::perspective_fov_lh_zo(1.3, w as f32, h as f32, 0.01, 100.0); + let v = Mat4::::identity() + * Mat4::translation_3d(Vec3::new(0.0, 0.0, dist)); + // Set up the teapot matrix + let m = Mat4::::translation_3d(-teapot_pos) + * Mat4::rotation_x(core::f32::consts::PI) + * Mat4::rotation_x(ori.x) + * Mat4::rotation_y(ori.y); + + // Colour pass + Teapot { m, v, p, light_pos, phantom: PhantomData }.render( + model.vertices(), + &mut color, + &mut depth, + ); + + win.update_with_buffer(color.raw(), w, h).unwrap(); + + if i % 60 == 0 { + let elapsed = start_time.elapsed(); + win.set_title(&format!("Teapot (Time = {:?}, FPS = {})", elapsed, 1.0 / elapsed.as_secs_f32())); + } + i += 1; + } +} + + + + + + +/* use euc::{buffer::Buffer2d, rasterizer, Pipeline, Target}; use std::path::Path; use vek::*; @@ -11,11 +136,11 @@ struct Teapot<'a> { impl<'a> Pipeline for Teapot<'a> { type Vertex = u32; // Vertex index - type VsOut = Vec3; // Normal + type VertexData = Vec3; // Normal + type Fragment = u32; // BGRA type Pixel = u32; // BGRA - #[inline(always)] - fn vert(&self, v_index: &Self::Vertex) -> ([f32; 4], Self::VsOut) { + fn vertex_shader(&self, v_index: &Self::Vertex) -> ([f32; 4], Self::VsOut) { let v_index = *v_index as usize; // Find vertex position ( @@ -26,8 +151,7 @@ impl<'a> Pipeline for Teapot<'a> { ) } - #[inline(always)] - fn frag(&self, norm: &Self::VsOut) -> Self::Pixel { + fn fragment_shader(&self, norm: Self::VertexData) -> Self::Pixel { let ambient = 0.2; let diffuse = norm.dot(self.light_dir).max(0.0) * 0.5; let specular = self @@ -55,11 +179,12 @@ struct Wireframe<'a> { impl<'a> Pipeline for Wireframe<'a> { type Vertex = u32; // Vertex index - type VsOut = (); + type VertexData = (); + type Fragment = u32; // BGRA type Pixel = u32; // BGRA #[inline] - fn vert(&self, v_index: &Self::Vertex) -> ([f32; 4], Self::VsOut) { + fn vertex_shader(&self, v_index: &Self::Vertex) -> ([f32; 4], Self::VsOut) { let v_index = *v_index as usize; // Offset position to avoid z fighting let offset = 0.002 * self.normals[v_index]; @@ -68,7 +193,7 @@ impl<'a> Pipeline for Wireframe<'a> { } #[inline] - fn frag(&self, _: &Self::VsOut) -> Self::Pixel { + fn fragment_shader(&self, _: Self::VertexData) -> Self::Pixel { 120 } } @@ -82,21 +207,21 @@ fn main() { let mut win = minifb::Window::new("Teapot", W, H, minifb::WindowOptions::default()).unwrap(); - let obj = tobj::load_obj(&Path::new("examples/data/teapot.obj"), false).unwrap(); - let indices = &obj.0[0].mesh.indices; - let wf_indices: Vec<_> = indices + let model = wavefront::Obj::from_file("examples/data/teapot.obj").unwrap(); + let wf_indices: Vec<_> = model + .triangles() .chunks(3) - .flat_map(|sl| vec![sl[0], sl[1], sl[1], sl[2], sl[2], sl[0]].into_iter()) + .flat_map(|sl| [sl[0], sl[1], sl[1], sl[2], sl[2], sl[0]] + .map(|v| v.position_index()) + .into_iter()) .collect(); - let positions = obj.0[0] - .mesh - .positions + let positions = model + .positions() .chunks(3) .map(|sl| Vec4::from_point(Vec3::from_slice(sl) + Vec3::new(0.0, -0.5, 0.0))) .collect::>(); - let normals = obj.0[0] - .mesh - .normals + let normals = model + .normals() .chunks(3) .map(|sl| Vec3::from_slice(sl)) .collect::>(); @@ -133,3 +258,4 @@ fn main() { } } } +*/ diff --git a/src/pipeline.rs b/src/pipeline.rs index 5950d73..72a80ad 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -192,6 +192,11 @@ pub trait Pipeline: Sized { #[inline(always)] fn aa_mode(&self) -> AaMode { AaMode::None } + /// Returns the rasterizer configuration (usually [`CullMode`], when using [`Triangles`]) of this pipeline. + fn rasterizer_config(&self) -> <>::Rasterizer as Rasterizer>::Config { + Default::default() + } + /// Transforms a [`Pipeline::Vertex`] into homogeneous NDCs (Normalised Device Coordinates) for the vertex and a /// [`Pipeline::VertexData`] to be interpolated and passed to the fragment shader. /// @@ -229,7 +234,6 @@ pub trait Pipeline: Sized { fn render( &self, vertices: S, - rasterizer_config: <>::Rasterizer as Rasterizer>::Config, pixel: &mut P, depth: &mut D, ) @@ -272,9 +276,9 @@ pub trait Pipeline: Sized { }); #[cfg(not(feature = "par"))] - let r = render_seq(self, fetch_vertex, rasterizer_config, target_size, pixel, depth); + let r = render_seq(self, fetch_vertex, target_size, pixel, depth); #[cfg(feature = "par")] - let r = render_par(self, fetch_vertex, rasterizer_config, target_size, pixel, depth); + let r = render_par(self, fetch_vertex, target_size, pixel, depth); r } } @@ -283,7 +287,6 @@ pub trait Pipeline: Sized { fn render_par( pipeline: &Pipe, fetch_vertex: S, - rasterizer_config: <>::Rasterizer as Rasterizer>::Config, tgt_size: [usize; 2], pixel: &mut P, depth: &mut D, @@ -301,11 +304,12 @@ where let vertices = fetch_vertex.collect::>(); let threads = num_cpus::get(); let row = AtomicUsize::new(0); - let group_rows = 32; + + const FRAGMENTS_PER_GROUP: usize = 40960; // Magic number, maybe make this configurable? + let group_rows = FRAGMENTS_PER_GROUP / tgt_size[0].max(1); let needed_threads = (tgt_size[1] / group_rows).min(threads); let vertices = &vertices; - let rasterizer_config = &rasterizer_config; let pixel = &*pixel; let depth = &*depth; @@ -325,7 +329,7 @@ where let tgt_max = [tgt_size[0], row_end]; // Safety: we have exclusive access to our specific regions of `pixel` and `depth` // TODO: Actually, unchecked_exclusive is UB, fix this - unsafe { render_inner(pipeline, vertices.iter().cloned(), rasterizer_config.clone(), (tgt_min, tgt_max), tgt_size, pixel, depth) } + unsafe { render_inner(pipeline, vertices.iter().cloned(), (tgt_min, tgt_max), tgt_size, pixel, depth) } } }); } @@ -335,7 +339,6 @@ where fn render_seq( pipeline: &Pipe, fetch_vertex: S, - rasterizer_config: <>::Rasterizer as Rasterizer>::Config, tgt_size: [usize; 2], pixel: &mut P, depth: &mut D, @@ -347,13 +350,13 @@ where D: Target + Send + Sync, { // Safety: we have exclusive access to `pixel` and `depth` - unsafe { render_inner(pipeline, fetch_vertex, rasterizer_config, ([0; 2], tgt_size), tgt_size, pixel, depth) } + // TODO: Actually, unchecked_exclusive is UB, fix this + unsafe { render_inner(pipeline, fetch_vertex, ([0; 2], tgt_size), tgt_size, pixel, depth) } } unsafe fn render_inner( pipeline: &Pipe, fetch_vertex: S, - rasterizer_config: <>::Rasterizer as Rasterizer>::Config, (tgt_min, tgt_max): ([usize; 2], [usize; 2]), tgt_size: [usize; 2], pixel: &P, @@ -499,7 +502,7 @@ where fetch_vertex, principal_x, pipeline.coordinate_mode(), - rasterizer_config, + pipeline.rasterizer_config(), BlitterImpl { write_pixels, depth_mode, diff --git a/src/rasterizer/mod.rs b/src/rasterizer/mod.rs index af91e97..1c7cbc8 100644 --- a/src/rasterizer/mod.rs +++ b/src/rasterizer/mod.rs @@ -51,7 +51,7 @@ pub trait Blitter: Sized { /// Rasterizers take an iterator of vertices and emit fragment positions. They do not, by themselves, perform shader /// execution, depth testing, etc. pub trait Rasterizer: Default { - type Config: Clone + Send + Sync; + type Config: Default + Send + Sync; /// Rasterize the given vertices into fragments. /// diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index b333f7b..f805d33 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -96,7 +96,7 @@ impl Rasterizer for Triangles { }; // Ensure we didn't accidentally end up with infinities or NaNs - debug_assert!(coords_to_weights.into_row_array().iter().all(|e| e.is_finite())); + assert!(coords_to_weights.into_row_array().iter().all(|e| e.is_finite())); // Convert vertex coordinates to screen space let verts_screen = verts_euc.map(|euc| size * (euc.xy() * Vec2::new(0.5, -0.5) + 0.5)); From a401056abb3391dddc2db4a26efbd84517120daa Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 21 Oct 2022 12:00:22 +0100 Subject: [PATCH 23/37] Improved sampler API --- Cargo.toml | 2 +- benches/teapot.rs | 4 +-- examples/teapot.rs | 2 +- src/lib.rs | 2 +- src/pipeline.rs | 3 +- src/rasterizer/mod.rs | 3 +- src/sampler/linear.rs | 9 +---- src/sampler/mod.rs | 74 ++++++++++++++++++++++++++++++++++++++---- src/sampler/nearest.rs | 12 +++---- src/texture.rs | 14 ++++++++ 10 files changed, 94 insertions(+), 31 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0a6693c..8278071 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ default = ["std", "image", "par"] std = ["vek/std"] libm = ["vek/libm"] nightly = [] -simd = ["vek/repr_simd"] +simd = ["vek/repr_simd", "vek/platform_intrinsics"] image = ["image_"] par = ["std", "num_cpus", "fxhash"] micromath = ["micromath_"] diff --git a/benches/teapot.rs b/benches/teapot.rs index adc9909..ff89ee8 100644 --- a/benches/teapot.rs +++ b/benches/teapot.rs @@ -147,7 +147,7 @@ fn teapot_benchmark(b: &mut Bencher, &[width, height]: &[usize; 2]) { ); // Colour pass - Teapot { m, v, p, light_pos, shadow: Clamped::new(Linear::new(&shadow)), light_vp: light_vp }.render( + Teapot { m, v, p, light_pos, shadow: (&shadow).linear().clamped(), light_vp: light_vp }.render( model.vertices(), &mut color, &mut depth, @@ -169,7 +169,7 @@ fn criterion_benchmark(c: &mut Criterion) { criterion_group! { name = benches; config = Criterion::default() - .sample_size(10) + .sample_size(32) .warm_up_time(Duration::from_millis(1000)); targets = criterion_benchmark } diff --git a/examples/teapot.rs b/examples/teapot.rs index 1a5d4be..8dae437 100644 --- a/examples/teapot.rs +++ b/examples/teapot.rs @@ -163,7 +163,7 @@ fn main() { ); // Colour pass - Teapot { m, v, p, light_pos, shadow: Clamped::new(Linear::new(&shadow)), light_vp: light_vp }.render( + Teapot { m, v, p, light_pos, shadow: (&shadow).linear().clamped(), light_vp: light_vp }.render( model.vertices(), &mut color, &mut depth, diff --git a/src/lib.rs b/src/lib.rs index 008a6b2..3e018c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,7 @@ #![no_std] -#![feature(array_map, type_alias_impl_trait)] +#![feature(type_alias_impl_trait)] extern crate alloc; diff --git a/src/pipeline.rs b/src/pipeline.rs index 72a80ad..9b7dc80 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -4,12 +4,11 @@ use crate::{ primitives::PrimitiveKind, math::WeightedSum, buffer::Buffer2d, - sampler::Linear, }; use alloc::{vec::Vec, collections::VecDeque}; use core::{ cmp::Ordering, - ops::{Add, Mul, Range}, + ops::Range, borrow::Borrow, marker::PhantomData, }; diff --git a/src/rasterizer/mod.rs b/src/rasterizer/mod.rs index 1c7cbc8..b7dd181 100644 --- a/src/rasterizer/mod.rs +++ b/src/rasterizer/mod.rs @@ -2,8 +2,7 @@ pub mod triangles; pub use self::triangles::Triangles; -use crate::{CoordinateMode, Pipeline, math::WeightedSum}; -use core::ops::{Mul, Add}; +use crate::{CoordinateMode, math::WeightedSum}; /// The face culling strategy used during rendering. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] diff --git a/src/sampler/linear.rs b/src/sampler/linear.rs index 7ef0a35..14c2255 100644 --- a/src/sampler/linear.rs +++ b/src/sampler/linear.rs @@ -8,14 +8,7 @@ use core::{ use micromath_::F32Ext; /// A sampler that uses nearest-neighbor sampling. -pub struct Linear(T, PhantomData); - -impl Linear { - /// Create a new - pub fn new(texture: T) -> Self { - Self(texture, PhantomData) - } -} +pub struct Linear(pub(crate) T, pub(crate) PhantomData); impl<'a, T> Sampler<2> for Linear where diff --git a/src/sampler/mod.rs b/src/sampler/mod.rs index f429afc..3ca4717 100644 --- a/src/sampler/mod.rs +++ b/src/sampler/mod.rs @@ -50,18 +50,27 @@ pub trait Sampler { unsafe fn sample_unchecked(&self, index: [Self::Index; N]) -> Self::Sample { self.sample(index) } + + /// Create a version of this sampler that clamps the index to the bounds of the sampler. + /// + /// See [`Clamped`]. + fn clamped(self) -> Clamped where Self: Sized { Clamped(self) } + + /// Create a version of this sampler that repeats the sampler when sampled out of bounds. + /// + /// See [`Tiled`]. + fn tiled(self) -> Tiled where Self: Sized { Tiled(self) } + + /// Create a version of this sampler that repeats the sampler when sampled out of bounds, mirroring it at each edge. + /// + /// See [`Tiled`]. + fn mirrored(self) -> Mirrored where Self: Sized { Mirrored(self) } } /// A sampler that clamps the index's components to the 0.0 <= x <= 1.0 range. #[derive(Copy, Clone)] pub struct Clamped(S); -impl Clamped { - pub fn new(sampler: S) -> Self { - Self(sampler) - } -} - impl, const N: usize> Sampler for Clamped { type Index = S::Index; type Sample = S::Sample; @@ -77,3 +86,56 @@ impl, const N: usize> Sampler for Clamped { self.0.sample_unchecked(index) } } + +/// A sampler that tiles the index's components, repeating the sampler when sampling out-of-bounds. +/// +/// See [`Sampler::tiled`]. +#[derive(Copy, Clone)] +pub struct Tiled(S); + +impl, const N: usize> Sampler for Tiled { + type Index = S::Index; + type Sample = S::Sample; + type Texture = S::Texture; + + fn raw_texture(&self) -> &Self::Texture { self.0.raw_texture() } + fn sample(&self, index: [Self::Index; N]) -> Self::Sample { + let index = index.map(|e| e.rem_euclid(1.0)); + self.0.sample(index) + } + unsafe fn sample_unchecked(&self, index: [Self::Index; N]) -> Self::Sample { + let index = index.map(|e| e.rem_euclid(1.0)); + self.0.sample_unchecked(index) + } +} + +/// A sampler that tiles the index's components, repeating the sampler when sampling out-of-bounds, but mirroring the +/// sampler along each edge such that the texture is seamless. +/// +/// See [`Sampler::mirrored`]. +#[derive(Copy, Clone)] +pub struct Mirrored(S); + +impl, const N: usize> Sampler for Mirrored { + type Index = S::Index; + type Sample = S::Sample; + type Texture = S::Texture; + + fn raw_texture(&self) -> &Self::Texture { self.0.raw_texture() } + fn sample(&self, index: [Self::Index; N]) -> Self::Sample { + let index = index.map(|e| if e.rem_euclid(2.0) >= 1.0 { + 1.0 - e.rem_euclid(1.0) + } else { + e.rem_euclid(1.0) + }); + self.0.sample(index) + } + unsafe fn sample_unchecked(&self, index: [Self::Index; N]) -> Self::Sample { + let index = index.map(|e| if e.rem_euclid(2.0) >= 1.0 { + 1.0 - e.rem_euclid(1.0) + } else { + e.rem_euclid(1.0) + }); + self.0.sample_unchecked(index) + } +} diff --git a/src/sampler/nearest.rs b/src/sampler/nearest.rs index 83d19f5..4189fdd 100644 --- a/src/sampler/nearest.rs +++ b/src/sampler/nearest.rs @@ -5,13 +5,9 @@ use core::{ }; /// A sampler that uses nearest-neighbor sampling. -pub struct Nearest(T, PhantomData); - -impl Nearest { - /// Create a new - pub fn new(texture: T) -> Self { - Self(texture, PhantomData) - } +pub struct Nearest { + pub(crate) texture: T, + pub(crate) phantom: PhantomData, } impl<'a, T, I, const N: usize> Sampler for Nearest @@ -26,7 +22,7 @@ where type Texture = T; #[inline(always)] - fn raw_texture(&self) -> &Self::Texture { &self.0 } + fn raw_texture(&self) -> &Self::Texture { &self.texture } #[inline(always)] fn sample(&self, index: [Self::Index; N]) -> Self::Sample { diff --git a/src/texture.rs b/src/texture.rs index e91ee25..2196376 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -1,4 +1,5 @@ use core::marker::PhantomData; +use super::sampler::{Linear, Nearest}; /// A trait implemented by types that may be treated as textures. pub trait Texture { @@ -44,6 +45,19 @@ pub trait Texture { unsafe fn read_unchecked(&self, index: [Self::Index; N]) -> Self::Texel { self.read(index) } + + /// Create a linearly (or bilinear/trilinear, if the texture is 2D/3D) interpolated (i.e: filtered) sampler from + /// this texture. + /// + /// See [`Linear`]. + fn linear(self) -> Linear where Self: Sized { Linear(self, PhantomData) } + + /// Create a nearest-neighbour (i.e: unfiltered) sampler from this texture. + /// + /// See [`Nearest`]. + fn nearest(self) -> Nearest where Self: Sized { + Nearest { texture: self, phantom: PhantomData } + } } impl<'a, T: Texture, const N: usize> Texture for &'a T { From 05c666f6f8dcb63cf8f34747df6c1d40e9b6c777 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 21 Oct 2022 17:54:26 +0100 Subject: [PATCH 24/37] Added Tiled and Mirrored samplers --- examples/texture_mapping.rs | 4 ++-- src/lib.rs | 2 +- src/pipeline.rs | 2 +- src/rasterizer/triangles.rs | 2 +- src/sampler/linear.rs | 2 +- src/sampler/mod.rs | 10 ++++++++++ 6 files changed, 16 insertions(+), 6 deletions(-) diff --git a/examples/texture_mapping.rs b/examples/texture_mapping.rs index f97b9d0..939bdf3 100644 --- a/examples/texture_mapping.rs +++ b/examples/texture_mapping.rs @@ -1,4 +1,4 @@ -use euc::{Buffer2d, Pipeline, Target, TriangleList, Sampler, Nearest}; +use euc::{Buffer2d, Pipeline, Target, TriangleList, Sampler, Nearest, Texture}; use image_::RgbaImage; use vek::{Mat4, Vec2, Vec3, Vec4, Rgba}; use minifb::{Key, Window, WindowOptions}; @@ -113,7 +113,7 @@ fn main() { return; } }; - let sampler = Nearest::new(texture); + let sampler = texture.nearest(); let mut win = Window::new("Texture Mapping", w, h, WindowOptions::default()).unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 3e018c8..94fb68b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,7 +71,7 @@ pub use crate::{ primitives::TriangleList, texture::{Texture, Target, Empty}, rasterizer::CullMode, - sampler::{Sampler, Nearest, Linear, Clamped}, + sampler::{Sampler, Nearest, Linear, Clamped, Tiled, Mirrored}, index::IndexedVertices, math::Unit, }; diff --git a/src/pipeline.rs b/src/pipeline.rs index 9b7dc80..2582e34 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -10,7 +10,6 @@ use core::{ cmp::Ordering, ops::Range, borrow::Borrow, - marker::PhantomData, }; #[cfg(feature = "micromath")] @@ -335,6 +334,7 @@ where }); } +#[cfg(not(feature = "par"))] fn render_seq( pipeline: &Pipe, fetch_vertex: S, diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index f805d33..5730193 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -15,7 +15,7 @@ impl Rasterizer for Triangles { unsafe fn rasterize( &self, mut vertices: I, - principal_x: bool, + _principal_x: bool, coordinate_mode: CoordinateMode, cull_mode: CullMode, mut blitter: B, diff --git a/src/sampler/linear.rs b/src/sampler/linear.rs index 14c2255..65ac21b 100644 --- a/src/sampler/linear.rs +++ b/src/sampler/linear.rs @@ -25,7 +25,7 @@ where fn raw_texture(&self) -> &Self::Texture { &self.0 } #[inline(always)] - fn sample(&self, mut index: [Self::Index; 2]) -> Self::Sample { + fn sample(&self, index: [Self::Index; 2]) -> Self::Sample { assert!(index[0] <= 1.0, "{:?}", index); assert!(index[1] <= 1.0, "{:?}", index); diff --git a/src/sampler/mod.rs b/src/sampler/mod.rs index 3ca4717..57a2952 100644 --- a/src/sampler/mod.rs +++ b/src/sampler/mod.rs @@ -67,6 +67,16 @@ pub trait Sampler { fn mirrored(self) -> Mirrored where Self: Sized { Mirrored(self) } } +impl<'a, S: Sampler, const N: usize> Sampler for &'a S { + type Index = S::Index; + type Sample = S::Sample; + type Texture = S::Texture; + + fn raw_texture(&self) -> &Self::Texture { (*self).raw_texture() } + fn sample(&self, index: [Self::Index; N]) -> Self::Sample { (*self).sample(index) } + unsafe fn sample_unchecked(&self, index: [Self::Index; N]) -> Self::Sample { (*self).sample_unchecked(index) } +} + /// A sampler that clamps the index's components to the 0.0 <= x <= 1.0 range. #[derive(Copy, Clone)] pub struct Clamped(S); From 1e104e198f86221139e65a81054f9308d67811e0 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 21 Oct 2022 18:34:23 +0100 Subject: [PATCH 25/37] Improved teapot example --- examples/teapot.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/examples/teapot.rs b/examples/teapot.rs index 8dae437..5de627f 100644 --- a/examples/teapot.rs +++ b/examples/teapot.rs @@ -1,6 +1,6 @@ use vek::*; use derive_more::{Add, Mul}; -use euc::{Pipeline, Buffer2d, Target, PixelMode, DepthMode, TriangleList, Empty, Linear, Texture, Sampler, AaMode, Unit, Clamped}; +use euc::{Pipeline, Buffer2d, Target, PixelMode, DepthMode, TriangleList, Empty, Linear, Texture, Sampler, AaMode, Unit, Clamped, CullMode}; use minifb::{Key, MouseButton, MouseMode, Window, WindowOptions}; use std::marker::PhantomData; @@ -18,6 +18,7 @@ impl<'a> Pipeline for TeapotShadow<'a> { fn pixel_mode(&self) -> PixelMode { PixelMode::PASS } fn depth_mode(&self) -> DepthMode { DepthMode::LESS_WRITE } + fn rasterizer_config(&self) -> CullMode { CullMode::None } #[inline(always)] fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { @@ -112,11 +113,12 @@ fn main() { let mut win = Window::new("Teapot", w, h, WindowOptions::default()).unwrap(); - let mut ori = Vec2::new(0.0, 0.0); - let mut dist = 6.0; + let mut ori = Vec2::new(-0.55, -0.25); + let mut dist = 4.5; let mut old_mouse_pos = (0.0, 0.0); let mut i = 0; + let init = std::time::Instant::now(); while win.is_open() && !win.is_key_down(Key::Escape) { let start_time = std::time::Instant::now(); @@ -128,8 +130,7 @@ fn main() { // Update camera as the mouse moves let mouse_pos = win.get_mouse_pos(MouseMode::Pass).unwrap_or_default(); if win.get_mouse_down(MouseButton::Left) { - ori.x -= (mouse_pos.1 - old_mouse_pos.1) * 0.003; - ori.y += (mouse_pos.0 - old_mouse_pos.0) * 0.003; + ori -= Vec2::new(mouse_pos.1 - old_mouse_pos.1, mouse_pos.0 - old_mouse_pos.0) * 0.003; } if win.get_mouse_down(MouseButton::Right) { dist = (dist + (mouse_pos.1 - old_mouse_pos.1) as f32 * 0.01).max(1.0).min(20.0); @@ -138,7 +139,8 @@ fn main() { // Position of objects in the scene let teapot_pos = Vec3::new(0.0, 0.0, 0.0); - let light_pos = Vec3::::new(-8.0, 5.0, -5.0); + let angle = init.elapsed().as_secs_f32(); + let light_pos = Vec3::new(angle.sin() * 8.0, 10.0, angle.cos() * 8.0); // Set up the light matrix let light_p = Mat4::perspective_fov_lh_zo(0.75, shadow.size()[0] as f32, shadow.size()[1] as f32, 0.1, 100.0); @@ -148,12 +150,12 @@ fn main() { // Set up the camera matrix let p = Mat4::perspective_fov_lh_zo(1.3, w as f32, h as f32, 0.01, 100.0); let v = Mat4::::identity() - * Mat4::translation_3d(Vec3::new(0.0, 0.0, dist)); - // Set up the teapot matrix - let m = Mat4::::translation_3d(-teapot_pos) - * Mat4::rotation_x(core::f32::consts::PI) + * Mat4::translation_3d(Vec3::new(0.0, 0.0, dist)) * Mat4::rotation_x(ori.x) * Mat4::rotation_y(ori.y); + // Set up the teapot matrix + let m = Mat4::::translation_3d(-teapot_pos) + * Mat4::rotation_x(core::f32::consts::PI); // Shadow pass TeapotShadow { mvp: light_vp * m, phantom: PhantomData }.render( From 3ae275ae0e0235c1d1332befed2b1ff1d36f4dc4 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 21 Oct 2022 21:01:02 +0100 Subject: [PATCH 26/37] API cleanups --- README.md | 36 +++++++++++++++++++---------- benches/teapot.rs | 6 ++--- examples/spinning_cube.rs | 6 ++--- examples/teapot.rs | 15 ++++++------ examples/texture_mapping.rs | 6 ++--- examples/triangle.rs | 6 ++--- examples/wireframes.rs | 6 ++--- src/pipeline.rs | 46 ++++++++++++++++++------------------- 8 files changed, 69 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index cce5f85..36735f8 100644 --- a/README.md +++ b/README.md @@ -10,30 +10,42 @@ ## Example ```rust +// A type that specifies a rendering pipeline. +// You can add fields to this type, like uniforms in traditional GPU shader programs. struct Triangle; impl Pipeline for Triangle { - type Vertex = [f32; 2]; - type VertexData = (); - type Pixel = [u8; 4]; + type Vertex = [f32; 2]; // Each vertex has an x and y component + type VertexData = Unit; // No data is passed from the vertex shader to the fragment shader + type Primitives = TriangleList; // Our vertices come in the form of a list of triangles + type Fragment = [u8; 3]; // Each fragment is 3 bytes: red, green, and blue + type Pixel = [u8; 3]; // Color buffer pixels have the same format as fragments. + + // Vertex shader (determines the screen-space position of each vertex) + fn vertex(&self, pos: &Self::Vertex) -> ([f32; 4], Self::VertexData) { + ([pos[0], pos[1], 0.0, 1.0], Unit) + } - // Vertex shader - fn vert(&self, pos: &Self::Vertex) -> ([f32; 4], Self::VertexData) { - ([pos[0], pos[1], 0.0, 1.0], ()) + // Fragment shader (determines the colour of each triangle fragment) + fn fragment(&self, _: Self::VertexData) -> Self::Fragment { + [255, 0, 0] // Paint the triangle red } - // Fragment shader - fn frag(&self, _: Self::VertexData) -> Self::Pixel { - [255, 0, 0, 255] // Red + // Blend shader (determines how to blend new fragments into the existing colour buffer) + fn blend(&self, _: Self::Pixel, col: Self::Fragment) -> Self::Pixel { + col // Just replace the color buffer's previous pixel } } -let mut color = Buffer2d::new([640, 480], [0; 4]); +// Create a new color buffer to render to +let mut color = Buffer2d::new([640, 480], [0; 3]); Triangle.render( - &[[-1.0, -1.0], [ 1.0, -1.0], [ 0.0, 1.0]], - CullMode::Back, + // Just render a single triangle to the buffer + &[[-1.0, -1.0], [1.0, -1.0], [0.0, 1.0]], + // Specify the color buffer to render to &mut color, + // We have no need for a depth buffer, so use `Empty` as a substitute &mut Empty::default(), ); ``` diff --git a/benches/teapot.rs b/benches/teapot.rs index ff89ee8..18d2598 100644 --- a/benches/teapot.rs +++ b/benches/teapot.rs @@ -59,7 +59,7 @@ impl<'a> Pipeline for Teapot<'a> { fn aa_mode(&self) -> AaMode { AaMode::Msaa { level: 1 } } #[inline(always)] - fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { + fn vertex(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { let wpos = self.m * Vec4::from_point(Vec3::from(vertex.position())); let wnorm = self.m * Vec4::from_direction(-Vec3::from(vertex.normal().unwrap())); @@ -72,7 +72,7 @@ impl<'a> Pipeline for Teapot<'a> { } #[inline(always)] - fn fragment_shader(&self, VertexData { wpos, wnorm, light_view_pos }: Self::VertexData) -> Self::Fragment { + fn fragment(&self, VertexData { wpos, wnorm, light_view_pos }: Self::VertexData) -> Self::Fragment { let wnorm = wnorm.normalized(); let cam_pos = Vec3::zero(); let cam_dir = (wpos - cam_pos).normalized(); @@ -94,7 +94,7 @@ impl<'a> Pipeline for Teapot<'a> { } #[inline(always)] - fn blend_shader(&self, _old: Self::Pixel, rgba: Self::Fragment) -> Self::Pixel { + fn blend(&self, _old: Self::Pixel, rgba: Self::Fragment) -> Self::Pixel { let rgba = rgba.map(|e| e.clamped(0.0, 1.0) * 255.0).as_(); // The window's framebuffer uses BGRA format let bgra = Rgba::new(rgba.b, rgba.g, rgba.r, rgba.a); diff --git a/examples/spinning_cube.rs b/examples/spinning_cube.rs index e2195e6..72c29fb 100644 --- a/examples/spinning_cube.rs +++ b/examples/spinning_cube.rs @@ -14,16 +14,16 @@ impl Pipeline for Cube { type Fragment = Rgba; #[inline(always)] - fn vertex_shader(&self, (pos, color): &Self::Vertex) -> ([f32; 4], Self::VertexData) { + fn vertex(&self, (pos, color): &Self::Vertex) -> ([f32; 4], Self::VertexData) { ((self.mvp * *pos).into_array(), *color) } #[inline(always)] - fn fragment_shader(&self, color: Self::VertexData) -> Self::Fragment { + fn fragment(&self, color: Self::VertexData) -> Self::Fragment { color } - fn blend_shader(&self, _: Self::Pixel, color: Self::Fragment) -> Self::Pixel { + fn blend(&self, _: Self::Pixel, color: Self::Fragment) -> Self::Pixel { u32::from_le_bytes((color * 255.0).as_().into_array()) } } diff --git a/examples/teapot.rs b/examples/teapot.rs index 5de627f..6d4a538 100644 --- a/examples/teapot.rs +++ b/examples/teapot.rs @@ -1,6 +1,6 @@ use vek::*; use derive_more::{Add, Mul}; -use euc::{Pipeline, Buffer2d, Target, PixelMode, DepthMode, TriangleList, Empty, Linear, Texture, Sampler, AaMode, Unit, Clamped, CullMode}; +use euc::{Pipeline, Buffer2d, Target, PixelMode, DepthMode, TriangleList, Empty, Linear, Texture, Sampler, Unit, Clamped, CullMode}; use minifb::{Key, MouseButton, MouseMode, Window, WindowOptions}; use std::marker::PhantomData; @@ -21,15 +21,15 @@ impl<'a> Pipeline for TeapotShadow<'a> { fn rasterizer_config(&self) -> CullMode { CullMode::None } #[inline(always)] - fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { + fn vertex(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { ((self.mvp * Vec4::from_point(Vec3::from(vertex.position()))).into_array(), 0.0) } #[inline(always)] - fn fragment_shader(&self, _: Self::VertexData) -> Self::Fragment { Unit } + fn fragment(&self, _: Self::VertexData) -> Self::Fragment { Unit } #[inline(always)] - fn blend_shader(&self, old: Self::Pixel, new: Self::Fragment) {} + fn blend(&self, old: Self::Pixel, new: Self::Fragment) {} } struct Teapot<'a> { @@ -56,10 +56,9 @@ impl<'a> Pipeline for Teapot<'a> { type Pixel = u32; fn depth_mode(&self) -> DepthMode { DepthMode::LESS_WRITE } - fn aa_mode(&self) -> AaMode { AaMode::Msaa { level: 1 } } #[inline(always)] - fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { + fn vertex(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { let wpos = self.m * Vec4::from_point(Vec3::from(vertex.position())); let wnorm = self.m * Vec4::from_direction(-Vec3::from(vertex.normal().unwrap())); @@ -72,7 +71,7 @@ impl<'a> Pipeline for Teapot<'a> { } #[inline(always)] - fn fragment_shader(&self, VertexData { wpos, wnorm, light_view_pos }: Self::VertexData) -> Self::Fragment { + fn fragment(&self, VertexData { wpos, wnorm, light_view_pos }: Self::VertexData) -> Self::Fragment { let wnorm = wnorm.normalized(); let cam_pos = Vec3::zero(); let cam_dir = (wpos - cam_pos).normalized(); @@ -94,7 +93,7 @@ impl<'a> Pipeline for Teapot<'a> { } #[inline(always)] - fn blend_shader(&self, _old: Self::Pixel, rgba: Self::Fragment) -> Self::Pixel { + fn blend(&self, _old: Self::Pixel, rgba: Self::Fragment) -> Self::Pixel { let rgba = rgba.map(|e| e.clamped(0.0, 1.0) * 255.0).as_(); // The window's framebuffer uses BGRA format let bgra = Rgba::new(rgba.b, rgba.g, rgba.r, rgba.a); diff --git a/examples/texture_mapping.rs b/examples/texture_mapping.rs index 939bdf3..beffa2b 100644 --- a/examples/texture_mapping.rs +++ b/examples/texture_mapping.rs @@ -18,7 +18,7 @@ impl<'a> Pipeline for Cube<'a> { type Pixel = u32; #[inline] - fn vertex_shader(&self, v_index: &Self::Vertex) -> ([f32; 4], Self::VertexData) { + fn vertex(&self, v_index: &Self::Vertex) -> ([f32; 4], Self::VertexData) { ( (self.mvp * self.positions[*v_index]).into_array(), self.uvs[*v_index], @@ -26,11 +26,11 @@ impl<'a> Pipeline for Cube<'a> { } #[inline] - fn fragment_shader(&self, v_uv: Self::VertexData) -> Self::Fragment { + fn fragment(&self, v_uv: Self::VertexData) -> Self::Fragment { Rgba::from(self.sampler.sample(v_uv.into_array()).0).map(|e: u8| e as f32) } - fn blend_shader(&self, _: Self::Pixel, color: Self::Fragment) -> Self::Pixel { + fn blend(&self, _: Self::Pixel, color: Self::Fragment) -> Self::Pixel { u32::from_le_bytes(color.map(|e| e as u8).into_array()) } } diff --git a/examples/triangle.rs b/examples/triangle.rs index b24332a..b3c2540 100644 --- a/examples/triangle.rs +++ b/examples/triangle.rs @@ -11,13 +11,13 @@ impl Pipeline for Triangle { type Fragment = Rgba; type Pixel = u32; - fn vertex_shader(&self, (pos, col): &Self::Vertex) -> ([f32; 4], Self::VertexData) { + fn vertex(&self, (pos, col): &Self::Vertex) -> ([f32; 4], Self::VertexData) { ([pos[0], pos[1], 0.0, 1.0], *col) } - fn fragment_shader(&self, col: Self::VertexData) -> Self::Fragment { col } + fn fragment(&self, col: Self::VertexData) -> Self::Fragment { col } - fn blend_shader(&self, _: Self::Pixel, col: Self::Fragment) -> Self::Pixel { + fn blend(&self, _: Self::Pixel, col: Self::Fragment) -> Self::Pixel { u32::from_le_bytes(col.map(|e| (e * 255.0) as u8).into_array()) } } diff --git a/examples/wireframes.rs b/examples/wireframes.rs index 663b97f..7d02953 100644 --- a/examples/wireframes.rs +++ b/examples/wireframes.rs @@ -29,7 +29,7 @@ impl<'a> Pipeline for Teapot<'a> { fn aa_mode(&self) -> AaMode { AaMode::Msaa { level: 1 } } #[inline(always)] - fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { + fn vertex(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { let wpos = self.m * Vec4::from_point(Vec3::from(vertex.position())); let wnorm = self.m * Vec4::from_direction(-Vec3::from(vertex.normal().unwrap())); @@ -40,12 +40,12 @@ impl<'a> Pipeline for Teapot<'a> { } #[inline(always)] - fn fragment_shader(&self, VertexData { wpos, wnorm }: Self::VertexData) -> Self::Fragment { + fn fragment(&self, VertexData { wpos, wnorm }: Self::VertexData) -> Self::Fragment { Rgba::red() } #[inline(always)] - fn blend_shader(&self, _old: Self::Pixel, rgba: Self::Fragment) -> Self::Pixel { + fn blend(&self, _old: Self::Pixel, rgba: Self::Fragment) -> Self::Pixel { let rgba = rgba.map(|e| e.clamped(0.0, 1.0) * 255.0).as_(); // The window's framebuffer uses BGRA format let bgra = Rgba::new(rgba.b, rgba.g, rgba.r, rgba.a); diff --git a/src/pipeline.rs b/src/pipeline.rs index 2582e34..abfcfaa 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -199,13 +199,13 @@ pub trait Pipeline: Sized { /// [`Pipeline::VertexData`] to be interpolated and passed to the fragment shader. /// /// This stage is executed at the beginning of pipeline execution. - fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData); + fn vertex(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData); /// Turn a primitive into many primitives. /// /// This stage sits between the vertex shader and the fragment shader. #[inline(always)] - fn geometry_shader(&self, primitive: >::Primitive, mut output: O) + fn geometry(&self, primitive: >::Primitive, mut output: O) where O: FnMut(>::Primitive), { @@ -215,7 +215,7 @@ pub trait Pipeline: Sized { /// Transforms a [`Pipeline::VertexData`] into a fragment to be rendered to a pixel target. /// /// This stage is executed for every fragment generated by the rasterizer. - fn fragment_shader(&self, vs_out: Self::VertexData) -> Self::Fragment; + fn fragment(&self, vs_out: Self::VertexData) -> Self::Fragment; /// Blend an old fragment with a new fragment. /// @@ -224,7 +224,7 @@ pub trait Pipeline: Sized { /// /// The default implementation simply returns the new fragment and ignores the old one. However, this may be used /// to implement techniques such as alpha blending. - fn blend_shader(&self, old: Self::Pixel, new: Self::Fragment) -> Self::Pixel; + fn blend(&self, old: Self::Pixel, new: Self::Fragment) -> Self::Pixel; /// Render a stream of vertices to given provided pixel target and depth target using the rasterizer. /// @@ -255,7 +255,7 @@ pub trait Pipeline: Sized { }; // Produce an iterator over vertices (using the vertex shader and geometry shader to produce them) - let mut vert_outs = vertices.into_iter().map(|v| self.vertex_shader(v.borrow())).peekable(); + let mut vert_outs = vertices.into_iter().map(|v| self.vertex(v.borrow())).peekable(); let mut vert_out_queue = VecDeque::new(); let fetch_vertex = core::iter::from_fn(move || { loop { @@ -264,19 +264,21 @@ pub trait Pipeline: Sized { None if vert_outs.peek().is_none() => break None, None => { let prim = Self::Primitives::collect_primitive(&mut vert_outs)?; - self.geometry_shader( - prim, - |prim| Self::Primitives::primitive_vertices(prim, |v| vert_out_queue.push_back(v)), - ); + self.geometry(prim, |prim| Self::Primitives::primitive_vertices(prim, |v| vert_out_queue.push_back(v))); }, } } }); + let msaa_level = match self.aa_mode() { + AaMode::None => 0, + AaMode::Msaa { level } => level.max(0).min(6) as usize, + }; + #[cfg(not(feature = "par"))] - let r = render_seq(self, fetch_vertex, target_size, pixel, depth); + let r = render_seq(self, fetch_vertex, target_size, pixel, depth, msaa_level); #[cfg(feature = "par")] - let r = render_par(self, fetch_vertex, target_size, pixel, depth); + let r = render_par(self, fetch_vertex, target_size, pixel, depth, msaa_level); r } } @@ -288,6 +290,7 @@ fn render_par( tgt_size: [usize; 2], pixel: &mut P, depth: &mut D, + msaa_level: usize, ) where Pipe: Pipeline + Send + Sync, @@ -303,8 +306,8 @@ where let threads = num_cpus::get(); let row = AtomicUsize::new(0); - const FRAGMENTS_PER_GROUP: usize = 40960; // Magic number, maybe make this configurable? - let group_rows = FRAGMENTS_PER_GROUP / tgt_size[0].max(1); + const FRAGMENTS_PER_GROUP: usize = 20_000; // Magic number, maybe make this configurable? + let group_rows = FRAGMENTS_PER_GROUP * (msaa_level + 1) / tgt_size[0].max(1); let needed_threads = (tgt_size[1] / group_rows).min(threads); let vertices = &vertices; @@ -327,7 +330,7 @@ where let tgt_max = [tgt_size[0], row_end]; // Safety: we have exclusive access to our specific regions of `pixel` and `depth` // TODO: Actually, unchecked_exclusive is UB, fix this - unsafe { render_inner(pipeline, vertices.iter().cloned(), (tgt_min, tgt_max), tgt_size, pixel, depth) } + unsafe { render_inner(pipeline, vertices.iter().cloned(), (tgt_min, tgt_max), tgt_size, pixel, depth, msaa_level) } } }); } @@ -341,6 +344,7 @@ fn render_seq( tgt_size: [usize; 2], pixel: &mut P, depth: &mut D, + msaa_level: usize, ) where Pipe: Pipeline + Send + Sync, @@ -350,7 +354,7 @@ where { // Safety: we have exclusive access to `pixel` and `depth` // TODO: Actually, unchecked_exclusive is UB, fix this - unsafe { render_inner(pipeline, fetch_vertex, ([0; 2], tgt_size), tgt_size, pixel, depth) } + unsafe { render_inner(pipeline, fetch_vertex, ([0; 2], tgt_size), tgt_size, pixel, depth, msaa_level) } } unsafe fn render_inner( @@ -360,6 +364,7 @@ unsafe fn render_inner( tgt_size: [usize; 2], pixel: &P, depth: &D, + msaa_level: usize, ) where Pipe: Pipeline + Send + Sync, @@ -417,7 +422,7 @@ where .get_mut([pos[0] + 1, pos[1] + 1]); if texel.0 != self.primitive_count { texel.0 = self.primitive_count; - texel.1 = Some(self.pipeline.fragment_shader(get_v_data(pos))); + texel.1 = Some(self.pipeline.fragment(get_v_data(pos))); } // Safety: We know this entry will always be occupied due to the code above texel.1.clone().unwrap_or_else(|| core::hint::unreachable_unchecked()) @@ -457,7 +462,7 @@ where if self.write_pixels { let frag = if self.msaa_level == 0 { - self.pipeline.fragment_shader(get_v_data([pos[0] as f32, pos[1] as f32])) + self.pipeline.fragment(get_v_data([pos[0] as f32, pos[1] as f32])) } else { let fract = [(pos[0], self.tgt_min[0]), (pos[1], self.tgt_min[1])] .map(|(e, tgt_min)| ((e - tgt_min) as f32 / (1 << self.msaa_level) as f32).fract()); @@ -486,17 +491,12 @@ where //self.fetch_pixel([posi[0] + 0, posi[1] + 0], v_data.clone()) }; let old_px = self.pixel.read_exclusive_unchecked(pos); - let blended_px = self.pipeline.blend_shader(old_px, frag); + let blended_px = self.pipeline.blend(old_px, frag); self.pixel.write_exclusive_unchecked(pos, blended_px); } } } - let msaa_level = match pipeline.aa_mode() { - AaMode::None => 0, - AaMode::Msaa { level } => level.max(0).min(6) as usize, - }; - >::Rasterizer::default().rasterize( fetch_vertex, principal_x, From b795e481d252e3290f9ba4ab152fe06497457e4e Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 17 Nov 2022 21:30:23 +0000 Subject: [PATCH 27/37] Initial, bad implementation of a line rasterizer --- Cargo.toml | 1 + README.md | 2 +- benches/teapot.rs | 6 +- src/buffer.rs | 24 +++--- src/lib.rs | 2 +- src/pipeline.rs | 19 ++--- src/primitives.rs | 70 +++++++++++++++- src/rasterizer/lines.rs | 156 ++++++++++++++++++++++++++++++++++++ src/rasterizer/mod.rs | 7 +- src/rasterizer/triangles.rs | 1 + src/texture.rs | 20 +++++ 11 files changed, 282 insertions(+), 26 deletions(-) create mode 100644 src/rasterizer/lines.rs diff --git a/Cargo.toml b/Cargo.toml index 8278071..4af3220 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ image_ = { package = "image", version = "0.23", optional = true } num_cpus = { version = "1.13", optional = true } fxhash = { version = "0.2", optional = true } micromath_ = { package = "micromath", version = "1.1", optional = true } +bresenham = "0.1" [features] default = ["std", "image", "par"] diff --git a/README.md b/README.md index 36735f8..40a8aaa 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ impl Pipeline for Triangle { let mut color = Buffer2d::new([640, 480], [0; 3]); Triangle.render( - // Just render a single triangle to the buffer + // Specify the coordinates of the triangle's corners &[[-1.0, -1.0], [1.0, -1.0], [0.0, 1.0]], // Specify the color buffer to render to &mut color, diff --git a/benches/teapot.rs b/benches/teapot.rs index 18d2598..c530120 100644 --- a/benches/teapot.rs +++ b/benches/teapot.rs @@ -21,15 +21,15 @@ impl<'a> Pipeline for TeapotShadow<'a> { fn depth_mode(&self) -> DepthMode { DepthMode::LESS_WRITE } #[inline(always)] - fn vertex_shader(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { + fn vertex(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { ((self.mvp * Vec4::from_point(Vec3::from(vertex.position()))).into_array(), 0.0) } #[inline(always)] - fn fragment_shader(&self, _: Self::VertexData) -> Self::Fragment { Unit } + fn fragment(&self, _: Self::VertexData) -> Self::Fragment { Unit } #[inline(always)] - fn blend_shader(&self, old: Self::Pixel, new: Self::Fragment) {} + fn blend(&self, old: Self::Pixel, new: Self::Fragment) {} } struct Teapot<'a> { diff --git a/src/buffer.rs b/src/buffer.rs index 2131e20..d784bf9 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -23,6 +23,7 @@ pub struct Buffer { impl Buffer { /// Create a new buffer with the given size, filled with duplicates of the given element. + #[inline] pub fn fill(size: [usize; N], item: T) -> Self where T: Clone { Self::fill_with(size, || item.clone()) } @@ -30,6 +31,7 @@ impl Buffer { /// Create a new buffer with the given size, filled by calling the function for each element. /// /// If your type implements [`Clone`], use [`Buffer::fill`] instead. + #[inline] pub fn fill_with T>(size: [usize; N], mut f: F) -> Self { let mut len = 1usize; (0..N).for_each(|i| len = len.checked_mul(size[i]).unwrap()); @@ -40,7 +42,7 @@ impl Buffer { } /// Convert the given index into a linear index that can be used to index into the raw data of this buffer. - #[inline(always)] + #[inline] pub fn linear_index(&self, index: [usize; N]) -> usize { let mut idx = 0; let mut factor = 1; @@ -52,9 +54,11 @@ impl Buffer { } /// View this buffer as a linear slice of elements. + #[inline] pub fn raw(&self) -> &[T] { &self.items } /// View this buffer as a linear mutable slice of elements. + #[inline] pub fn raw_mut(&mut self) -> &mut [T] { &mut self.items } /// Get a mutable reference to the item at the given index. @@ -62,6 +66,7 @@ impl Buffer { /// # Panics /// /// This function will panic if the index is not within bounds. + #[inline] pub fn get_mut(&mut self, index: [usize; N]) -> &mut T { let idx = self.linear_index(index); match self.items.get_mut(idx) { @@ -75,6 +80,7 @@ impl Buffer { /// # Safety /// /// Undefined behaviour will occur if the index is not within bounds. + #[inline] pub unsafe fn get_unchecked_mut(&mut self, index: [usize; N]) -> &mut T { let idx = self.linear_index(index); self.items.get_unchecked_mut(idx) @@ -86,10 +92,10 @@ impl Texture for Buffer { type Texel = T; - #[inline(always)] + #[inline] fn size(&self) -> [Self::Index; N] { self.size } - #[inline(always)] + #[inline] fn read(&self, index: [Self::Index; N]) -> Self::Texel { self.items .get(self.linear_index(index)) @@ -97,14 +103,14 @@ impl Texture for Buffer { .clone() } - #[inline(always)] + #[inline] unsafe fn read_unchecked(&self, index: [Self::Index; N]) -> Self::Texel { self.items.get_unchecked(self.linear_index(index)).clone() } } impl Target for Buffer { - #[inline(always)] + #[inline] unsafe fn read_exclusive_unchecked(&self, index: [Self::Index; 2]) -> Self::Texel { // This is safe to do (provided the caller has exclusive access to this buffer) because `Vec` internally uses // a `RawVec`, which represents its internal buffer using raw pointers. Ergo, no other references to the items @@ -113,7 +119,7 @@ impl Target for Buffer { (&*((&*item).get())).clone() } - #[inline(always)] + #[inline] unsafe fn write_exclusive_unchecked(&self, index: [usize; 2], texel: Self::Texel) { // This is safe to do (provided the caller has exclusive access to this buffer) because `Vec` internally uses // a `RawVec`, which represents its internal buffer using raw pointers. Ergo, no other references to the items @@ -122,19 +128,19 @@ impl Target for Buffer { *(&*item).get() = texel; } - #[inline(always)] + #[inline] unsafe fn write_unchecked(&mut self, index: [usize; 2], texel: Self::Texel) { let idx = self.linear_index(index); *self.items.get_unchecked_mut(idx) = texel; } - #[inline(always)] + #[inline] fn write(&mut self, index: [usize; 2], texel: Self::Texel) { let idx = self.linear_index(index); self.items[idx] = texel; } - #[inline(always)] + #[inline] fn clear(&mut self, texel: Self::Texel) { self.items.iter_mut().for_each(|item| *item = texel.clone()); } diff --git a/src/lib.rs b/src/lib.rs index 94fb68b..56a12ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,7 +68,7 @@ pub mod texture; pub use crate::{ buffer::{Buffer, Buffer1d, Buffer2d, Buffer3d, Buffer4d}, pipeline::{Pipeline, DepthMode, PixelMode, CoordinateMode, Handedness, YAxisDirection, AaMode}, - primitives::TriangleList, + primitives::{TriangleList, LineList, LineTriangleList}, texture::{Texture, Target, Empty}, rasterizer::CullMode, sampler::{Sampler, Nearest, Linear, Clamped, Tiled, Mirrored}, diff --git a/src/pipeline.rs b/src/pipeline.rs index abfcfaa..9fc3914 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -175,22 +175,23 @@ pub trait Pipeline: Sized { type Pixel: Clone; /// Returns the [`PixelMode`] of this pipeline. - #[inline(always)] + #[inline] fn pixel_mode(&self) -> PixelMode { PixelMode::default() } /// Returns the [`DepthMode`] of this pipeline. - #[inline(always)] + #[inline] fn depth_mode(&self) -> DepthMode { DepthMode::NONE } /// Returns the [`CoordinateMode`] of this pipeline. - #[inline(always)] + #[inline] fn coordinate_mode(&self) -> CoordinateMode { CoordinateMode::default() } /// Returns the [`AaMode`] of this pipeline. - #[inline(always)] + #[inline] fn aa_mode(&self) -> AaMode { AaMode::None } /// Returns the rasterizer configuration (usually [`CullMode`], when using [`Triangles`]) of this pipeline. + #[inline] fn rasterizer_config(&self) -> <>::Rasterizer as Rasterizer>::Config { Default::default() } @@ -204,7 +205,7 @@ pub trait Pipeline: Sized { /// Turn a primitive into many primitives. /// /// This stage sits between the vertex shader and the fragment shader. - #[inline(always)] + #[inline] fn geometry(&self, primitive: >::Primitive, mut output: O) where O: FnMut(>::Primitive), @@ -413,7 +414,7 @@ where P: Target + Send + Sync, D: Target + Send + Sync, { - #[inline(always)] + #[inline] unsafe fn msaa_fragment Pipe::VertexData>(&mut self, pos: [usize; 2], mut get_v_data: F) -> Pipe::Fragment { // Safety: MSAA buffer will always be large enough let texel = self.msaa_buf @@ -439,12 +440,12 @@ where fn target_min(&self) -> [usize; 2] { self.tgt_min } fn target_max(&self) -> [usize; 2] { self.tgt_max } - #[inline(always)] + #[inline] fn begin_primitive(&mut self) { self.primitive_count = self.primitive_count.wrapping_add(1); } - #[inline(always)] + #[inline] unsafe fn test_fragment(&mut self, pos: [usize; 2], z: f32) -> bool { if let Some(test) = self.depth_mode.test { let old_z = self.depth.read_exclusive_unchecked(pos); @@ -454,7 +455,7 @@ where } } - #[inline(always)] + #[inline] unsafe fn emit_fragment Pipe::VertexData>(&mut self, pos: [usize; 2], mut get_v_data: F, z: f32) { if self.depth_mode.write { self.depth.write_exclusive_unchecked(pos, z); diff --git a/src/primitives.rs b/src/primitives.rs index 84b8e46..4df63c9 100644 --- a/src/primitives.rs +++ b/src/primitives.rs @@ -1,4 +1,5 @@ -use crate::rasterizer::{Rasterizer, Triangles}; +use crate::rasterizer::{Rasterizer, Triangles, Lines}; +use core::marker::PhantomData; pub trait PrimitiveKind { type Rasterizer: Rasterizer; @@ -15,12 +16,16 @@ pub trait PrimitiveKind { O: FnMut(([f32; 4], V)); } -pub struct TriangleList; +/// A list of triangles. +/// +/// `0 1 2 3 4 5` produces triangles `0 1 2` and `3 4 5`. +pub struct TriangleList(()); impl PrimitiveKind for TriangleList { type Rasterizer = Triangles; type Primitive = [([f32; 4], V); 3]; + #[inline] fn collect_primitive(mut iter: I) -> Option where I: Iterator @@ -28,6 +33,7 @@ impl PrimitiveKind for TriangleList { Some([iter.next()?, iter.next()?, iter.next()?]) } + #[inline] fn primitive_vertices([a, b, c]: Self::Primitive, mut output: O) where O: FnMut(([f32; 4], V)) @@ -37,3 +43,63 @@ impl PrimitiveKind for TriangleList { output(c); } } + +/// A list of triangles, rasterised as lines. +/// +/// `0 1 2 3 4 5` produces lines `0 1`, `1 2`, `2 0`, `3 4`, `4 5`, and `5 3`. +pub struct LineTriangleList(()); + +impl PrimitiveKind for LineTriangleList { + type Rasterizer = Lines; + type Primitive = [([f32; 4], V); 3]; + + #[inline] + fn collect_primitive(mut iter: I) -> Option + where + I: Iterator + { + Some([iter.next()?, iter.next()?, iter.next()?]) + } + + #[inline] + fn primitive_vertices([a, b, c]: Self::Primitive, mut output: O) + where + O: FnMut(([f32; 4], V)) + { + output(a.clone()); + output(b.clone()); + + output(b); + output(c.clone()); + + output(c); + output(a); + } +} + +/// A list of lines. +/// +/// `0 1 2 3 4 5` produces lines `0 1`, `2 3`, and `4 5`. +pub struct LineList(()); + +impl PrimitiveKind for LineList { + type Rasterizer = Lines; + type Primitive = [([f32; 4], V); 2]; + + #[inline] + fn collect_primitive(mut iter: I) -> Option + where + I: Iterator + { + Some([iter.next()?, iter.next()?]) + } + + #[inline] + fn primitive_vertices([a, b]: Self::Primitive, mut output: O) + where + O: FnMut(([f32; 4], V)) + { + output(a); + output(b); + } +} diff --git a/src/rasterizer/lines.rs b/src/rasterizer/lines.rs new file mode 100644 index 0000000..dbdca8b --- /dev/null +++ b/src/rasterizer/lines.rs @@ -0,0 +1,156 @@ +use super::*; +use crate::{CoordinateMode, YAxisDirection}; +use vek::*; + +#[cfg(feature = "micromath")] +use micromath_::F32Ext; + +/// A rasterizer that produces filled triangles. +#[derive(Copy, Clone, Debug, Default)] +pub struct Lines; + +impl Rasterizer for Lines { + type Config = CullMode; + + #[inline] + unsafe fn rasterize( + &self, + mut vertices: I, + _principal_x: bool, + coordinate_mode: CoordinateMode, + cull_mode: CullMode, + mut blitter: B, + ) + where + V: Clone + WeightedSum, + I: Iterator, + B: Blitter, + { + let tgt_size = blitter.target_size(); + let tgt_min = blitter.target_min(); + let tgt_max = blitter.target_max(); + + let cull_dir = match cull_mode { + CullMode::None => None, + CullMode::Back => Some(1.0), + CullMode::Front => Some(-1.0), + }; + + let flip = match coordinate_mode.y_axis_direction { + YAxisDirection::Down => Vec2::new(1.0, 1.0), + YAxisDirection::Up => Vec2::new(1.0, -1.0), + }; + + let size = Vec2::::from(tgt_size).map(|e| e as f32); + + let to_ndc = Mat3::from_row_arrays([ + [2.0 / size.x, 0.0, -1.0], + [0.0, -2.0 / size.y, 1.0], + [0.0, 0.0, 1.0], + ]); + + let verts_hom_out = core::iter::from_fn(move || { + Some(Vec2::new(vertices.next()?, vertices.next()?)) + }); + + verts_hom_out.for_each(|verts_hom_out: Vec2<([f32; 4], V)>| { + blitter.begin_primitive(); + + // Calculate vertex shader outputs and vertex homogeneous coordinates + let verts_hom = Vec3::new(verts_hom_out.x.0, verts_hom_out.y.0, verts_hom_out.x.0).map(Vec4::::from) + Vec3::new(Vec4::zero(), Vec4::zero(), Vec4::new(0.001, 0.001, 0.0, 0.0)); + let verts_out = Vec3::new(verts_hom_out.x.1.clone(), verts_hom_out.y.1, verts_hom_out.x.1); + + let verts_hom = verts_hom.map(|v| v * Vec4::new(flip.x, flip.y, 1.0, 1.0)); + + // Convert homogenous to euclidean coordinates + let verts_euc = verts_hom.map(|v_hom| v_hom.xyz() / v_hom.w); + + // Create a matrix that allows conversion between screen coordinates and interpolation weights + let coords_to_weights = { + let c = Vec3::new(verts_hom.z.x, verts_hom.z.y, verts_hom.z.w); + let ca = Vec3::new(verts_hom.x.x, verts_hom.x.y, verts_hom.x.w) - c; + let cb = Vec3::new(verts_hom.y.x, verts_hom.y.y, verts_hom.y.w) - c; + let n = ca.cross(cb); + let rec_det = if n.magnitude_squared() > 0.0 { + 1.0 / n.dot(c).min(-core::f32::EPSILON) + } else { + 1.0 + }; + + Mat3::from_row_arrays([cb.cross(c), c.cross(ca), n].map(|v| v.into_array())) * rec_det * to_ndc + }; + + // Ensure we didn't accidentally end up with infinities or NaNs + assert!(coords_to_weights.into_row_array().iter().all(|e| e.is_finite())); + + // Convert vertex coordinates to screen space + let verts_screen = verts_euc.map(|euc| size * (euc.xy() * Vec2::new(0.5, -0.5) + 0.5)); + + // Calculate the triangle bounds as a bounding box + let screen_min = Vec2::::from(tgt_min).map(|e| e as f32); + let screen_max = Vec2::::from(tgt_max).map(|e| e as f32); + let tri_bounds_clamped = Aabr:: { + min: (verts_screen.reduce(|a, b| Vec2::partial_min(a, b)) + 0.0).clamped(screen_min, screen_max).as_(), + max: (verts_screen.reduce(|a, b| Vec2::partial_max(a, b)) + 1.0).clamped(screen_min, screen_max).as_(), + }; + + // Calculate change in vertex weights for each pixel + let weights_at = |p: Vec2| coords_to_weights * Vec3::new(p.x, p.y, 1.0); + let w_hom_origin = weights_at(Vec2::zero()); + let w_hom_dx = (weights_at(Vec2::unit_x() * 1000.0) - w_hom_origin) / 1000.0; + let w_hom_dy = (weights_at(Vec2::unit_y() * 1000.0) - w_hom_origin) / 1000.0; + + let verts_clamped = verts_screen/*verts_screen.xy().map2(verts_screen.xy().yx(), |mut a, b| { + let dir = b - a; + if a.y < screen_min.y { a += Vec2::new(dir.x / dir.y, 1.0) * -(a.y - screen_min.y); } + if a.x < screen_min.x { a += Vec2::new(1.0, dir.y / dir.x) * -(a.x - screen_min.x); } + + if a.y > screen_max.y { a += Vec2::new(dir.x / dir.y, 1.0) * (a.y - screen_max.y); } + if a.x > screen_max.x { a += Vec2::new(1.0, dir.y / dir.x) * (a.x - screen_max.x); } + + a + })*/; + + // TODO: This sucks. A lot. It uses 3-vertex homogeneous coordinates with the last vertex being very close + // to the first, it does loads of unnecessary work for stuff outside the viewport, and it's not even fast. + for (x, y) in + // [ + // verts_screen.x.as_().into_tuple(), + // verts_screen.y.as_().into_tuple(), + // ] + bresenham::Bresenham::new(verts_clamped.x.as_().into_tuple(), verts_clamped.y.as_().into_tuple()) + { + if (tri_bounds_clamped.min.x as isize..tri_bounds_clamped.max.x as isize).contains(&x) && + (tri_bounds_clamped.min.y as isize..tri_bounds_clamped.max.y as isize).contains(&y) + { + let (x, y) = (x as usize, y as usize); + // Find the barycentric weights for the start of this row + let w_hom = w_hom_origin + w_hom_dy * y as f32 + w_hom_dx * x as f32; + // Calculate vertex weights to determine vs_out lerping and intersection + let w_unbalanced = Vec3::new(w_hom.x, w_hom.y, w_hom.z - w_hom.x - w_hom.y); + + // Calculate the interpolated z coordinate for the depth target + let z: f32 = verts_hom.map(|v| v.z).dot(w_unbalanced); + + if blitter.test_fragment([x, y], z) { + // Don't use `.contains(&z)`, it isn't inclusive + if coordinate_mode.z_clip_range.clone().map_or(true, |clip_range| z >= clip_range.start && z <= clip_range.end) { + let get_v_data = |[x, y]: [f32; 2]| { + let w_hom = w_hom_origin + w_hom_dy * y + w_hom_dx * x; + + // Calculate vertex weights to determine vs_out lerping and intersection + let w_unbalanced = Vec3::new(w_hom.x, w_hom.y, w_hom.z - w_hom.x - w_hom.y); + let w = w_unbalanced * w_hom.z.recip(); + let w = Vec2::new(w.x.max(w.z), w.y); + + V::weighted_sum(verts_out.as_slice(), w.as_slice()) + }; + + blitter.emit_fragment([x, y], get_v_data, z); + } + } + } + } + }); + } +} diff --git a/src/rasterizer/mod.rs b/src/rasterizer/mod.rs index b7dd181..2d3aba0 100644 --- a/src/rasterizer/mod.rs +++ b/src/rasterizer/mod.rs @@ -1,6 +1,10 @@ +pub mod lines; pub mod triangles; -pub use self::triangles::Triangles; +pub use self::{ + lines::Lines, + triangles::Triangles, +}; use crate::{CoordinateMode, math::WeightedSum}; @@ -22,6 +26,7 @@ impl Default for CullMode { } /// A trait for types that define an interface for blitting fragments to surfaces +#[doc(hidden)] pub trait Blitter: Sized { fn target_size(&self) -> [usize; 2]; fn target_min(&self) -> [usize; 2]; diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index 5730193..839ead7 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -12,6 +12,7 @@ pub struct Triangles; impl Rasterizer for Triangles { type Config = CullMode; + #[inline] unsafe fn rasterize( &self, mut vertices: I, diff --git a/src/texture.rs b/src/texture.rs index 2196376..6904f91 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -63,16 +63,22 @@ pub trait Texture { impl<'a, T: Texture, const N: usize> Texture for &'a T { type Index = T::Index; type Texel = T::Texel; + #[inline] fn size(&self) -> [Self::Index; N] { (**self).size() } + #[inline] fn read(&self, index: [Self::Index; N]) -> Self::Texel { (**self).read(index) } + #[inline] unsafe fn read_unchecked(&self, index: [Self::Index; N]) -> Self::Texel { (**self).read_unchecked(index) } } impl<'a, T: Texture, const N: usize> Texture for &'a mut T { type Index = T::Index; type Texel = T::Texel; + #[inline] fn size(&self) -> [Self::Index; N] { (**self).size() } + #[inline] fn read(&self, index: [Self::Index; N]) -> Self::Texel { (**self).read(index) } + #[inline] unsafe fn read_unchecked(&self, index: [Self::Index; N]) -> Self::Texel { (**self).read_unchecked(index) } } @@ -126,6 +132,7 @@ pub trait Target: Texture<2, Index = usize> { /// /// If the index is invalid, undefined behaviour can be assumed to occur. Ensure that the index is valid before /// use. + #[inline] unsafe fn write_unchecked(&mut self, index: [usize; 2], texel: Self::Texel) { self.write_exclusive_unchecked(index, texel); } @@ -136,6 +143,7 @@ pub trait Target: Texture<2, Index = usize> { /// /// The behaviour of this function is *unspecified* (but not *undefined*) when the index is out of bounds. The /// implementation is free to panic, write to an entirely different texel, or do nothing. + #[inline] fn write(&mut self, [x, y]: [usize; 2], texel: Self::Texel) { if x < self.size()[0] && y < self.size()[1] { unsafe { self.write_unchecked([x, y], texel); } @@ -143,6 +151,7 @@ pub trait Target: Texture<2, Index = usize> { } /// Clears the entire target with the given texel. + #[inline] fn clear(&mut self, texel: Self::Texel) { for y in 0..self.size()[1] { for x in 0..self.size()[0] { @@ -153,10 +162,15 @@ pub trait Target: Texture<2, Index = usize> { } impl<'a, T: Target> Target for &'a mut T { + #[inline] unsafe fn read_exclusive_unchecked(&self, index: [Self::Index; 2]) -> Self::Texel { T::read_exclusive_unchecked(self, index) } + #[inline] unsafe fn write_exclusive_unchecked(&self, index: [usize; 2], texel: Self::Texel) { T::write_exclusive_unchecked(self, index, texel) } + #[inline] unsafe fn write_unchecked(&mut self, index: [usize; 2], texel: Self::Texel) { T::write_unchecked(self, index, texel) } + #[inline] fn write(&mut self, index: [usize; 2], texel: Self::Texel) { T::write(self, index, texel); } + #[inline] fn clear(&mut self, texel: Self::Texel) { T::clear(self, texel); } } @@ -172,12 +186,16 @@ impl Default for Empty { impl Texture for Empty { type Index = usize; type Texel = T; + #[inline] fn size(&self) -> [Self::Index; N] { [0; N] } + #[inline] fn read(&self, _: [Self::Index; N]) -> Self::Texel { panic!("Cannot read from an empty texture"); } } impl Target for Empty { + #[inline] unsafe fn read_exclusive_unchecked(&self, _: [Self::Index; 2]) -> Self::Texel { T::default() } + #[inline] unsafe fn write_exclusive_unchecked(&self, _: [usize; 2], _: Self::Texel) {} } @@ -190,10 +208,12 @@ where type Index = usize; type Texel = P; + #[inline] fn size(&self) -> [Self::Index; 2] { [self.width() as usize, self.height() as usize] } + #[inline] fn read(&self, [x, y]: [Self::Index; 2]) -> Self::Texel { self.get_pixel(x as u32, y as u32).clone() } From a5006fdc2e70edbf908ff2bc922d8c616f2c6cb0 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 17 Feb 2023 11:23:07 +0000 Subject: [PATCH 28/37] Fmt, remoted nightly feature --- benches/teapot.rs | 108 ++++++++++++++----- examples/spinning_cube.rs | 18 ++-- examples/teapot.rs | 104 +++++++++++++----- examples/texture_mapping.rs | 12 +-- examples/triangle.rs | 6 +- examples/wireframes.rs | 53 +++++---- src/buffer.rs | 38 +++++-- src/index.rs | 47 ++++++-- src/lib.rs | 14 +-- src/math.rs | 18 +++- src/pipeline.rs | 207 ++++++++++++++++++++++++------------ src/primitives.rs | 14 +-- src/rasterizer/lines.rs | 69 ++++++++---- src/rasterizer/mod.rs | 17 +-- src/rasterizer/triangles.rs | 48 ++++++--- src/sampler/linear.rs | 47 ++++++-- src/sampler/mod.rs | 77 +++++++++----- src/sampler/nearest.rs | 17 +-- src/texture.rs | 87 +++++++++++---- 19 files changed, 701 insertions(+), 300 deletions(-) diff --git a/benches/teapot.rs b/benches/teapot.rs index c530120..4e4fc31 100644 --- a/benches/teapot.rs +++ b/benches/teapot.rs @@ -1,9 +1,12 @@ -use criterion::{criterion_group, criterion_main, Bencher, Criterion, black_box}; -use vek::*; +use criterion::{black_box, criterion_group, criterion_main, Bencher, Criterion}; use derive_more::{Add, Mul}; -use euc::{Pipeline, Buffer2d, Target, PixelMode, DepthMode, TriangleList, CullMode, Empty, Linear, Texture, Sampler, AaMode, Unit, Clamped}; +use euc::{ + AaMode, Buffer2d, Clamped, CullMode, DepthMode, Empty, Linear, Pipeline, PixelMode, Sampler, + Target, Texture, TriangleList, Unit, +}; use minifb::{Key, MouseButton, MouseMode, Window, WindowOptions}; use std::{marker::PhantomData, time::Duration}; +use vek::*; struct TeapotShadow<'a> { mvp: Mat4, @@ -17,16 +20,25 @@ impl<'a> Pipeline for TeapotShadow<'a> { type Fragment = Unit; type Pixel = (); - fn pixel_mode(&self) -> PixelMode { PixelMode::PASS } - fn depth_mode(&self) -> DepthMode { DepthMode::LESS_WRITE } + fn pixel_mode(&self) -> PixelMode { + PixelMode::PASS + } + fn depth_mode(&self) -> DepthMode { + DepthMode::LESS_WRITE + } #[inline(always)] fn vertex(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { - ((self.mvp * Vec4::from_point(Vec3::from(vertex.position()))).into_array(), 0.0) + ( + (self.mvp * Vec4::from_point(Vec3::from(vertex.position()))).into_array(), + 0.0, + ) } #[inline(always)] - fn fragment(&self, _: Self::VertexData) -> Self::Fragment { Unit } + fn fragment(&self, _: Self::VertexData) -> Self::Fragment { + Unit + } #[inline(always)] fn blend(&self, old: Self::Pixel, new: Self::Fragment) {} @@ -55,8 +67,12 @@ impl<'a> Pipeline for Teapot<'a> { type Fragment = Rgba; type Pixel = u32; - fn depth_mode(&self) -> DepthMode { DepthMode::LESS_WRITE } - fn aa_mode(&self) -> AaMode { AaMode::Msaa { level: 1 } } + fn depth_mode(&self) -> DepthMode { + DepthMode::LESS_WRITE + } + fn aa_mode(&self) -> AaMode { + AaMode::Msaa { level: 1 } + } #[inline(always)] fn vertex(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { @@ -67,12 +83,23 @@ impl<'a> Pipeline for Teapot<'a> { let light_view_pos = light_view_pos.xyz() / light_view_pos.w; ( (self.p * self.v * wpos).into_array(), - VertexData { wpos: wpos.xyz(), wnorm: wnorm.xyz(), light_view_pos }, + VertexData { + wpos: wpos.xyz(), + wnorm: wnorm.xyz(), + light_view_pos, + }, ) } #[inline(always)] - fn fragment(&self, VertexData { wpos, wnorm, light_view_pos }: Self::VertexData) -> Self::Fragment { + fn fragment( + &self, + VertexData { + wpos, + wnorm, + light_view_pos, + }: Self::VertexData, + ) -> Self::Fragment { let wnorm = wnorm.normalized(); let cam_pos = Vec3::zero(); let cam_dir = (wpos - cam_pos).normalized(); @@ -82,10 +109,18 @@ impl<'a> Pipeline for Teapot<'a> { // Phong reflection model let ambient = 0.1; let diffuse = wnorm.dot(-light_dir).max(0.0) * 0.5; - let specular = (-light_dir).reflected(wnorm).dot(-cam_dir).max(0.0).powf(30.0) * 3.0; + let specular = (-light_dir) + .reflected(wnorm) + .dot(-cam_dir) + .max(0.0) + .powf(30.0) + * 3.0; // Shadow-mapping - let light_depth = self.shadow.sample((light_view_pos.xy() * Vec2::new(1.0, -1.0) * 0.5 + 0.5).into_array()) + 0.0001; + let light_depth = self + .shadow + .sample((light_view_pos.xy() * Vec2::new(1.0, -1.0) * 0.5 + 0.5).into_array()) + + 0.0001; let depth = light_view_pos.z; let in_light = depth < light_depth; @@ -109,7 +144,8 @@ fn teapot_benchmark(b: &mut Bencher, &[width, height]: &[usize; 2]) { let mut depth = Buffer2d::fill([w, h], 1.0); let mut shadow = Buffer2d::fill([512; 2], 1.0); - let model = wavefront::Obj::from_reader(&include_bytes!("../examples/data/teapot.obj")[..]).unwrap(); + let model = + wavefront::Obj::from_reader(&include_bytes!("../examples/data/teapot.obj")[..]).unwrap(); let mut ori = Vec2::new(0.0, 0.0); let mut dist = 6.0; @@ -119,14 +155,19 @@ fn teapot_benchmark(b: &mut Bencher, &[width, height]: &[usize; 2]) { let light_pos = Vec3::::new(-8.0, 5.0, -5.0); // Set up the light matrix - let light_p = Mat4::perspective_fov_lh_zo(0.75, shadow.size()[0] as f32, shadow.size()[1] as f32, 0.1, 100.0); + let light_p = Mat4::perspective_fov_lh_zo( + 0.75, + shadow.size()[0] as f32, + shadow.size()[1] as f32, + 0.1, + 100.0, + ); let light_v = Mat4::look_at_lh(light_pos, -teapot_pos, Vec3::unit_y()); let light_vp = light_p * light_v; // Set up the camera matrix let p = Mat4::perspective_fov_lh_zo(1.3, w as f32, h as f32, 0.01, 100.0); - let v = Mat4::::identity() - * Mat4::translation_3d(Vec3::new(0.0, 0.0, dist)); + let v = Mat4::::identity() * Mat4::translation_3d(Vec3::new(0.0, 0.0, dist)); // Set up the teapot matrix let m = Mat4::::translation_3d(-teapot_pos) * Mat4::rotation_x(core::f32::consts::PI) @@ -140,18 +181,22 @@ fn teapot_benchmark(b: &mut Bencher, &[width, height]: &[usize; 2]) { shadow.clear(1.0); // Shadow pass - TeapotShadow { mvp: light_vp * m, phantom: PhantomData }.render( - model.vertices(), - &mut Empty::default(), - &mut shadow, - ); + TeapotShadow { + mvp: light_vp * m, + phantom: PhantomData, + } + .render(model.vertices(), &mut Empty::default(), &mut shadow); // Colour pass - Teapot { m, v, p, light_pos, shadow: (&shadow).linear().clamped(), light_vp: light_vp }.render( - model.vertices(), - &mut color, - &mut depth, - ); + Teapot { + m, + v, + p, + light_pos, + shadow: (&shadow).linear().clamped(), + light_vp: light_vp, + } + .render(model.vertices(), &mut color, &mut depth); black_box(&mut color); black_box(&mut depth); @@ -162,7 +207,14 @@ fn criterion_benchmark(c: &mut Criterion) { c.bench_function_over_inputs( "teapot", |b, &size| teapot_benchmark(b, size), - &[[1, 1], [32, 32], [640, 480], [1024, 800], [2048, 1600], [4096, 3200]], + &[ + [1, 1], + [32, 32], + [640, 480], + [1024, 800], + [2048, 1600], + [4096, 3200], + ], ); } diff --git a/examples/spinning_cube.rs b/examples/spinning_cube.rs index 72c29fb..4a51b9f 100644 --- a/examples/spinning_cube.rs +++ b/examples/spinning_cube.rs @@ -1,6 +1,6 @@ -use vek::*; -use euc::{Pipeline, Buffer2d, Target, TriangleList, IndexedVertices}; +use euc::{Buffer2d, IndexedVertices, Pipeline, Target, TriangleList}; use minifb::{Key, Window, WindowOptions}; +use vek::*; struct Cube { mvp: Mat4, @@ -35,13 +35,13 @@ const B: Rgba = Rgba::new(0.0, 0.0, 1.0, 1.0); const VERTICES: &[(Vec4, Rgba)] = &[ (Vec4::new(-1.0, -1.0, -1.0, 1.0), R), - (Vec4::new(-1.0, -1.0, 1.0, 1.0), Y), - (Vec4::new(-1.0, 1.0, -1.0, 1.0), G), - (Vec4::new(-1.0, 1.0, 1.0, 1.0), B), - (Vec4::new( 1.0, -1.0, -1.0, 1.0), B), - (Vec4::new( 1.0, -1.0, 1.0, 1.0), G), - (Vec4::new( 1.0, 1.0, -1.0, 1.0), Y), - (Vec4::new( 1.0, 1.0, 1.0, 1.0), R), + (Vec4::new(-1.0, -1.0, 1.0, 1.0), Y), + (Vec4::new(-1.0, 1.0, -1.0, 1.0), G), + (Vec4::new(-1.0, 1.0, 1.0, 1.0), B), + (Vec4::new(1.0, -1.0, -1.0, 1.0), B), + (Vec4::new(1.0, -1.0, 1.0, 1.0), G), + (Vec4::new(1.0, 1.0, -1.0, 1.0), Y), + (Vec4::new(1.0, 1.0, 1.0, 1.0), R), ]; const INDICES: &[usize] = &[ diff --git a/examples/teapot.rs b/examples/teapot.rs index 6d4a538..dd550e7 100644 --- a/examples/teapot.rs +++ b/examples/teapot.rs @@ -1,8 +1,11 @@ -use vek::*; use derive_more::{Add, Mul}; -use euc::{Pipeline, Buffer2d, Target, PixelMode, DepthMode, TriangleList, Empty, Linear, Texture, Sampler, Unit, Clamped, CullMode}; +use euc::{ + Buffer2d, Clamped, CullMode, DepthMode, Empty, Linear, Pipeline, PixelMode, Sampler, Target, + Texture, TriangleList, Unit, +}; use minifb::{Key, MouseButton, MouseMode, Window, WindowOptions}; use std::marker::PhantomData; +use vek::*; struct TeapotShadow<'a> { mvp: Mat4, @@ -16,17 +19,28 @@ impl<'a> Pipeline for TeapotShadow<'a> { type Fragment = Unit; type Pixel = (); - fn pixel_mode(&self) -> PixelMode { PixelMode::PASS } - fn depth_mode(&self) -> DepthMode { DepthMode::LESS_WRITE } - fn rasterizer_config(&self) -> CullMode { CullMode::None } + fn pixel_mode(&self) -> PixelMode { + PixelMode::PASS + } + fn depth_mode(&self) -> DepthMode { + DepthMode::LESS_WRITE + } + fn rasterizer_config(&self) -> CullMode { + CullMode::None + } #[inline(always)] fn vertex(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { - ((self.mvp * Vec4::from_point(Vec3::from(vertex.position()))).into_array(), 0.0) + ( + (self.mvp * Vec4::from_point(Vec3::from(vertex.position()))).into_array(), + 0.0, + ) } #[inline(always)] - fn fragment(&self, _: Self::VertexData) -> Self::Fragment { Unit } + fn fragment(&self, _: Self::VertexData) -> Self::Fragment { + Unit + } #[inline(always)] fn blend(&self, old: Self::Pixel, new: Self::Fragment) {} @@ -55,7 +69,9 @@ impl<'a> Pipeline for Teapot<'a> { type Fragment = Rgba; type Pixel = u32; - fn depth_mode(&self) -> DepthMode { DepthMode::LESS_WRITE } + fn depth_mode(&self) -> DepthMode { + DepthMode::LESS_WRITE + } #[inline(always)] fn vertex(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { @@ -66,12 +82,23 @@ impl<'a> Pipeline for Teapot<'a> { let light_view_pos = light_view_pos.xyz() / light_view_pos.w; ( (self.p * self.v * wpos).into_array(), - VertexData { wpos: wpos.xyz(), wnorm: wnorm.xyz(), light_view_pos }, + VertexData { + wpos: wpos.xyz(), + wnorm: wnorm.xyz(), + light_view_pos, + }, ) } #[inline(always)] - fn fragment(&self, VertexData { wpos, wnorm, light_view_pos }: Self::VertexData) -> Self::Fragment { + fn fragment( + &self, + VertexData { + wpos, + wnorm, + light_view_pos, + }: Self::VertexData, + ) -> Self::Fragment { let wnorm = wnorm.normalized(); let cam_pos = Vec3::zero(); let cam_dir = (wpos - cam_pos).normalized(); @@ -81,10 +108,18 @@ impl<'a> Pipeline for Teapot<'a> { // Phong reflection model let ambient = 0.1; let diffuse = wnorm.dot(-light_dir).max(0.0) * 0.5; - let specular = (-light_dir).reflected(wnorm).dot(-cam_dir).max(0.0).powf(30.0) * 3.0; + let specular = (-light_dir) + .reflected(wnorm) + .dot(-cam_dir) + .max(0.0) + .powf(30.0) + * 3.0; // Shadow-mapping - let light_depth = self.shadow.sample((light_view_pos.xy() * Vec2::new(1.0, -1.0) * 0.5 + 0.5).into_array()) + 0.0001; + let light_depth = self + .shadow + .sample((light_view_pos.xy() * Vec2::new(1.0, -1.0) * 0.5 + 0.5).into_array()) + + 0.0001; let depth = light_view_pos.z; let in_light = depth < light_depth; @@ -132,7 +167,9 @@ fn main() { ori -= Vec2::new(mouse_pos.1 - old_mouse_pos.1, mouse_pos.0 - old_mouse_pos.0) * 0.003; } if win.get_mouse_down(MouseButton::Right) { - dist = (dist + (mouse_pos.1 - old_mouse_pos.1) as f32 * 0.01).max(1.0).min(20.0); + dist = (dist + (mouse_pos.1 - old_mouse_pos.1) as f32 * 0.01) + .max(1.0) + .min(20.0); } old_mouse_pos = mouse_pos; @@ -142,7 +179,13 @@ fn main() { let light_pos = Vec3::new(angle.sin() * 8.0, 10.0, angle.cos() * 8.0); // Set up the light matrix - let light_p = Mat4::perspective_fov_lh_zo(0.75, shadow.size()[0] as f32, shadow.size()[1] as f32, 0.1, 100.0); + let light_p = Mat4::perspective_fov_lh_zo( + 0.75, + shadow.size()[0] as f32, + shadow.size()[1] as f32, + 0.1, + 100.0, + ); let light_v = Mat4::look_at_lh(light_pos, -teapot_pos, Vec3::unit_y()); let light_vp = light_p * light_v; @@ -153,28 +196,35 @@ fn main() { * Mat4::rotation_x(ori.x) * Mat4::rotation_y(ori.y); // Set up the teapot matrix - let m = Mat4::::translation_3d(-teapot_pos) - * Mat4::rotation_x(core::f32::consts::PI); + let m = Mat4::::translation_3d(-teapot_pos) * Mat4::rotation_x(core::f32::consts::PI); // Shadow pass - TeapotShadow { mvp: light_vp * m, phantom: PhantomData }.render( - model.vertices(), - &mut Empty::default(), - &mut shadow, - ); + TeapotShadow { + mvp: light_vp * m, + phantom: PhantomData, + } + .render(model.vertices(), &mut Empty::default(), &mut shadow); // Colour pass - Teapot { m, v, p, light_pos, shadow: (&shadow).linear().clamped(), light_vp: light_vp }.render( - model.vertices(), - &mut color, - &mut depth, - ); + Teapot { + m, + v, + p, + light_pos, + shadow: (&shadow).linear().clamped(), + light_vp: light_vp, + } + .render(model.vertices(), &mut color, &mut depth); win.update_with_buffer(color.raw(), w, h).unwrap(); if i % 60 == 0 { let elapsed = start_time.elapsed(); - win.set_title(&format!("Teapot (Time = {:?}, FPS = {})", elapsed, 1.0 / elapsed.as_secs_f32())); + win.set_title(&format!( + "Teapot (Time = {:?}, FPS = {})", + elapsed, + 1.0 / elapsed.as_secs_f32() + )); } i += 1; } diff --git a/examples/texture_mapping.rs b/examples/texture_mapping.rs index beffa2b..679aeaa 100644 --- a/examples/texture_mapping.rs +++ b/examples/texture_mapping.rs @@ -1,7 +1,7 @@ -use euc::{Buffer2d, Pipeline, Target, TriangleList, Sampler, Nearest, Texture}; +use euc::{Buffer2d, Nearest, Pipeline, Sampler, Target, Texture, TriangleList}; use image_::RgbaImage; -use vek::{Mat4, Vec2, Vec3, Vec4, Rgba}; use minifb::{Key, Window, WindowOptions}; +use vek::{Mat4, Rgba, Vec2, Vec3, Vec4}; struct Cube<'a> { mvp: Mat4, @@ -138,12 +138,8 @@ fn main() { }; cube.render( &[ - 0, 3, 1, 1, 3, 2, - 4, 5, 7, 5, 6, 7, - 8, 11, 9, 9, 11, 10, - 12, 13, 15, 13, 14, 15, - 16, 17, 19, 17, 18, 19, - 20, 23, 21, 21, 23, 22, + 0, 3, 1, 1, 3, 2, 4, 5, 7, 5, 6, 7, 8, 11, 9, 9, 11, 10, 12, 13, 15, 13, 14, 15, + 16, 17, 19, 17, 18, 19, 20, 23, 21, 21, 23, 22, ], &mut color, &mut depth, diff --git a/examples/triangle.rs b/examples/triangle.rs index b3c2540..6b909c5 100644 --- a/examples/triangle.rs +++ b/examples/triangle.rs @@ -1,4 +1,4 @@ -use euc::{Buffer2d, Pipeline, TriangleList, Empty}; +use euc::{Buffer2d, Empty, Pipeline, TriangleList}; use minifb::{Key, Window, WindowOptions}; use vek::*; @@ -15,7 +15,9 @@ impl Pipeline for Triangle { ([pos[0], pos[1], 0.0, 1.0], *col) } - fn fragment(&self, col: Self::VertexData) -> Self::Fragment { col } + fn fragment(&self, col: Self::VertexData) -> Self::Fragment { + col + } fn blend(&self, _: Self::Pixel, col: Self::Fragment) -> Self::Pixel { u32::from_le_bytes(col.map(|e| (e * 255.0) as u8).into_array()) diff --git a/examples/wireframes.rs b/examples/wireframes.rs index 7d02953..09ae1ad 100644 --- a/examples/wireframes.rs +++ b/examples/wireframes.rs @@ -1,8 +1,11 @@ -use vek::*; use derive_more::{Add, Mul}; -use euc::{Pipeline, Buffer2d, Target, PixelMode, DepthMode, TriangleList, Empty, Linear, Texture, Sampler, AaMode, Unit, Clamped}; +use euc::{ + AaMode, Buffer2d, Clamped, DepthMode, Empty, Linear, Pipeline, PixelMode, Sampler, Target, + Texture, TriangleList, Unit, +}; use minifb::{Key, MouseButton, MouseMode, Window, WindowOptions}; use std::marker::PhantomData; +use vek::*; struct Teapot<'a> { m: Mat4, @@ -21,12 +24,16 @@ struct VertexData { impl<'a> Pipeline for Teapot<'a> { type Vertex = wavefront::Vertex<'a>; type VertexData = VertexData; - type Primitives = TriangleList;//Lines; + type Primitives = TriangleList; //Lines; type Fragment = Rgba; type Pixel = u32; - fn depth_mode(&self) -> DepthMode { DepthMode::LESS_WRITE } - fn aa_mode(&self) -> AaMode { AaMode::Msaa { level: 1 } } + fn depth_mode(&self) -> DepthMode { + DepthMode::LESS_WRITE + } + fn aa_mode(&self) -> AaMode { + AaMode::Msaa { level: 1 } + } #[inline(always)] fn vertex(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { @@ -35,7 +42,10 @@ impl<'a> Pipeline for Teapot<'a> { ( (self.p * self.v * wpos).into_array(), - VertexData { wpos: wpos.xyz(), wnorm: wnorm.xyz() }, + VertexData { + wpos: wpos.xyz(), + wnorm: wnorm.xyz(), + }, ) } @@ -82,7 +92,9 @@ fn main() { ori.y += (mouse_pos.0 - old_mouse_pos.0) * 0.003; } if win.get_mouse_down(MouseButton::Right) { - dist = (dist + (mouse_pos.1 - old_mouse_pos.1) as f32 * 0.01).max(1.0).min(20.0); + dist = (dist + (mouse_pos.1 - old_mouse_pos.1) as f32 * 0.01) + .max(1.0) + .min(20.0); } old_mouse_pos = mouse_pos; @@ -92,8 +104,7 @@ fn main() { // Set up the camera matrix let p = Mat4::perspective_fov_lh_zo(1.3, w as f32, h as f32, 0.01, 100.0); - let v = Mat4::::identity() - * Mat4::translation_3d(Vec3::new(0.0, 0.0, dist)); + let v = Mat4::::identity() * Mat4::translation_3d(Vec3::new(0.0, 0.0, dist)); // Set up the teapot matrix let m = Mat4::::translation_3d(-teapot_pos) * Mat4::rotation_x(core::f32::consts::PI) @@ -101,27 +112,29 @@ fn main() { * Mat4::rotation_y(ori.y); // Colour pass - Teapot { m, v, p, light_pos, phantom: PhantomData }.render( - model.vertices(), - &mut color, - &mut depth, - ); + Teapot { + m, + v, + p, + light_pos, + phantom: PhantomData, + } + .render(model.vertices(), &mut color, &mut depth); win.update_with_buffer(color.raw(), w, h).unwrap(); if i % 60 == 0 { let elapsed = start_time.elapsed(); - win.set_title(&format!("Teapot (Time = {:?}, FPS = {})", elapsed, 1.0 / elapsed.as_secs_f32())); + win.set_title(&format!( + "Teapot (Time = {:?}, FPS = {})", + elapsed, + 1.0 / elapsed.as_secs_f32() + )); } i += 1; } } - - - - - /* use euc::{buffer::Buffer2d, rasterizer, Pipeline, Target}; use std::path::Path; diff --git a/src/buffer.rs b/src/buffer.rs index d784bf9..4a7eaa5 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,4 +1,4 @@ -use crate::texture::{Texture, Target}; +use crate::texture::{Target, Texture}; use alloc::vec::Vec; use core::cell::UnsafeCell; @@ -24,7 +24,10 @@ pub struct Buffer { impl Buffer { /// Create a new buffer with the given size, filled with duplicates of the given element. #[inline] - pub fn fill(size: [usize; N], item: T) -> Self where T: Clone { + pub fn fill(size: [usize; N], item: T) -> Self + where + T: Clone, + { Self::fill_with(size, || item.clone()) } @@ -55,11 +58,15 @@ impl Buffer { /// View this buffer as a linear slice of elements. #[inline] - pub fn raw(&self) -> &[T] { &self.items } + pub fn raw(&self) -> &[T] { + &self.items + } /// View this buffer as a linear mutable slice of elements. #[inline] - pub fn raw_mut(&mut self) -> &mut [T] { &mut self.items } + pub fn raw_mut(&mut self) -> &mut [T] { + &mut self.items + } /// Get a mutable reference to the item at the given index. /// @@ -71,7 +78,10 @@ impl Buffer { let idx = self.linear_index(index); match self.items.get_mut(idx) { Some(item) => item, - None => panic!("Attempted to read buffer of size {:?} at out-of-bounds location {:?}", self.size, index), + None => panic!( + "Attempted to read buffer of size {:?} at out-of-bounds location {:?}", + self.size, index + ), } } @@ -93,13 +103,21 @@ impl Texture for Buffer { type Texel = T; #[inline] - fn size(&self) -> [Self::Index; N] { self.size } + fn size(&self) -> [Self::Index; N] { + self.size + } #[inline] fn read(&self, index: [Self::Index; N]) -> Self::Texel { self.items .get(self.linear_index(index)) - .unwrap_or_else(|| panic!("Attempted to read buffer of size {:?} at out-of-bounds location {:?}", self.size(), index)) + .unwrap_or_else(|| { + panic!( + "Attempted to read buffer of size {:?} at out-of-bounds location {:?}", + self.size(), + index + ) + }) .clone() } @@ -115,7 +133,8 @@ impl Target for Buffer { // This is safe to do (provided the caller has exclusive access to this buffer) because `Vec` internally uses // a `RawVec`, which represents its internal buffer using raw pointers. Ergo, no other references to the items // exist and so this does not break aliasing rules. - let item = self.items.get_unchecked(self.linear_index(index)) as *const _ as *const UnsafeCell; + let item = + self.items.get_unchecked(self.linear_index(index)) as *const _ as *const UnsafeCell; (&*((&*item).get())).clone() } @@ -124,7 +143,8 @@ impl Target for Buffer { // This is safe to do (provided the caller has exclusive access to this buffer) because `Vec` internally uses // a `RawVec`, which represents its internal buffer using raw pointers. Ergo, no other references to the items // exist and so this does not break aliasing rules. - let item = self.items.get_unchecked(self.linear_index(index)) as *const _ as *const UnsafeCell; + let item = + self.items.get_unchecked(self.linear_index(index)) as *const _ as *const UnsafeCell; *(&*item).get() = texel; } diff --git a/src/index.rs b/src/index.rs index 68e4442..98e08d4 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1,14 +1,19 @@ -use core::{ - borrow::Borrow, - marker::PhantomData, -}; +use core::{borrow::Borrow, marker::PhantomData}; /// A helper type that makes indexed vertex access easier. -pub struct IndexedVertices<'a, Is, Vs, I, V>(Is, Vs, PhantomData<&'a (I, V)>); +pub struct IndexedVertices<'a, Is, Vs, I, V> { + indices: Is, + verts: Vs, + phantom: PhantomData<&'a (I, V)>, +} impl<'a, Is, Vs, I, V> IndexedVertices<'a, Is, Vs, I, V> { - pub fn new(is: Is, vs: Vs) -> Self { - Self(is, vs, PhantomData) + pub fn new(indices: Is, verts: Vs) -> Self { + Self { + indices, + verts, + phantom: PhantomData, + } } } @@ -19,10 +24,32 @@ where Vs: Borrow<&'a [V]> + 'a, { type Item = &'a V; - type IntoIter = impl Iterator; + type IntoIter = IndexedVerticesIter<'a, Is::IntoIter, Vs, I, V>; fn into_iter(self) -> Self::IntoIter { - let verts = self.1; - self.0.into_iter().map(move |i| &verts.borrow()[*i.borrow()]) + IndexedVerticesIter { + indices: self.indices.into_iter(), + verts: self.verts, + phantom: PhantomData, + } + } +} + +pub struct IndexedVerticesIter<'a, Is: Iterator, Vs, I, V> { + indices: Is, + verts: Vs, + phantom: PhantomData<&'a (I, V)>, +} + +impl<'a, Is: Iterator, Vs, I, V> Iterator for IndexedVerticesIter<'a, Is, Vs, I, V> +where + I: Borrow, + Is: Iterator + 'a, + Vs: Borrow<&'a [V]> + 'a, +{ + type Item = &'a V; + + fn next(&mut self) -> Option { + Some(&self.verts.borrow()[*self.indices.next()?.borrow()]) } } diff --git a/src/lib.rs b/src/lib.rs index 56a12ba..be3b0a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,8 +40,6 @@ #![no_std] -#![feature(type_alias_impl_trait)] - extern crate alloc; #[cfg(feature = "std")] @@ -67,11 +65,13 @@ pub mod texture; // Reexports pub use crate::{ buffer::{Buffer, Buffer1d, Buffer2d, Buffer3d, Buffer4d}, - pipeline::{Pipeline, DepthMode, PixelMode, CoordinateMode, Handedness, YAxisDirection, AaMode}, - primitives::{TriangleList, LineList, LineTriangleList}, - texture::{Texture, Target, Empty}, - rasterizer::CullMode, - sampler::{Sampler, Nearest, Linear, Clamped, Tiled, Mirrored}, index::IndexedVertices, math::Unit, + pipeline::{ + AaMode, CoordinateMode, DepthMode, Handedness, Pipeline, PixelMode, YAxisDirection, + }, + primitives::{LineList, LineTriangleList, TriangleList}, + rasterizer::CullMode, + sampler::{Clamped, Linear, Mirrored, Nearest, Sampler, Tiled}, + texture::{Empty, Target, Texture}, }; diff --git a/src/math.rs b/src/math.rs index 2629ccd..2881a1d 100644 --- a/src/math.rs +++ b/src/math.rs @@ -1,4 +1,4 @@ -use core::ops::{Mul, Add}; +use core::ops::{Add, Mul}; pub trait WeightedSum: Sized { fn weighted_sum(values: &[Self], weights: &[f32]) -> Self; @@ -8,13 +8,20 @@ pub trait WeightedSum: Sized { pub struct Unit; impl WeightedSum for Unit { - fn weighted_sum(_: &[Self], _: &[f32]) -> Self { Unit } + fn weighted_sum(_: &[Self], _: &[f32]) -> Self { + Unit + } } impl + Add> WeightedSum for T { #[inline(always)] fn weighted_sum(values: &[Self], weights: &[f32]) -> Self { - values[1..].iter().zip(weights[1..].iter()).fold(values[0].clone() * weights[0], |a, (x, w)| a + x.clone() * *w) + values[1..] + .iter() + .zip(weights[1..].iter()) + .fold(values[0].clone() * weights[0], |a, (x, w)| { + a + x.clone() * *w + }) } } @@ -30,7 +37,10 @@ macro_rules! impl_denormalize { ((self * scale as $this).max(0.0) as $other).min(scale - 1) } - fn denormalize_array(this: [Self; N], other: [$other; N]) -> [$other; N] { + fn denormalize_array( + this: [Self; N], + other: [$other; N], + ) -> [$other; N] { let mut out = [0; N]; (0..N).for_each(|i| out[i] = this[i].denormalize_to(other[i])); out diff --git a/src/pipeline.rs b/src/pipeline.rs index 9fc3914..6e39d0a 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -1,16 +1,9 @@ use crate::{ + buffer::Buffer2d, math::WeightedSum, primitives::PrimitiveKind, rasterizer::Rasterizer, texture::Target, - rasterizer::Rasterizer, - primitives::PrimitiveKind, - math::WeightedSum, - buffer::Buffer2d, -}; -use alloc::{vec::Vec, collections::VecDeque}; -use core::{ - cmp::Ordering, - ops::Range, - borrow::Borrow, }; +use alloc::{collections::VecDeque, vec::Vec}; +use core::{borrow::Borrow, cmp::Ordering, ops::Range}; #[cfg(feature = "micromath")] use micromath_::F32Ext; @@ -66,13 +59,9 @@ pub struct PixelMode { } impl PixelMode { - pub const WRITE: Self = Self { - write: true, - }; + pub const WRITE: Self = Self { write: true }; - pub const PASS: Self = Self { - write: false, - }; + pub const PASS: Self = Self { write: false }; } impl Default for PixelMode { @@ -176,23 +165,34 @@ pub trait Pipeline: Sized { /// Returns the [`PixelMode`] of this pipeline. #[inline] - fn pixel_mode(&self) -> PixelMode { PixelMode::default() } + fn pixel_mode(&self) -> PixelMode { + PixelMode::default() + } /// Returns the [`DepthMode`] of this pipeline. #[inline] - fn depth_mode(&self) -> DepthMode { DepthMode::NONE } + fn depth_mode(&self) -> DepthMode { + DepthMode::NONE + } /// Returns the [`CoordinateMode`] of this pipeline. #[inline] - fn coordinate_mode(&self) -> CoordinateMode { CoordinateMode::default() } + fn coordinate_mode(&self) -> CoordinateMode { + CoordinateMode::default() + } /// Returns the [`AaMode`] of this pipeline. #[inline] - fn aa_mode(&self) -> AaMode { AaMode::None } + fn aa_mode(&self) -> AaMode { + AaMode::None + } /// Returns the rasterizer configuration (usually [`CullMode`], when using [`Triangles`]) of this pipeline. #[inline] - fn rasterizer_config(&self) -> <>::Rasterizer as Rasterizer>::Config { + fn rasterizer_config( + &self, + ) -> <>::Rasterizer as Rasterizer>::Config + { Default::default() } @@ -206,8 +206,11 @@ pub trait Pipeline: Sized { /// /// This stage sits between the vertex shader and the fragment shader. #[inline] - fn geometry(&self, primitive: >::Primitive, mut output: O) - where + fn geometry( + &self, + primitive: >::Primitive, + mut output: O, + ) where O: FnMut(>::Primitive), { output(primitive); @@ -230,12 +233,7 @@ pub trait Pipeline: Sized { /// Render a stream of vertices to given provided pixel target and depth target using the rasterizer. /// /// **Do not implement this method** - fn render( - &self, - vertices: S, - pixel: &mut P, - depth: &mut D, - ) + fn render(&self, vertices: S, pixel: &mut P, depth: &mut D) where Self: Send + Sync, S: IntoIterator, @@ -249,24 +247,31 @@ pub trait Pipeline: Sized { (false, true) => depth.size(), (true, true) => { // Ensure that the pixel target and depth target are compatible - assert_eq!(pixel.size(), depth.size(), "Pixel target size is compatible with depth target size"); + assert_eq!( + pixel.size(), + depth.size(), + "Pixel target size is compatible with depth target size" + ); // Prefer pixel.size() - }, + } }; // Produce an iterator over vertices (using the vertex shader and geometry shader to produce them) - let mut vert_outs = vertices.into_iter().map(|v| self.vertex(v.borrow())).peekable(); + let mut vert_outs = vertices + .into_iter() + .map(|v| self.vertex(v.borrow())) + .peekable(); let mut vert_out_queue = VecDeque::new(); - let fetch_vertex = core::iter::from_fn(move || { - loop { - match vert_out_queue.pop_front() { - Some(v) => break Some(v), - None if vert_outs.peek().is_none() => break None, - None => { - let prim = Self::Primitives::collect_primitive(&mut vert_outs)?; - self.geometry(prim, |prim| Self::Primitives::primitive_vertices(prim, |v| vert_out_queue.push_back(v))); - }, + let fetch_vertex = core::iter::from_fn(move || loop { + match vert_out_queue.pop_front() { + Some(v) => break Some(v), + None if vert_outs.peek().is_none() => break None, + None => { + let prim = Self::Primitives::collect_primitive(&mut vert_outs)?; + self.geometry(prim, |prim| { + Self::Primitives::primitive_vertices(prim, |v| vert_out_queue.push_back(v)) + }); } } }); @@ -292,15 +297,14 @@ fn render_par( pixel: &mut P, depth: &mut D, msaa_level: usize, -) -where +) where Pipe: Pipeline + Send + Sync, S: Iterator, P: Target + Send + Sync, D: Target + Send + Sync, { - use std::thread; use core::sync::atomic::{AtomicUsize, Ordering}; + use std::thread; // TODO: Don't pull all vertices at once let vertices = fetch_vertex.collect::>(); @@ -331,7 +335,17 @@ where let tgt_max = [tgt_size[0], row_end]; // Safety: we have exclusive access to our specific regions of `pixel` and `depth` // TODO: Actually, unchecked_exclusive is UB, fix this - unsafe { render_inner(pipeline, vertices.iter().cloned(), (tgt_min, tgt_max), tgt_size, pixel, depth, msaa_level) } + unsafe { + render_inner( + pipeline, + vertices.iter().cloned(), + (tgt_min, tgt_max), + tgt_size, + pixel, + depth, + msaa_level, + ) + } } }); } @@ -346,8 +360,7 @@ fn render_seq( pixel: &mut P, depth: &mut D, msaa_level: usize, -) -where +) where Pipe: Pipeline + Send + Sync, S: Iterator, P: Target + Send + Sync, @@ -355,7 +368,17 @@ where { // Safety: we have exclusive access to `pixel` and `depth` // TODO: Actually, unchecked_exclusive is UB, fix this - unsafe { render_inner(pipeline, fetch_vertex, ([0; 2], tgt_size), tgt_size, pixel, depth, msaa_level) } + unsafe { + render_inner( + pipeline, + fetch_vertex, + ([0; 2], tgt_size), + tgt_size, + pixel, + depth, + msaa_level, + ) + } } unsafe fn render_inner( @@ -366,8 +389,7 @@ unsafe fn render_inner( pixel: &P, depth: &D, msaa_level: usize, -) -where +) where Pipe: Pipeline + Send + Sync, S: Iterator, P: Target + Send + Sync, @@ -378,12 +400,36 @@ where for i in 0..2 { // Safety check if write_pixels { - assert!(tgt_min[i] <= pixel.size()[i], "{}, {}, {}", i, tgt_min[i], pixel.size()[i]); - assert!(tgt_max[i] <= pixel.size()[i], "{}, {}, {}", i, tgt_min[i], pixel.size()[i]); + assert!( + tgt_min[i] <= pixel.size()[i], + "{}, {}, {}", + i, + tgt_min[i], + pixel.size()[i] + ); + assert!( + tgt_max[i] <= pixel.size()[i], + "{}, {}, {}", + i, + tgt_min[i], + pixel.size()[i] + ); } if depth_mode.uses_depth() { - assert!(tgt_min[i] <= depth.size()[i], "{}, {}, {}", i, tgt_min[i], depth.size()[i]); - assert!(tgt_max[i] <= depth.size()[i], "{}, {}, {}", i, tgt_min[i], depth.size()[i]); + assert!( + tgt_min[i] <= depth.size()[i], + "{}, {}, {}", + i, + tgt_min[i], + depth.size()[i] + ); + assert!( + tgt_max[i] <= depth.size()[i], + "{}, {}, {}", + i, + tgt_min[i], + depth.size()[i] + ); } } @@ -415,9 +461,14 @@ where D: Target + Send + Sync, { #[inline] - unsafe fn msaa_fragment Pipe::VertexData>(&mut self, pos: [usize; 2], mut get_v_data: F) -> Pipe::Fragment { + unsafe fn msaa_fragment Pipe::VertexData>( + &mut self, + pos: [usize; 2], + mut get_v_data: F, + ) -> Pipe::Fragment { // Safety: MSAA buffer will always be large enough - let texel = self.msaa_buf + let texel = self + .msaa_buf .as_mut() .unwrap() .get_mut([pos[0] + 1, pos[1] + 1]); @@ -426,7 +477,10 @@ where texel.1 = Some(self.pipeline.fragment(get_v_data(pos))); } // Safety: We know this entry will always be occupied due to the code above - texel.1.clone().unwrap_or_else(|| core::hint::unreachable_unchecked()) + texel + .1 + .clone() + .unwrap_or_else(|| core::hint::unreachable_unchecked()) } } @@ -436,9 +490,15 @@ where P: Target + Send + Sync, D: Target + Send + Sync, { - fn target_size(&self) -> [usize; 2] { self.tgt_size } - fn target_min(&self) -> [usize; 2] { self.tgt_min } - fn target_max(&self) -> [usize; 2] { self.tgt_max } + fn target_size(&self) -> [usize; 2] { + self.tgt_size + } + fn target_min(&self) -> [usize; 2] { + self.tgt_min + } + fn target_max(&self) -> [usize; 2] { + self.tgt_max + } #[inline] fn begin_primitive(&mut self) { @@ -456,18 +516,30 @@ where } #[inline] - unsafe fn emit_fragment Pipe::VertexData>(&mut self, pos: [usize; 2], mut get_v_data: F, z: f32) { + unsafe fn emit_fragment Pipe::VertexData>( + &mut self, + pos: [usize; 2], + mut get_v_data: F, + z: f32, + ) { if self.depth_mode.write { self.depth.write_exclusive_unchecked(pos, z); } if self.write_pixels { let frag = if self.msaa_level == 0 { - self.pipeline.fragment(get_v_data([pos[0] as f32, pos[1] as f32])) + self.pipeline + .fragment(get_v_data([pos[0] as f32, pos[1] as f32])) } else { - let fract = [(pos[0], self.tgt_min[0]), (pos[1], self.tgt_min[1])] - .map(|(e, tgt_min)| ((e - tgt_min) as f32 / (1 << self.msaa_level) as f32).fract()); - let posi = [(pos[0] - self.tgt_min[0]) >> self.msaa_level, (pos[1] - self.tgt_min[1]) >> self.msaa_level]; + let fract = [(pos[0], self.tgt_min[0]), (pos[1], self.tgt_min[1])].map( + |(e, tgt_min)| { + ((e - tgt_min) as f32 / (1 << self.msaa_level) as f32).fract() + }, + ); + let posi = [ + (pos[0] - self.tgt_min[0]) >> self.msaa_level, + (pos[1] - self.tgt_min[1]) >> self.msaa_level, + ]; let tgt_min = self.tgt_min; let msaa_level = self.msaa_level; @@ -519,7 +591,10 @@ where msaa_level, msaa_buf: if msaa_level > 0 { Some(Buffer2d::fill_with( - [((tgt_max[0] - tgt_min[0]) >> msaa_level) + 3, ((tgt_max[1] - tgt_min[1]) >> msaa_level) + 3], + [ + ((tgt_max[0] - tgt_min[0]) >> msaa_level) + 3, + ((tgt_max[1] - tgt_min[1]) >> msaa_level) + 3, + ], || (u64::MAX, None), )) } else { diff --git a/src/primitives.rs b/src/primitives.rs index 4df63c9..2807663 100644 --- a/src/primitives.rs +++ b/src/primitives.rs @@ -1,4 +1,4 @@ -use crate::rasterizer::{Rasterizer, Triangles, Lines}; +use crate::rasterizer::{Lines, Rasterizer, Triangles}; use core::marker::PhantomData; pub trait PrimitiveKind { @@ -28,7 +28,7 @@ impl PrimitiveKind for TriangleList { #[inline] fn collect_primitive(mut iter: I) -> Option where - I: Iterator + I: Iterator, { Some([iter.next()?, iter.next()?, iter.next()?]) } @@ -36,7 +36,7 @@ impl PrimitiveKind for TriangleList { #[inline] fn primitive_vertices([a, b, c]: Self::Primitive, mut output: O) where - O: FnMut(([f32; 4], V)) + O: FnMut(([f32; 4], V)), { output(a); output(b); @@ -56,7 +56,7 @@ impl PrimitiveKind for LineTriangleList { #[inline] fn collect_primitive(mut iter: I) -> Option where - I: Iterator + I: Iterator, { Some([iter.next()?, iter.next()?, iter.next()?]) } @@ -64,7 +64,7 @@ impl PrimitiveKind for LineTriangleList { #[inline] fn primitive_vertices([a, b, c]: Self::Primitive, mut output: O) where - O: FnMut(([f32; 4], V)) + O: FnMut(([f32; 4], V)), { output(a.clone()); output(b.clone()); @@ -89,7 +89,7 @@ impl PrimitiveKind for LineList { #[inline] fn collect_primitive(mut iter: I) -> Option where - I: Iterator + I: Iterator, { Some([iter.next()?, iter.next()?]) } @@ -97,7 +97,7 @@ impl PrimitiveKind for LineList { #[inline] fn primitive_vertices([a, b]: Self::Primitive, mut output: O) where - O: FnMut(([f32; 4], V)) + O: FnMut(([f32; 4], V)), { output(a); output(b); diff --git a/src/rasterizer/lines.rs b/src/rasterizer/lines.rs index dbdca8b..1dce6d3 100644 --- a/src/rasterizer/lines.rs +++ b/src/rasterizer/lines.rs @@ -20,8 +20,7 @@ impl Rasterizer for Lines { coordinate_mode: CoordinateMode, cull_mode: CullMode, mut blitter: B, - ) - where + ) where V: Clone + WeightedSum, I: Iterator, B: Blitter, @@ -49,16 +48,25 @@ impl Rasterizer for Lines { [0.0, 0.0, 1.0], ]); - let verts_hom_out = core::iter::from_fn(move || { - Some(Vec2::new(vertices.next()?, vertices.next()?)) - }); + let verts_hom_out = + core::iter::from_fn(move || Some(Vec2::new(vertices.next()?, vertices.next()?))); verts_hom_out.for_each(|verts_hom_out: Vec2<([f32; 4], V)>| { blitter.begin_primitive(); // Calculate vertex shader outputs and vertex homogeneous coordinates - let verts_hom = Vec3::new(verts_hom_out.x.0, verts_hom_out.y.0, verts_hom_out.x.0).map(Vec4::::from) + Vec3::new(Vec4::zero(), Vec4::zero(), Vec4::new(0.001, 0.001, 0.0, 0.0)); - let verts_out = Vec3::new(verts_hom_out.x.1.clone(), verts_hom_out.y.1, verts_hom_out.x.1); + let verts_hom = Vec3::new(verts_hom_out.x.0, verts_hom_out.y.0, verts_hom_out.x.0) + .map(Vec4::::from) + + Vec3::new( + Vec4::zero(), + Vec4::zero(), + Vec4::new(0.001, 0.001, 0.0, 0.0), + ); + let verts_out = Vec3::new( + verts_hom_out.x.1.clone(), + verts_hom_out.y.1, + verts_hom_out.x.1, + ); let verts_hom = verts_hom.map(|v| v * Vec4::new(flip.x, flip.y, 1.0, 1.0)); @@ -77,11 +85,16 @@ impl Rasterizer for Lines { 1.0 }; - Mat3::from_row_arrays([cb.cross(c), c.cross(ca), n].map(|v| v.into_array())) * rec_det * to_ndc + Mat3::from_row_arrays([cb.cross(c), c.cross(ca), n].map(|v| v.into_array())) + * rec_det + * to_ndc }; // Ensure we didn't accidentally end up with infinities or NaNs - assert!(coords_to_weights.into_row_array().iter().all(|e| e.is_finite())); + assert!(coords_to_weights + .into_row_array() + .iter() + .all(|e| e.is_finite())); // Convert vertex coordinates to screen space let verts_screen = verts_euc.map(|euc| size * (euc.xy() * Vec2::new(0.5, -0.5) + 0.5)); @@ -90,8 +103,12 @@ impl Rasterizer for Lines { let screen_min = Vec2::::from(tgt_min).map(|e| e as f32); let screen_max = Vec2::::from(tgt_max).map(|e| e as f32); let tri_bounds_clamped = Aabr:: { - min: (verts_screen.reduce(|a, b| Vec2::partial_min(a, b)) + 0.0).clamped(screen_min, screen_max).as_(), - max: (verts_screen.reduce(|a, b| Vec2::partial_max(a, b)) + 1.0).clamped(screen_min, screen_max).as_(), + min: (verts_screen.reduce(|a, b| Vec2::partial_min(a, b)) + 0.0) + .clamped(screen_min, screen_max) + .as_(), + max: (verts_screen.reduce(|a, b| Vec2::partial_max(a, b)) + 1.0) + .clamped(screen_min, screen_max) + .as_(), }; // Calculate change in vertex weights for each pixel @@ -114,14 +131,19 @@ impl Rasterizer for Lines { // TODO: This sucks. A lot. It uses 3-vertex homogeneous coordinates with the last vertex being very close // to the first, it does loads of unnecessary work for stuff outside the viewport, and it's not even fast. for (x, y) in - // [ - // verts_screen.x.as_().into_tuple(), - // verts_screen.y.as_().into_tuple(), - // ] - bresenham::Bresenham::new(verts_clamped.x.as_().into_tuple(), verts_clamped.y.as_().into_tuple()) + // [ + // verts_screen.x.as_().into_tuple(), + // verts_screen.y.as_().into_tuple(), + // ] + bresenham::Bresenham::new( + verts_clamped.x.as_().into_tuple(), + verts_clamped.y.as_().into_tuple(), + ) { - if (tri_bounds_clamped.min.x as isize..tri_bounds_clamped.max.x as isize).contains(&x) && - (tri_bounds_clamped.min.y as isize..tri_bounds_clamped.max.y as isize).contains(&y) + if (tri_bounds_clamped.min.x as isize..tri_bounds_clamped.max.x as isize) + .contains(&x) + && (tri_bounds_clamped.min.y as isize..tri_bounds_clamped.max.y as isize) + .contains(&y) { let (x, y) = (x as usize, y as usize); // Find the barycentric weights for the start of this row @@ -134,12 +156,19 @@ impl Rasterizer for Lines { if blitter.test_fragment([x, y], z) { // Don't use `.contains(&z)`, it isn't inclusive - if coordinate_mode.z_clip_range.clone().map_or(true, |clip_range| z >= clip_range.start && z <= clip_range.end) { + if coordinate_mode + .z_clip_range + .clone() + .map_or(true, |clip_range| { + z >= clip_range.start && z <= clip_range.end + }) + { let get_v_data = |[x, y]: [f32; 2]| { let w_hom = w_hom_origin + w_hom_dy * y + w_hom_dx * x; // Calculate vertex weights to determine vs_out lerping and intersection - let w_unbalanced = Vec3::new(w_hom.x, w_hom.y, w_hom.z - w_hom.x - w_hom.y); + let w_unbalanced = + Vec3::new(w_hom.x, w_hom.y, w_hom.z - w_hom.x - w_hom.y); let w = w_unbalanced * w_hom.z.recip(); let w = Vec2::new(w.x.max(w.z), w.y); diff --git a/src/rasterizer/mod.rs b/src/rasterizer/mod.rs index 2d3aba0..b92652b 100644 --- a/src/rasterizer/mod.rs +++ b/src/rasterizer/mod.rs @@ -1,12 +1,9 @@ pub mod lines; pub mod triangles; -pub use self::{ - lines::Lines, - triangles::Triangles, -}; +pub use self::{lines::Lines, triangles::Triangles}; -use crate::{CoordinateMode, math::WeightedSum}; +use crate::{math::WeightedSum, CoordinateMode}; /// The face culling strategy used during rendering. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] @@ -47,7 +44,12 @@ pub trait Blitter: Sized { /// # Safety /// /// This function *must* be called with a position that is valid for size and bounds that this type provides. - unsafe fn emit_fragment V>(&mut self, pos: [usize; 2], get_v_data: F, z: f32); + unsafe fn emit_fragment V>( + &mut self, + pos: [usize; 2], + get_v_data: F, + z: f32, + ); } /// A trait that represents types that turn vertex streams into fragment coordinates. @@ -77,8 +79,7 @@ pub trait Rasterizer: Default { coordinate_mode: CoordinateMode, config: Self::Config, blitter: B, - ) - where + ) where V: Clone + WeightedSum, I: Iterator, B: Blitter; diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index 839ead7..190088e 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -20,8 +20,7 @@ impl Rasterizer for Triangles { coordinate_mode: CoordinateMode, cull_mode: CullMode, mut blitter: B, - ) - where + ) where V: Clone + WeightedSum, I: Iterator, B: Blitter, @@ -50,14 +49,19 @@ impl Rasterizer for Triangles { ]); let verts_hom_out = core::iter::from_fn(move || { - Some(Vec3::new(vertices.next()?, vertices.next()?, vertices.next()?)) + Some(Vec3::new( + vertices.next()?, + vertices.next()?, + vertices.next()?, + )) }); verts_hom_out.for_each(|verts_hom_out: Vec3<([f32; 4], V)>| { blitter.begin_primitive(); // Calculate vertex shader outputs and vertex homogeneous coordinates - let verts_hom = Vec3::new(verts_hom_out.x.0, verts_hom_out.y.0, verts_hom_out.z.0).map(Vec4::::from); + let verts_hom = Vec3::new(verts_hom_out.x.0, verts_hom_out.y.0, verts_hom_out.z.0) + .map(Vec4::::from); let verts_out = Vec3::new(verts_hom_out.x.1, verts_hom_out.y.1, verts_hom_out.z.1); let verts_hom = verts_hom.map(|v| v * Vec4::new(flip.x, flip.y, 1.0, 1.0)); @@ -66,7 +70,9 @@ impl Rasterizer for Triangles { let verts_euc = verts_hom.map(|v_hom| v_hom.xyz() / v_hom.w); // Calculate winding direction to determine culling behaviour - let winding = (verts_euc.y - verts_euc.x).cross(verts_euc.z - verts_euc.x).z; + let winding = (verts_euc.y - verts_euc.x) + .cross(verts_euc.z - verts_euc.x) + .z; // Culling and correcting for winding let (verts_hom, verts_euc, verts_out) = if cull_dir @@ -93,11 +99,16 @@ impl Rasterizer for Triangles { 1.0 }; - Mat3::from_row_arrays([cb.cross(c), c.cross(ca), n].map(|v| v.into_array())) * rec_det * to_ndc + Mat3::from_row_arrays([cb.cross(c), c.cross(ca), n].map(|v| v.into_array())) + * rec_det + * to_ndc }; // Ensure we didn't accidentally end up with infinities or NaNs - assert!(coords_to_weights.into_row_array().iter().all(|e| e.is_finite())); + assert!(coords_to_weights + .into_row_array() + .iter() + .all(|e| e.is_finite())); // Convert vertex coordinates to screen space let verts_screen = verts_euc.map(|euc| size * (euc.xy() * Vec2::new(0.5, -0.5) + 0.5)); @@ -106,8 +117,12 @@ impl Rasterizer for Triangles { let screen_min = Vec2::::from(tgt_min).map(|e| e as f32); let screen_max = Vec2::::from(tgt_max).map(|e| e as f32); let tri_bounds_clamped = Aabr:: { - min: (verts_screen.reduce(|a, b| Vec2::partial_min(a, b)) + 0.0).clamped(screen_min, screen_max).as_(), - max: (verts_screen.reduce(|a, b| Vec2::partial_max(a, b)) + 1.0).clamped(screen_min, screen_max).as_(), + min: (verts_screen.reduce(|a, b| Vec2::partial_min(a, b)) + 0.0) + .clamped(screen_min, screen_max) + .as_(), + max: (verts_screen.reduce(|a, b| Vec2::partial_max(a, b)) + 1.0) + .clamped(screen_min, screen_max) + .as_(), }; // Calculate change in vertex weights for each pixel @@ -161,7 +176,9 @@ impl Rasterizer for Triangles { // Now we have screen-space bounds for the row. Clean it up and clamp it to the screen bounds let row_range = Vec2::new( - (row_bounds.x as usize).saturating_sub(1).max(tri_bounds_clamped.min.x), + (row_bounds.x as usize) + .saturating_sub(1) + .max(tri_bounds_clamped.min.x), (row_bounds.y.ceil() as usize).min(tri_bounds_clamped.max.x), ); @@ -183,12 +200,19 @@ impl Rasterizer for Triangles { if blitter.test_fragment([x, y], z) { // Don't use `.contains(&z)`, it isn't inclusive - if coordinate_mode.z_clip_range.clone().map_or(true, |clip_range| z >= clip_range.start && z <= clip_range.end) { + if coordinate_mode + .z_clip_range + .clone() + .map_or(true, |clip_range| { + z >= clip_range.start && z <= clip_range.end + }) + { let get_v_data = |[x, y]: [f32; 2]| { let w_hom = w_hom_origin + w_hom_dy * y + w_hom_dx * x; // Calculate vertex weights to determine vs_out lerping and intersection - let w_unbalanced = Vec3::new(w_hom.x, w_hom.y, w_hom.z - w_hom.x - w_hom.y); + let w_unbalanced = + Vec3::new(w_hom.x, w_hom.y, w_hom.z - w_hom.x - w_hom.y); let w = w_unbalanced * w_hom.z.recip(); V::weighted_sum(verts_out.as_slice(), w.as_slice()) diff --git a/src/sampler/linear.rs b/src/sampler/linear.rs index 65ac21b..553cf5b 100644 --- a/src/sampler/linear.rs +++ b/src/sampler/linear.rs @@ -1,7 +1,7 @@ use super::*; use core::{ - ops::{Add, Mul}, marker::PhantomData, + ops::{Add, Mul}, }; #[cfg(feature = "micromath")] @@ -22,7 +22,9 @@ where type Texture = T; #[inline(always)] - fn raw_texture(&self) -> &Self::Texture { &self.0 } + fn raw_texture(&self) -> &Self::Texture { + &self.0 + } #[inline(always)] fn sample(&self, index: [Self::Index; 2]) -> Self::Sample { @@ -32,19 +34,46 @@ where let size = self.raw_texture().size(); let size_f32 = size.map(|e| e as f32); // Index in texture coordinates - let index_tex = [index[0].fract() * size_f32[0], index[1].fract() * size_f32[1]]; + let index_tex = [ + index[0].fract() * size_f32[0], + index[1].fract() * size_f32[1], + ]; // Find texel sample coordinates let posi = index_tex.map(|e| e.trunc() as usize); // Find interpolation values let fract = index_tex.map(|e| e.fract()); - assert!(posi[0] < size[0], "pos: {:?}, sz: {:?}, idx: {:?}", posi, size, index); - assert!(posi[1] < size[1], "pos: {:?}, sz: {:?}, idx: {:?}", posi, size, index); + assert!( + posi[0] < size[0], + "pos: {:?}, sz: {:?}, idx: {:?}", + posi, + size, + index + ); + assert!( + posi[1] < size[1], + "pos: {:?}, sz: {:?}, idx: {:?}", + posi, + size, + index + ); - let t00 = self.raw_texture().read([(posi[0] + 0).min(size[0] - 1), (posi[1] + 0).min(size[1] - 1)]); - let t10 = self.raw_texture().read([(posi[0] + 1).min(size[0] - 1), (posi[1] + 0).min(size[1] - 1)]); - let t01 = self.raw_texture().read([(posi[0] + 0).min(size[0] - 1), (posi[1] + 1).min(size[1] - 1)]); - let t11 = self.raw_texture().read([(posi[0] + 1).min(size[0] - 1), (posi[1] + 1).min(size[1] - 1)]); + let t00 = self.raw_texture().read([ + (posi[0] + 0).min(size[0] - 1), + (posi[1] + 0).min(size[1] - 1), + ]); + let t10 = self.raw_texture().read([ + (posi[0] + 1).min(size[0] - 1), + (posi[1] + 0).min(size[1] - 1), + ]); + let t01 = self.raw_texture().read([ + (posi[0] + 0).min(size[0] - 1), + (posi[1] + 1).min(size[1] - 1), + ]); + let t11 = self.raw_texture().read([ + (posi[0] + 1).min(size[0] - 1), + (posi[1] + 1).min(size[1] - 1), + ]); let t0 = t00 * (1.0 - fract[1]) + t01 * fract[1]; let t1 = t10 * (1.0 - fract[1]) + t11 * fract[1]; diff --git a/src/sampler/mod.rs b/src/sampler/mod.rs index 57a2952..f6f4648 100644 --- a/src/sampler/mod.rs +++ b/src/sampler/mod.rs @@ -1,15 +1,9 @@ -pub mod nearest; pub mod linear; +pub mod nearest; -pub use self::{ - nearest::Nearest, - linear::Linear, -}; +pub use self::{linear::Linear, nearest::Nearest}; -use crate::{ - texture::Texture, - math::*, -}; +use crate::{math::*, texture::Texture}; /// A trait that describes a sampler of a texture. /// @@ -54,17 +48,32 @@ pub trait Sampler { /// Create a version of this sampler that clamps the index to the bounds of the sampler. /// /// See [`Clamped`]. - fn clamped(self) -> Clamped where Self: Sized { Clamped(self) } + fn clamped(self) -> Clamped + where + Self: Sized, + { + Clamped(self) + } /// Create a version of this sampler that repeats the sampler when sampled out of bounds. /// /// See [`Tiled`]. - fn tiled(self) -> Tiled where Self: Sized { Tiled(self) } + fn tiled(self) -> Tiled + where + Self: Sized, + { + Tiled(self) + } /// Create a version of this sampler that repeats the sampler when sampled out of bounds, mirroring it at each edge. /// /// See [`Tiled`]. - fn mirrored(self) -> Mirrored where Self: Sized { Mirrored(self) } + fn mirrored(self) -> Mirrored + where + Self: Sized, + { + Mirrored(self) + } } impl<'a, S: Sampler, const N: usize> Sampler for &'a S { @@ -72,9 +81,15 @@ impl<'a, S: Sampler, const N: usize> Sampler for &'a S { type Sample = S::Sample; type Texture = S::Texture; - fn raw_texture(&self) -> &Self::Texture { (*self).raw_texture() } - fn sample(&self, index: [Self::Index; N]) -> Self::Sample { (*self).sample(index) } - unsafe fn sample_unchecked(&self, index: [Self::Index; N]) -> Self::Sample { (*self).sample_unchecked(index) } + fn raw_texture(&self) -> &Self::Texture { + (*self).raw_texture() + } + fn sample(&self, index: [Self::Index; N]) -> Self::Sample { + (*self).sample(index) + } + unsafe fn sample_unchecked(&self, index: [Self::Index; N]) -> Self::Sample { + (*self).sample_unchecked(index) + } } /// A sampler that clamps the index's components to the 0.0 <= x <= 1.0 range. @@ -86,7 +101,9 @@ impl, const N: usize> Sampler for Clamped { type Sample = S::Sample; type Texture = S::Texture; - fn raw_texture(&self) -> &Self::Texture { self.0.raw_texture() } + fn raw_texture(&self) -> &Self::Texture { + self.0.raw_texture() + } fn sample(&self, index: [Self::Index; N]) -> Self::Sample { let index = index.map(|e| e.max(0.0).min(1.0)); self.0.sample(index) @@ -108,7 +125,9 @@ impl, const N: usize> Sampler for Tiled { type Sample = S::Sample; type Texture = S::Texture; - fn raw_texture(&self) -> &Self::Texture { self.0.raw_texture() } + fn raw_texture(&self) -> &Self::Texture { + self.0.raw_texture() + } fn sample(&self, index: [Self::Index; N]) -> Self::Sample { let index = index.map(|e| e.rem_euclid(1.0)); self.0.sample(index) @@ -131,20 +150,26 @@ impl, const N: usize> Sampler for Mirrored { type Sample = S::Sample; type Texture = S::Texture; - fn raw_texture(&self) -> &Self::Texture { self.0.raw_texture() } + fn raw_texture(&self) -> &Self::Texture { + self.0.raw_texture() + } fn sample(&self, index: [Self::Index; N]) -> Self::Sample { - let index = index.map(|e| if e.rem_euclid(2.0) >= 1.0 { - 1.0 - e.rem_euclid(1.0) - } else { - e.rem_euclid(1.0) + let index = index.map(|e| { + if e.rem_euclid(2.0) >= 1.0 { + 1.0 - e.rem_euclid(1.0) + } else { + e.rem_euclid(1.0) + } }); self.0.sample(index) } unsafe fn sample_unchecked(&self, index: [Self::Index; N]) -> Self::Sample { - let index = index.map(|e| if e.rem_euclid(2.0) >= 1.0 { - 1.0 - e.rem_euclid(1.0) - } else { - e.rem_euclid(1.0) + let index = index.map(|e| { + if e.rem_euclid(2.0) >= 1.0 { + 1.0 - e.rem_euclid(1.0) + } else { + e.rem_euclid(1.0) + } }); self.0.sample_unchecked(index) } diff --git a/src/sampler/nearest.rs b/src/sampler/nearest.rs index 4189fdd..70d8cb9 100644 --- a/src/sampler/nearest.rs +++ b/src/sampler/nearest.rs @@ -1,8 +1,5 @@ use super::*; -use core::{ - ops::Mul, - marker::PhantomData, -}; +use core::{marker::PhantomData, ops::Mul}; /// A sampler that uses nearest-neighbor sampling. pub struct Nearest { @@ -22,15 +19,21 @@ where type Texture = T; #[inline(always)] - fn raw_texture(&self) -> &Self::Texture { &self.texture } + fn raw_texture(&self) -> &Self::Texture { + &self.texture + } #[inline(always)] fn sample(&self, index: [Self::Index; N]) -> Self::Sample { - unsafe { self.raw_texture().read_unchecked(I::denormalize_array(index, self.raw_texture().size())) } + unsafe { + self.raw_texture() + .read_unchecked(I::denormalize_array(index, self.raw_texture().size())) + } } #[inline(always)] unsafe fn sample_unchecked(&self, index: [Self::Index; N]) -> Self::Sample { - self.raw_texture().read_unchecked(I::denormalize_array(index, self.raw_texture().size())) + self.raw_texture() + .read_unchecked(I::denormalize_array(index, self.raw_texture().size())) } } diff --git a/src/texture.rs b/src/texture.rs index 6904f91..9e8aa55 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -1,5 +1,5 @@ -use core::marker::PhantomData; use super::sampler::{Linear, Nearest}; +use core::marker::PhantomData; /// A trait implemented by types that may be treated as textures. pub trait Texture { @@ -25,7 +25,9 @@ pub trait Texture { /// always the case. This function allows the texture to signal to users what access patterns are most performant. /// /// The default implementation is a principal axis of x, which corresponds to the most common in-memory texture layouts. - fn principal_axis(&self) -> usize { 0 } + fn principal_axis(&self) -> usize { + 0 + } /// Read a texel at the given index. /// @@ -50,13 +52,24 @@ pub trait Texture { /// this texture. /// /// See [`Linear`]. - fn linear(self) -> Linear where Self: Sized { Linear(self, PhantomData) } + fn linear(self) -> Linear + where + Self: Sized, + { + Linear(self, PhantomData) + } /// Create a nearest-neighbour (i.e: unfiltered) sampler from this texture. /// /// See [`Nearest`]. - fn nearest(self) -> Nearest where Self: Sized { - Nearest { texture: self, phantom: PhantomData } + fn nearest(self) -> Nearest + where + Self: Sized, + { + Nearest { + texture: self, + phantom: PhantomData, + } } } @@ -64,22 +77,34 @@ impl<'a, T: Texture, const N: usize> Texture for &'a T { type Index = T::Index; type Texel = T::Texel; #[inline] - fn size(&self) -> [Self::Index; N] { (**self).size() } + fn size(&self) -> [Self::Index; N] { + (**self).size() + } #[inline] - fn read(&self, index: [Self::Index; N]) -> Self::Texel { (**self).read(index) } + fn read(&self, index: [Self::Index; N]) -> Self::Texel { + (**self).read(index) + } #[inline] - unsafe fn read_unchecked(&self, index: [Self::Index; N]) -> Self::Texel { (**self).read_unchecked(index) } + unsafe fn read_unchecked(&self, index: [Self::Index; N]) -> Self::Texel { + (**self).read_unchecked(index) + } } impl<'a, T: Texture, const N: usize> Texture for &'a mut T { type Index = T::Index; type Texel = T::Texel; #[inline] - fn size(&self) -> [Self::Index; N] { (**self).size() } + fn size(&self) -> [Self::Index; N] { + (**self).size() + } #[inline] - fn read(&self, index: [Self::Index; N]) -> Self::Texel { (**self).read(index) } + fn read(&self, index: [Self::Index; N]) -> Self::Texel { + (**self).read(index) + } #[inline] - unsafe fn read_unchecked(&self, index: [Self::Index; N]) -> Self::Texel { (**self).read_unchecked(index) } + unsafe fn read_unchecked(&self, index: [Self::Index; N]) -> Self::Texel { + (**self).read_unchecked(index) + } } // impl<'a, T: Clone, F: Fn([usize; N]) -> T, const N: usize> Texture for (F, [usize; N], PhantomData) { @@ -146,7 +171,9 @@ pub trait Target: Texture<2, Index = usize> { #[inline] fn write(&mut self, [x, y]: [usize; 2], texel: Self::Texel) { if x < self.size()[0] && y < self.size()[1] { - unsafe { self.write_unchecked([x, y], texel); } + unsafe { + self.write_unchecked([x, y], texel); + } } } @@ -155,7 +182,9 @@ pub trait Target: Texture<2, Index = usize> { fn clear(&mut self, texel: Self::Texel) { for y in 0..self.size()[1] { for x in 0..self.size()[0] { - unsafe { self.write_unchecked([x, y], texel.clone()); } + unsafe { + self.write_unchecked([x, y], texel.clone()); + } } } } @@ -163,15 +192,25 @@ pub trait Target: Texture<2, Index = usize> { impl<'a, T: Target> Target for &'a mut T { #[inline] - unsafe fn read_exclusive_unchecked(&self, index: [Self::Index; 2]) -> Self::Texel { T::read_exclusive_unchecked(self, index) } + unsafe fn read_exclusive_unchecked(&self, index: [Self::Index; 2]) -> Self::Texel { + T::read_exclusive_unchecked(self, index) + } #[inline] - unsafe fn write_exclusive_unchecked(&self, index: [usize; 2], texel: Self::Texel) { T::write_exclusive_unchecked(self, index, texel) } + unsafe fn write_exclusive_unchecked(&self, index: [usize; 2], texel: Self::Texel) { + T::write_exclusive_unchecked(self, index, texel) + } #[inline] - unsafe fn write_unchecked(&mut self, index: [usize; 2], texel: Self::Texel) { T::write_unchecked(self, index, texel) } + unsafe fn write_unchecked(&mut self, index: [usize; 2], texel: Self::Texel) { + T::write_unchecked(self, index, texel) + } #[inline] - fn write(&mut self, index: [usize; 2], texel: Self::Texel) { T::write(self, index, texel); } + fn write(&mut self, index: [usize; 2], texel: Self::Texel) { + T::write(self, index, texel); + } #[inline] - fn clear(&mut self, texel: Self::Texel) { T::clear(self, texel); } + fn clear(&mut self, texel: Self::Texel) { + T::clear(self, texel); + } } /// An always-empty texture. Useful as a placeholder for an unused target. @@ -187,14 +226,20 @@ impl Texture for Empty { type Index = usize; type Texel = T; #[inline] - fn size(&self) -> [Self::Index; N] { [0; N] } + fn size(&self) -> [Self::Index; N] { + [0; N] + } #[inline] - fn read(&self, _: [Self::Index; N]) -> Self::Texel { panic!("Cannot read from an empty texture"); } + fn read(&self, _: [Self::Index; N]) -> Self::Texel { + panic!("Cannot read from an empty texture"); + } } impl Target for Empty { #[inline] - unsafe fn read_exclusive_unchecked(&self, _: [Self::Index; 2]) -> Self::Texel { T::default() } + unsafe fn read_exclusive_unchecked(&self, _: [Self::Index; 2]) -> Self::Texel { + T::default() + } #[inline] unsafe fn write_exclusive_unchecked(&self, _: [usize; 2], _: Self::Texel) {} } From 89b43ffa8cc73b4b0d26e34dd61a7072fc032bc3 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 22 Mar 2023 11:35:57 +0000 Subject: [PATCH 29/37] Fixed incorrect triangle bounds calculation --- examples/teapot.rs | 8 +++- src/rasterizer/triangles.rs | 74 ++++++++++++++----------------------- 2 files changed, 35 insertions(+), 47 deletions(-) diff --git a/examples/teapot.rs b/examples/teapot.rs index dd550e7..02a751d 100644 --- a/examples/teapot.rs +++ b/examples/teapot.rs @@ -19,12 +19,17 @@ impl<'a> Pipeline for TeapotShadow<'a> { type Fragment = Unit; type Pixel = (); + #[inline(always)] fn pixel_mode(&self) -> PixelMode { PixelMode::PASS } + + #[inline(always)] fn depth_mode(&self) -> DepthMode { DepthMode::LESS_WRITE } + + #[inline(always)] fn rasterizer_config(&self) -> CullMode { CullMode::None } @@ -43,7 +48,7 @@ impl<'a> Pipeline for TeapotShadow<'a> { } #[inline(always)] - fn blend(&self, old: Self::Pixel, new: Self::Fragment) {} + fn blend(&self, old: Self::Pixel, _new: Self::Fragment) {} } struct Teapot<'a> { @@ -69,6 +74,7 @@ impl<'a> Pipeline for Teapot<'a> { type Fragment = Rgba; type Pixel = u32; + #[inline(always)] fn depth_mode(&self) -> DepthMode { DepthMode::LESS_WRITE } diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index 190088e..52addd3 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -89,9 +89,10 @@ impl Rasterizer for Triangles { // Create a matrix that allows conversion between screen coordinates and interpolation weights let coords_to_weights = { - let c = Vec3::new(verts_hom.z.x, verts_hom.z.y, verts_hom.z.w); - let ca = Vec3::new(verts_hom.x.x, verts_hom.x.y, verts_hom.x.w) - c; - let cb = Vec3::new(verts_hom.y.x, verts_hom.y.y, verts_hom.y.w) - c; + let (a, b, c) = verts_hom.into_tuple(); + let c = Vec3::new(c.x, c.y, c.w); + let ca = Vec3::new(a.x, a.y, a.w) - c; + let cb = Vec3::new(b.x, b.y, b.w) - c; let n = ca.cross(cb); let rec_det = if n.magnitude_squared() > 0.0 { 1.0 / n.dot(c).min(-core::f32::EPSILON) @@ -131,55 +132,36 @@ impl Rasterizer for Triangles { let w_hom_dx = (weights_at(Vec2::unit_x() * 1000.0) - w_hom_origin) / 1000.0; let w_hom_dy = (weights_at(Vec2::unit_y() * 1000.0) - w_hom_origin) / 1000.0; + // First, order vertices by height + let mut verts_by_y = verts_screen; + verts_by_y.sort_unstable_by_key(|e| e.y as isize); + // Iterate over fragment candidates within the triangle's bounding box (tri_bounds_clamped.min.y..tri_bounds_clamped.max.y).for_each(|y| { - // More precisely find the required draw bounds for this row with a little maths - // First, order vertices by height - let verts_by_y = if verts_screen.x.y < verts_screen.y.y.min(verts_screen.z.y) { - if verts_screen.y.y < verts_screen.z.y { - Vec3::new(verts_screen.x, verts_screen.y, verts_screen.z) - } else { - Vec3::new(verts_screen.x, verts_screen.z, verts_screen.y) - } - } else if verts_screen.y.y < verts_screen.x.y.min(verts_screen.z.y) { - if verts_screen.x.y < verts_screen.z.y { - Vec3::new(verts_screen.y, verts_screen.x, verts_screen.z) - } else { - Vec3::new(verts_screen.y, verts_screen.z, verts_screen.x) - } + let Vec3 { x: a, y: b, z: c } = verts_by_y; + // For each of the lines, calculate the point at which our row intersects it + let ac = Lerp::lerp(a.x, c.x, (y as f32 - a.y) / (c.y - a.y)); // Longest side + // Then, depending on the half of the triangle we're in, we need to check different lines + let row_bounds = if (y as f32) < b.y { + let ab = Lerp::lerp(a.x, b.x, (y as f32 - a.y) / (b.y - a.y)); + Vec2::new(ab.min(ac), ab.max(ac)) } else { - if verts_screen.x.y < verts_screen.y.y { - Vec3::new(verts_screen.z, verts_screen.x, verts_screen.y) - } else { - Vec3::new(verts_screen.z, verts_screen.y, verts_screen.x) - } + let bc = Lerp::lerp(b.x, c.x, (y as f32 - b.y) / (c.y - b.y)); + Vec2::new(bc.min(ac), bc.max(ac)) }; - // Then, depending on the half of the triangle we're in, we need to check different lines - let edge_lines = if (y as f32) < verts_by_y.y.y { - Vec2::new((verts_by_y.x, verts_by_y.y), (verts_by_y.x, verts_by_y.z)) - } else { - Vec2::new((verts_by_y.y, verts_by_y.z), (verts_by_y.x, verts_by_y.z)) - }; - - // Finally, for each of the lines, calculate the point at which our row intersects it - let row_bounds = edge_lines - .map(|(a, b)| { - // Could be more efficient - let x = Lerp::lerp(a.x, b.x, (y as f32 - a.y) / (b.y - a.y)); - let x2 = Lerp::lerp(a.x, b.x, (y as f32 + 1.0 - a.y) / (b.y - a.y)); - let (x_min, x_max) = (x.min(x2), x.max(x2)); - Vec2::new(x_min, x_max) - }) - .reduce(|a, b| Vec2::new(a.x.min(b.x), a.y.max(b.y))) - .map(|e| e.max(0.0)); - // Now we have screen-space bounds for the row. Clean it up and clamp it to the screen bounds - let row_range = Vec2::new( - (row_bounds.x as usize) - .saturating_sub(1) - .max(tri_bounds_clamped.min.x), - (row_bounds.y.ceil() as usize).min(tri_bounds_clamped.max.x), + let row_range = Vec2::new(row_bounds.x.floor(), row_bounds.y.ceil()).map2( + Vec2::new(tri_bounds_clamped.min.x, tri_bounds_clamped.max.x), + |e, b| { + if e >= tri_bounds_clamped.min.x as f32 + && e < tri_bounds_clamped.max.x as f32 + { + e as usize + } else { + b + } + }, ); // Stupid version From 994b1ddbd2e94d7fdb3bb74b07658976e625358c Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 16 Sep 2023 12:41:29 +0100 Subject: [PATCH 30/37] Updated image --- Cargo.toml | 2 +- src/primitives.rs | 1 - src/rasterizer/lines.rs | 8 +------- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4af3220..86a3872 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ exclude = [ [dependencies] vek = { version = "0.15", default-features = false, features = [] } -image_ = { package = "image", version = "0.23", optional = true } +image_ = { package = "image", version = "0.24", optional = true } num_cpus = { version = "1.13", optional = true } fxhash = { version = "0.2", optional = true } micromath_ = { package = "micromath", version = "1.1", optional = true } diff --git a/src/primitives.rs b/src/primitives.rs index 2807663..075f66a 100644 --- a/src/primitives.rs +++ b/src/primitives.rs @@ -1,5 +1,4 @@ use crate::rasterizer::{Lines, Rasterizer, Triangles}; -use core::marker::PhantomData; pub trait PrimitiveKind { type Rasterizer: Rasterizer; diff --git a/src/rasterizer/lines.rs b/src/rasterizer/lines.rs index 1dce6d3..ba0aaf1 100644 --- a/src/rasterizer/lines.rs +++ b/src/rasterizer/lines.rs @@ -18,7 +18,7 @@ impl Rasterizer for Lines { mut vertices: I, _principal_x: bool, coordinate_mode: CoordinateMode, - cull_mode: CullMode, + _cull_mode: CullMode, mut blitter: B, ) where V: Clone + WeightedSum, @@ -29,12 +29,6 @@ impl Rasterizer for Lines { let tgt_min = blitter.target_min(); let tgt_max = blitter.target_max(); - let cull_dir = match cull_mode { - CullMode::None => None, - CullMode::Back => Some(1.0), - CullMode::Front => Some(-1.0), - }; - let flip = match coordinate_mode.y_axis_direction { YAxisDirection::Down => Vec2::new(1.0, 1.0), YAxisDirection::Up => Vec2::new(1.0, -1.0), From 764cc47c402ae1c0b05822e5070bb0bbf4d027f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nurzhan=20Sak=C3=A9n?= Date: Wed, 25 Oct 2023 20:54:32 +0400 Subject: [PATCH 31/37] Replaced `bresenham` with `clipline` (untested) --- Cargo.toml | 2 +- src/rasterizer/lines.rs | 23 ++++++++++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 86a3872..72f5537 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ image_ = { package = "image", version = "0.24", optional = true } num_cpus = { version = "1.13", optional = true } fxhash = { version = "0.2", optional = true } micromath_ = { package = "micromath", version = "1.1", optional = true } -bresenham = "0.1" +clipline = { version = "0.1.0" } [features] default = ["std", "image", "par"] diff --git a/src/rasterizer/lines.rs b/src/rasterizer/lines.rs index ba0aaf1..b18eeee 100644 --- a/src/rasterizer/lines.rs +++ b/src/rasterizer/lines.rs @@ -1,5 +1,6 @@ use super::*; use crate::{CoordinateMode, YAxisDirection}; +use core::cmp::max; use vek::*; #[cfg(feature = "micromath")] @@ -121,19 +122,19 @@ impl Rasterizer for Lines { a })*/; + let (x1, y1) = verts_screen.x.as_::().into_tuple(); + let (x2, y2) = { + let (x2, y2) = verts_screen.y.as_::().into_tuple(); + // `clipline` uses inclusive ranges, might be slightly wrong to just subtract 1 + (max((x2 - 1), x1), max((y2 - 1), y1)) + }; + + let (wx1, wy1) = screen_min.as_::().into_tuple(); + let (wx2, wy2) = screen_max.as_::().into_tuple(); // TODO: This sucks. A lot. It uses 3-vertex homogeneous coordinates with the last vertex being very close // to the first, it does loads of unnecessary work for stuff outside the viewport, and it's not even fast. - for (x, y) in - // [ - // verts_screen.x.as_().into_tuple(), - // verts_screen.y.as_().into_tuple(), - // ] - bresenham::Bresenham::new( - verts_clamped.x.as_().into_tuple(), - verts_clamped.y.as_().into_tuple(), - ) - { + clipline::clipline(((x1, y1), (x2, y2)), ((wx1, wy1), (wx2, wy2)), |x, y| { if (tri_bounds_clamped.min.x as isize..tri_bounds_clamped.max.x as isize) .contains(&x) && (tri_bounds_clamped.min.y as isize..tri_bounds_clamped.max.y as isize) @@ -173,7 +174,7 @@ impl Rasterizer for Lines { } } } - } + }); }); } } From 30484b2289f0d8abf96d8a09e7b32110575cced7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nurzhan=20Sak=C3=A9n?= Date: Thu, 26 Oct 2023 17:11:53 +0400 Subject: [PATCH 32/37] Fixed line rasterization issue --- src/rasterizer/lines.rs | 37 ++++++++++--------------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/src/rasterizer/lines.rs b/src/rasterizer/lines.rs index b18eeee..b4749c4 100644 --- a/src/rasterizer/lines.rs +++ b/src/rasterizer/lines.rs @@ -1,6 +1,5 @@ use super::*; use crate::{CoordinateMode, YAxisDirection}; -use core::cmp::max; use vek::*; #[cfg(feature = "micromath")] @@ -112,34 +111,18 @@ impl Rasterizer for Lines { let w_hom_dx = (weights_at(Vec2::unit_x() * 1000.0) - w_hom_origin) / 1000.0; let w_hom_dy = (weights_at(Vec2::unit_y() * 1000.0) - w_hom_origin) / 1000.0; - let verts_clamped = verts_screen/*verts_screen.xy().map2(verts_screen.xy().yx(), |mut a, b| { - let dir = b - a; - if a.y < screen_min.y { a += Vec2::new(dir.x / dir.y, 1.0) * -(a.y - screen_min.y); } - if a.x < screen_min.x { a += Vec2::new(1.0, dir.y / dir.x) * -(a.x - screen_min.x); } - - if a.y > screen_max.y { a += Vec2::new(dir.x / dir.y, 1.0) * (a.y - screen_max.y); } - if a.x > screen_max.x { a += Vec2::new(1.0, dir.y / dir.x) * (a.x - screen_max.x); } - - a - })*/; let (x1, y1) = verts_screen.x.as_::().into_tuple(); - let (x2, y2) = { - let (x2, y2) = verts_screen.y.as_::().into_tuple(); - // `clipline` uses inclusive ranges, might be slightly wrong to just subtract 1 - (max((x2 - 1), x1), max((y2 - 1), y1)) - }; + let (x2, y2) = verts_screen.y.as_::().into_tuple(); - let (wx1, wy1) = screen_min.as_::().into_tuple(); - let (wx2, wy2) = screen_max.as_::().into_tuple(); + let (wx1, wy1) = tri_bounds_clamped.min.as_::().into_tuple(); + let (wx2, wy2) = tri_bounds_clamped.max.as_::().into_tuple(); // TODO: This sucks. A lot. It uses 3-vertex homogeneous coordinates with the last vertex being very close - // to the first, it does loads of unnecessary work for stuff outside the viewport, and it's not even fast. - clipline::clipline(((x1, y1), (x2, y2)), ((wx1, wy1), (wx2, wy2)), |x, y| { - if (tri_bounds_clamped.min.x as isize..tri_bounds_clamped.max.x as isize) - .contains(&x) - && (tri_bounds_clamped.min.y as isize..tri_bounds_clamped.max.y as isize) - .contains(&y) - { + // TODO: `clipline` is endpoints-inclusive compared to `bresenham`, how problematic is that? + clipline::clipline( + ((x1, y1), (x2, y2)), + ((wx1, wy1), (wx2 - 1, wy2 - 1)), + |x, y| { let (x, y) = (x as usize, y as usize); // Find the barycentric weights for the start of this row let w_hom = w_hom_origin + w_hom_dy * y as f32 + w_hom_dx * x as f32; @@ -173,8 +156,8 @@ impl Rasterizer for Lines { blitter.emit_fragment([x, y], get_v_data, z); } } - } - }); + }, + ); }); } } From a83207159e5cfbcdd5a34a33de5c59eadc3dd803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nurzhan=20Sak=C3=A9n?= Date: Fri, 27 Oct 2023 17:28:32 +0400 Subject: [PATCH 33/37] Changed the primitives type to LineTriangleList --- examples/wireframes.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/wireframes.rs b/examples/wireframes.rs index 09ae1ad..eec703e 100644 --- a/examples/wireframes.rs +++ b/examples/wireframes.rs @@ -1,7 +1,7 @@ use derive_more::{Add, Mul}; use euc::{ - AaMode, Buffer2d, Clamped, DepthMode, Empty, Linear, Pipeline, PixelMode, Sampler, Target, - Texture, TriangleList, Unit, + AaMode, Buffer2d, Clamped, DepthMode, Empty, LineTriangleList, Linear, Pipeline, PixelMode, + Sampler, Target, Texture, Unit, }; use minifb::{Key, MouseButton, MouseMode, Window, WindowOptions}; use std::marker::PhantomData; @@ -24,7 +24,7 @@ struct VertexData { impl<'a> Pipeline for Teapot<'a> { type Vertex = wavefront::Vertex<'a>; type VertexData = VertexData; - type Primitives = TriangleList; //Lines; + type Primitives = LineTriangleList; type Fragment = Rgba; type Pixel = u32; From 83f1828d77314383947d9eb61a9126bcd47a5717 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 2 Nov 2023 19:44:58 +0000 Subject: [PATCH 34/37] Fixed line rasterizer depth issue --- examples/teapot.rs | 4 +- examples/wireframes.rs | 188 ++---------------------------------- src/rasterizer/lines.rs | 115 +++++++--------------- src/rasterizer/triangles.rs | 34 +++---- src/texture.rs | 8 +- 5 files changed, 71 insertions(+), 278 deletions(-) diff --git a/examples/teapot.rs b/examples/teapot.rs index 02a751d..14e7f0a 100644 --- a/examples/teapot.rs +++ b/examples/teapot.rs @@ -48,7 +48,7 @@ impl<'a> Pipeline for TeapotShadow<'a> { } #[inline(always)] - fn blend(&self, old: Self::Pixel, _new: Self::Fragment) {} + fn blend(&self, _old: Self::Pixel, _new: Self::Fragment) {} } struct Teapot<'a> { @@ -87,7 +87,7 @@ impl<'a> Pipeline for Teapot<'a> { let light_view_pos = self.light_vp * Vec4::from_point(wpos); let light_view_pos = light_view_pos.xyz() / light_view_pos.w; ( - (self.p * self.v * wpos).into_array(), + (self.p * (self.v * wpos)).into_array(), VertexData { wpos: wpos.xyz(), wnorm: wnorm.xyz(), diff --git a/examples/wireframes.rs b/examples/wireframes.rs index eec703e..7d2f171 100644 --- a/examples/wireframes.rs +++ b/examples/wireframes.rs @@ -1,8 +1,4 @@ -use derive_more::{Add, Mul}; -use euc::{ - AaMode, Buffer2d, Clamped, DepthMode, Empty, LineTriangleList, Linear, Pipeline, PixelMode, - Sampler, Target, Texture, Unit, -}; +use euc::{Buffer2d, Empty, LineTriangleList, Pipeline, Target, Unit}; use minifb::{Key, MouseButton, MouseMode, Window, WindowOptions}; use std::marker::PhantomData; use vek::*; @@ -11,46 +7,25 @@ struct Teapot<'a> { m: Mat4, v: Mat4, p: Mat4, - light_pos: Vec3, phantom: PhantomData<&'a ()>, } -#[derive(Add, Mul, Clone)] -struct VertexData { - wpos: Vec3, - wnorm: Vec3, -} - impl<'a> Pipeline for Teapot<'a> { type Vertex = wavefront::Vertex<'a>; - type VertexData = VertexData; + type VertexData = Unit; type Primitives = LineTriangleList; type Fragment = Rgba; type Pixel = u32; - fn depth_mode(&self) -> DepthMode { - DepthMode::LESS_WRITE - } - fn aa_mode(&self) -> AaMode { - AaMode::Msaa { level: 1 } - } - #[inline(always)] fn vertex(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { let wpos = self.m * Vec4::from_point(Vec3::from(vertex.position())); - let wnorm = self.m * Vec4::from_direction(-Vec3::from(vertex.normal().unwrap())); - ( - (self.p * self.v * wpos).into_array(), - VertexData { - wpos: wpos.xyz(), - wnorm: wnorm.xyz(), - }, - ) + ((self.p * (self.v * wpos)).into_array(), Unit) } #[inline(always)] - fn fragment(&self, VertexData { wpos, wnorm }: Self::VertexData) -> Self::Fragment { + fn fragment(&self, _: Self::VertexData) -> Self::Fragment { Rgba::red() } @@ -67,7 +42,6 @@ fn main() { let [w, h] = [1280, 960]; let mut color = Buffer2d::fill([w, h], 0x0); - let mut depth = Buffer2d::fill([w, h], 1.0); let model = wavefront::Obj::from_file("examples/data/teapot.obj").unwrap(); @@ -83,13 +57,11 @@ fn main() { // Clear the render targets ready for the next frame color.clear(0x0); - depth.clear(1.0); // Update camera as the mouse moves let mouse_pos = win.get_mouse_pos(MouseMode::Pass).unwrap_or_default(); if win.get_mouse_down(MouseButton::Left) { - ori.x -= (mouse_pos.1 - old_mouse_pos.1) * 0.003; - ori.y += (mouse_pos.0 - old_mouse_pos.0) * 0.003; + ori -= Vec2::new(mouse_pos.1 - old_mouse_pos.1, mouse_pos.0 - old_mouse_pos.0) * 0.003; } if win.get_mouse_down(MouseButton::Right) { dist = (dist + (mouse_pos.1 - old_mouse_pos.1) as f32 * 0.01) @@ -100,26 +72,24 @@ fn main() { // Position of objects in the scene let teapot_pos = Vec3::new(0.0, 0.0, 0.0); - let light_pos = Vec3::::new(-8.0, 5.0, -5.0); // Set up the camera matrix let p = Mat4::perspective_fov_lh_zo(1.3, w as f32, h as f32, 0.01, 100.0); - let v = Mat4::::identity() * Mat4::translation_3d(Vec3::new(0.0, 0.0, dist)); - // Set up the teapot matrix - let m = Mat4::::translation_3d(-teapot_pos) - * Mat4::rotation_x(core::f32::consts::PI) + let v = Mat4::::identity() + * Mat4::translation_3d(Vec3::new(0.0, 0.0, dist)) * Mat4::rotation_x(ori.x) * Mat4::rotation_y(ori.y); + // Set up the teapot matrix + let m = Mat4::::translation_3d(-teapot_pos) * Mat4::rotation_x(core::f32::consts::PI); // Colour pass Teapot { m, v, p, - light_pos, phantom: PhantomData, } - .render(model.vertices(), &mut color, &mut depth); + .render(model.vertices(), &mut color, &mut Empty::default()); win.update_with_buffer(color.raw(), w, h).unwrap(); @@ -134,141 +104,3 @@ fn main() { i += 1; } } - -/* -use euc::{buffer::Buffer2d, rasterizer, Pipeline, Target}; -use std::path::Path; -use vek::*; - -struct Teapot<'a> { - mvp: Mat4, - positions: &'a [Vec4], - normals: &'a [Vec3], - light_dir: Vec3, -} - -impl<'a> Pipeline for Teapot<'a> { - type Vertex = u32; // Vertex index - type VertexData = Vec3; // Normal - type Fragment = u32; // BGRA - type Pixel = u32; // BGRA - - fn vertex_shader(&self, v_index: &Self::Vertex) -> ([f32; 4], Self::VsOut) { - let v_index = *v_index as usize; - // Find vertex position - ( - // Calculate vertex position in camera space - (self.mvp * self.positions[v_index]).into_array(), - // Find vertex normal - self.normals[v_index], - ) - } - - fn fragment_shader(&self, norm: Self::VertexData) -> Self::Pixel { - let ambient = 0.2; - let diffuse = norm.dot(self.light_dir).max(0.0) * 0.5; - let specular = self - .light_dir - .reflected(Vec3::from(self.mvp * Vec4::from(*norm)).normalized()) - .dot(-Vec3::unit_z()) - .powf(20.0); - - let light = ambient + diffuse + specular; - let color = (Rgba::new(1.0, 0.7, 0.1, 1.0) * light).clamped(Rgba::zero(), Rgba::one()); - - let bytes = (color * 255.0).map(|e| e as u8).into_array(); - (bytes[2] as u32) << 0 - | (bytes[1] as u32) << 8 - | (bytes[0] as u32) << 16 - | (bytes[3] as u32) << 24 - } -} - -struct Wireframe<'a> { - mvp: &'a Mat4, - positions: &'a [Vec4], - normals: &'a [Vec3], -} - -impl<'a> Pipeline for Wireframe<'a> { - type Vertex = u32; // Vertex index - type VertexData = (); - type Fragment = u32; // BGRA - type Pixel = u32; // BGRA - - #[inline] - fn vertex_shader(&self, v_index: &Self::Vertex) -> ([f32; 4], Self::VsOut) { - let v_index = *v_index as usize; - // Offset position to avoid z fighting - let offset = 0.002 * self.normals[v_index]; - let v_pos = self.positions[v_index] + offset; - ((*self.mvp * Vec4::from_point(v_pos)).into_array(), ()) - } - - #[inline] - fn fragment_shader(&self, _: Self::VertexData) -> Self::Pixel { - 120 - } -} - -const W: usize = 800; -const H: usize = 600; - -fn main() { - let mut color = Buffer2d::new([W, H], 0); - let mut depth = Buffer2d::new([W, H], 1.0); - - let mut win = minifb::Window::new("Teapot", W, H, minifb::WindowOptions::default()).unwrap(); - - let model = wavefront::Obj::from_file("examples/data/teapot.obj").unwrap(); - let wf_indices: Vec<_> = model - .triangles() - .chunks(3) - .flat_map(|sl| [sl[0], sl[1], sl[1], sl[2], sl[2], sl[0]] - .map(|v| v.position_index()) - .into_iter()) - .collect(); - let positions = model - .positions() - .chunks(3) - .map(|sl| Vec4::from_point(Vec3::from_slice(sl) + Vec3::new(0.0, -0.5, 0.0))) - .collect::>(); - let normals = model - .normals() - .chunks(3) - .map(|sl| Vec3::from_slice(sl)) - .collect::>(); - - for i in 0.. { - let mvp = Mat4::perspective_fov_rh_no(1.3, W as f32, H as f32, 0.01, 100.0) - * Mat4::translation_3d(Vec3::new(0.0, 0.0, -1.5)) - * Mat4::::scaling_3d(0.8) - * Mat4::rotation_x((i as f32 * 0.002).sin() * 8.0) - * Mat4::rotation_y((i as f32 * 0.004).cos() * 4.0) - * Mat4::rotation_z((i as f32 * 0.008).sin() * 2.0); - - color.clear(0); - depth.clear(1.0); - - Teapot { - mvp: mvp, - positions: &positions, - normals: &normals, - light_dir: Vec3::new(1.0, 1.0, 1.0).normalized(), - } - .draw::, _>(indices, &mut color, Some(&mut depth)); - Wireframe { - mvp: &mvp, - positions: &positions, - normals: &normals, - } - .draw::, _>(&wf_indices, &mut color, Some(&mut depth)); - - if win.is_open() { - win.update_with_buffer(color.as_ref(), W, H).unwrap(); - } else { - break; - } - } -} -*/ diff --git a/src/rasterizer/lines.rs b/src/rasterizer/lines.rs index b4749c4..321f5bd 100644 --- a/src/rasterizer/lines.rs +++ b/src/rasterizer/lines.rs @@ -10,7 +10,7 @@ use micromath_::F32Ext; pub struct Lines; impl Rasterizer for Lines { - type Config = CullMode; + type Config = (); #[inline] unsafe fn rasterize( @@ -18,7 +18,7 @@ impl Rasterizer for Lines { mut vertices: I, _principal_x: bool, coordinate_mode: CoordinateMode, - _cull_mode: CullMode, + _config: (), mut blitter: B, ) where V: Clone + WeightedSum, @@ -36,12 +36,6 @@ impl Rasterizer for Lines { let size = Vec2::::from(tgt_size).map(|e| e as f32); - let to_ndc = Mat3::from_row_arrays([ - [2.0 / size.x, 0.0, -1.0], - [0.0, -2.0 / size.y, 1.0], - [0.0, 0.0, 1.0], - ]); - let verts_hom_out = core::iter::from_fn(move || Some(Vec2::new(vertices.next()?, vertices.next()?))); @@ -49,46 +43,13 @@ impl Rasterizer for Lines { blitter.begin_primitive(); // Calculate vertex shader outputs and vertex homogeneous coordinates - let verts_hom = Vec3::new(verts_hom_out.x.0, verts_hom_out.y.0, verts_hom_out.x.0) - .map(Vec4::::from) - + Vec3::new( - Vec4::zero(), - Vec4::zero(), - Vec4::new(0.001, 0.001, 0.0, 0.0), - ); - let verts_out = Vec3::new( - verts_hom_out.x.1.clone(), - verts_hom_out.y.1, - verts_hom_out.x.1, - ); + let verts_hom = Vec2::new(verts_hom_out.x.0, verts_hom_out.y.0).map(Vec4::::from); + let verts_out = verts_hom_out.map(|e| e.1); let verts_hom = verts_hom.map(|v| v * Vec4::new(flip.x, flip.y, 1.0, 1.0)); // Convert homogenous to euclidean coordinates - let verts_euc = verts_hom.map(|v_hom| v_hom.xyz() / v_hom.w); - - // Create a matrix that allows conversion between screen coordinates and interpolation weights - let coords_to_weights = { - let c = Vec3::new(verts_hom.z.x, verts_hom.z.y, verts_hom.z.w); - let ca = Vec3::new(verts_hom.x.x, verts_hom.x.y, verts_hom.x.w) - c; - let cb = Vec3::new(verts_hom.y.x, verts_hom.y.y, verts_hom.y.w) - c; - let n = ca.cross(cb); - let rec_det = if n.magnitude_squared() > 0.0 { - 1.0 / n.dot(c).min(-core::f32::EPSILON) - } else { - 1.0 - }; - - Mat3::from_row_arrays([cb.cross(c), c.cross(ca), n].map(|v| v.into_array())) - * rec_det - * to_ndc - }; - - // Ensure we didn't accidentally end up with infinities or NaNs - assert!(coords_to_weights - .into_row_array() - .iter() - .all(|e| e.is_finite())); + let verts_euc = verts_hom.map(|v_hom| v_hom.xyz() / v_hom.w.max(0.0001)); // Convert vertex coordinates to screen space let verts_screen = verts_euc.map(|euc| size * (euc.xy() * Vec2::new(0.5, -0.5) + 0.5)); @@ -96,7 +57,7 @@ impl Rasterizer for Lines { // Calculate the triangle bounds as a bounding box let screen_min = Vec2::::from(tgt_min).map(|e| e as f32); let screen_max = Vec2::::from(tgt_max).map(|e| e as f32); - let tri_bounds_clamped = Aabr:: { + let bounds_clamped = Aabr:: { min: (verts_screen.reduce(|a, b| Vec2::partial_min(a, b)) + 0.0) .clamped(screen_min, screen_max) .as_(), @@ -105,52 +66,50 @@ impl Rasterizer for Lines { .as_(), }; - // Calculate change in vertex weights for each pixel - let weights_at = |p: Vec2| coords_to_weights * Vec3::new(p.x, p.y, 1.0); - let w_hom_origin = weights_at(Vec2::zero()); - let w_hom_dx = (weights_at(Vec2::unit_x() * 1000.0) - w_hom_origin) / 1000.0; - let w_hom_dy = (weights_at(Vec2::unit_y() * 1000.0) - w_hom_origin) / 1000.0; - let (x1, y1) = verts_screen.x.as_::().into_tuple(); let (x2, y2) = verts_screen.y.as_::().into_tuple(); - let (wx1, wy1) = tri_bounds_clamped.min.as_::().into_tuple(); - let (wx2, wy2) = tri_bounds_clamped.max.as_::().into_tuple(); + let (wx1, wy1) = bounds_clamped.min.as_::().into_tuple(); + let (wx2, wy2) = bounds_clamped.max.as_::().into_tuple(); + + let use_x = (x1 - x2).abs() > (y1 - y2).abs(); + let norm = 1.0 + / if use_x { + verts_screen.y.x - verts_screen.x.x + } else { + verts_screen.y.y - verts_screen.x.y + }; - // TODO: This sucks. A lot. It uses 3-vertex homogeneous coordinates with the last vertex being very close - // TODO: `clipline` is endpoints-inclusive compared to `bresenham`, how problematic is that? clipline::clipline( ((x1, y1), (x2, y2)), ((wx1, wy1), (wx2 - 1, wy2 - 1)), |x, y| { let (x, y) = (x as usize, y as usize); - // Find the barycentric weights for the start of this row - let w_hom = w_hom_origin + w_hom_dy * y as f32 + w_hom_dx * x as f32; - // Calculate vertex weights to determine vs_out lerping and intersection - let w_unbalanced = Vec3::new(w_hom.x, w_hom.y, w_hom.z - w_hom.x - w_hom.y); + + let frac = if use_x { + x as f32 - verts_screen.x.x + } else { + y as f32 - verts_screen.x.y + } * norm; // Calculate the interpolated z coordinate for the depth target - let z: f32 = verts_hom.map(|v| v.z).dot(w_unbalanced); - - if blitter.test_fragment([x, y], z) { - // Don't use `.contains(&z)`, it isn't inclusive - if coordinate_mode - .z_clip_range - .clone() - .map_or(true, |clip_range| { - z >= clip_range.start && z <= clip_range.end - }) - { + let z: f32 = Lerp::lerp(verts_euc.x.z, verts_euc.y.z, frac); + + // Don't use `.contains(&z)`, it isn't inclusive + if coordinate_mode + .z_clip_range + .clone() + .map_or(true, |clip| clip.start <= z && z <= clip.end) + { + if blitter.test_fragment([x, y], z) { let get_v_data = |[x, y]: [f32; 2]| { - let w_hom = w_hom_origin + w_hom_dy * y + w_hom_dx * x; - - // Calculate vertex weights to determine vs_out lerping and intersection - let w_unbalanced = - Vec3::new(w_hom.x, w_hom.y, w_hom.z - w_hom.x - w_hom.y); - let w = w_unbalanced * w_hom.z.recip(); - let w = Vec2::new(w.x.max(w.z), w.y); + let frac = if use_x { + x - verts_screen.x.x + } else { + y - verts_screen.x.y + } * norm; - V::weighted_sum(verts_out.as_slice(), w.as_slice()) + V::weighted_sum(verts_out.as_slice(), &[1.0 - frac, frac]) }; blitter.emit_fragment([x, y], get_v_data, z); diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index 52addd3..e154488 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -106,7 +106,7 @@ impl Rasterizer for Triangles { }; // Ensure we didn't accidentally end up with infinities or NaNs - assert!(coords_to_weights + debug_assert!(coords_to_weights .into_row_array() .iter() .all(|e| e.is_finite())); @@ -117,7 +117,7 @@ impl Rasterizer for Triangles { // Calculate the triangle bounds as a bounding box let screen_min = Vec2::::from(tgt_min).map(|e| e as f32); let screen_max = Vec2::::from(tgt_max).map(|e| e as f32); - let tri_bounds_clamped = Aabr:: { + let bounds_clamped = Aabr:: { min: (verts_screen.reduce(|a, b| Vec2::partial_min(a, b)) + 0.0) .clamped(screen_min, screen_max) .as_(), @@ -129,15 +129,15 @@ impl Rasterizer for Triangles { // Calculate change in vertex weights for each pixel let weights_at = |p: Vec2| coords_to_weights * Vec3::new(p.x, p.y, 1.0); let w_hom_origin = weights_at(Vec2::zero()); - let w_hom_dx = (weights_at(Vec2::unit_x() * 1000.0) - w_hom_origin) / 1000.0; - let w_hom_dy = (weights_at(Vec2::unit_y() * 1000.0) - w_hom_origin) / 1000.0; + let w_hom_dx = (weights_at(Vec2::unit_x() * 1000.0) - w_hom_origin) * (1.0 / 1000.0); + let w_hom_dy = (weights_at(Vec2::unit_y() * 1000.0) - w_hom_origin) * (1.0 / 1000.0); // First, order vertices by height let mut verts_by_y = verts_screen; verts_by_y.sort_unstable_by_key(|e| e.y as isize); // Iterate over fragment candidates within the triangle's bounding box - (tri_bounds_clamped.min.y..tri_bounds_clamped.max.y).for_each(|y| { + (bounds_clamped.min.y..bounds_clamped.max.y).for_each(|y| { let Vec3 { x: a, y: b, z: c } = verts_by_y; // For each of the lines, calculate the point at which our row intersects it let ac = Lerp::lerp(a.x, c.x, (y as f32 - a.y) / (c.y - a.y)); // Longest side @@ -152,11 +152,9 @@ impl Rasterizer for Triangles { // Now we have screen-space bounds for the row. Clean it up and clamp it to the screen bounds let row_range = Vec2::new(row_bounds.x.floor(), row_bounds.y.ceil()).map2( - Vec2::new(tri_bounds_clamped.min.x, tri_bounds_clamped.max.x), + Vec2::new(bounds_clamped.min.x, bounds_clamped.max.x), |e, b| { - if e >= tri_bounds_clamped.min.x as f32 - && e < tri_bounds_clamped.max.x as f32 - { + if e >= bounds_clamped.min.x as f32 && e < bounds_clamped.max.x as f32 { e as usize } else { b @@ -165,7 +163,7 @@ impl Rasterizer for Triangles { ); // Stupid version - //let row_range = Vec2::new(tri_bounds_clamped.min.x, tri_bounds_clamped.max.x); + //let row_range = Vec2::new(bounds_clamped.min.x, bounds_clamped.max.x); // Find the barycentric weights for the start of this row let mut w_hom = w_hom_origin + w_hom_dy * y as f32 + w_hom_dx * row_range.x as f32; @@ -180,15 +178,13 @@ impl Rasterizer for Triangles { // Calculate the interpolated z coordinate for the depth target let z: f32 = verts_hom.map(|v| v.z).dot(w_unbalanced); - if blitter.test_fragment([x, y], z) { - // Don't use `.contains(&z)`, it isn't inclusive - if coordinate_mode - .z_clip_range - .clone() - .map_or(true, |clip_range| { - z >= clip_range.start && z <= clip_range.end - }) - { + // Don't use `.contains(&z)`, it isn't inclusive + if coordinate_mode + .z_clip_range + .clone() + .map_or(true, |clip| clip.start <= z && z <= clip.end) + { + if blitter.test_fragment([x, y], z) { let get_v_data = |[x, y]: [f32; 2]| { let w_hom = w_hom_origin + w_hom_dy * y + w_hom_dx * x; diff --git a/src/texture.rs b/src/texture.rs index 9e8aa55..d0bab6a 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -216,9 +216,15 @@ impl<'a, T: Target> Target for &'a mut T { /// An always-empty texture. Useful as a placeholder for an unused target. pub struct Empty(core::marker::PhantomData); +impl Empty { + pub const fn new() -> Self { + Self(core::marker::PhantomData) + } +} + impl Default for Empty { fn default() -> Self { - Self(core::marker::PhantomData) + Self::new() } } From 4c0861cef9263113227de78a27448d0169d1f2a6 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 2 Nov 2023 20:08:17 +0000 Subject: [PATCH 35/37] Improved examples, API --- Cargo.toml | 16 ++++++++-------- README.md | 4 ++-- examples/spinning_cube.rs | 4 ++-- examples/teapot.rs | 26 ++++++++++++-------------- examples/texture_mapping.rs | 8 ++++---- examples/triangle.rs | 4 ++-- examples/wireframes.rs | 18 +++++------------- src/pipeline.rs | 10 +++++----- src/rasterizer/lines.rs | 2 +- src/rasterizer/triangles.rs | 2 +- src/sampler/linear.rs | 2 +- src/texture.rs | 12 ++++++------ 12 files changed, 49 insertions(+), 59 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 72f5537..b8087be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,12 +14,12 @@ exclude = [ ] [dependencies] -vek = { version = "0.15", default-features = false, features = [] } -image_ = { package = "image", version = "0.24", optional = true } +vek = { version = "0.16", default-features = false, features = [] } +image = { version = "0.24", optional = true } num_cpus = { version = "1.13", optional = true } fxhash = { version = "0.2", optional = true } -micromath_ = { package = "micromath", version = "1.1", optional = true } -clipline = { version = "0.1.0" } +micromath = { version = "1.1", optional = true } +clipline = "0.1.2" [features] default = ["std", "image", "par"] @@ -27,16 +27,16 @@ std = ["vek/std"] libm = ["vek/libm"] nightly = [] simd = ["vek/repr_simd", "vek/platform_intrinsics"] -image = ["image_"] +image = ["dep:image"] par = ["std", "num_cpus", "fxhash"] -micromath = ["micromath_"] +micromath = ["dep:micromath"] [dev-dependencies] +vek = { version = "0.16", features = ["rgb"] } minifb = "0.20" wavefront = "0.2" criterion = "0.3.3" -image_ = { package = "image", version = "0.23" } -vek = "0.15" +image = "0.24" derive_more = "0.99" [lib] diff --git a/README.md b/README.md index 40a8aaa..9f1c74b 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,14 @@ struct Triangle; impl Pipeline for Triangle { - type Vertex = [f32; 2]; // Each vertex has an x and y component + type Vertex<'v> = [f32; 2]; // Each vertex has an x and y component type VertexData = Unit; // No data is passed from the vertex shader to the fragment shader type Primitives = TriangleList; // Our vertices come in the form of a list of triangles type Fragment = [u8; 3]; // Each fragment is 3 bytes: red, green, and blue type Pixel = [u8; 3]; // Color buffer pixels have the same format as fragments. // Vertex shader (determines the screen-space position of each vertex) - fn vertex(&self, pos: &Self::Vertex) -> ([f32; 4], Self::VertexData) { + fn vertex(&self, pos: &Self::Vertex<'_>) -> ([f32; 4], Self::VertexData) { ([pos[0], pos[1], 0.0, 1.0], Unit) } diff --git a/examples/spinning_cube.rs b/examples/spinning_cube.rs index 4a51b9f..ef1d002 100644 --- a/examples/spinning_cube.rs +++ b/examples/spinning_cube.rs @@ -7,14 +7,14 @@ struct Cube { } impl Pipeline for Cube { - type Vertex = (Vec4, Rgba); + type Vertex<'v> = (Vec4, Rgba); type VertexData = Rgba; type Primitives = TriangleList; type Pixel = u32; type Fragment = Rgba; #[inline(always)] - fn vertex(&self, (pos, color): &Self::Vertex) -> ([f32; 4], Self::VertexData) { + fn vertex(&self, (pos, color): &Self::Vertex<'_>) -> ([f32; 4], Self::VertexData) { ((self.mvp * *pos).into_array(), *color) } diff --git a/examples/teapot.rs b/examples/teapot.rs index 14e7f0a..e857b29 100644 --- a/examples/teapot.rs +++ b/examples/teapot.rs @@ -4,16 +4,14 @@ use euc::{ Texture, TriangleList, Unit, }; use minifb::{Key, MouseButton, MouseMode, Window, WindowOptions}; -use std::marker::PhantomData; use vek::*; -struct TeapotShadow<'a> { +struct TeapotShadow { mvp: Mat4, - phantom: PhantomData<&'a ()>, } -impl<'a> Pipeline for TeapotShadow<'a> { - type Vertex = wavefront::Vertex<'a>; +impl Pipeline for TeapotShadow { + type Vertex<'v> = wavefront::Vertex<'v>; type VertexData = f32; type Primitives = TriangleList; type Fragment = Unit; @@ -35,7 +33,7 @@ impl<'a> Pipeline for TeapotShadow<'a> { } #[inline(always)] - fn vertex(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { + fn vertex(&self, vertex: &Self::Vertex<'_>) -> ([f32; 4], Self::VertexData) { ( (self.mvp * Vec4::from_point(Vec3::from(vertex.position()))).into_array(), 0.0, @@ -68,7 +66,7 @@ struct VertexData { } impl<'a> Pipeline for Teapot<'a> { - type Vertex = wavefront::Vertex<'a>; + type Vertex<'v> = wavefront::Vertex<'v>; type VertexData = VertexData; type Primitives = TriangleList; type Fragment = Rgba; @@ -80,7 +78,7 @@ impl<'a> Pipeline for Teapot<'a> { } #[inline(always)] - fn vertex(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { + fn vertex(&self, vertex: &Self::Vertex<'_>) -> ([f32; 4], Self::VertexData) { let wpos = self.m * Vec4::from_point(Vec3::from(vertex.position())); let wnorm = self.m * Vec4::from_direction(-Vec3::from(vertex.normal().unwrap())); @@ -205,11 +203,11 @@ fn main() { let m = Mat4::::translation_3d(-teapot_pos) * Mat4::rotation_x(core::f32::consts::PI); // Shadow pass - TeapotShadow { - mvp: light_vp * m, - phantom: PhantomData, - } - .render(model.vertices(), &mut Empty::default(), &mut shadow); + TeapotShadow { mvp: light_vp * m }.render( + model.vertices(), + &mut Empty::default(), + &mut shadow, + ); // Colour pass Teapot { @@ -218,7 +216,7 @@ fn main() { p, light_pos, shadow: (&shadow).linear().clamped(), - light_vp: light_vp, + light_vp, } .render(model.vertices(), &mut color, &mut depth); diff --git a/examples/texture_mapping.rs b/examples/texture_mapping.rs index 679aeaa..96d814e 100644 --- a/examples/texture_mapping.rs +++ b/examples/texture_mapping.rs @@ -1,5 +1,5 @@ use euc::{Buffer2d, Nearest, Pipeline, Sampler, Target, Texture, TriangleList}; -use image_::RgbaImage; +use image::RgbaImage; use minifb::{Key, Window, WindowOptions}; use vek::{Mat4, Rgba, Vec2, Vec3, Vec4}; @@ -11,14 +11,14 @@ struct Cube<'a> { } impl<'a> Pipeline for Cube<'a> { - type Vertex = usize; + type Vertex<'v> = usize; type VertexData = Vec2; type Primitives = TriangleList; type Fragment = Rgba; type Pixel = u32; #[inline] - fn vertex(&self, v_index: &Self::Vertex) -> ([f32; 4], Self::VertexData) { + fn vertex(&self, v_index: &Self::Vertex<'_>) -> ([f32; 4], Self::VertexData) { ( (self.mvp * self.positions[*v_index]).into_array(), self.uvs[*v_index], @@ -106,7 +106,7 @@ fn main() { Vec2::new(1.0, 1.0), ]; - let texture = match image_::open("examples/data/rust.png") { + let texture = match image::open("examples/data/rust.png") { Ok(image) => image.to_rgba8(), Err(err) => { eprintln!("{}", err); diff --git a/examples/triangle.rs b/examples/triangle.rs index 6b909c5..e725507 100644 --- a/examples/triangle.rs +++ b/examples/triangle.rs @@ -5,13 +5,13 @@ use vek::*; struct Triangle; impl Pipeline for Triangle { - type Vertex = ([f32; 2], Rgba); + type Vertex<'v> = ([f32; 2], Rgba); type VertexData = Rgba; type Primitives = TriangleList; type Fragment = Rgba; type Pixel = u32; - fn vertex(&self, (pos, col): &Self::Vertex) -> ([f32; 4], Self::VertexData) { + fn vertex(&self, (pos, col): &Self::Vertex<'_>) -> ([f32; 4], Self::VertexData) { ([pos[0], pos[1], 0.0, 1.0], *col) } diff --git a/examples/wireframes.rs b/examples/wireframes.rs index 7d2f171..bc38653 100644 --- a/examples/wireframes.rs +++ b/examples/wireframes.rs @@ -1,24 +1,22 @@ use euc::{Buffer2d, Empty, LineTriangleList, Pipeline, Target, Unit}; use minifb::{Key, MouseButton, MouseMode, Window, WindowOptions}; -use std::marker::PhantomData; use vek::*; -struct Teapot<'a> { +struct Teapot { m: Mat4, v: Mat4, p: Mat4, - phantom: PhantomData<&'a ()>, } -impl<'a> Pipeline for Teapot<'a> { - type Vertex = wavefront::Vertex<'a>; +impl Pipeline for Teapot { + type Vertex<'v> = wavefront::Vertex<'v>; type VertexData = Unit; type Primitives = LineTriangleList; type Fragment = Rgba; type Pixel = u32; #[inline(always)] - fn vertex(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData) { + fn vertex(&self, vertex: &Self::Vertex<'_>) -> ([f32; 4], Self::VertexData) { let wpos = self.m * Vec4::from_point(Vec3::from(vertex.position())); ((self.p * (self.v * wpos)).into_array(), Unit) @@ -83,13 +81,7 @@ fn main() { let m = Mat4::::translation_3d(-teapot_pos) * Mat4::rotation_x(core::f32::consts::PI); // Colour pass - Teapot { - m, - v, - p, - phantom: PhantomData, - } - .render(model.vertices(), &mut color, &mut Empty::default()); + Teapot { m, v, p }.render(model.vertices(), &mut color, &mut Empty::default()); win.update_with_buffer(color.raw(), w, h).unwrap(); diff --git a/src/pipeline.rs b/src/pipeline.rs index 6e39d0a..b3e48f7 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -6,7 +6,7 @@ use alloc::{collections::VecDeque, vec::Vec}; use core::{borrow::Borrow, cmp::Ordering, ops::Range}; #[cfg(feature = "micromath")] -use micromath_::F32Ext; +use micromath::F32Ext; /// Defines how a [`Pipeline`] will interact with the depth target. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] @@ -157,7 +157,7 @@ impl Default for CoordinateMode { /// Additional methods such as [`Pipeline::depth_mode`], [Pipeline::`cull_mode`], etc. may be implemented to customize /// the behaviour of the pipeline even further. pub trait Pipeline: Sized { - type Vertex; + type Vertex<'a>; type VertexData: Clone + WeightedSum + Send + Sync; type Primitives: PrimitiveKind; type Fragment: Clone + WeightedSum; @@ -200,7 +200,7 @@ pub trait Pipeline: Sized { /// [`Pipeline::VertexData`] to be interpolated and passed to the fragment shader. /// /// This stage is executed at the beginning of pipeline execution. - fn vertex(&self, vertex: &Self::Vertex) -> ([f32; 4], Self::VertexData); + fn vertex(&self, vertex: &Self::Vertex<'_>) -> ([f32; 4], Self::VertexData); /// Turn a primitive into many primitives. /// @@ -233,11 +233,11 @@ pub trait Pipeline: Sized { /// Render a stream of vertices to given provided pixel target and depth target using the rasterizer. /// /// **Do not implement this method** - fn render(&self, vertices: S, pixel: &mut P, depth: &mut D) + fn render<'a, S, V, P, D>(&self, vertices: S, pixel: &mut P, depth: &mut D) where Self: Send + Sync, S: IntoIterator, - V: Borrow, + V: Borrow>, P: Target + Send + Sync, D: Target + Send + Sync, { diff --git a/src/rasterizer/lines.rs b/src/rasterizer/lines.rs index 321f5bd..fc4de68 100644 --- a/src/rasterizer/lines.rs +++ b/src/rasterizer/lines.rs @@ -3,7 +3,7 @@ use crate::{CoordinateMode, YAxisDirection}; use vek::*; #[cfg(feature = "micromath")] -use micromath_::F32Ext; +use micromath::F32Ext; /// A rasterizer that produces filled triangles. #[derive(Copy, Clone, Debug, Default)] diff --git a/src/rasterizer/triangles.rs b/src/rasterizer/triangles.rs index e154488..920c06d 100644 --- a/src/rasterizer/triangles.rs +++ b/src/rasterizer/triangles.rs @@ -3,7 +3,7 @@ use crate::{CoordinateMode, YAxisDirection}; use vek::*; #[cfg(feature = "micromath")] -use micromath_::F32Ext; +use micromath::F32Ext; /// A rasterizer that produces filled triangles. #[derive(Copy, Clone, Debug, Default)] diff --git a/src/sampler/linear.rs b/src/sampler/linear.rs index 553cf5b..cd1c63d 100644 --- a/src/sampler/linear.rs +++ b/src/sampler/linear.rs @@ -5,7 +5,7 @@ use core::{ }; #[cfg(feature = "micromath")] -use micromath_::F32Ext; +use micromath::F32Ext; /// A sampler that uses nearest-neighbor sampling. pub struct Linear(pub(crate) T, pub(crate) PhantomData); diff --git a/src/texture.rs b/src/texture.rs index d0bab6a..38b5ca8 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -48,7 +48,7 @@ pub trait Texture { self.read(index) } - /// Create a linearly (or bilinear/trilinear, if the texture is 2D/3D) interpolated (i.e: filtered) sampler from + /// Create a linearly (bilinear or trilinear, if the texture is 2D or 3D) interpolated (i.e: filtered) sampler from /// this texture. /// /// See [`Linear`]. @@ -251,9 +251,9 @@ impl Target for Empty { } #[cfg(feature = "image")] -impl Texture<2> for image_::ImageBuffer +impl Texture<2> for image::ImageBuffer where - P: image_::Pixel + Clone + 'static, + P: image::Pixel + Clone + 'static, C: core::ops::Deref, { type Index = usize; @@ -271,9 +271,9 @@ where } // #[cfg(feature = "image")] -// impl Target for image_::ImageBuffer +// impl Target for image::ImageBuffer // where -// P: image_::Pixel + 'static, +// P: image::Pixel + 'static, // C: core::ops::DerefMut, // { // fn write(&mut self, [x, y]: [usize; 2], texel: Self::Texel) { @@ -281,6 +281,6 @@ where // } // unsafe fn write_unchecked(&mut self, [x, y]: [usize; 2], texel: Self::Texel) { -// image_::GenericImage::unsafe_put_pixel(self, x as u32, y as u32, texel); +// image::GenericImage::unsafe_put_pixel(self, x as u32, y as u32, texel); // } // } From 1039642ac6331a185060b4352d8c38e119200383 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 2 Nov 2023 20:33:56 +0000 Subject: [PATCH 36/37] Better texture API --- src/pipeline.rs | 2 +- src/texture.rs | 53 ++++++++++++++++++++++++++++++++----------------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/pipeline.rs b/src/pipeline.rs index b3e48f7..0e53e0d 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -433,7 +433,7 @@ unsafe fn render_inner( } } - let principal_x = depth.principal_axis() == 0; + let principal_x = depth.preferred_axes().map_or(true, |[a, _]| a == 0); use crate::rasterizer::Blitter; diff --git a/src/texture.rs b/src/texture.rs index 38b5ca8..bd7cc6f 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -18,15 +18,19 @@ pub trait Texture { /// by users of the texture. fn size(&self) -> [Self::Index; N]; - /// Get the texture axis with highest contiguous access times. + /// Get the texture's preferred access order, if it has one. /// - /// The ordering of textures in memory can have a very significant impact on the cost of accessing them. It is - /// typical for textures to be ordered first in rows (i.e: a principal x axis) and then columns but this is not - /// always the case. This function allows the texture to signal to users what access patterns are most performant. + /// Texture data is generally laid out in memory in such a way that iteration over a particular axis is preferred + /// over others. For example, it is typical for framebuffers to be laid out in rows of columns, rather than columns + /// of rows. As such, it is more performant to iterate over columns and then rows to take maximum advantage of the + /// CPU's cache. /// - /// The default implementation is a principal axis of x, which corresponds to the most common in-memory texture layouts. - fn principal_axis(&self) -> usize { - 0 + /// You can use this function to switch between different iteration strategies to improve performance. + /// + /// In most cases, the preferred axes will be `[0, 1]` (i.e: sequential accesses to texels nearby in the x axis + /// will be much faster than those in the y axis). + fn preferred_axes(&self) -> Option<[usize; N]> { + None } /// Read a texel at the given index. @@ -76,15 +80,19 @@ pub trait Texture { impl<'a, T: Texture, const N: usize> Texture for &'a T { type Index = T::Index; type Texel = T::Texel; - #[inline] + #[inline(always)] fn size(&self) -> [Self::Index; N] { (**self).size() } - #[inline] + #[inline(always)] + fn preferred_axes(&self) -> Option<[usize; N]> { + (**self).preferred_axes() + } + #[inline(always)] fn read(&self, index: [Self::Index; N]) -> Self::Texel { (**self).read(index) } - #[inline] + #[inline(always)] unsafe fn read_unchecked(&self, index: [Self::Index; N]) -> Self::Texel { (**self).read_unchecked(index) } @@ -93,15 +101,19 @@ impl<'a, T: Texture, const N: usize> Texture for &'a T { impl<'a, T: Texture, const N: usize> Texture for &'a mut T { type Index = T::Index; type Texel = T::Texel; - #[inline] + #[inline(always)] fn size(&self) -> [Self::Index; N] { (**self).size() } - #[inline] + #[inline(always)] + fn preferred_axes(&self) -> Option<[usize; N]> { + (**self).preferred_axes() + } + #[inline(always)] fn read(&self, index: [Self::Index; N]) -> Self::Texel { (**self).read(index) } - #[inline] + #[inline(always)] unsafe fn read_unchecked(&self, index: [Self::Index; N]) -> Self::Texel { (**self).read_unchecked(index) } @@ -191,23 +203,23 @@ pub trait Target: Texture<2, Index = usize> { } impl<'a, T: Target> Target for &'a mut T { - #[inline] + #[inline(always)] unsafe fn read_exclusive_unchecked(&self, index: [Self::Index; 2]) -> Self::Texel { T::read_exclusive_unchecked(self, index) } - #[inline] + #[inline(always)] unsafe fn write_exclusive_unchecked(&self, index: [usize; 2], texel: Self::Texel) { T::write_exclusive_unchecked(self, index, texel) } - #[inline] + #[inline(always)] unsafe fn write_unchecked(&mut self, index: [usize; 2], texel: Self::Texel) { T::write_unchecked(self, index, texel) } - #[inline] + #[inline(always)] fn write(&mut self, index: [usize; 2], texel: Self::Texel) { T::write(self, index, texel); } - #[inline] + #[inline(always)] fn clear(&mut self, texel: Self::Texel) { T::clear(self, texel); } @@ -264,6 +276,11 @@ where [self.width() as usize, self.height() as usize] } + #[inline] + fn preferred_axes(&self) -> Option<[usize; 2]> { + Some([0, 1]) + } + #[inline] fn read(&self, [x, y]: [Self::Index; 2]) -> Self::Texel { self.get_pixel(x as u32, y as u32).clone() From 45f2a7ec0cf2d1c0fab3442f979aa8871f68bf11 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Thu, 2 Nov 2023 20:39:04 +0000 Subject: [PATCH 37/37] Updated version and README --- Cargo.toml | 6 +++--- README.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b8087be..ede5107 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "euc" -version = "0.5.2" +version = "0.6.0" description = "A software rendering crate that lets you write shaders with Rust" authors = ["Joshua Barretto ", "Martin Sandfuchs "] license = "Apache-2.0 AND MIT" repository = "https://github.com/zesterer/euc" readme = "README.md" edition = "2018" -keywords = ["renderer", "3D", "graphics", "raster"] +keywords = ["renderer", "3D", "graphics", "rasterizer", "shader"] exclude = [ "/misc", "/misc/*", @@ -32,7 +32,7 @@ par = ["std", "num_cpus", "fxhash"] micromath = ["dep:micromath"] [dev-dependencies] -vek = { version = "0.16", features = ["rgb"] } +vek = { version = "0.16", default-features = false, features = ["rgba"] } minifb = "0.20" wavefront = "0.2" criterion = "0.3.3" diff --git a/README.md b/README.md index 9f1c74b..5b04ec5 100644 --- a/README.md +++ b/README.md @@ -63,9 +63,9 @@ thread-based parallelism to accelerate rendering. - Write shaders in Rust (vertex, geometry, fragment and blend shaders) - Multithreading support for parallel rendering acceleration -- Many supported primitives (triangles, lines, points, etc.) -- Textures and texture samplers -- Customisable coordinate space +- Many supported primitives and vertex formats (triangle lists, line pairs, etc.) +- N-dimensional textures and samplers (including support for filtering, clamping, tiling, mirroring, etc.) +- Customisable coordinate space (choose compatibility with OpenGL, Vulkan, DirectX, or Metal) - Built-in support for index buffers ## Why?