Skip to content

Commit f1d0df4

Browse files
committed
work on gltf viewer, beginning gltf animations
1 parent 4b014a3 commit f1d0df4

File tree

14 files changed

+497
-169
lines changed

14 files changed

+497
-169
lines changed

crates/example/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,5 @@ gltf = "1.1.0"
1313
icosahedron = "^0.1"
1414
log = "0.4.17"
1515
renderling = { path = "../renderling" }
16-
reqwest = { version = "0.11.14", features = ["blocking"] }
1716
winit = "^0.27"
1817
wgpu = "^0.14"

crates/example/src/gltf.rs

Lines changed: 131 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
//! Runs through all the gltf sample models to test and show-off renderling's gltf capabilities.
22
//!
33
//! This demo requires an internet connection to download the samples.
4+
use core::f32;
5+
46
use renderling::{
57
math::{Mat4, Vec3},
68
Camera, FontArc, ForwardPipeline, GltfLoader, GlyphCache, Object, Renderling, Section, Text,
@@ -11,35 +13,28 @@ use winit::event::KeyboardInput;
1113
const RADIUS_SCROLL_DAMPENING: f32 = 0.001;
1214
const DX_DY_DRAG_DAMPENING: f32 = 0.01;
1315

14-
const MODELS: [(&str, &str); 3] = [
15-
// standard
16-
(
17-
"Box",
18-
"https://github.com/KhronosGroup/glTF-Sample-Models/raw/master/2.0/Box/glTF-Binary/Box.glb",
19-
),
20-
(
21-
"Box Interleaved",
22-
"https://github.com/KhronosGroup/glTF-Sample-Models/raw/master/2.0/BoxInterleaved/glTF-Binary/BoxInterleaved.glb"
23-
),
24-
(
25-
"Box Textured",
26-
"https://github.com/KhronosGroup/glTF-Sample-Models/raw/master/2.0/BoxTextured/glTF-Binary/BoxTextured.glb"
27-
),
28-
];
16+
#[derive(Default)]
17+
struct Ui {
18+
title_text: String,
19+
}
2920

3021
struct App {
31-
index: usize,
22+
renderling_ui: Renderling<UiPipeline>,
23+
renderling_forward: Renderling<ForwardPipeline>,
3224

33-
ui: Renderling<UiPipeline>,
34-
text: Object,
25+
ui: Ui,
26+
27+
text_title: Object,
28+
text_camera: Object,
3529
ui_camera: Camera,
3630
cache: GlyphCache,
3731

38-
forward: Renderling<ForwardPipeline>,
3932
forward_camera: Camera,
4033

4134
loader: GltfLoader,
4235

36+
// look at
37+
eye: Vec3,
4338
// distance from the origin
4439
radius: f32,
4540
// anglular position on a circle `radius` away from the origin on x,z
@@ -53,7 +48,7 @@ struct App {
5348
}
5449

5550
impl App {
56-
fn new(gpu: &mut WgpuState, starting_index: usize) -> Self {
51+
fn new(gpu: &mut WgpuState) -> Self {
5752
let radius = 6.0;
5853
let phi = 0.0;
5954
let theta = std::f32::consts::FRAC_PI_4;
@@ -63,8 +58,10 @@ impl App {
6358
let mut ui: Renderling<UiPipeline> = gpu.new_ui_renderling();
6459
let ui_camera = ui.new_camera().with_projection_ortho2d().build();
6560
let text = ui.new_object().build().unwrap();
61+
let text_camera = ui.new_object().build().unwrap();
6662
// get the font for the UI
67-
let bytes: Vec<u8> = std::fs::read("fonts/Font Awesome 6 Free-Regular-400.otf").unwrap();
63+
let bytes: Vec<u8> =
64+
std::fs::read("fonts/Recursive Mn Lnr St Med Nerd Font Complete.ttf").unwrap();
6865

6966
let font = FontArc::try_from_vec(bytes).unwrap();
7067
let cache = gpu.new_glyph_cache(vec![font]);
@@ -73,14 +70,18 @@ impl App {
7370
topology: wgpu::PrimitiveTopology::TriangleList,
7471
strip_index_format: None,
7572
front_face: wgpu::FrontFace::Ccw,
76-
cull_mode: Some(wgpu::Face::Back),
73+
cull_mode: None, //Some(wgpu::Face::Back),
7774
polygon_mode: wgpu::PolygonMode::Fill,
7875
conservative: false,
7976
unclipped_depth: false,
8077
}));
8178
let forward_camera = forward
8279
.new_camera()
83-
.with_projection_perspective()
80+
.with_projection(Mat4::perspective_infinite_rh(
81+
std::f32::consts::FRAC_PI_4,
82+
window_size.0 as f32 / window_size.1 as f32,
83+
0.01,
84+
))
8485
.with_view(Mat4::look_at_rh(
8586
Self::camera_position(radius, phi, theta),
8687
Vec3::ZERO,
@@ -90,23 +91,25 @@ impl App {
9091
let loader = gpu.new_gltf_loader();
9192

9293
let mut app = Self {
93-
index: starting_index,
94-
ui,
95-
text,
94+
renderling_ui: ui,
95+
text_title: text,
96+
text_camera,
9697
ui_camera,
9798
cache,
98-
forward,
99+
renderling_forward: forward,
99100
forward_camera,
100101
loader,
101102
radius,
103+
eye: Vec3::ZERO,
102104
phi,
103105
theta,
104106
left_mb_down,
105107
last_cursor_position,
108+
ui: Ui {
109+
title_text: "drag and drop a `.gltf` or `.glb` file to load".to_string(),
110+
},
106111
};
107-
let (name, url) = MODELS[app.index];
108-
app.load(url);
109-
app.update_ui(name);
112+
app.update_ui();
110113
app
111114
}
112115

@@ -118,48 +121,98 @@ impl App {
118121
Vec3::new(x, z, y)
119122
}
120123

121-
fn update_ui(&mut self, name: &str) {
124+
fn update_ui(&mut self) {
122125
self.cache.queue(
123-
Section::default().add_text(
124-
Text::new(name)
125-
.with_color([1.0, 1.0, 1.0, 1.0])
126-
.with_scale(64.0),
127-
),
126+
Section::default()
127+
.add_text(
128+
Text::new(&self.ui.title_text)
129+
.with_color([1.0, 1.0, 1.0, 1.0])
130+
.with_scale(46.0),
131+
)
132+
.add_text(Text::new("\n"))
133+
.add_text(
134+
Text::new(&format!("distance: {}, center: {}", self.radius, self.eye))
135+
.with_color([0.8, 0.8, 0.8, 1.0])
136+
.with_scale(32.0)
137+
),
128138
);
129139

130140
let (material, mesh) = self.cache.get_updated();
131141

132142
if let Some(material) = material {
133-
self.text.set_material(material);
143+
self.text_title.set_material(material);
134144
}
135145
if let Some(mesh) = mesh {
136-
self.text.set_mesh(mesh);
146+
self.text_title.set_mesh(mesh);
137147
}
138148
}
139149

140-
fn load(&mut self, url: &str) {
150+
fn update_camera(&mut self) {
151+
self.forward_camera.set_view(Mat4::look_at_rh(
152+
Self::camera_position(self.radius, self.phi, self.theta),
153+
self.eye,
154+
Vec3::Y,
155+
));
156+
}
157+
158+
fn update(&mut self) {
159+
self.update_camera();
160+
self.update_ui();
161+
}
162+
163+
fn load(&mut self, file: impl AsRef<std::path::Path>) {
141164
self.loader.unload();
142165

143-
self.radius = 6.0;
144166
self.phi = 0.0;
145167
self.theta = std::f32::consts::FRAC_PI_4;
146168
self.left_mb_down = false;
147169
self.last_cursor_position = None;
148170

149-
let bytes = reqwest::blocking::get(url).unwrap().bytes().unwrap();
150-
let (document, buffers, images) = gltf::import_slice(bytes).unwrap();
171+
let (document, buffers, images) = gltf::import(&file).unwrap();
151172
self.loader
152-
.load_scene(None, &mut self.forward, &document, &buffers, &images)
173+
.load_scene(
174+
None,
175+
&mut self.renderling_forward,
176+
&document,
177+
&buffers,
178+
&images,
179+
)
153180
.unwrap();
181+
182+
if self.loader.lights().count() == 0 {
183+
let name = get_name(file);
184+
self.ui.title_text = format!("{name} (unlit)");
185+
}
186+
187+
// find the bounding box of the model so we can display it correctly
188+
let mut min = Vec3::splat(f32::INFINITY);
189+
let mut max = Vec3::splat(f32::NEG_INFINITY);
190+
for node in document.nodes() {
191+
if let Some(mesh) = node.mesh() {
192+
for primitive in mesh.primitives() {
193+
let aabb = primitive.bounding_box();
194+
min.x = min.x.min(aabb.min[0]);
195+
min.y = min.y.min(aabb.min[1]);
196+
min.z = min.z.min(aabb.min[2]);
197+
max.x = max.x.max(aabb.max[0]);
198+
max.y = max.y.max(aabb.max[1]);
199+
max.z = max.z.max(aabb.max[2]);
200+
}
201+
}
202+
}
203+
204+
let halfway_point = min + ((max - min).normalize() * ((max - min).length() / 2.0));
205+
let length = min.distance(max);
206+
let radius = length * 1.25;
207+
208+
self.radius = radius;
209+
self.eye = halfway_point;
210+
self.update();
154211
}
155212

156213
fn zoom(&mut self, delta: f32) {
157-
self.radius -= delta * RADIUS_SCROLL_DAMPENING;
158-
self.forward_camera.set_view(Mat4::look_at_rh(
159-
Self::camera_position(self.radius, self.phi, self.theta),
160-
Vec3::ZERO,
161-
Vec3::Y,
162-
));
214+
self.radius = (self.radius - (delta * RADIUS_SCROLL_DAMPENING)).max(0.0);
215+
self.update();
163216
}
164217

165218
fn pan(&mut self, position: winit::dpi::PhysicalPosition<f64>) {
@@ -173,11 +226,7 @@ impl App {
173226
let next_theta = self.theta - dy as f32 * DX_DY_DRAG_DAMPENING;
174227
self.theta = next_theta.max(0.0001).min(std::f32::consts::PI);
175228

176-
self.forward_camera.set_view(Mat4::look_at_rh(
177-
Self::camera_position(self.radius, self.phi, self.theta),
178-
Vec3::ZERO,
179-
Vec3::Y,
180-
));
229+
self.update();
181230
}
182231
self.last_cursor_position = Some(position);
183232
}
@@ -208,26 +257,15 @@ impl App {
208257
if matches!(state, winit::event::ElementState::Pressed) {
209258
return;
210259
}
211-
let should_load = match virtual_keycode {
212-
Some(winit::event::VirtualKeyCode::Left) => {
213-
self.index = (self.index + MODELS.len() - 1) % MODELS.len();
214-
true
260+
match virtual_keycode {
261+
Some(winit::event::VirtualKeyCode::Space) => {
262+
// clear all objects, cameras and lights
263+
self.loader.unload();
264+
self.ui.title_text = "awaiting drag and dropped `.gltf` or `.glb` file".to_string();
265+
self.update();
215266
}
216-
Some(winit::event::VirtualKeyCode::Right) => {
217-
self.index = (self.index + 1) % MODELS.len();
218-
true
219-
}
220-
_ => false,
267+
_ => {}
221268
};
222-
if should_load {
223-
let (name, url) = MODELS[self.index];
224-
self.update_ui(name);
225-
self.loader.unload();
226-
self.forward.update().unwrap();
227-
self.render(gpu);
228-
229-
self.load(url);
230-
}
231269
}
232270

233271
fn resize(&mut self, gpu: &mut WgpuState, width: u32, height: u32) {
@@ -240,48 +278,44 @@ impl App {
240278
1.0,
241279
-1.0,
242280
));
243-
self.forward_camera.set_projection(Mat4::perspective_rh(
281+
self.forward_camera.set_projection(Mat4::perspective_infinite_rh(
244282
std::f32::consts::FRAC_PI_4,
245283
width as f32 / height as f32,
246-
0.1,
247-
100.0,
284+
0.01,
248285
));
249286
}
250287

251288
fn render(&mut self, gpu: &mut WgpuState) {
252289
let (frame, depth) = gpu.next_frame_cleared().unwrap();
253290

254-
self.forward.update().unwrap();
255-
self.forward.render(&frame, &depth).unwrap();
291+
self.renderling_forward.update().unwrap();
292+
self.renderling_forward.render(&frame, &depth).unwrap();
256293

257294
// just clear the depth texture, because we want to render the 2d UI over the 3d background
258295
gpu.clear(None, Some(&depth));
259-
self.ui.update().unwrap();
260-
self.ui.render(&frame, &depth).unwrap();
296+
self.renderling_ui.update().unwrap();
297+
self.renderling_ui.render(&frame, &depth).unwrap();
261298

262299
gpu.present().unwrap();
263300
}
264301
}
265302

303+
fn get_name(path: impl AsRef<std::path::Path>) -> String {
304+
let file = path.as_ref().file_name();
305+
let name = file.map(|s| s.to_str()).flatten().unwrap_or("unknown");
306+
name.to_string()
307+
}
308+
266309
/// Sets up the demo for a given model
267310
pub fn demo(
268311
gpu: &mut WgpuState,
269312
start: Option<impl AsRef<str>>,
270313
) -> impl FnMut(&mut WgpuState, Option<&winit::event::WindowEvent>) {
271-
// find which model we're going to view
272-
let index = start
273-
.map(|input_name| {
274-
for (i, (name, _)) in MODELS.iter().enumerate() {
275-
if input_name.as_ref().to_lowercase() == name.to_lowercase() {
276-
return Some(i);
277-
}
278-
}
279-
None
280-
})
281-
.flatten()
282-
.unwrap_or_else(|| 0);
314+
let mut app = App::new(gpu);
283315

284-
let mut app = App::new(gpu, index);
316+
if let Some(file) = start {
317+
app.load(file.as_ref());
318+
}
285319

286320
move |gpu, ev: Option<&winit::event::WindowEvent>| {
287321
if let Some(ev) = ev {
@@ -306,6 +340,11 @@ pub fn demo(
306340
winit::event::WindowEvent::Resized(size) => {
307341
app.resize(gpu, size.width, size.height);
308342
}
343+
winit::event::WindowEvent::DroppedFile(path) => {
344+
app.ui.title_text = get_name(&path);
345+
app.update_ui();
346+
app.load(path);
347+
}
309348
_ => {}
310349
}
311350
} else {

0 commit comments

Comments
 (0)