1#![allow(dead_code)]
4
5#[derive(Clone, Debug)]
9pub struct AFrameEntity {
10 pub tag: String,
11 pub attributes: Vec<(String, String)>,
12}
13
14#[derive(Clone, Debug, Default)]
16pub struct AFrameScene {
17 pub entities: Vec<AFrameEntity>,
18 pub title: String,
19}
20
21pub fn new_aframe_scene(title: &str) -> AFrameScene {
23 AFrameScene {
24 entities: Vec::new(),
25 title: title.to_string(),
26 }
27}
28
29pub fn aframe_push_entity(scene: &mut AFrameScene, tag: &str, attrs: Vec<(&str, &str)>) {
31 scene.entities.push(AFrameEntity {
32 tag: tag.to_string(),
33 attributes: attrs
34 .iter()
35 .map(|(k, v)| (k.to_string(), v.to_string()))
36 .collect(),
37 });
38}
39
40pub fn aframe_entity_count(scene: &AFrameScene) -> usize {
42 scene.entities.len()
43}
44
45pub fn render_aframe_html(scene: &AFrameScene) -> String {
47 let mut out = format!(
48 "<!DOCTYPE html>\n<html>\n<head><title>{}</title>\n\
49 <script src=\"https://aframe.io/releases/1.4.0/aframe.min.js\"></script>\n\
50 </head>\n<body>\n<a-scene>\n",
51 html_escape_af(&scene.title)
52 );
53 for entity in &scene.entities {
54 out.push_str(&format!(" <{}", entity.tag));
55 for (k, v) in &entity.attributes {
56 out.push_str(&format!(" {}=\"{}\"", k, html_escape_af(v)));
57 }
58 out.push_str(&format!("></{}>", entity.tag));
59 out.push('\n');
60 }
61 out.push_str("</a-scene>\n</body>\n</html>\n");
62 out
63}
64
65pub fn aframe_add_box(scene: &mut AFrameScene, pos: [f32; 3], color: &str) {
67 let pos_str = format!("{} {} {}", pos[0], pos[1], pos[2]);
68 aframe_push_entity(
69 scene,
70 "a-box",
71 vec![("position", &pos_str), ("color", color)],
72 );
73}
74
75pub fn aframe_add_sphere(scene: &mut AFrameScene, radius: f32, pos: [f32; 3], color: &str) {
77 let pos_str = format!("{} {} {}", pos[0], pos[1], pos[2]);
78 let r_str = format!("{}", radius);
79 aframe_push_entity(
80 scene,
81 "a-sphere",
82 vec![("position", &pos_str), ("radius", &r_str), ("color", color)],
83 );
84}
85
86pub fn export_mesh_as_aframe(positions: &[[f32; 3]], title: &str) -> AFrameScene {
88 let mut scene = new_aframe_scene(title);
89 for &p in positions {
90 aframe_add_box(&mut scene, p, "#4CC3D9");
91 }
92 scene
93}
94
95pub fn validate_aframe(scene: &AFrameScene) -> bool {
97 !scene.title.is_empty() && scene.entities.iter().all(|e| !e.tag.is_empty())
98}
99
100fn html_escape_af(s: &str) -> String {
101 s.replace('&', "&")
102 .replace('<', "<")
103 .replace('>', ">")
104 .replace('"', """)
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn new_scene_empty() {
113 let s = new_aframe_scene("test");
114 assert_eq!(aframe_entity_count(&s), 0);
115 assert_eq!(s.title, "test");
116 }
117
118 #[test]
119 fn push_entity_increments_count() {
120 let mut s = new_aframe_scene("test");
121 aframe_push_entity(&mut s, "a-box", vec![("color", "red")]);
122 assert_eq!(aframe_entity_count(&s), 1);
123 }
124
125 #[test]
126 fn render_html_contains_aframe_tag() {
127 let s = new_aframe_scene("demo");
128 let html = render_aframe_html(&s);
129 assert!(html.contains("<a-scene>"));
130 }
131
132 #[test]
133 fn add_box_adds_entity() {
134 let mut s = new_aframe_scene("test");
135 aframe_add_box(&mut s, [0.0, 1.0, 0.0], "#fff");
136 assert_eq!(aframe_entity_count(&s), 1);
137 assert_eq!(s.entities[0].tag, "a-box");
138 }
139
140 #[test]
141 fn add_sphere_adds_entity() {
142 let mut s = new_aframe_scene("test");
143 aframe_add_sphere(&mut s, 1.5, [0.0, 0.0, 0.0], "blue");
144 assert_eq!(s.entities[0].tag, "a-sphere");
145 }
146
147 #[test]
148 fn export_mesh_creates_one_entity_per_vertex() {
149 let pos = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
150 let s = export_mesh_as_aframe(&pos, "mesh");
151 assert_eq!(aframe_entity_count(&s), 3);
152 }
153
154 #[test]
155 fn validate_valid_scene() {
156 let mut s = new_aframe_scene("test");
157 aframe_push_entity(&mut s, "a-box", vec![]);
158 assert!(validate_aframe(&s));
159 }
160
161 #[test]
162 fn html_escape_works() {
163 let s = html_escape_af("<script>");
164 assert!(s.contains("<") && s.contains(">"));
165 }
166}