|
| 1 | +use std::fs::File; |
| 2 | +use std::io::{BufReader, BufRead}; |
| 3 | + |
| 4 | +trait ExpectNone { |
| 5 | + fn expect_none(&self, msg: &str); |
| 6 | +} |
| 7 | +impl<T> ExpectNone for Option<T> { |
| 8 | + fn expect_none(&self, msg: &str) { |
| 9 | + if self.is_some() { |
| 10 | + panic!("{}", msg); |
| 11 | + } |
| 12 | + } |
| 13 | +} |
| 14 | + |
| 15 | +#[derive(Copy, Clone)] |
| 16 | +struct Vector<const T: usize>([f32; T]); |
| 17 | + |
| 18 | +impl<const T: usize> std::ops::Index<usize> for Vector<T> { |
| 19 | + type Output = f32; |
| 20 | + fn index(&self, index: usize) -> &f32 { |
| 21 | + &self.0[index] |
| 22 | + } |
| 23 | +} |
| 24 | + |
| 25 | +macro_rules! impl_vec_op { |
| 26 | + ($trait:tt, $method:ident, $op:tt) => { |
| 27 | + impl<const T: usize> std::ops::$trait<Vector<T>> for Vector<T> { |
| 28 | + type Output = Vector<T>; |
| 29 | + fn $method(self, rhs: Vector<T>) -> Vector<T> { |
| 30 | + let mut res = [0f32; T]; |
| 31 | + for i in 0..T { |
| 32 | + res[i] = self[i] $op rhs[i]; |
| 33 | + } |
| 34 | + Vector(res) |
| 35 | + } |
| 36 | + } |
| 37 | + impl<const T: usize> std::ops::$trait<f32> for Vector<T> { |
| 38 | + type Output = Vector<T>; |
| 39 | + fn $method(self, rhs: f32) -> Vector<T> { |
| 40 | + let mut res = [0f32; T]; |
| 41 | + for i in 0..T { |
| 42 | + res[i] = self[i] $op rhs; |
| 43 | + } |
| 44 | + Vector(res) |
| 45 | + } |
| 46 | + } |
| 47 | + impl<const T: usize> std::ops::$trait<Vector<T>> for f32 { |
| 48 | + type Output = Vector<T>; |
| 49 | + fn $method(self, rhs: Vector<T>) -> Vector<T> { |
| 50 | + let mut res = [0f32; T]; |
| 51 | + for i in 0..T { |
| 52 | + res[i] = self $op rhs[i]; |
| 53 | + } |
| 54 | + Vector(res) |
| 55 | + } |
| 56 | + } |
| 57 | + } |
| 58 | +} |
| 59 | +impl_vec_op!(Add, add, +); |
| 60 | +impl_vec_op!(Sub, sub, -); |
| 61 | +impl_vec_op!(Mul, mul, *); |
| 62 | +impl_vec_op!(Div, div, /); |
| 63 | + |
| 64 | +impl<const T: usize> Vector<T> { |
| 65 | + fn min(self, rhs: Vector<T>) -> Vector<T> { |
| 66 | + let mut res = [0f32; T]; |
| 67 | + for i in 0..T { |
| 68 | + res[i] = self[i].min(rhs[i]); |
| 69 | + } |
| 70 | + Vector(res) |
| 71 | + } |
| 72 | + fn max(self, rhs: Vector<T>) -> Vector<T> { |
| 73 | + let mut res = [0f32; T]; |
| 74 | + for i in 0..T { |
| 75 | + res[i] = self[i].max(rhs[i]); |
| 76 | + } |
| 77 | + Vector(res) |
| 78 | + } |
| 79 | +} |
| 80 | + |
| 81 | +type Vertex = Vector<3>; |
| 82 | + |
| 83 | +struct ParserState { |
| 84 | + vertices: Vec<Vertex>, |
| 85 | + vertex_range_min: Vector<3>, |
| 86 | + vertex_range_max: Vector<3>, |
| 87 | +} |
| 88 | + |
| 89 | +fn parse_triple(args: &[&str; 3]) -> Result<(f32, f32, f32), ()> { |
| 90 | + let x = args[0].parse().map_err(|_| ())?; |
| 91 | + let y = args[1].parse().map_err(|_| ())?; |
| 92 | + let z = args[2].parse().map_err(|_| ())?; |
| 93 | + Ok((x, y, z)) |
| 94 | +} |
| 95 | + |
| 96 | +fn parse_data(state: &mut ParserState, data_type: &str, args: &[&str]) { |
| 97 | + //println!("{}", data_type); |
| 98 | + |
| 99 | + match data_type { |
| 100 | + "v" => { |
| 101 | + let (x, y, z) = parse_triple(args.try_into().unwrap()).unwrap(); |
| 102 | + let v = Vector::<3>([x, y, z]); |
| 103 | + state.vertices.push(v); |
| 104 | + state.vertex_range_min = state.vertex_range_min.min(v); |
| 105 | + state.vertex_range_max = state.vertex_range_max.max(v); |
| 106 | + }, |
| 107 | + _ => (), |
| 108 | + } |
| 109 | +} |
| 110 | + |
| 111 | +fn main() { |
| 112 | + let obj_path = { |
| 113 | + let mut args = std::env::args(); |
| 114 | + let _ = args.next().unwrap(); // skip argv[0] |
| 115 | + let obj_path = args.next().expect("A .obj filename should be specified"); |
| 116 | + args.next().expect_none("There should only be one argument"); |
| 117 | + obj_path |
| 118 | + }; |
| 119 | + |
| 120 | + let obj_file = File::open(obj_path).expect("Couldn't open file"); |
| 121 | + let obj_file = BufReader::new(obj_file); |
| 122 | + |
| 123 | + let mut state = ParserState { |
| 124 | + vertices: Vec::new(), |
| 125 | + vertex_range_min: Vector::<3>([f32::INFINITY, f32::INFINITY, f32::INFINITY]), |
| 126 | + vertex_range_max: Vector::<3>([0f32, 0f32, 0f32]), |
| 127 | + }; |
| 128 | + |
| 129 | + // Using .split() rather than .lines() means UTF-8 decoding can be delayed |
| 130 | + // until after detecting comments, which should hopefully avoid problems |
| 131 | + // with files that have non-UTF-8 comments. |
| 132 | + for line in obj_file.split(b'\n') { |
| 133 | + let line = line.expect("Couldn't read line from file"); |
| 134 | + |
| 135 | + // comment line |
| 136 | + if let Some(b'#') = line.get(0) { |
| 137 | + continue; |
| 138 | + } |
| 139 | + |
| 140 | + let line = std::str::from_utf8(&line).expect("Non-comment lines should be UTF-8"); |
| 141 | + let line = line.trim_end(); // might end with \r on Windows |
| 142 | + |
| 143 | + let Some((data_type, args)) = line.split_once(' ') else { |
| 144 | + continue; |
| 145 | + }; |
| 146 | + |
| 147 | + let args: Vec<&str> = args.split(' ').collect(); |
| 148 | + |
| 149 | + parse_data(&mut state, data_type, &args); |
| 150 | + } |
| 151 | + |
| 152 | + let vertex_range = state.vertex_range_max - state.vertex_range_min; |
| 153 | + |
| 154 | + |
| 155 | + let target_dimension = 300.0; // rough pixel width of a cohost post? |
| 156 | + let scale = target_dimension / vertex_range[0].max(vertex_range[1]); |
| 157 | + |
| 158 | + let center = Vector::<3>([target_dimension / 2.0, target_dimension / 2.0, 0f32]); |
| 159 | + |
| 160 | + let offset = center - (vertex_range * scale) / 2.0 - state.vertex_range_min * scale; |
| 161 | + // avoid having negative z values, it looks bad with perspective on |
| 162 | + let offset = Vector::<3>([offset[0], offset[1], vertex_range[2] * scale]); |
| 163 | + |
| 164 | + // spin animation from the cohost CSS (this does a Z-axis rotation) |
| 165 | + println!("<style>@keyframes spin {{to{{transform:rotate(360deg)}}}}</style>"); |
| 166 | + |
| 167 | + println!("<div style=\"width: {}px; height: {}px; perspective: {}px; background: grey; position: relative; overflow: hidden;\">", target_dimension, target_dimension, vertex_range[2] * scale * 10.0); |
| 168 | + |
| 169 | + // continuously spin around the Y axis |
| 170 | + print!("<div style=\"transform-style: preserve-3d; transform: rotateX(-90deg);\">"); |
| 171 | + print!("<div style=\"transform-style: preserve-3d; animation: 5s linear infinite spin;\">"); |
| 172 | + println!("<div style=\"transform-style: preserve-3d; transform: rotateX(90deg);\">"); |
| 173 | + |
| 174 | + for vertex in state.vertices { |
| 175 | + let vertex = vertex * scale + offset; |
| 176 | + // hack to fix Z and Y being wrong direction by rotating |
| 177 | + let vertex = Vector::<3>([target_dimension, target_dimension, 0f32]) - vertex; |
| 178 | + println!("<div style=\"position: absolute; translate: {}px {}px {}px; width: 4px; height: 4px; background: black;\"></div>", vertex[0], vertex[1], vertex[2]); |
| 179 | + } |
| 180 | + |
| 181 | + println!("</div></div></div>"); |
| 182 | + |
| 183 | + println!("</div>"); |
| 184 | +} |
0 commit comments