1use anyhow::Result;
2use std::collections::HashMap;
3use std::path::Path;
4use bytemuck::{Pod, Zeroable};
5use glam::{Mat4, Vec2, Vec3};
6use kengaai_model_loader;
7use kengaai_scene_fps::{BoxDef, FpsScene};
8use log::info;
9use wgpu::util::DeviceExt;
10use winit::window::Window;
11use rapier3d::prelude::*;
12use rapier3d::control::{KinematicCharacterController, CharacterLength};
13
14#[cfg(target_arch = "wasm32")]
15use wasm_bindgen::prelude::*;
16
17#[repr(C)]
19#[derive(Clone, Copy, Debug, Pod, Zeroable)]
20struct Vertex {
21 pos: [f32; 3],
22 normal: [f32; 3],
23 tex_coords: [f32; 2],
24}
25
26fn cube_vertices() -> Vec<Vertex> {
27 let p = [
28 [-0.5, -0.5, 0.5], [0.5, -0.5, 0.5], [0.5, 0.5, 0.5], [-0.5, 0.5, 0.5],
29 [-0.5, -0.5, -0.5], [0.5, -0.5, -0.5], [0.5, 0.5, -0.5], [-0.5, 0.5, -0.5],
30 ];
31 let uv = [
32 [0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0],
33 ];
34 let faces: [([usize; 4], [f32; 3]); 6] = [
35 ([0, 1, 2, 3], [0.0, 0.0, 1.0]), ([5, 4, 7, 6], [0.0, 0.0, -1.0]), ([4, 0, 3, 7], [-1.0, 0.0, 0.0]), ([1, 5, 6, 2], [1.0, 0.0, 0.0]), ([3, 2, 6, 7], [0.0, 1.0, 0.0]), ([4, 5, 1, 0], [0.0, -1.0, 0.0]), ];
42 let mut v = Vec::with_capacity(36);
43 for (idx, n) in faces {
44 let tri = [
45 Vertex { pos: p[idx[0]], normal: n, tex_coords: uv[0] },
46 Vertex { pos: p[idx[1]], normal: n, tex_coords: uv[1] },
47 Vertex { pos: p[idx[2]], normal: n, tex_coords: uv[2] },
48 Vertex { pos: p[idx[0]], normal: n, tex_coords: uv[0] },
49 Vertex { pos: p[idx[2]], normal: n, tex_coords: uv[2] },
50 Vertex { pos: p[idx[3]], normal: n, tex_coords: uv[3] },
51 ];
52 v.extend_from_slice(&tri);
53 }
54 v
55}
56
57#[repr(C)]
58#[derive(Clone, Copy, Debug, Pod, Zeroable)]
59struct Instance {
60 pos: [f32; 3],
61 scale: [f32; 3],
62 rot_y: f32,
63 color: [f32; 3],
64 _pad: f32,
65}
66
67impl From<&BoxDef> for Instance {
68 fn from(b: &BoxDef) -> Self {
69 Self {
70 pos: b.pos,
71 scale: b.size,
72 rot_y: b.rot_y,
73 color: b.color,
74 _pad: 0.0,
75 }
76 }
77}
78
79#[repr(C)]
80#[derive(Clone, Copy, Debug, Pod, Zeroable)]
81struct CameraUBO {
82 view_proj: [[f32; 4]; 4],
83 pos: [f32; 4],
84}
85
86#[repr(C, align(16))]
87#[derive(Clone, Copy, Debug)]
88struct LightRaw {
89 position: [f32; 3],
90 _pad0: f32, color: [f32; 3],
92 intensity: f32,
93 kind: u32,
94 _pad1: [u32; 2], direction: [f32; 3],
96 inner_cone_angle: f32,
97 outer_cone_angle: f32,
98 _pad2: [f32; 2], }
100
101unsafe impl Pod for LightRaw {}
102unsafe impl Zeroable for LightRaw {}
103
104#[repr(C)]
105#[derive(Clone, Copy, Debug, Pod, Zeroable)]
106struct LightsUBO {
107 count: u32,
108 _pad: [u32; 3],
109 lights: [LightRaw; 16],
110}
111
112pub struct MeshBuffers {
113 vbo: wgpu::Buffer,
114 ibo: wgpu::Buffer,
115 num_indices: u32,
116 material_index: usize,
117}
118
119#[repr(C)]
120#[derive(Clone, Copy, Debug, Pod, Zeroable)]
121struct MaterialUBO {
122 base_color_factor: [f32; 4],
123 metallic_factor: f32,
124 roughness_factor: f32,
125 _pad: [f32; 2],
126}
127
128pub struct LoadedModel {
129 meshes: Vec<MeshBuffers>,
130 material_bind_groups: Vec<wgpu::BindGroup>,
131}
132
133pub struct FpsRenderer<'w> {
134 surface: wgpu::Surface<'w>,
135 device: wgpu::Device,
136 queue: wgpu::Queue,
137 config: wgpu::SurfaceConfiguration,
138 size: winit::dpi::PhysicalSize<u32>,
139 color: wgpu::Color,
140
141 depth_tex: wgpu::Texture,
142 depth_view: wgpu::TextureView,
143
144 _shadow_texture: wgpu::Texture,
145 shadow_texture_view: wgpu::TextureView,
146
147 instance_pipeline: wgpu::RenderPipeline,
148 model_pipeline: wgpu::RenderPipeline,
149 shadow_pipeline: wgpu::RenderPipeline,
150 skybox_pipeline: wgpu::RenderPipeline,
151
152 vbo: wgpu::Buffer,
153 _skybox_vbo: wgpu::Buffer,
154 cam_buf: wgpu::Buffer,
155 cam_bind: wgpu::BindGroup,
156 light_cam_buf: wgpu::Buffer,
157 light_cam_bind: wgpu::BindGroup,
158 _lights_buf: wgpu::Buffer,
159 lights_bind: wgpu::BindGroup,
160 _shadow_bind_group_layout: wgpu::BindGroupLayout,
161 shadow_bind_group: wgpu::BindGroup,
162 _light_space_bind_group_layout: wgpu::BindGroupLayout,
163 light_space_bind_group: wgpu::BindGroup,
164
165 instance_groups: HashMap<Option<String>, Vec<Instance>>,
166 instance_buffers: HashMap<Option<String>, wgpu::Buffer>,
167
168 texture_bind_group_layout: wgpu::BindGroupLayout,
169 pbr_material_bind_group_layout: wgpu::BindGroupLayout,
170 textures: HashMap<String, (wgpu::Texture, wgpu::BindGroup)>,
171 texture_sampler: wgpu::Sampler,
172
173 pub camera: Camera,
174 loaded_models: HashMap<String, LoadedModel>,
175}
176
177pub struct Camera {
178 pub pos: Vec3,
179 pub yaw: f32,
180 pub pitch: f32,
181 pub fov_y: f32,
182 pub z_near: f32,
183 pub z_far: f32,
184}
185
186impl Camera {
187 pub fn view(&self) -> Mat4 {
188 let dir = Self::dir(self.yaw, self.pitch);
189 Mat4::look_to_rh(self.pos, dir, Vec3::Y)
190 }
191
192 pub fn proj(&self, aspect: f32) -> Mat4 {
193 Mat4::perspective_rh(self.fov_y.to_radians(), aspect, self.z_near, self.z_far)
194 }
195
196 pub fn dir(yaw: f32, pitch: f32) -> Vec3 {
197 let (sy, cy) = yaw.sin_cos();
198 let (sp, cp) = pitch.sin_cos();
199 Vec3::new(cy * cp, sp, sy * cp)
200 }
201}
202
203pub struct PhysicsWorld {
205 pub gravity: Vector<f32>,
206 pub integration_parameters: IntegrationParameters,
207 pub physics_pipeline: PhysicsPipeline,
208 pub island_manager: IslandManager,
209 pub broad_phase: BroadPhase,
210 pub narrow_phase: NarrowPhase,
211 pub rigid_body_set: RigidBodySet,
212 pub collider_set: ColliderSet,
213 pub impulse_joint_set: ImpulseJointSet,
214 pub multibody_joint_set: MultibodyJointSet,
215 pub ccd_solver: CCDSolver,
216 pub query_pipeline: QueryPipeline,
217 character_controller: KinematicCharacterController,
218 player_vertical_velocity: f32,
219 player_grounded: bool,
220 jump_velocity: f32,
221}
222
223impl PhysicsWorld {
224 pub fn new() -> Self {
225 let mut controller = KinematicCharacterController::default();
226 controller.offset = CharacterLength::Absolute(0.01);
227 Self {
228 gravity: vector![0.0, -9.81, 0.0],
229 integration_parameters: IntegrationParameters::default(),
230 physics_pipeline: PhysicsPipeline::new(),
231 island_manager: IslandManager::new(),
232 broad_phase: BroadPhase::new(),
233 narrow_phase: NarrowPhase::new(),
234 rigid_body_set: RigidBodySet::new(),
235 collider_set: ColliderSet::new(),
236 impulse_joint_set: ImpulseJointSet::new(),
237 multibody_joint_set: MultibodyJointSet::new(),
238 ccd_solver: CCDSolver::new(),
239 query_pipeline: QueryPipeline::new(),
240 character_controller: controller,
241 player_vertical_velocity: 0.0,
242 player_grounded: true,
243 jump_velocity: 12.0,
244 }
245 }
246
247 pub fn step(&mut self, player_handle: RigidBodyHandle, wish_dir: Vec3, jump: bool, dt: f32) {
248 self.physics_pipeline.step(
249 &self.gravity,
250 &self.integration_parameters,
251 &mut self.island_manager,
252 &mut self.broad_phase,
253 &mut self.narrow_phase,
254 &mut self.rigid_body_set,
255 &mut self.collider_set,
256 &mut self.impulse_joint_set,
257 &mut self.multibody_joint_set,
258 &mut self.ccd_solver,
259 None,
260 &(),
261 &(),
262 );
263 self.query_pipeline.update(&self.rigid_body_set, &self.collider_set);
264
265 let player_body = if let Some(body) = self.rigid_body_set.get(player_handle) {
266 body
267 } else {
268 return;
269 };
270
271 let player_position = *player_body.position();
272 let player_collider_handle = player_body.colliders()[0];
273
274 let was_grounded = self.player_grounded;
276 let mut vertical_velocity = if was_grounded { 0.0 } else { self.player_vertical_velocity };
277
278 let mut jump_started = false;
279 if jump && was_grounded {
280 vertical_velocity = self.jump_velocity;
281 jump_started = true;
282 }
283
284 vertical_velocity += self.gravity.y * dt;
286
287 let mut desired_translation = vector![wish_dir.x, 0.0, wish_dir.z] * dt;
288 desired_translation.y = vertical_velocity * dt;
289
290 let mut collisions = Vec::new();
291 let filter = QueryFilter::default().exclude_rigid_body(player_handle);
292
293 let computed_movement = self.character_controller.move_shape(
294 dt,
295 &self.rigid_body_set,
296 &self.collider_set,
297 &self.query_pipeline,
298 self.collider_set.get(player_collider_handle).unwrap().shape(),
299 &player_position,
300 desired_translation,
301 filter,
302 |c| { collisions.push(c); },
303 );
304
305 let mut grounded = false;
307 let mut hit_ceiling = false;
308
309 for collision in &collisions {
311 if collision.toi.normal1.y > 0.3 {
313 grounded = true;
314 }
315 if collision.toi.normal1.y < -0.3 {
316 hit_ceiling = true;
317 }
318 }
319
320 if desired_translation.y > 0.0 {
322 grounded = false;
323 }
324
325 if !grounded && vertical_velocity.abs() < 0.5 && !collisions.is_empty() {
327 grounded = true;
329 }
330
331 if grounded {
333 vertical_velocity = 0.0;
334 } else if hit_ceiling && vertical_velocity > 0.0 {
335 vertical_velocity = 0.0;
336 }
337
338 self.player_vertical_velocity = vertical_velocity;
339 self.player_grounded = grounded;
340
341 let player_body_mut = self.rigid_body_set.get_mut(player_handle).unwrap();
342
343 if jump {
345 if jump_started {
346 info!("JUMP! grounded(prev)={}, new_velocity_y={}", was_grounded, self.player_vertical_velocity);
347 } else if !was_grounded {
348 info!("JUMP blocked: grounded(prev)={}, vertical_velocity={}", was_grounded, self.player_vertical_velocity);
349 }
350 }
351
352 player_body_mut.set_linvel(vector![wish_dir.x, self.player_vertical_velocity, wish_dir.z], true);
354
355 let new_pos = player_position.translation.vector + computed_movement.translation;
356 player_body_mut.set_next_kinematic_position(Isometry::from_parts(Translation::from(new_pos), player_position.rotation));
357 }
358}
359
360impl Default for PhysicsWorld {
361 fn default() -> Self {
362 Self::new()
363 }
364}
365
366impl<'w> FpsRenderer<'w> {
369 pub fn new(window: &'w Window, scene: &FpsScene) -> Result<Self, anyhow::Error> {
370 #[cfg(target_arch = "wasm32")]
371 {
372 return Err(anyhow::anyhow!("WASM initialization requires async setup. Use new_async instead."));
374 }
375
376 #[cfg(not(target_arch = "wasm32"))]
377 {
378 pollster::block_on(Self::new_async_native(window, scene))
380 }
381 }
382
383 #[cfg(not(target_arch = "wasm32"))]
384 async fn new_async_native(window: &'w Window, scene: &FpsScene) -> Result<Self, anyhow::Error> {
385 let size = window.inner_size();
386 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
387 backends: wgpu::Backends::all(),
388 ..Default::default()
389 });
390 let surface = instance.create_surface(window)
391 .map_err(|e| anyhow::anyhow!("Failed to create surface: {:?}", e))?;
392 let adapter = instance
393 .request_adapter(&wgpu::RequestAdapterOptions {
394 power_preference: wgpu::PowerPreference::HighPerformance,
395 compatible_surface: Some(&surface),
396 force_fallback_adapter: false,
397 })
398 .await
399 .ok_or_else(|| anyhow::anyhow!("Failed to find an appropriate adapter"))?;
400
401 let (device, queue) = adapter
402 .request_device(
403 &wgpu::DeviceDescriptor {
404 label: Some("device"),
405 required_features: wgpu::Features::empty(),
406 required_limits: adapter.limits(),
407 },
408 None,
409 )
410 .await
411 .map_err(|e| anyhow::anyhow!("Failed to request device: {:?}", e))?;
412
413 let caps = surface.get_capabilities(&adapter);
414 let format = caps.formats.iter().copied().find(|f| f.is_srgb()).unwrap_or(caps.formats[0]);
415
416 let config = wgpu::SurfaceConfiguration {
417 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
418 format,
419 width: size.width.max(1),
420 height: size.height.max(1),
421 present_mode: wgpu::PresentMode::AutoVsync,
422 alpha_mode: caps.alpha_modes[0],
423 view_formats: vec![],
424 desired_maximum_frame_latency: 2,
425 };
426 surface.configure(&device, &config);
427
428 let (depth_tex, depth_view) = create_depth(&device, size.width, size.height);
429 let (shadow_texture, shadow_texture_view) = create_shadow_texture(&device, 2048, 2048);
430
431 let cam_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
433 label: Some("cam-layout"),
434 entries: &[wgpu::BindGroupLayoutEntry{
435 binding: 0,
436 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
437 ty: wgpu::BindingType::Buffer{ ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None },
438 count: None
439 }],
440 });
441
442
443
444 let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
445 label: Some("texture_bind_group_layout"),
446 entries: &[
447 wgpu::BindGroupLayoutEntry {
448 binding: 0,
449 visibility: wgpu::ShaderStages::FRAGMENT,
450 ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false },
451 count: None,
452 },
453 wgpu::BindGroupLayoutEntry {
454 binding: 1,
455 visibility: wgpu::ShaderStages::FRAGMENT,
456 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
457 count: None,
458 },
459 ],
460 });
461
462 let lights_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
463 label: Some("lights-bind-group-layout"),
464 entries: &[
465 wgpu::BindGroupLayoutEntry {
466 binding: 0,
467 visibility: wgpu::ShaderStages::FRAGMENT,
468 ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None },
469 count: None,
470 },
471 ],
472 });
473
474 let shadow_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
475 label: Some("shadow_bind_group_layout"),
476 entries: &[
477 wgpu::BindGroupLayoutEntry {
478 binding: 0,
479 visibility: wgpu::ShaderStages::FRAGMENT,
480 ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Depth, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false },
481 count: None,
482 },
483 wgpu::BindGroupLayoutEntry {
484 binding: 1,
485 visibility: wgpu::ShaderStages::FRAGMENT,
486 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
487 count: None,
488 },
489 ],
490 });
491
492 let light_space_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
493 label: Some("light_space_bind_group_layout"),
494 entries: &[
495 wgpu::BindGroupLayoutEntry {
496 binding: 0,
497 visibility: wgpu::ShaderStages::VERTEX,
498 ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None },
499 count: None,
500 },
501 ],
502 });
503
504 let instance_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
506 label: Some("lighting_simple"),
507 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/lighting_simple.wgsl").into()),
508 });
509 let instance_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
510 label: Some("instance_pipeline_layout"),
511 bind_group_layouts: &[&cam_layout, &texture_bind_group_layout, &lights_bind_group_layout],
512 push_constant_ranges: &[],
513 });
514 let instance_v_layout = wgpu::VertexBufferLayout {
515 array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
516 step_mode: wgpu::VertexStepMode::Vertex,
517 attributes: &wgpu::vertex_attr_array![0=>Float32x3,1=>Float32x3,2=>Float32x2],
518 };
519 let instance_i_layout = wgpu::VertexBufferLayout {
520 array_stride: std::mem::size_of::<Instance>() as wgpu::BufferAddress,
521 step_mode: wgpu::VertexStepMode::Instance,
522 attributes: &wgpu::vertex_attr_array![3=>Float32x3, 4=>Float32x3, 5=>Float32, 6=>Float32x3],
523 };
524 let instance_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
525 label: Some("instance_pipeline"),
526 layout: Some(&instance_pipeline_layout),
527 vertex: wgpu::VertexState { module: &instance_shader, entry_point: "vs_main", buffers: &[instance_v_layout, instance_i_layout], compilation_options: wgpu::PipelineCompilationOptions::default() },
528 fragment: Some(wgpu::FragmentState { module: &instance_shader, entry_point: "fs_main", targets: &[Some(wgpu::ColorTargetState{ format, blend: Some(wgpu::BlendState::REPLACE), write_mask: wgpu::ColorWrites::ALL })], compilation_options: wgpu::PipelineCompilationOptions::default() }),
529 primitive: wgpu::PrimitiveState::default(),
530 depth_stencil: Some(wgpu::DepthStencilState{ format: wgpu::TextureFormat::Depth24Plus, depth_write_enabled: true, depth_compare: wgpu::CompareFunction::Less, stencil: wgpu::StencilState::default(), bias: wgpu::DepthBiasState::default() }),
531 multisample: wgpu::MultisampleState::default(),
532 multiview: None,
533 });
534
535 let model_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
537 label: Some("model_shader"),
538 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/model.wgsl").into()),
539 });
540
541
542 let pbr_material_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
543 label: Some("pbr_material_bind_group_layout"),
544 entries: &[
545 wgpu::BindGroupLayoutEntry {
547 binding: 0,
548 visibility: wgpu::ShaderStages::FRAGMENT,
549 ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None },
550 count: None,
551 },
552 wgpu::BindGroupLayoutEntry {
554 binding: 1,
555 visibility: wgpu::ShaderStages::FRAGMENT,
556 ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false },
557 count: None,
558 },
559 wgpu::BindGroupLayoutEntry {
561 binding: 2,
562 visibility: wgpu::ShaderStages::FRAGMENT,
563 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
564 count: None,
565 },
566 wgpu::BindGroupLayoutEntry {
568 binding: 3,
569 visibility: wgpu::ShaderStages::FRAGMENT,
570 ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false },
571 count: None,
572 },
573 wgpu::BindGroupLayoutEntry {
575 binding: 4,
576 visibility: wgpu::ShaderStages::FRAGMENT,
577 ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false },
578 count: None,
579 },
580 wgpu::BindGroupLayoutEntry {
582 binding: 5,
583 visibility: wgpu::ShaderStages::FRAGMENT,
584 ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false },
585 count: None,
586 },
587 ],
588 });
589
590 let model_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
591 label: Some("model_pipeline_layout"),
592 bind_group_layouts: &[&cam_layout, &pbr_material_bind_group_layout, &lights_bind_group_layout, &shadow_bind_group_layout, &light_space_bind_group_layout],
593 push_constant_ranges: &[],
594 });
595 let model_v_layout_model = wgpu::VertexBufferLayout {
596 array_stride: std::mem::size_of::<kengaai_model_loader::Vertex>() as wgpu::BufferAddress,
597 step_mode: wgpu::VertexStepMode::Vertex,
598 attributes: &wgpu::vertex_attr_array![0=>Float32x3, 1=>Float32x3, 2=>Float32x2, 3=>Float32x4, 4=>Uint32x4, 5=>Float32x4],
599 };
600 let model_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
601 label: Some("model_pipeline"),
602 layout: Some(&model_pipeline_layout),
603 vertex: wgpu::VertexState { module: &model_shader, entry_point: "vs_main", buffers: &[model_v_layout_model], compilation_options: wgpu::PipelineCompilationOptions::default() },
604 fragment: Some(wgpu::FragmentState { module: &model_shader, entry_point: "fs_main", targets: &[Some(wgpu::ColorTargetState{ format, blend: Some(wgpu::BlendState::REPLACE), write_mask: wgpu::ColorWrites::ALL })], compilation_options: wgpu::PipelineCompilationOptions::default() }),
605 primitive: wgpu::PrimitiveState::default(),
606 depth_stencil: Some(wgpu::DepthStencilState{ format: wgpu::TextureFormat::Depth24Plus, depth_write_enabled: true, depth_compare: wgpu::CompareFunction::Less, stencil: wgpu::StencilState::default(), bias: wgpu::DepthBiasState::default() }),
607 multisample: wgpu::MultisampleState::default(),
608 multiview: None,
609 });
610
611 let shadow_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
613 label: Some("shadow_shader"),
614 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/shadow.wgsl").into()),
615 });
616 let shadow_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
617 label: Some("shadow_pipeline_layout"),
618 bind_group_layouts: &[&cam_layout],
619 push_constant_ranges: &[],
620 });
621 let model_v_layout_shadow = wgpu::VertexBufferLayout {
622 array_stride: std::mem::size_of::<kengaai_model_loader::Vertex>() as wgpu::BufferAddress,
623 step_mode: wgpu::VertexStepMode::Vertex,
624 attributes: &wgpu::vertex_attr_array![0=>Float32x3, 1=>Float32x3, 2=>Float32x2, 3=>Float32x4, 4=>Uint32x4, 5=>Float32x4],
625 };
626 let shadow_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
627 label: Some("shadow_pipeline"),
628 layout: Some(&shadow_pipeline_layout),
629 vertex: wgpu::VertexState { module: &shadow_shader, entry_point: "vs_main", buffers: &[model_v_layout_shadow], compilation_options: wgpu::PipelineCompilationOptions::default() },
630 fragment: None,
631 primitive: wgpu::PrimitiveState::default(),
632 depth_stencil: Some(wgpu::DepthStencilState{ format: wgpu::TextureFormat::Depth24Plus, depth_write_enabled: true, depth_compare: wgpu::CompareFunction::Less, stencil: wgpu::StencilState::default(), bias: wgpu::DepthBiasState::default() }),
633 multisample: wgpu::MultisampleState::default(),
634 multiview: None,
635 });
636
637 let texture_sampler = device.create_sampler(&wgpu::SamplerDescriptor::default());
639 let verts = cube_vertices();
640 let vbo = device.create_buffer_init(&wgpu::util::BufferInitDescriptor{ label: Some("vbo"), contents: bytemuck::cast_slice(&verts), usage: wgpu::BufferUsages::VERTEX });
641
642 let skybox_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
644 label: Some("skybox_shader"),
645 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/skybox.wgsl").into()),
646 });
647 let skybox_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
648 label: Some("skybox_pipeline_layout"),
649 bind_group_layouts: &[&cam_layout],
650 push_constant_ranges: &[],
651 });
652 let skybox_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
653 label: Some("skybox_pipeline"),
654 layout: Some(&skybox_pipeline_layout),
655 vertex: wgpu::VertexState { module: &skybox_shader, entry_point: "vs_main", buffers: &[], compilation_options: wgpu::PipelineCompilationOptions::default() },
656 fragment: Some(wgpu::FragmentState { module: &skybox_shader, entry_point: "fs_main", targets: &[Some(wgpu::ColorTargetState{ format, blend: Some(wgpu::BlendState::REPLACE), write_mask: wgpu::ColorWrites::ALL })], compilation_options: wgpu::PipelineCompilationOptions::default() }),
657 primitive: wgpu::PrimitiveState {
658 topology: wgpu::PrimitiveTopology::TriangleStrip,
659 ..Default::default()
660 },
661 depth_stencil: Some(wgpu::DepthStencilState{
662 format: wgpu::TextureFormat::Depth24Plus,
663 depth_write_enabled: false,
664 depth_compare: wgpu::CompareFunction::Always,
665 stencil: wgpu::StencilState::default(),
666 bias: wgpu::DepthBiasState::default()
667 }),
668 multisample: wgpu::MultisampleState::default(),
669 multiview: None,
670 });
671
672 let skybox_vbo = device.create_buffer_init(&wgpu::util::BufferInitDescriptor{ label: Some("skybox_vbo"), contents: &[], usage: wgpu::BufferUsages::VERTEX });
674
675 let mut instance_groups: HashMap<Option<String>, Vec<Instance>> = HashMap::new();
676 for box_def in &scene.level.boxes {
677 instance_groups.entry(box_def.texture.clone()).or_default().push(Instance::from(box_def));
678 }
679
680 let mut instance_buffers = HashMap::new();
681 for (texture_name, instances) in &instance_groups {
682 let inst_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
683 label: Some(&format!("inst_buf_{:?}", texture_name)),
684 contents: bytemuck::cast_slice(instances),
685 usage: wgpu::BufferUsages::VERTEX,
686 });
687 instance_buffers.insert(texture_name.clone(), inst_buf);
688 }
689
690 let camera = Camera { pos: Vec3::from(scene.player.spawn), yaw: scene.player.yaw, pitch: scene.player.pitch, fov_y: 70.0, z_near: 0.1, z_far: 200.0 };
691 let cam_ubo = CameraUBO {
692 view_proj: (camera.proj(size.width as f32 / size.height as f32) * camera.view()).to_cols_array_2d(),
693 pos: [camera.pos.x, camera.pos.y, camera.pos.z, 1.0],
694 };
695 let cam_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor{ label: Some("cam-ubo"), contents: bytemuck::bytes_of(&cam_ubo), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST });
696 let cam_bind = device.create_bind_group(&wgpu::BindGroupDescriptor{ label: Some("cam-bind"), layout: &cam_layout, entries: &[wgpu::BindGroupEntry{ binding:0, resource: cam_buf.as_entire_binding() }] });
697
698 let light_camera = Camera {
699 pos: Vec3::new(0.0, 10.0, 0.0),
700 yaw: 0.0,
701 pitch: -std::f32::consts::FRAC_PI_2,
702 fov_y: 90.0,
703 z_near: 0.1,
704 z_far: 100.0,
705 };
706 let light_cam_ubo = CameraUBO {
707 view_proj: (light_camera.proj(1.0) * light_camera.view()).to_cols_array_2d(),
708 pos: [light_camera.pos.x, light_camera.pos.y, light_camera.pos.z, 1.0],
709 };
710 let light_cam_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor{ label: Some("light-cam-ubo"), contents: bytemuck::bytes_of(&light_cam_ubo), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST });
711 let light_cam_bind = device.create_bind_group(&wgpu::BindGroupDescriptor{ label: Some("light-cam-bind"), layout: &cam_layout, entries: &[wgpu::BindGroupEntry{ binding:0, resource: light_cam_buf.as_entire_binding() }] });
712
713 let mut lights_raw = LightsUBO { count: 0, _pad: [0; 3], lights: [LightRaw { position: [0.0; 3], _pad0: 0.0, color: [0.0; 3], intensity: 0.0, kind: 0, _pad1: [0; 2], direction: [0.0, -1.0, 0.0], inner_cone_angle: 0.9, outer_cone_angle: 0.8, _pad2: [0.0; 2] }; 16] };
714 for (i, light) in scene.lights.iter().enumerate().take(16) {
715 lights_raw.count += 1;
716 let kind = match light.kind.as_str() {
717 "point" => 0,
718 "directional" => 1,
719 "spot" => 2,
720 _ => 0,
721 };
722 lights_raw.lights[i] = LightRaw {
723 position: light.position,
724 _pad0: 0.0,
725 color: light.color,
726 intensity: light.intensity,
727 kind,
728 _pad1: [0; 2],
729 direction: light.direction.unwrap_or([0.0, -1.0, 0.0]),
730 inner_cone_angle: light.inner_cone_angle.unwrap_or(0.9),
731 outer_cone_angle: light.outer_cone_angle.unwrap_or(0.8),
732 _pad2: [0.0; 2],
733 };
734 }
735 let lights_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor{ label: Some("lights-ubo"), contents: bytemuck::bytes_of(&lights_raw), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST });
736 let lights_bind = device.create_bind_group(&wgpu::BindGroupDescriptor{ label: Some("lights-bind"), layout: &lights_bind_group_layout, entries: &[wgpu::BindGroupEntry{ binding:0, resource: lights_buf.as_entire_binding() }] });
737
738 let shadow_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
739 label: Some("shadow_sampler"),
740 compare: Some(wgpu::CompareFunction::LessEqual),
741 ..Default::default()
742 });
743
744 let shadow_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
745 label: Some("shadow_bind_group"),
746 layout: &shadow_bind_group_layout,
747 entries: &[
748 wgpu::BindGroupEntry {
749 binding: 0,
750 resource: wgpu::BindingResource::TextureView(&shadow_texture_view),
751 },
752 wgpu::BindGroupEntry {
753 binding: 1,
754 resource: wgpu::BindingResource::Sampler(&shadow_sampler),
755 },
756 ],
757 });
758
759 let light_space_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
760 label: Some("light_space_bind_group"),
761 layout: &light_space_bind_group_layout,
762 entries: &[
763 wgpu::BindGroupEntry {
764 binding: 0,
765 resource: light_cam_buf.as_entire_binding(),
766 },
767 ],
768 });
769
770 let mut textures = HashMap::new();
771 let white_texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("default_white_texture"), size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8UnormSrgb, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, view_formats: &[] });
772 queue.write_texture(wgpu::ImageCopyTexture { texture: &white_texture, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All }, &[255, 255, 255, 255], wgpu::ImageDataLayout { offset: 0, bytes_per_row: Some(4), rows_per_image: Some(1) }, wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 });
773 let white_texture_view = white_texture.create_view(&wgpu::TextureViewDescriptor::default());
774 let default_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("default_texture_bind_group"), layout: &texture_bind_group_layout, entries: &[
775 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&white_texture_view) },
776 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&texture_sampler) },
777 ]});
778 textures.insert("default_white".to_string(), (white_texture, default_bind_group));
779
780 let normal_texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("default_normal_texture"), size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8Unorm, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, view_formats: &[] });
781 queue.write_texture(wgpu::ImageCopyTexture { texture: &normal_texture, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All }, &[128, 128, 255, 255], wgpu::ImageDataLayout { offset: 0, bytes_per_row: Some(4), rows_per_image: Some(1) }, wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1 });
782 let normal_texture_view = normal_texture.create_view(&wgpu::TextureViewDescriptor::default());
785 let dummy_bg = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("dummy_normal_bg"), layout: &texture_bind_group_layout, entries: &[
786 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&normal_texture_view) },
787 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&texture_sampler) },
788 ]});
789 textures.insert("default_normal".to_string(), (normal_texture, dummy_bg));
790
791
792 Ok(Self{
793 surface, device, queue, config, size,
794 color: wgpu::Color{ r: scene.render.clear_color[0] as f64, g: scene.render.clear_color[1] as f64, b: scene.render.clear_color[2] as f64, a: scene.render.clear_color[3] as f64 },
795 depth_tex, depth_view, _shadow_texture: shadow_texture, shadow_texture_view, instance_pipeline, model_pipeline, shadow_pipeline, skybox_pipeline, vbo, _skybox_vbo: skybox_vbo, cam_buf, cam_bind, light_cam_buf, light_cam_bind, _lights_buf: lights_buf, lights_bind, _shadow_bind_group_layout: shadow_bind_group_layout, shadow_bind_group, _light_space_bind_group_layout: light_space_bind_group_layout, light_space_bind_group,
796 instance_groups, instance_buffers,
797 texture_bind_group_layout, pbr_material_bind_group_layout, textures, texture_sampler, camera,
798 loaded_models: HashMap::new(),
799 })
800 }
801
802 #[cfg(target_arch = "wasm32")]
803 pub async fn new_async(window: &'w Window, scene: &FpsScene) -> Result<Self, wasm_bindgen::JsValue> {
804 let size = window.inner_size();
805 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends: wgpu::Backends::GL, ..Default::default() });
806 let surface = instance.create_surface(window).map_err(|e| wasm_bindgen::JsValue::from_str(&format!("Failed to create surface: {:?}", e)))?;
807 let adapter = instance
808 .request_adapter(&wgpu::RequestAdapterOptions {
809 power_preference: wgpu::PowerPreference::LowPower,
810 compatible_surface: Some(&surface),
811 force_fallback_adapter: true,
812 })
813 .await
814 .ok_or_else(|| wasm_bindgen::JsValue::from_str("Failed to find an appropriate adapter for WASM"))?;
815
816 let (device, queue) = adapter
817 .request_device(
818 &wgpu::DeviceDescriptor {
819 label: Some("device"),
820 required_features: wgpu::Features::empty(),
821 required_limits: adapter.limits(),
822 },
823 None,
824 )
825 .await
826 .map_err(|e| wasm_bindgen::JsValue::from_str(&format!("Failed to request device: {:?}", e)))?;
827
828 Err(wasm_bindgen::JsValue::from_str("WASM initialization not fully implemented"))
830 }
831
832 pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
833 if new_size.width == 0 || new_size.height == 0 { return; }
834 self.size = new_size;
835 self.config.width = new_size.width;
836 self.config.height = new_size.height;
837 self.surface.configure(&self.device, &self.config);
838 let (dt, view) = create_depth(&self.device, self.config.width, self.config.height);
839 self.depth_tex = dt;
840 self.depth_view = view;
841 }
842
843 pub fn update_camera(&mut self) {
844 let vp = self.camera.proj(self.config.width as f32 / self.config.height as f32) * self.camera.view();
845 let ubo = CameraUBO {
846 view_proj: vp.to_cols_array_2d(),
847 pos: [self.camera.pos.x, self.camera.pos.y, self.camera.pos.z, 1.0],
848 };
849 self.queue.write_buffer(&self.cam_buf, 0, bytemuck::bytes_of(&ubo));
850
851 let light_vp = self.camera.proj(1.0) * self.camera.view();
852 let light_ubo = CameraUBO {
853 view_proj: light_vp.to_cols_array_2d(),
854 pos: [self.camera.pos.x, self.camera.pos.y, self.camera.pos.z, 1.0],
855 };
856 self.queue.write_buffer(&self.light_cam_buf, 0, bytemuck::bytes_of(&light_ubo));
857 }
858
859 pub fn render(&mut self) -> Result<()> {
860 let frame = self.surface.get_current_texture()?;
861 let view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
862 let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor{ label: Some("encoder") });
863
864 {
865 let mut shadow_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor{
866 label: Some("shadow-pass"),
867 color_attachments: &[],
868 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment{
869 view: &self.shadow_texture_view,
870 depth_ops: Some(wgpu::Operations{ load: wgpu::LoadOp::Clear(1.0), store: wgpu::StoreOp::Store }),
871 stencil_ops: None,
872 }),
873 occlusion_query_set: None,
874 timestamp_writes: None,
875 });
876
877 shadow_pass.set_pipeline(&self.shadow_pipeline);
878 shadow_pass.set_bind_group(0, &self.light_cam_bind, &[]);
879
880 for (_model_name, model) in &self.loaded_models {
881 for mesh in &model.meshes {
882 shadow_pass.set_vertex_buffer(0, mesh.vbo.slice(..));
883 shadow_pass.set_index_buffer(mesh.ibo.slice(..), wgpu::IndexFormat::Uint32);
884 shadow_pass.draw_indexed(0..mesh.num_indices, 0, 0..1);
885 }
886 }
887 }
888
889 {
890 let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor{
891 label: Some("main-pass"),
892 color_attachments: &[Some(wgpu::RenderPassColorAttachment{
893 view: &view,
894 resolve_target: None,
895 ops: wgpu::Operations{ load: wgpu::LoadOp::Clear(self.color), store: wgpu::StoreOp::Store },
896 })],
897 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment{
898 view: &self.depth_view,
899 depth_ops: Some(wgpu::Operations{ load: wgpu::LoadOp::Clear(1.0), store: wgpu::StoreOp::Store }),
900 stencil_ops: None,
901 }),
902 occlusion_query_set: None,
903 timestamp_writes: None,
904 });
905
906 rp.set_pipeline(&self.skybox_pipeline);
909 rp.set_bind_group(0, &self.cam_bind, &[]);
910 rp.draw(0..4, 0..1);
912
913 rp.set_pipeline(&self.instance_pipeline);
918 rp.set_bind_group(0, &self.cam_bind, &[]);
919 rp.set_bind_group(2, &self.lights_bind, &[]);
920 rp.set_vertex_buffer(0, self.vbo.slice(..));
921
922 let default_texture_bg = &self.textures.get("default_white").unwrap().1;
923
924 for (texture_name, inst_buf) in &self.instance_buffers {
925 let bg = match texture_name {
926 Some(name) => self.textures.get(name).map_or(default_texture_bg, |(_, bg)| bg),
927 None => default_texture_bg,
928 };
929 rp.set_bind_group(1, bg, &[]);
930
931 let num_instances = self.instance_groups.get(texture_name).unwrap().len() as u32;
932 if num_instances > 0 {
933 rp.set_vertex_buffer(1, inst_buf.slice(..));
934 rp.draw(0..36, 0..num_instances);
935 }
936 }
937
938 rp.set_pipeline(&self.model_pipeline);
940 rp.set_bind_group(0, &self.cam_bind, &[]);
941 rp.set_bind_group(2, &self.lights_bind, &[]);
942 rp.set_bind_group(3, &self.shadow_bind_group, &[]);
943 rp.set_bind_group(4, &self.light_space_bind_group, &[]);
944
945 for (_model_name, model) in &self.loaded_models {
946 for mesh in &model.meshes {
947 rp.set_bind_group(1, &model.material_bind_groups[mesh.material_index], &[]);
948 rp.set_vertex_buffer(0, mesh.vbo.slice(..));
949 rp.set_index_buffer(mesh.ibo.slice(..), wgpu::IndexFormat::Uint32);
950 rp.draw_indexed(0..mesh.num_indices, 0, 0..1);
951 }
952 }
953 }
954
955 self.queue.submit([encoder.finish()]);
956 frame.present();
957 Ok(())
958 }
959
960 pub fn set_clear(&mut self, c: [f32;4]) {
961 self.color = wgpu::Color{ r: c[0] as f64, g: c[1] as f64, b: c[2] as f64, a: c[3] as f64 };
962 }
963
964 pub fn load_texture_from_file<P: AsRef<Path>>(&mut self, name: String, path: P) -> Result<()> {
965 if self.textures.contains_key(&name) { return Ok(()); }
966
967 let img = image::open(path)?.to_rgba8();
968 let dimensions = img.dimensions();
969
970 let texture_size = wgpu::Extent3d { width: dimensions.0, height: dimensions.1, depth_or_array_layers: 1 };
971 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
972 label: Some(&name),
973 size: texture_size,
974 mip_level_count: 1, sample_count: 1,
975 dimension: wgpu::TextureDimension::D2,
976 format: wgpu::TextureFormat::Rgba8UnormSrgb,
977 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
978 view_formats: &[],
979 });
980
981 self.queue.write_texture(
982 wgpu::ImageCopyTexture { texture: &texture, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All },
983 &img,
984 wgpu::ImageDataLayout { offset: 0, bytes_per_row: Some(4 * dimensions.0), rows_per_image: Some(dimensions.1) },
985 texture_size,
986 );
987
988 let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
989 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
990 label: Some(&format!("bind_group_{}", name)),
991 layout: &self.texture_bind_group_layout,
992 entries: &[
993 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&texture_view) },
994 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&self.texture_sampler) },
995 ],
996 });
997
998 self.textures.insert(name, (texture, bind_group));
999 Ok(())
1000 }
1001
1002 pub fn load_gltf_model(&mut self, name: &str, path: &Path) -> Result<()> {
1003 info!("Loading glTF model: {}", path.display());
1004 let model = kengaai_model_loader::GltfLoader::load(path)?;
1005 self.load_procedural_model(name, &model)
1006 }
1007
1008 pub fn load_procedural_model(&mut self, name: &str, model: &kengaai_model_loader::Model) -> Result<()> {
1009 for (i, image_data) in model.images.iter().enumerate() {
1011 let texture_name = format!("{}:{}", name, i);
1012 if self.textures.contains_key(&texture_name) { continue; }
1013
1014 let texture_size = wgpu::Extent3d { width: image_data.width, height: image_data.height, depth_or_array_layers: 1 };
1015 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
1016 label: Some(&texture_name),
1017 size: texture_size,
1018 mip_level_count: 1, sample_count: 1,
1019 dimension: wgpu::TextureDimension::D2,
1020 format: wgpu::TextureFormat::Rgba8UnormSrgb, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1022 view_formats: &[],
1023 });
1024
1025 self.queue.write_texture(
1026 wgpu::ImageCopyTexture { texture: &texture, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All },
1027 &image_data.pixels,
1028 wgpu::ImageDataLayout { offset: 0, bytes_per_row: Some(4 * image_data.width), rows_per_image: Some(image_data.height) },
1029 texture_size,
1030 );
1031
1032 let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
1033 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1034 label: Some(&format!("bind_group_{}", texture_name)),
1035 layout: &self.texture_bind_group_layout,
1036 entries: &[
1037 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&texture_view) },
1038 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&self.texture_sampler) },
1039 ],
1040 });
1041 self.textures.insert(texture_name, (texture, bind_group));
1042 }
1043
1044 let mut meshes = Vec::new();
1046 for mesh_data in &model.meshes {
1047 if mesh_data.vertices.is_empty() || mesh_data.indices.is_empty() {
1048 continue;
1049 }
1050 let vbo = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1051 label: Some(&format!("{}_{}_vbo", name, mesh_data.name)),
1052 contents: bytemuck::cast_slice(&mesh_data.vertices),
1053 usage: wgpu::BufferUsages::VERTEX,
1054 });
1055 let ibo = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1056 label: Some(&format!("{}_{}_ibo", name, mesh_data.name)),
1057 contents: bytemuck::cast_slice(&mesh_data.indices),
1058 usage: wgpu::BufferUsages::INDEX,
1059 });
1060 meshes.push(MeshBuffers {
1061 vbo,
1062 ibo,
1063 num_indices: mesh_data.indices.len() as u32,
1064 material_index: mesh_data.material_index.unwrap_or(0), });
1066 }
1067
1068 if meshes.is_empty() {
1069 return Err(anyhow::anyhow!("glTF Model has no valid meshes"));
1070 }
1071
1072 let default_white_texture_view = self.textures.get("default_white").unwrap().0.create_view(&wgpu::TextureViewDescriptor::default());
1073 let default_normal_texture_view = self.textures.get("default_normal").unwrap().0.create_view(&wgpu::TextureViewDescriptor::default());
1074
1075 let material_bind_groups = model.materials.iter().map(|m| {
1077 let ubo = MaterialUBO {
1078 base_color_factor: m.base_color_factor,
1079 metallic_factor: m.metallic_factor,
1080 roughness_factor: m.roughness_factor,
1081 _pad: [0.0, 0.0],
1082 };
1083 let buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1084 label: Some(&format!("{}_{}_ubo", name, m.name)),
1085 contents: bytemuck::bytes_of(&ubo),
1086 usage: wgpu::BufferUsages::UNIFORM,
1087 });
1088
1089 let base_color_texture_view = m.base_color_texture_index
1090 .and_then(|i| self.textures.get(&format!("{}:{}", name, i)))
1091 .map(|(tex, _)| tex.create_view(&wgpu::TextureViewDescriptor::default()));
1092
1093 let normal_texture_view = m.normal_texture_index
1094 .and_then(|i| self.textures.get(&format!("{}:{}", name, i)))
1095 .map(|(tex, _)| tex.create_view(&wgpu::TextureViewDescriptor::default()));
1096
1097 let metallic_roughness_texture_view = m.metallic_roughness_texture_index
1098 .and_then(|i| self.textures.get(&format!("{}:{}", name, i)))
1099 .map(|(tex, _)| tex.create_view(&wgpu::TextureViewDescriptor::default()));
1100
1101 let occlusion_texture_view = m.occlusion_texture_index
1102 .and_then(|i| self.textures.get(&format!("{}:{}", name, i)))
1103 .map(|(tex, _)| tex.create_view(&wgpu::TextureViewDescriptor::default()));
1104
1105 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1106 label: Some(&format!("pbr_bind_group_{}_{}", name, m.name)),
1107 layout: &self.pbr_material_bind_group_layout,
1108 entries: &[
1109 wgpu::BindGroupEntry { binding: 0, resource: buffer.as_entire_binding() },
1110 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(base_color_texture_view.as_ref().unwrap_or(&default_white_texture_view)) },
1111 wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&self.texture_sampler) },
1112 wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(normal_texture_view.as_ref().unwrap_or(&default_normal_texture_view)) },
1113 wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::TextureView(metallic_roughness_texture_view.as_ref().unwrap_or(&default_white_texture_view)) },
1114 wgpu::BindGroupEntry { binding: 5, resource: wgpu::BindingResource::TextureView(occlusion_texture_view.as_ref().unwrap_or(&default_white_texture_view)) },
1115 ],
1116 })
1117 }).collect();
1118
1119 let loaded_model = LoadedModel {
1120 meshes,
1121 material_bind_groups,
1122 };
1123
1124 self.loaded_models.insert(name.to_string(), loaded_model);
1125 info!("glTF Model '{}' loaded successfully", name);
1126 Ok(())
1127 }
1128}
1129
1130fn create_depth(device: &wgpu::Device, width: u32, height: u32) -> (wgpu::Texture, wgpu::TextureView) {
1131 let tex = device.create_texture(&wgpu::TextureDescriptor{
1132 label: Some("depth"),
1133 size: wgpu::Extent3d{ width, height, depth_or_array_layers:1 },
1134 mip_level_count: 1, sample_count: 1,
1135 dimension: wgpu::TextureDimension::D2,
1136 format: wgpu::TextureFormat::Depth24Plus,
1137 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1138 view_formats: &[],
1139 });
1140 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1141 (tex, view)
1142}
1143
1144fn create_shadow_texture(device: &wgpu::Device, width: u32, height: u32) -> (wgpu::Texture, wgpu::TextureView) {
1145 let tex = device.create_texture(&wgpu::TextureDescriptor{
1146 label: Some("shadow"),
1147 size: wgpu::Extent3d{ width, height, depth_or_array_layers:1 },
1148 mip_level_count: 1, sample_count: 1,
1149 dimension: wgpu::TextureDimension::D2,
1150 format: wgpu::TextureFormat::Depth24Plus,
1151 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
1152 view_formats: &[],
1153 });
1154 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1155 (tex, view)
1156}
1157
1158pub struct FpsController {
1159 pub player_body_handle: RigidBodyHandle,
1160 pub forward: bool,
1161 pub back: bool,
1162 pub left: bool,
1163 pub right: bool,
1164 pub run: bool,
1165 pub jump: bool,
1166 pub move_speed: f32,
1167 pub run_speed: f32,
1168 pub mouse_sensitivity: f32,
1169 pub mouse_delta: Vec2,
1170}
1171
1172impl FpsController {
1173 pub fn new(player_body_handle: RigidBodyHandle, move_speed: f32, run_speed: f32) -> Self {
1174 Self {
1175 player_body_handle,
1176 forward: false,
1177 back: false,
1178 left: false,
1179 right: false,
1180 run: false,
1181 jump: false,
1182 move_speed,
1183 run_speed,
1184 mouse_sensitivity: 0.12,
1185 mouse_delta: Vec2::ZERO,
1186 }
1187 }
1188
1189 pub fn step(&mut self, cam: &mut Camera, physics: &mut PhysicsWorld, dt: f32) {
1190 cam.yaw += self.mouse_delta.x * self.mouse_sensitivity * dt;
1192 cam.pitch += -self.mouse_delta.y * self.mouse_sensitivity * dt;
1193 cam.pitch = cam.pitch.clamp(-std::f32::consts::FRAC_PI_2, std::f32::consts::FRAC_PI_2);
1194 self.mouse_delta = Vec2::ZERO;
1195
1196 let (yaw_sin, yaw_cos) = cam.yaw.sin_cos();
1198 let forward = Vec3::new(yaw_cos, 0.0, yaw_sin).normalize_or_zero();
1199 let right = Vec3::new(-yaw_sin, 0.0, yaw_cos).normalize_or_zero();
1200 let speed = if self.run { self.run_speed } else { self.move_speed };
1201 let mut wish_dir = Vec3::ZERO;
1202 if self.forward { wish_dir += forward; }
1203 if self.back { wish_dir -= forward; }
1204 if self.left { wish_dir -= right; }
1205 if self.right { wish_dir += right; }
1206 wish_dir = wish_dir.normalize_or_zero() * speed;
1207
1208 let jump_requested = self.jump;
1210 physics.step(self.player_body_handle, wish_dir, jump_requested, dt);
1211 if jump_requested {
1213 self.jump = false;
1214 }
1215
1216 if let Some(player_body) = physics.rigid_body_set.get(self.player_body_handle) {
1218 let player_pos = player_body.translation();
1219 cam.pos = Vec3::new(player_pos.x, player_pos.y + 0.5, player_pos.z);
1220 }
1221 }
1222}