Skip to main content

kengaai_fps/
lib.rs

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// Vertex for simple instanced cubes
18#[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]),    // front
36        ([5, 4, 7, 6], [0.0, 0.0, -1.0]),   // back
37        ([4, 0, 3, 7], [-1.0, 0.0, 0.0]),   // left
38        ([1, 5, 6, 2], [1.0, 0.0, 0.0]),    // right
39        ([3, 2, 6, 7], [0.0, 1.0, 0.0]),    // top
40        ([4, 5, 1, 0], [0.0, -1.0, 0.0]),   // bottom
41    ];
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, // padding для выравнивания vec3 до 16 байт
91    color: [f32; 3],
92    intensity: f32,
93    kind: u32,
94    _pad1: [u32; 2], // padding для выравнивания до 16 байт (intensity + kind + pad = 16)
95    direction: [f32; 3],
96    inner_cone_angle: f32,
97    outer_cone_angle: f32,
98    _pad2: [f32; 2], // padding для выравнивания до 16 байт
99}
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
203// --- Physics --- 
204pub 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        // Управление вертикальной скоростью и прыжками
275        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        // Применяем гравитацию
285        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        // Проверяем grounded через collisions
306        let mut grounded = false;
307        let mut hit_ceiling = false;
308        
309        // Проверка через collisions - основная
310        for collision in &collisions {
311            // Проверяем нормаль - если она направлена вверх, значит мы на земле
312            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        // Если стремимся вверх, не считаем себя на земле в этом кадре
321        if desired_translation.y > 0.0 {
322            grounded = false;
323        }
324        
325        // Дополнительная проверка: если скорость вниз очень маленькая и есть collisions
326        if !grounded && vertical_velocity.abs() < 0.5 && !collisions.is_empty() {
327            // Если есть collisions и мы не падаем быстро, считаем что на земле
328            grounded = true;
329        }
330        
331        // Если приземлились или ударились о потолок - останавливаем вертикальную скорость
332        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        // Прыжок - применяем ДО установки позиции
344        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        // Применяем скорость
353        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
366// --- End Physics ---
367
368impl<'w> FpsRenderer<'w> {
369    pub fn new(window: &'w Window, scene: &FpsScene) -> Result<Self, anyhow::Error> {
370        #[cfg(target_arch = "wasm32")]
371        {
372            // WASM версия - возвращаем ошибку, требует async инициализацию
373            return Err(anyhow::anyhow!("WASM initialization requires async setup. Use new_async instead."));
374        }
375        
376        #[cfg(not(target_arch = "wasm32"))]
377        {
378            // Нативная версия - используем pollster для блокирующего ожидания
379            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        // --- Layouts ---
432        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        // --- Instance Pipeline ---
505        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        // --- Model Pipeline ---
536        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                // Material UBO
546                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                // Base Color Texture
553                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                // Sampler
560                wgpu::BindGroupLayoutEntry {
561                    binding: 2,
562                    visibility: wgpu::ShaderStages::FRAGMENT,
563                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
564                    count: None,
565                },
566                // Normal Map Texture
567                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                // Metallic Roughness Texture
574                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                // Occlusion Texture
581                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        // --- Shadow Pipeline ---
612        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        // --- Buffers and Bind Groups ---
638        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        // Create skybox pipeline (placeholder)
643        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        // Create skybox VBO (placeholder - empty buffer)
673        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        // The bind group for the default normal texture is not needed, as it will be part of the material bind group.
783        // However, to avoid changing the textures map, we create a dummy bind group.
784        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        // ... остальной код инициализации для WASM ...
829        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            // --- Draw Skybox First (background) ---
907            // Skybox рендерится первым как фон с отключенным depth test
908            rp.set_pipeline(&self.skybox_pipeline);
909            rp.set_bind_group(0, &self.cam_bind, &[]);
910            // Рисуем fullscreen quad (TriangleStrip: 4 вершины = 2 треугольника)
911            rp.draw(0..4, 0..1);
912            
913            // Включаем depth test обратно для остальных объектов
914            // (skybox pipeline уже настроен с Always, но мы переключаемся на другой pipeline)
915
916            // --- Draw Instanced Cubes ---
917            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            // --- Draw Loaded Models ---
939            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        // Load textures
1010        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, // Assuming this format, might need to convert from gltf format
1021                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        // Load meshes
1045        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), // Default to material 0 if not specified
1065            });
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        // Create material bind groups
1076        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        // Camera rotation based on mouse
1191        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        // Calculate desired movement direction
1197        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        // Update player physics
1209        let jump_requested = self.jump;
1210        physics.step(self.player_body_handle, wish_dir, jump_requested, dt);
1211        // Reset jump flag after physics step
1212        if jump_requested {
1213            self.jump = false;
1214        }
1215
1216        // Update camera position to follow the player
1217        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}