Skip to content

Commit fb557e6

Browse files
Initial commit: parse vertices and generate point cloud CSS
0 parents  commit fb557e6

File tree

4 files changed

+200
-0
lines changed

4 files changed

+200
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/target

Cargo.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "obj-to-html"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]

src/main.rs

+184
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
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

Comments
 (0)