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+
46use renderling:: {
57 math:: { Mat4 , Vec3 } ,
68 Camera , FontArc , ForwardPipeline , GltfLoader , GlyphCache , Object , Renderling , Section , Text ,
@@ -11,35 +13,28 @@ use winit::event::KeyboardInput;
1113const RADIUS_SCROLL_DAMPENING : f32 = 0.001 ;
1214const 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
3021struct 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
5550impl 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
267310pub 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