use anyhow::Result;
use std::collections::HashMap;
use std::path::Path;
use bytemuck::{Pod, Zeroable};
use glam::{Mat4, Vec2, Vec3};
use kengaai_model_loader;
use kengaai_scene_fps::{BoxDef, FpsScene};
use log::info;
use wgpu::util::DeviceExt;
use winit::window::Window;
use rapier3d::prelude::*;
use rapier3d::control::{KinematicCharacterController, CharacterLength};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[repr(C)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
struct Vertex {
pos: [f32; 3],
normal: [f32; 3],
tex_coords: [f32; 2],
}
fn cube_vertices() -> Vec<Vertex> {
let p = [
[-0.5, -0.5, 0.5], [0.5, -0.5, 0.5], [0.5, 0.5, 0.5], [-0.5, 0.5, 0.5],
[-0.5, -0.5, -0.5], [0.5, -0.5, -0.5], [0.5, 0.5, -0.5], [-0.5, 0.5, -0.5],
];
let uv = [
[0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0],
];
let faces: [([usize; 4], [f32; 3]); 6] = [
([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]), ];
let mut v = Vec::with_capacity(36);
for (idx, n) in faces {
let tri = [
Vertex { pos: p[idx[0]], normal: n, tex_coords: uv[0] },
Vertex { pos: p[idx[1]], normal: n, tex_coords: uv[1] },
Vertex { pos: p[idx[2]], normal: n, tex_coords: uv[2] },
Vertex { pos: p[idx[0]], normal: n, tex_coords: uv[0] },
Vertex { pos: p[idx[2]], normal: n, tex_coords: uv[2] },
Vertex { pos: p[idx[3]], normal: n, tex_coords: uv[3] },
];
v.extend_from_slice(&tri);
}
v
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
struct Instance {
pos: [f32; 3],
scale: [f32; 3],
rot_y: f32,
color: [f32; 3],
_pad: f32,
}
impl From<&BoxDef> for Instance {
fn from(b: &BoxDef) -> Self {
Self {
pos: b.pos,
scale: b.size,
rot_y: b.rot_y,
color: b.color,
_pad: 0.0,
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
struct CameraUBO {
view_proj: [[f32; 4]; 4],
pos: [f32; 4],
}
#[repr(C, align(16))]
#[derive(Clone, Copy, Debug)]
struct LightRaw {
position: [f32; 3],
_pad0: f32, color: [f32; 3],
intensity: f32,
kind: u32,
_pad1: [u32; 2], direction: [f32; 3],
inner_cone_angle: f32,
outer_cone_angle: f32,
_pad2: [f32; 2], }
unsafe impl Pod for LightRaw {}
unsafe impl Zeroable for LightRaw {}
#[repr(C)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
struct LightsUBO {
count: u32,
_pad: [u32; 3],
lights: [LightRaw; 16],
}
pub struct MeshBuffers {
vbo: wgpu::Buffer,
ibo: wgpu::Buffer,
num_indices: u32,
material_index: usize,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
struct MaterialUBO {
base_color_factor: [f32; 4],
metallic_factor: f32,
roughness_factor: f32,
_pad: [f32; 2],
}
pub struct LoadedModel {
meshes: Vec<MeshBuffers>,
material_bind_groups: Vec<wgpu::BindGroup>,
}
pub struct FpsRenderer<'w> {
surface: wgpu::Surface<'w>,
device: wgpu::Device,
queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration,
size: winit::dpi::PhysicalSize<u32>,
color: wgpu::Color,
depth_tex: wgpu::Texture,
depth_view: wgpu::TextureView,
_shadow_texture: wgpu::Texture,
shadow_texture_view: wgpu::TextureView,
instance_pipeline: wgpu::RenderPipeline,
model_pipeline: wgpu::RenderPipeline,
shadow_pipeline: wgpu::RenderPipeline,
skybox_pipeline: wgpu::RenderPipeline,
vbo: wgpu::Buffer,
_skybox_vbo: wgpu::Buffer,
cam_buf: wgpu::Buffer,
cam_bind: wgpu::BindGroup,
light_cam_buf: wgpu::Buffer,
light_cam_bind: wgpu::BindGroup,
_lights_buf: wgpu::Buffer,
lights_bind: wgpu::BindGroup,
_shadow_bind_group_layout: wgpu::BindGroupLayout,
shadow_bind_group: wgpu::BindGroup,
_light_space_bind_group_layout: wgpu::BindGroupLayout,
light_space_bind_group: wgpu::BindGroup,
instance_groups: HashMap<Option<String>, Vec<Instance>>,
instance_buffers: HashMap<Option<String>, wgpu::Buffer>,
texture_bind_group_layout: wgpu::BindGroupLayout,
pbr_material_bind_group_layout: wgpu::BindGroupLayout,
textures: HashMap<String, (wgpu::Texture, wgpu::BindGroup)>,
texture_sampler: wgpu::Sampler,
pub camera: Camera,
loaded_models: HashMap<String, LoadedModel>,
}
pub struct Camera {
pub pos: Vec3,
pub yaw: f32,
pub pitch: f32,
pub fov_y: f32,
pub z_near: f32,
pub z_far: f32,
}
impl Camera {
pub fn view(&self) -> Mat4 {
let dir = Self::dir(self.yaw, self.pitch);
Mat4::look_to_rh(self.pos, dir, Vec3::Y)
}
pub fn proj(&self, aspect: f32) -> Mat4 {
Mat4::perspective_rh(self.fov_y.to_radians(), aspect, self.z_near, self.z_far)
}
pub fn dir(yaw: f32, pitch: f32) -> Vec3 {
let (sy, cy) = yaw.sin_cos();
let (sp, cp) = pitch.sin_cos();
Vec3::new(cy * cp, sp, sy * cp)
}
}
pub struct PhysicsWorld {
pub gravity: Vector<f32>,
pub integration_parameters: IntegrationParameters,
pub physics_pipeline: PhysicsPipeline,
pub island_manager: IslandManager,
pub broad_phase: BroadPhase,
pub narrow_phase: NarrowPhase,
pub rigid_body_set: RigidBodySet,
pub collider_set: ColliderSet,
pub impulse_joint_set: ImpulseJointSet,
pub multibody_joint_set: MultibodyJointSet,
pub ccd_solver: CCDSolver,
pub query_pipeline: QueryPipeline,
character_controller: KinematicCharacterController,
player_vertical_velocity: f32,
player_grounded: bool,
jump_velocity: f32,
}
impl PhysicsWorld {
pub fn new() -> Self {
let mut controller = KinematicCharacterController::default();
controller.offset = CharacterLength::Absolute(0.01);
Self {
gravity: vector![0.0, -9.81, 0.0],
integration_parameters: IntegrationParameters::default(),
physics_pipeline: PhysicsPipeline::new(),
island_manager: IslandManager::new(),
broad_phase: BroadPhase::new(),
narrow_phase: NarrowPhase::new(),
rigid_body_set: RigidBodySet::new(),
collider_set: ColliderSet::new(),
impulse_joint_set: ImpulseJointSet::new(),
multibody_joint_set: MultibodyJointSet::new(),
ccd_solver: CCDSolver::new(),
query_pipeline: QueryPipeline::new(),
character_controller: controller,
player_vertical_velocity: 0.0,
player_grounded: true,
jump_velocity: 12.0,
}
}
pub fn step(&mut self, player_handle: RigidBodyHandle, wish_dir: Vec3, jump: bool, dt: f32) {
self.physics_pipeline.step(
&self.gravity,
&self.integration_parameters,
&mut self.island_manager,
&mut self.broad_phase,
&mut self.narrow_phase,
&mut self.rigid_body_set,
&mut self.collider_set,
&mut self.impulse_joint_set,
&mut self.multibody_joint_set,
&mut self.ccd_solver,
None,
&(),
&(),
);
self.query_pipeline.update(&self.rigid_body_set, &self.collider_set);
let player_body = if let Some(body) = self.rigid_body_set.get(player_handle) {
body
} else {
return;
};
let player_position = *player_body.position();
let player_collider_handle = player_body.colliders()[0];
let was_grounded = self.player_grounded;
let mut vertical_velocity = if was_grounded { 0.0 } else { self.player_vertical_velocity };
let mut jump_started = false;
if jump && was_grounded {
vertical_velocity = self.jump_velocity;
jump_started = true;
}
vertical_velocity += self.gravity.y * dt;
let mut desired_translation = vector![wish_dir.x, 0.0, wish_dir.z] * dt;
desired_translation.y = vertical_velocity * dt;
let mut collisions = Vec::new();
let filter = QueryFilter::default().exclude_rigid_body(player_handle);
let computed_movement = self.character_controller.move_shape(
dt,
&self.rigid_body_set,
&self.collider_set,
&self.query_pipeline,
self.collider_set.get(player_collider_handle).unwrap().shape(),
&player_position,
desired_translation,
filter,
|c| { collisions.push(c); },
);
let mut grounded = false;
let mut hit_ceiling = false;
for collision in &collisions {
if collision.toi.normal1.y > 0.3 {
grounded = true;
}
if collision.toi.normal1.y < -0.3 {
hit_ceiling = true;
}
}
if desired_translation.y > 0.0 {
grounded = false;
}
if !grounded && vertical_velocity.abs() < 0.5 && !collisions.is_empty() {
grounded = true;
}
if grounded {
vertical_velocity = 0.0;
} else if hit_ceiling && vertical_velocity > 0.0 {
vertical_velocity = 0.0;
}
self.player_vertical_velocity = vertical_velocity;
self.player_grounded = grounded;
let player_body_mut = self.rigid_body_set.get_mut(player_handle).unwrap();
if jump {
if jump_started {
info!("JUMP! grounded(prev)={}, new_velocity_y={}", was_grounded, self.player_vertical_velocity);
} else if !was_grounded {
info!("JUMP blocked: grounded(prev)={}, vertical_velocity={}", was_grounded, self.player_vertical_velocity);
}
}
player_body_mut.set_linvel(vector![wish_dir.x, self.player_vertical_velocity, wish_dir.z], true);
let new_pos = player_position.translation.vector + computed_movement.translation;
player_body_mut.set_next_kinematic_position(Isometry::from_parts(Translation::from(new_pos), player_position.rotation));
}
}
impl Default for PhysicsWorld {
fn default() -> Self {
Self::new()
}
}
impl<'w> FpsRenderer<'w> {
pub fn new(window: &'w Window, scene: &FpsScene) -> Result<Self, anyhow::Error> {
#[cfg(target_arch = "wasm32")]
{
return Err(anyhow::anyhow!("WASM initialization requires async setup. Use new_async instead."));
}
#[cfg(not(target_arch = "wasm32"))]
{
pollster::block_on(Self::new_async_native(window, scene))
}
}
#[cfg(not(target_arch = "wasm32"))]
async fn new_async_native(window: &'w Window, scene: &FpsScene) -> Result<Self, anyhow::Error> {
let size = window.inner_size();
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
..Default::default()
});
let surface = instance.create_surface(window)
.map_err(|e| anyhow::anyhow!("Failed to create surface: {:?}", e))?;
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.ok_or_else(|| anyhow::anyhow!("Failed to find an appropriate adapter"))?;
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: Some("device"),
required_features: wgpu::Features::empty(),
required_limits: adapter.limits(),
},
None,
)
.await
.map_err(|e| anyhow::anyhow!("Failed to request device: {:?}", e))?;
let caps = surface.get_capabilities(&adapter);
let format = caps.formats.iter().copied().find(|f| f.is_srgb()).unwrap_or(caps.formats[0]);
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format,
width: size.width.max(1),
height: size.height.max(1),
present_mode: wgpu::PresentMode::AutoVsync,
alpha_mode: caps.alpha_modes[0],
view_formats: vec![],
desired_maximum_frame_latency: 2,
};
surface.configure(&device, &config);
let (depth_tex, depth_view) = create_depth(&device, size.width, size.height);
let (shadow_texture, shadow_texture_view) = create_shadow_texture(&device, 2048, 2048);
let cam_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("cam-layout"),
entries: &[wgpu::BindGroupLayoutEntry{
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer{ ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None },
count: None
}],
});
let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("texture_bind_group_layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false },
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let lights_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("lights-bind-group-layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None },
count: None,
},
],
});
let shadow_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("shadow_bind_group_layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Depth, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false },
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
count: None,
},
],
});
let light_space_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("light_space_bind_group_layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None },
count: None,
},
],
});
let instance_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("lighting_simple"),
source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/lighting_simple.wgsl").into()),
});
let instance_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("instance_pipeline_layout"),
bind_group_layouts: &[&cam_layout, &texture_bind_group_layout, &lights_bind_group_layout],
push_constant_ranges: &[],
});
let instance_v_layout = wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &wgpu::vertex_attr_array![0=>Float32x3,1=>Float32x3,2=>Float32x2],
};
let instance_i_layout = wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Instance>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &wgpu::vertex_attr_array![3=>Float32x3, 4=>Float32x3, 5=>Float32, 6=>Float32x3],
};
let instance_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("instance_pipeline"),
layout: Some(&instance_pipeline_layout),
vertex: wgpu::VertexState { module: &instance_shader, entry_point: "vs_main", buffers: &[instance_v_layout, instance_i_layout], compilation_options: wgpu::PipelineCompilationOptions::default() },
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() }),
primitive: wgpu::PrimitiveState::default(),
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() }),
multisample: wgpu::MultisampleState::default(),
multiview: None,
});
let model_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("model_shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/model.wgsl").into()),
});
let pbr_material_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("pbr_material_bind_group_layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None },
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false },
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false },
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 4,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false },
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 5,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false },
count: None,
},
],
});
let model_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("model_pipeline_layout"),
bind_group_layouts: &[&cam_layout, &pbr_material_bind_group_layout, &lights_bind_group_layout, &shadow_bind_group_layout, &light_space_bind_group_layout],
push_constant_ranges: &[],
});
let model_v_layout_model = wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<kengaai_model_loader::Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &wgpu::vertex_attr_array![0=>Float32x3, 1=>Float32x3, 2=>Float32x2, 3=>Float32x4, 4=>Uint32x4, 5=>Float32x4],
};
let model_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("model_pipeline"),
layout: Some(&model_pipeline_layout),
vertex: wgpu::VertexState { module: &model_shader, entry_point: "vs_main", buffers: &[model_v_layout_model], compilation_options: wgpu::PipelineCompilationOptions::default() },
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() }),
primitive: wgpu::PrimitiveState::default(),
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() }),
multisample: wgpu::MultisampleState::default(),
multiview: None,
});
let shadow_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("shadow_shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/shadow.wgsl").into()),
});
let shadow_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("shadow_pipeline_layout"),
bind_group_layouts: &[&cam_layout],
push_constant_ranges: &[],
});
let model_v_layout_shadow = wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<kengaai_model_loader::Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &wgpu::vertex_attr_array![0=>Float32x3, 1=>Float32x3, 2=>Float32x2, 3=>Float32x4, 4=>Uint32x4, 5=>Float32x4],
};
let shadow_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("shadow_pipeline"),
layout: Some(&shadow_pipeline_layout),
vertex: wgpu::VertexState { module: &shadow_shader, entry_point: "vs_main", buffers: &[model_v_layout_shadow], compilation_options: wgpu::PipelineCompilationOptions::default() },
fragment: None,
primitive: wgpu::PrimitiveState::default(),
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() }),
multisample: wgpu::MultisampleState::default(),
multiview: None,
});
let texture_sampler = device.create_sampler(&wgpu::SamplerDescriptor::default());
let verts = cube_vertices();
let vbo = device.create_buffer_init(&wgpu::util::BufferInitDescriptor{ label: Some("vbo"), contents: bytemuck::cast_slice(&verts), usage: wgpu::BufferUsages::VERTEX });
let skybox_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("skybox_shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/skybox.wgsl").into()),
});
let skybox_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("skybox_pipeline_layout"),
bind_group_layouts: &[&cam_layout],
push_constant_ranges: &[],
});
let skybox_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("skybox_pipeline"),
layout: Some(&skybox_pipeline_layout),
vertex: wgpu::VertexState { module: &skybox_shader, entry_point: "vs_main", buffers: &[], compilation_options: wgpu::PipelineCompilationOptions::default() },
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() }),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleStrip,
..Default::default()
},
depth_stencil: Some(wgpu::DepthStencilState{
format: wgpu::TextureFormat::Depth24Plus,
depth_write_enabled: false,
depth_compare: wgpu::CompareFunction::Always,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default()
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
});
let skybox_vbo = device.create_buffer_init(&wgpu::util::BufferInitDescriptor{ label: Some("skybox_vbo"), contents: &[], usage: wgpu::BufferUsages::VERTEX });
let mut instance_groups: HashMap<Option<String>, Vec<Instance>> = HashMap::new();
for box_def in &scene.level.boxes {
instance_groups.entry(box_def.texture.clone()).or_default().push(Instance::from(box_def));
}
let mut instance_buffers = HashMap::new();
for (texture_name, instances) in &instance_groups {
let inst_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("inst_buf_{:?}", texture_name)),
contents: bytemuck::cast_slice(instances),
usage: wgpu::BufferUsages::VERTEX,
});
instance_buffers.insert(texture_name.clone(), inst_buf);
}
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 };
let cam_ubo = CameraUBO {
view_proj: (camera.proj(size.width as f32 / size.height as f32) * camera.view()).to_cols_array_2d(),
pos: [camera.pos.x, camera.pos.y, camera.pos.z, 1.0],
};
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 });
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() }] });
let light_camera = Camera {
pos: Vec3::new(0.0, 10.0, 0.0),
yaw: 0.0,
pitch: -std::f32::consts::FRAC_PI_2,
fov_y: 90.0,
z_near: 0.1,
z_far: 100.0,
};
let light_cam_ubo = CameraUBO {
view_proj: (light_camera.proj(1.0) * light_camera.view()).to_cols_array_2d(),
pos: [light_camera.pos.x, light_camera.pos.y, light_camera.pos.z, 1.0],
};
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 });
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() }] });
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] };
for (i, light) in scene.lights.iter().enumerate().take(16) {
lights_raw.count += 1;
let kind = match light.kind.as_str() {
"point" => 0,
"directional" => 1,
"spot" => 2,
_ => 0,
};
lights_raw.lights[i] = LightRaw {
position: light.position,
_pad0: 0.0,
color: light.color,
intensity: light.intensity,
kind,
_pad1: [0; 2],
direction: light.direction.unwrap_or([0.0, -1.0, 0.0]),
inner_cone_angle: light.inner_cone_angle.unwrap_or(0.9),
outer_cone_angle: light.outer_cone_angle.unwrap_or(0.8),
_pad2: [0.0; 2],
};
}
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 });
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() }] });
let shadow_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("shadow_sampler"),
compare: Some(wgpu::CompareFunction::LessEqual),
..Default::default()
});
let shadow_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("shadow_bind_group"),
layout: &shadow_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&shadow_texture_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&shadow_sampler),
},
],
});
let light_space_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("light_space_bind_group"),
layout: &light_space_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: light_cam_buf.as_entire_binding(),
},
],
});
let mut textures = HashMap::new();
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: &[] });
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 });
let white_texture_view = white_texture.create_view(&wgpu::TextureViewDescriptor::default());
let default_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("default_texture_bind_group"), layout: &texture_bind_group_layout, entries: &[
wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&white_texture_view) },
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&texture_sampler) },
]});
textures.insert("default_white".to_string(), (white_texture, default_bind_group));
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: &[] });
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 });
let normal_texture_view = normal_texture.create_view(&wgpu::TextureViewDescriptor::default());
let dummy_bg = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("dummy_normal_bg"), layout: &texture_bind_group_layout, entries: &[
wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&normal_texture_view) },
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&texture_sampler) },
]});
textures.insert("default_normal".to_string(), (normal_texture, dummy_bg));
Ok(Self{
surface, device, queue, config, size,
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 },
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,
instance_groups, instance_buffers,
texture_bind_group_layout, pbr_material_bind_group_layout, textures, texture_sampler, camera,
loaded_models: HashMap::new(),
})
}
#[cfg(target_arch = "wasm32")]
pub async fn new_async(window: &'w Window, scene: &FpsScene) -> Result<Self, wasm_bindgen::JsValue> {
let size = window.inner_size();
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends: wgpu::Backends::GL, ..Default::default() });
let surface = instance.create_surface(window).map_err(|e| wasm_bindgen::JsValue::from_str(&format!("Failed to create surface: {:?}", e)))?;
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::LowPower,
compatible_surface: Some(&surface),
force_fallback_adapter: true,
})
.await
.ok_or_else(|| wasm_bindgen::JsValue::from_str("Failed to find an appropriate adapter for WASM"))?;
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: Some("device"),
required_features: wgpu::Features::empty(),
required_limits: adapter.limits(),
},
None,
)
.await
.map_err(|e| wasm_bindgen::JsValue::from_str(&format!("Failed to request device: {:?}", e)))?;
Err(wasm_bindgen::JsValue::from_str("WASM initialization not fully implemented"))
}
pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
if new_size.width == 0 || new_size.height == 0 { return; }
self.size = new_size;
self.config.width = new_size.width;
self.config.height = new_size.height;
self.surface.configure(&self.device, &self.config);
let (dt, view) = create_depth(&self.device, self.config.width, self.config.height);
self.depth_tex = dt;
self.depth_view = view;
}
pub fn update_camera(&mut self) {
let vp = self.camera.proj(self.config.width as f32 / self.config.height as f32) * self.camera.view();
let ubo = CameraUBO {
view_proj: vp.to_cols_array_2d(),
pos: [self.camera.pos.x, self.camera.pos.y, self.camera.pos.z, 1.0],
};
self.queue.write_buffer(&self.cam_buf, 0, bytemuck::bytes_of(&ubo));
let light_vp = self.camera.proj(1.0) * self.camera.view();
let light_ubo = CameraUBO {
view_proj: light_vp.to_cols_array_2d(),
pos: [self.camera.pos.x, self.camera.pos.y, self.camera.pos.z, 1.0],
};
self.queue.write_buffer(&self.light_cam_buf, 0, bytemuck::bytes_of(&light_ubo));
}
pub fn render(&mut self) -> Result<()> {
let frame = self.surface.get_current_texture()?;
let view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor{ label: Some("encoder") });
{
let mut shadow_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor{
label: Some("shadow-pass"),
color_attachments: &[],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment{
view: &self.shadow_texture_view,
depth_ops: Some(wgpu::Operations{ load: wgpu::LoadOp::Clear(1.0), store: wgpu::StoreOp::Store }),
stencil_ops: None,
}),
occlusion_query_set: None,
timestamp_writes: None,
});
shadow_pass.set_pipeline(&self.shadow_pipeline);
shadow_pass.set_bind_group(0, &self.light_cam_bind, &[]);
for (_model_name, model) in &self.loaded_models {
for mesh in &model.meshes {
shadow_pass.set_vertex_buffer(0, mesh.vbo.slice(..));
shadow_pass.set_index_buffer(mesh.ibo.slice(..), wgpu::IndexFormat::Uint32);
shadow_pass.draw_indexed(0..mesh.num_indices, 0, 0..1);
}
}
}
{
let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor{
label: Some("main-pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment{
view: &view,
resolve_target: None,
ops: wgpu::Operations{ load: wgpu::LoadOp::Clear(self.color), store: wgpu::StoreOp::Store },
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment{
view: &self.depth_view,
depth_ops: Some(wgpu::Operations{ load: wgpu::LoadOp::Clear(1.0), store: wgpu::StoreOp::Store }),
stencil_ops: None,
}),
occlusion_query_set: None,
timestamp_writes: None,
});
rp.set_pipeline(&self.skybox_pipeline);
rp.set_bind_group(0, &self.cam_bind, &[]);
rp.draw(0..4, 0..1);
rp.set_pipeline(&self.instance_pipeline);
rp.set_bind_group(0, &self.cam_bind, &[]);
rp.set_bind_group(2, &self.lights_bind, &[]);
rp.set_vertex_buffer(0, self.vbo.slice(..));
let default_texture_bg = &self.textures.get("default_white").unwrap().1;
for (texture_name, inst_buf) in &self.instance_buffers {
let bg = match texture_name {
Some(name) => self.textures.get(name).map_or(default_texture_bg, |(_, bg)| bg),
None => default_texture_bg,
};
rp.set_bind_group(1, bg, &[]);
let num_instances = self.instance_groups.get(texture_name).unwrap().len() as u32;
if num_instances > 0 {
rp.set_vertex_buffer(1, inst_buf.slice(..));
rp.draw(0..36, 0..num_instances);
}
}
rp.set_pipeline(&self.model_pipeline);
rp.set_bind_group(0, &self.cam_bind, &[]);
rp.set_bind_group(2, &self.lights_bind, &[]);
rp.set_bind_group(3, &self.shadow_bind_group, &[]);
rp.set_bind_group(4, &self.light_space_bind_group, &[]);
for (_model_name, model) in &self.loaded_models {
for mesh in &model.meshes {
rp.set_bind_group(1, &model.material_bind_groups[mesh.material_index], &[]);
rp.set_vertex_buffer(0, mesh.vbo.slice(..));
rp.set_index_buffer(mesh.ibo.slice(..), wgpu::IndexFormat::Uint32);
rp.draw_indexed(0..mesh.num_indices, 0, 0..1);
}
}
}
self.queue.submit([encoder.finish()]);
frame.present();
Ok(())
}
pub fn set_clear(&mut self, c: [f32;4]) {
self.color = wgpu::Color{ r: c[0] as f64, g: c[1] as f64, b: c[2] as f64, a: c[3] as f64 };
}
pub fn load_texture_from_file<P: AsRef<Path>>(&mut self, name: String, path: P) -> Result<()> {
if self.textures.contains_key(&name) { return Ok(()); }
let img = image::open(path)?.to_rgba8();
let dimensions = img.dimensions();
let texture_size = wgpu::Extent3d { width: dimensions.0, height: dimensions.1, depth_or_array_layers: 1 };
let texture = self.device.create_texture(&wgpu::TextureDescriptor {
label: Some(&name),
size: texture_size,
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: &[],
});
self.queue.write_texture(
wgpu::ImageCopyTexture { texture: &texture, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All },
&img,
wgpu::ImageDataLayout { offset: 0, bytes_per_row: Some(4 * dimensions.0), rows_per_image: Some(dimensions.1) },
texture_size,
);
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(&format!("bind_group_{}", name)),
layout: &self.texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&texture_view) },
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&self.texture_sampler) },
],
});
self.textures.insert(name, (texture, bind_group));
Ok(())
}
pub fn load_gltf_model(&mut self, name: &str, path: &Path) -> Result<()> {
info!("Loading glTF model: {}", path.display());
let model = kengaai_model_loader::GltfLoader::load(path)?;
self.load_procedural_model(name, &model)
}
pub fn load_procedural_model(&mut self, name: &str, model: &kengaai_model_loader::Model) -> Result<()> {
for (i, image_data) in model.images.iter().enumerate() {
let texture_name = format!("{}:{}", name, i);
if self.textures.contains_key(&texture_name) { continue; }
let texture_size = wgpu::Extent3d { width: image_data.width, height: image_data.height, depth_or_array_layers: 1 };
let texture = self.device.create_texture(&wgpu::TextureDescriptor {
label: Some(&texture_name),
size: texture_size,
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: &[],
});
self.queue.write_texture(
wgpu::ImageCopyTexture { texture: &texture, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All },
&image_data.pixels,
wgpu::ImageDataLayout { offset: 0, bytes_per_row: Some(4 * image_data.width), rows_per_image: Some(image_data.height) },
texture_size,
);
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(&format!("bind_group_{}", texture_name)),
layout: &self.texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&texture_view) },
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&self.texture_sampler) },
],
});
self.textures.insert(texture_name, (texture, bind_group));
}
let mut meshes = Vec::new();
for mesh_data in &model.meshes {
if mesh_data.vertices.is_empty() || mesh_data.indices.is_empty() {
continue;
}
let vbo = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("{}_{}_vbo", name, mesh_data.name)),
contents: bytemuck::cast_slice(&mesh_data.vertices),
usage: wgpu::BufferUsages::VERTEX,
});
let ibo = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("{}_{}_ibo", name, mesh_data.name)),
contents: bytemuck::cast_slice(&mesh_data.indices),
usage: wgpu::BufferUsages::INDEX,
});
meshes.push(MeshBuffers {
vbo,
ibo,
num_indices: mesh_data.indices.len() as u32,
material_index: mesh_data.material_index.unwrap_or(0), });
}
if meshes.is_empty() {
return Err(anyhow::anyhow!("glTF Model has no valid meshes"));
}
let default_white_texture_view = self.textures.get("default_white").unwrap().0.create_view(&wgpu::TextureViewDescriptor::default());
let default_normal_texture_view = self.textures.get("default_normal").unwrap().0.create_view(&wgpu::TextureViewDescriptor::default());
let material_bind_groups = model.materials.iter().map(|m| {
let ubo = MaterialUBO {
base_color_factor: m.base_color_factor,
metallic_factor: m.metallic_factor,
roughness_factor: m.roughness_factor,
_pad: [0.0, 0.0],
};
let buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("{}_{}_ubo", name, m.name)),
contents: bytemuck::bytes_of(&ubo),
usage: wgpu::BufferUsages::UNIFORM,
});
let base_color_texture_view = m.base_color_texture_index
.and_then(|i| self.textures.get(&format!("{}:{}", name, i)))
.map(|(tex, _)| tex.create_view(&wgpu::TextureViewDescriptor::default()));
let normal_texture_view = m.normal_texture_index
.and_then(|i| self.textures.get(&format!("{}:{}", name, i)))
.map(|(tex, _)| tex.create_view(&wgpu::TextureViewDescriptor::default()));
let metallic_roughness_texture_view = m.metallic_roughness_texture_index
.and_then(|i| self.textures.get(&format!("{}:{}", name, i)))
.map(|(tex, _)| tex.create_view(&wgpu::TextureViewDescriptor::default()));
let occlusion_texture_view = m.occlusion_texture_index
.and_then(|i| self.textures.get(&format!("{}:{}", name, i)))
.map(|(tex, _)| tex.create_view(&wgpu::TextureViewDescriptor::default()));
self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(&format!("pbr_bind_group_{}_{}", name, m.name)),
layout: &self.pbr_material_bind_group_layout,
entries: &[
wgpu::BindGroupEntry { binding: 0, resource: buffer.as_entire_binding() },
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(base_color_texture_view.as_ref().unwrap_or(&default_white_texture_view)) },
wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&self.texture_sampler) },
wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(normal_texture_view.as_ref().unwrap_or(&default_normal_texture_view)) },
wgpu::BindGroupEntry { binding: 4, resource: wgpu::BindingResource::TextureView(metallic_roughness_texture_view.as_ref().unwrap_or(&default_white_texture_view)) },
wgpu::BindGroupEntry { binding: 5, resource: wgpu::BindingResource::TextureView(occlusion_texture_view.as_ref().unwrap_or(&default_white_texture_view)) },
],
})
}).collect();
let loaded_model = LoadedModel {
meshes,
material_bind_groups,
};
self.loaded_models.insert(name.to_string(), loaded_model);
info!("glTF Model '{}' loaded successfully", name);
Ok(())
}
}
fn create_depth(device: &wgpu::Device, width: u32, height: u32) -> (wgpu::Texture, wgpu::TextureView) {
let tex = device.create_texture(&wgpu::TextureDescriptor{
label: Some("depth"),
size: wgpu::Extent3d{ width, height, depth_or_array_layers:1 },
mip_level_count: 1, sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth24Plus,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
(tex, view)
}
fn create_shadow_texture(device: &wgpu::Device, width: u32, height: u32) -> (wgpu::Texture, wgpu::TextureView) {
let tex = device.create_texture(&wgpu::TextureDescriptor{
label: Some("shadow"),
size: wgpu::Extent3d{ width, height, depth_or_array_layers:1 },
mip_level_count: 1, sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth24Plus,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
(tex, view)
}
pub struct FpsController {
pub player_body_handle: RigidBodyHandle,
pub forward: bool,
pub back: bool,
pub left: bool,
pub right: bool,
pub run: bool,
pub jump: bool,
pub move_speed: f32,
pub run_speed: f32,
pub mouse_sensitivity: f32,
pub mouse_delta: Vec2,
}
impl FpsController {
pub fn new(player_body_handle: RigidBodyHandle, move_speed: f32, run_speed: f32) -> Self {
Self {
player_body_handle,
forward: false,
back: false,
left: false,
right: false,
run: false,
jump: false,
move_speed,
run_speed,
mouse_sensitivity: 0.12,
mouse_delta: Vec2::ZERO,
}
}
pub fn step(&mut self, cam: &mut Camera, physics: &mut PhysicsWorld, dt: f32) {
cam.yaw += self.mouse_delta.x * self.mouse_sensitivity * dt;
cam.pitch += -self.mouse_delta.y * self.mouse_sensitivity * dt;
cam.pitch = cam.pitch.clamp(-std::f32::consts::FRAC_PI_2, std::f32::consts::FRAC_PI_2);
self.mouse_delta = Vec2::ZERO;
let (yaw_sin, yaw_cos) = cam.yaw.sin_cos();
let forward = Vec3::new(yaw_cos, 0.0, yaw_sin).normalize_or_zero();
let right = Vec3::new(-yaw_sin, 0.0, yaw_cos).normalize_or_zero();
let speed = if self.run { self.run_speed } else { self.move_speed };
let mut wish_dir = Vec3::ZERO;
if self.forward { wish_dir += forward; }
if self.back { wish_dir -= forward; }
if self.left { wish_dir -= right; }
if self.right { wish_dir += right; }
wish_dir = wish_dir.normalize_or_zero() * speed;
let jump_requested = self.jump;
physics.step(self.player_body_handle, wish_dir, jump_requested, dt);
if jump_requested {
self.jump = false;
}
if let Some(player_body) = physics.rigid_body_set.get(self.player_body_handle) {
let player_pos = player_body.translation();
cam.pos = Vec3::new(player_pos.x, player_pos.y + 0.5, player_pos.z);
}
}
}