MAJOR rendering overhaul. Snow deformation, persistent light, flowmap out. Also ECS architexture overhaul
This commit is contained in:
3
build.rs
3
build.rs
@@ -1,7 +1,6 @@
|
||||
fn main()
|
||||
{
|
||||
let wesl = wesl::Wesl::new("src/shaders");
|
||||
wesl.build_artifact(&"package::standard".parse().unwrap(), "standard");
|
||||
wesl.build_artifact(&"package::main".parse().unwrap(), "main");
|
||||
wesl.build_artifact(&"package::shadow".parse().unwrap(), "shadow");
|
||||
wesl.build_artifact(&"package::terrain".parse().unwrap(), "terrain");
|
||||
}
|
||||
|
||||
25
src/bundles/camera.rs
Normal file
25
src/bundles/camera.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use glam::Vec3;
|
||||
|
||||
use crate::bundles::Bundle;
|
||||
use crate::components::CameraComponent;
|
||||
use crate::entity::EntityHandle;
|
||||
use crate::render;
|
||||
use crate::world::{Transform, World};
|
||||
|
||||
pub struct CameraBundle
|
||||
{
|
||||
pub position: Vec3,
|
||||
}
|
||||
|
||||
impl Bundle for CameraBundle
|
||||
{
|
||||
fn spawn(self, world: &mut World) -> Result<EntityHandle, String>
|
||||
{
|
||||
let camera_entity = world.spawn();
|
||||
let camera_component = CameraComponent::new(render::aspect_ratio());
|
||||
let transform = Transform::from_position(self.position);
|
||||
world.cameras.insert(camera_entity, camera_component);
|
||||
world.transforms.insert(camera_entity, transform);
|
||||
Ok(camera_entity)
|
||||
}
|
||||
}
|
||||
12
src/bundles/mod.rs
Normal file
12
src/bundles/mod.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
pub mod camera;
|
||||
pub mod player;
|
||||
pub mod spotlight;
|
||||
pub mod terrain;
|
||||
|
||||
use crate::entity::EntityHandle;
|
||||
use crate::world::World;
|
||||
|
||||
pub trait Bundle
|
||||
{
|
||||
fn spawn(self, world: &mut World) -> Result<EntityHandle, String>;
|
||||
}
|
||||
207
src/bundles/player.rs
Normal file
207
src/bundles/player.rs
Normal file
@@ -0,0 +1,207 @@
|
||||
use std::f32::consts::PI;
|
||||
use std::rc::Rc;
|
||||
|
||||
use glam::Vec3;
|
||||
use rapier3d::control::{CharacterAutostep, KinematicCharacterController};
|
||||
use rapier3d::prelude::{ColliderBuilder, RigidBodyBuilder};
|
||||
|
||||
use crate::bundles::Bundle;
|
||||
use crate::components::jump::JumpComponent;
|
||||
use crate::components::lights::spot::SpotlightComponent;
|
||||
use crate::components::{InputComponent, MeshComponent, MovementComponent, PhysicsComponent};
|
||||
use crate::entity::EntityHandle;
|
||||
use crate::loaders::mesh::Mesh;
|
||||
use crate::physics::PhysicsManager;
|
||||
use crate::render::Pipeline;
|
||||
use crate::state::StateMachine;
|
||||
use crate::systems::player_states::{
|
||||
PlayerFallingState, PlayerIdleState, PlayerJumpingState, PlayerWalkingState,
|
||||
};
|
||||
use crate::world::{Transform, World};
|
||||
|
||||
pub struct PlayerBundle
|
||||
{
|
||||
pub position: Vec3,
|
||||
}
|
||||
|
||||
impl Bundle for PlayerBundle
|
||||
{
|
||||
fn spawn(self, world: &mut World) -> Result<EntityHandle, String>
|
||||
{
|
||||
let entity = world.spawn();
|
||||
|
||||
let spawn_transform = Transform::from_position(self.position);
|
||||
|
||||
let rigidbody = RigidBodyBuilder::kinematic_position_based()
|
||||
.translation(spawn_transform.position.into())
|
||||
.build();
|
||||
let collider = ColliderBuilder::capsule_y(0.5, 0.5).build();
|
||||
let _controller = KinematicCharacterController {
|
||||
slide: true,
|
||||
autostep: Some(CharacterAutostep::default()),
|
||||
max_slope_climb_angle: 45.0,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let rigidbody_handle = PhysicsManager::add_rigidbody(rigidbody);
|
||||
let collider_handle = PhysicsManager::add_collider(collider, Some(rigidbody_handle));
|
||||
|
||||
let mesh = Mesh::load_mesh("meshes/player_mesh.glb")
|
||||
.map_err(|e| format!("missing player mesh: {}", e))?;
|
||||
|
||||
let falling_state = PlayerFallingState { entity };
|
||||
let idle_state = PlayerIdleState { entity };
|
||||
let walking_state = PlayerWalkingState {
|
||||
entity,
|
||||
enter_time_stamp: 0.0,
|
||||
};
|
||||
let jumping_state = PlayerJumpingState {
|
||||
entity,
|
||||
enter_time_stamp: 0.0,
|
||||
};
|
||||
|
||||
let mut state_machine = StateMachine::new(Box::new(falling_state));
|
||||
state_machine.add_state(walking_state);
|
||||
state_machine.add_state(idle_state);
|
||||
state_machine.add_state(jumping_state);
|
||||
|
||||
let entity_id = entity;
|
||||
|
||||
state_machine.add_transition::<PlayerFallingState, PlayerIdleState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
let has_input = world
|
||||
.inputs
|
||||
.with(entity_id, |i| i.move_direction.length() > 0.01)
|
||||
.unwrap_or(false);
|
||||
is_grounded && !has_input
|
||||
});
|
||||
|
||||
state_machine.add_transition::<PlayerFallingState, PlayerWalkingState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
let has_input = world
|
||||
.inputs
|
||||
.with(entity_id, |i| i.move_direction.length() > 0.01)
|
||||
.unwrap_or(false);
|
||||
is_grounded && has_input
|
||||
});
|
||||
|
||||
state_machine.add_transition::<PlayerIdleState, PlayerWalkingState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
let has_input = world
|
||||
.inputs
|
||||
.with(entity_id, |i| i.move_direction.length() > 0.01)
|
||||
.unwrap_or(false);
|
||||
is_grounded && has_input
|
||||
});
|
||||
|
||||
state_machine.add_transition::<PlayerWalkingState, PlayerIdleState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
let has_input = world
|
||||
.inputs
|
||||
.with(entity_id, |i| i.move_direction.length() > 0.01)
|
||||
.unwrap_or(false);
|
||||
is_grounded && !has_input
|
||||
});
|
||||
|
||||
state_machine.add_transition::<PlayerIdleState, PlayerFallingState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
!is_grounded
|
||||
});
|
||||
|
||||
state_machine.add_transition::<PlayerWalkingState, PlayerFallingState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
!is_grounded
|
||||
});
|
||||
|
||||
state_machine.add_transition::<PlayerIdleState, PlayerJumpingState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
let jump_pressed = world
|
||||
.inputs
|
||||
.with(entity_id, |i| i.jump_just_pressed)
|
||||
.unwrap_or(false);
|
||||
is_grounded && jump_pressed
|
||||
});
|
||||
|
||||
state_machine.add_transition::<PlayerWalkingState, PlayerJumpingState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
let jump_pressed = world
|
||||
.inputs
|
||||
.with(entity_id, |i| i.jump_just_pressed)
|
||||
.unwrap_or(false);
|
||||
is_grounded && jump_pressed
|
||||
});
|
||||
|
||||
state_machine.add_transition::<PlayerJumpingState, PlayerFallingState>(move |world| {
|
||||
world
|
||||
.jumps
|
||||
.with(entity_id, |jump| {
|
||||
jump.jump_config.jump_context.duration >= jump.jump_config.jump_duration
|
||||
})
|
||||
.unwrap_or(true)
|
||||
});
|
||||
|
||||
world.transforms.insert(entity, spawn_transform);
|
||||
world.movements.insert(entity, MovementComponent::new());
|
||||
world.jumps.insert(entity, JumpComponent::new());
|
||||
world.inputs.insert(entity, InputComponent::default());
|
||||
world.physics.insert(
|
||||
entity,
|
||||
PhysicsComponent {
|
||||
rigidbody: rigidbody_handle,
|
||||
collider: Some(collider_handle),
|
||||
},
|
||||
);
|
||||
world.meshes.insert(
|
||||
entity,
|
||||
MeshComponent {
|
||||
mesh: Rc::new(mesh),
|
||||
pipeline: Pipeline::Standard,
|
||||
instance_buffer: None,
|
||||
num_instances: 1,
|
||||
tile_scale: 4.0,
|
||||
enable_dissolve: false,
|
||||
enable_snow_light: false,
|
||||
},
|
||||
);
|
||||
world.player_tags.insert(entity, ());
|
||||
world.state_machines.insert(entity, state_machine);
|
||||
|
||||
let outer_angle = PI / 2.0 * 0.9;
|
||||
world.spotlights.insert(
|
||||
entity,
|
||||
SpotlightComponent::new(
|
||||
Vec3::new(1.0, 2.5, 1.0),
|
||||
Vec3::new(0.0, -1.0, 0.0),
|
||||
100.0,
|
||||
outer_angle * 0.5,
|
||||
outer_angle,
|
||||
),
|
||||
);
|
||||
|
||||
Ok(entity)
|
||||
}
|
||||
}
|
||||
53
src/bundles/spotlight.rs
Normal file
53
src/bundles/spotlight.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use glam::Vec3;
|
||||
|
||||
use crate::bundles::Bundle;
|
||||
use crate::components::RotateComponent;
|
||||
use crate::entity::EntityHandle;
|
||||
use crate::loaders::lights::LightData;
|
||||
use crate::world::{Transform, World};
|
||||
|
||||
pub struct SpotlightBundle
|
||||
{
|
||||
pub light_data: LightData,
|
||||
}
|
||||
|
||||
impl Bundle for SpotlightBundle
|
||||
{
|
||||
fn spawn(self, world: &mut World) -> Result<EntityHandle, String>
|
||||
{
|
||||
let entity = world.spawn();
|
||||
let transform = Transform::from_matrix(self.light_data.transform);
|
||||
world.transforms.insert(entity, transform);
|
||||
world.spotlights.insert(entity, self.light_data.component);
|
||||
if let Some(tag) = self.light_data.tag
|
||||
{
|
||||
if tag == "lighthouse"
|
||||
{
|
||||
world
|
||||
.rotates
|
||||
.insert(entity, RotateComponent::new(Vec3::Y, 1.0));
|
||||
}
|
||||
}
|
||||
Ok(entity)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_spotlights(world: &mut World, spotlights: Vec<LightData>)
|
||||
{
|
||||
for light_data in spotlights
|
||||
{
|
||||
let entity = world.spawn();
|
||||
let transform = Transform::from_matrix(light_data.transform);
|
||||
world.transforms.insert(entity, transform);
|
||||
world.spotlights.insert(entity, light_data.component);
|
||||
if let Some(tag) = light_data.tag
|
||||
{
|
||||
if tag == "lighthouse"
|
||||
{
|
||||
world
|
||||
.rotates
|
||||
.insert(entity, RotateComponent::new(Vec3::Y, 1.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,16 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use exr::prelude::{ReadChannels, ReadLayers};
|
||||
use glam::Vec2;
|
||||
use nalgebra::{vector, DMatrix};
|
||||
use rapier3d::{
|
||||
math::Isometry,
|
||||
prelude::{ColliderBuilder, RigidBodyBuilder},
|
||||
};
|
||||
use wesl::Wesl;
|
||||
use nalgebra::vector;
|
||||
use rapier3d::prelude::{ColliderBuilder, RigidBodyBuilder};
|
||||
|
||||
use crate::{
|
||||
components::{MeshComponent, PhysicsComponent},
|
||||
entity::EntityHandle,
|
||||
mesh::{InstanceData, InstanceRaw, Mesh, Vertex},
|
||||
physics::PhysicsManager,
|
||||
render,
|
||||
world::{Transform, World},
|
||||
};
|
||||
use crate::components::{MeshComponent, PhysicsComponent};
|
||||
use crate::entity::EntityHandle;
|
||||
use crate::loaders::mesh::{InstanceData, InstanceRaw, Mesh};
|
||||
use crate::loaders::terrain::load_heightfield_from_exr;
|
||||
use crate::physics::PhysicsManager;
|
||||
use crate::render;
|
||||
use crate::world::{Transform, World};
|
||||
|
||||
pub struct TerrainConfig
|
||||
{
|
||||
@@ -46,9 +40,9 @@ impl TerrainConfig
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Terrain;
|
||||
pub struct TerrainBundle;
|
||||
|
||||
impl Terrain
|
||||
impl TerrainBundle
|
||||
{
|
||||
pub fn spawn(
|
||||
world: &mut World,
|
||||
@@ -76,15 +70,18 @@ impl Terrain
|
||||
entity,
|
||||
MeshComponent {
|
||||
mesh: Rc::new(mesh),
|
||||
pipeline: render::Pipeline::Terrain,
|
||||
pipeline: render::Pipeline::Standard,
|
||||
instance_buffer: None,
|
||||
num_instances: 1,
|
||||
tile_scale: 2.0,
|
||||
enable_dissolve: false,
|
||||
enable_snow_light: true,
|
||||
},
|
||||
);
|
||||
|
||||
if !physics_added
|
||||
{
|
||||
let heights = Self::load_heightfield_from_exr(&config.heightmap_path)?;
|
||||
let heights = load_heightfield_from_exr(&config.heightmap_path)?;
|
||||
|
||||
let height_scale = 1.0;
|
||||
let scale = vector![config.size.x, height_scale, config.size.y];
|
||||
@@ -137,9 +134,12 @@ impl Terrain
|
||||
entity,
|
||||
MeshComponent {
|
||||
mesh: Rc::new(mesh),
|
||||
pipeline: render::Pipeline::Environment,
|
||||
pipeline: render::Pipeline::Standard,
|
||||
instance_buffer: Some(instance_buffer),
|
||||
num_instances: num_instances as u32,
|
||||
tile_scale: 4.0,
|
||||
enable_dissolve: true,
|
||||
enable_snow_light: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -147,94 +147,4 @@ impl Terrain
|
||||
|
||||
first_entity.ok_or_else(|| anyhow::anyhow!("No meshes found in glTF file"))
|
||||
}
|
||||
|
||||
fn load_heightfield_from_exr(path: &str) -> anyhow::Result<DMatrix<f32>>
|
||||
{
|
||||
let image = exr::prelude::read()
|
||||
.no_deep_data()
|
||||
.largest_resolution_level()
|
||||
.all_channels()
|
||||
.all_layers()
|
||||
.all_attributes()
|
||||
.from_file(path)?;
|
||||
|
||||
let layer = &image.layer_data[0];
|
||||
let channel = &layer.channel_data.list[0];
|
||||
|
||||
let width = layer.size.width();
|
||||
let height = layer.size.height();
|
||||
|
||||
let heights: Vec<f32> = channel.sample_data.values_as_f32().collect();
|
||||
|
||||
Ok(DMatrix::from_row_slice(height, width, &heights))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_terrain_render_pipeline(
|
||||
device: &wgpu::Device,
|
||||
config: &wgpu::SurfaceConfiguration,
|
||||
bind_group_layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::RenderPipeline
|
||||
{
|
||||
let compiler = Wesl::new("src/shaders");
|
||||
let shader_source = compiler
|
||||
.compile(&"package::terrain".parse().unwrap())
|
||||
.inspect_err(|e| eprintln!("WESL error: {e}"))
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Terrain Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
|
||||
});
|
||||
|
||||
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Terrain Render Pipeline Layout"),
|
||||
bind_group_layouts: &[bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Terrain Render Pipeline"),
|
||||
layout: Some(&render_pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[Vertex::desc(), InstanceRaw::desc()],
|
||||
compilation_options: Default::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: config.format,
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: Default::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: Some(wgpu::Face::Back),
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: wgpu::CompareFunction::Less,
|
||||
stencil: wgpu::StencilState::default(),
|
||||
bias: wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
cache: None,
|
||||
})
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use glam::{Mat4, Vec3};
|
||||
|
||||
use crate::components::CameraComponent;
|
||||
use crate::entity::EntityHandle;
|
||||
use crate::render;
|
||||
use crate::world::{Transform, World};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
pub struct CameraUniforms
|
||||
{
|
||||
pub model: [[f32; 4]; 4],
|
||||
pub view: [[f32; 4]; 4],
|
||||
pub projection: [[f32; 4]; 4],
|
||||
pub light_direction: [f32; 3],
|
||||
pub _padding: f32,
|
||||
}
|
||||
|
||||
impl CameraUniforms
|
||||
{
|
||||
pub fn new(model: Mat4, view: Mat4, projection: Mat4, light_direction: Vec3) -> Self
|
||||
{
|
||||
Self {
|
||||
model: model.to_cols_array_2d(),
|
||||
view: view.to_cols_array_2d(),
|
||||
projection: projection.to_cols_array_2d(),
|
||||
light_direction: light_direction.to_array(),
|
||||
_padding: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Camera;
|
||||
|
||||
impl Camera
|
||||
{
|
||||
pub fn spawn(world: &mut World, position: Vec3) -> EntityHandle
|
||||
{
|
||||
let camera_entity = world.spawn();
|
||||
|
||||
let camera_component = CameraComponent::new(render::aspect_ratio());
|
||||
|
||||
let transform = Transform::from_position(position);
|
||||
|
||||
world.cameras.insert(camera_entity, camera_component);
|
||||
world.transforms.insert(camera_entity, transform);
|
||||
|
||||
camera_entity
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
use glam::Vec3;
|
||||
|
||||
pub struct DirectionallightComponent
|
||||
{
|
||||
pub offset: Vec3,
|
||||
pub direction: Vec3,
|
||||
}
|
||||
|
||||
impl DirectionallightComponent
|
||||
{
|
||||
pub fn new(offset: Vec3, direction: Vec3) -> Self
|
||||
{
|
||||
Self {
|
||||
offset,
|
||||
direction: direction.normalize(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1 @@
|
||||
pub mod directional;
|
||||
pub mod point;
|
||||
pub mod spot;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
use glam::Vec3;
|
||||
|
||||
pub struct PointlightComponent
|
||||
{
|
||||
pub offset: Vec3,
|
||||
}
|
||||
|
||||
impl PointlightComponent
|
||||
{
|
||||
pub fn new(offset: Vec3) -> Self
|
||||
{
|
||||
Self { offset }
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,13 @@ pub struct SpotlightComponent
|
||||
|
||||
impl SpotlightComponent
|
||||
{
|
||||
pub fn new(offset: Vec3, direction: Vec3, range: f32, inner_angle: f32, outer_angle: f32) -> Self
|
||||
pub fn new(
|
||||
offset: Vec3,
|
||||
direction: Vec3,
|
||||
range: f32,
|
||||
inner_angle: f32,
|
||||
outer_angle: f32,
|
||||
) -> Self
|
||||
{
|
||||
Self {
|
||||
offset,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::mesh::Mesh;
|
||||
use crate::loaders::mesh::Mesh;
|
||||
use crate::render::Pipeline;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -10,4 +10,7 @@ pub struct MeshComponent
|
||||
pub pipeline: Pipeline,
|
||||
pub instance_buffer: Option<wgpu::Buffer>,
|
||||
pub num_instances: u32,
|
||||
pub tile_scale: f32,
|
||||
pub enable_dissolve: bool,
|
||||
pub enable_snow_light: bool,
|
||||
}
|
||||
|
||||
@@ -6,11 +6,9 @@ pub mod jump;
|
||||
pub mod lights;
|
||||
pub mod mesh;
|
||||
pub mod movement;
|
||||
pub mod noclip;
|
||||
pub mod physics;
|
||||
pub mod player_tag;
|
||||
pub mod rotate;
|
||||
pub mod state_machine;
|
||||
pub mod tree_tag;
|
||||
|
||||
pub use camera::CameraComponent;
|
||||
pub use dissolve::DissolveComponent;
|
||||
|
||||
1
src/components/noclip.rs
Normal file
1
src/components/noclip.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub struct NoclipTag;
|
||||
@@ -1,2 +0,0 @@
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct PlayerTag;
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
pub struct TreeTag;
|
||||
@@ -6,7 +6,7 @@ use nalgebra::DMatrix;
|
||||
use rapier3d::parry::shape::HeightField;
|
||||
|
||||
use crate::{
|
||||
mesh::{InstanceRaw, Mesh, Vertex},
|
||||
loaders::mesh::{InstanceRaw, Mesh, Vertex},
|
||||
physics::PhysicsManager,
|
||||
render::{self, DrawCall, Pipeline},
|
||||
};
|
||||
@@ -143,9 +143,13 @@ pub fn render_collider_debug() -> Vec<DrawCall>
|
||||
index_buffer: wireframe_box.index_buffer.clone(),
|
||||
num_indices: wireframe_box.num_indices,
|
||||
model,
|
||||
pipeline: Pipeline::Wireframe,
|
||||
pipeline: Pipeline::Standard,
|
||||
instance_buffer: Some(instance_buffer),
|
||||
num_instances: 1,
|
||||
tile_scale: 4.0,
|
||||
enable_dissolve: false,
|
||||
enable_snow_light: false,
|
||||
displacement_bind_group: None,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -172,9 +176,13 @@ pub fn render_collider_debug() -> Vec<DrawCall>
|
||||
index_buffer: heightfield_mesh.index_buffer.clone(),
|
||||
num_indices: heightfield_mesh.num_indices,
|
||||
model: Mat4::IDENTITY,
|
||||
pipeline: Pipeline::Wireframe,
|
||||
pipeline: Pipeline::Standard,
|
||||
instance_buffer: Some(instance_buffer),
|
||||
num_instances: 1,
|
||||
tile_scale: 4.0,
|
||||
enable_dissolve: false,
|
||||
enable_snow_light: false,
|
||||
displacement_bind_group: None,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
pub mod collider_debug;
|
||||
|
||||
pub use collider_debug::render_collider_debug;
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
use glam::Vec3;
|
||||
|
||||
use crate::camera::Camera;
|
||||
use crate::utility::input::InputState;
|
||||
use crate::world::World;
|
||||
|
||||
pub fn update_noclip_camera(camera: &mut Camera, input_state: &InputState, delta: f32)
|
||||
{
|
||||
camera.update_rotation(input_state.mouse_delta, 0.0008);
|
||||
|
||||
let mut input_vec = Vec3::ZERO;
|
||||
|
||||
if input_state.w
|
||||
{
|
||||
input_vec.z += 1.0;
|
||||
}
|
||||
if input_state.s
|
||||
{
|
||||
input_vec.z -= 1.0;
|
||||
}
|
||||
if input_state.d
|
||||
{
|
||||
input_vec.x += 1.0;
|
||||
}
|
||||
if input_state.a
|
||||
{
|
||||
input_vec.x -= 1.0;
|
||||
}
|
||||
if input_state.space
|
||||
{
|
||||
input_vec.y += 1.0;
|
||||
}
|
||||
|
||||
if input_vec.length_squared() > 0.0
|
||||
{
|
||||
input_vec = input_vec.normalize();
|
||||
}
|
||||
|
||||
let mut speed = 10.0 * delta;
|
||||
if input_state.shift
|
||||
{
|
||||
speed *= 2.0;
|
||||
}
|
||||
|
||||
camera.update_noclip(input_vec, speed);
|
||||
}
|
||||
|
||||
pub fn update_follow_camera(camera: &mut Camera, world: &World, input_state: &InputState)
|
||||
{
|
||||
let player_entities = world.player_tags.all();
|
||||
|
||||
if let Some(&player_entity) = player_entities.first()
|
||||
{
|
||||
if let Some(player_transform) = world.transforms.get(player_entity)
|
||||
{
|
||||
camera.update_follow(player_transform.position, input_state.mouse_delta, 0.0008);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::entity::EntityHandle;
|
||||
use crate::mesh::Mesh;
|
||||
use crate::loaders::mesh::Mesh;
|
||||
use crate::render::{DrawCall, Pipeline};
|
||||
|
||||
pub type DrawHandle = usize;
|
||||
|
||||
@@ -80,10 +80,7 @@ impl Empties
|
||||
Self::load_gltf_empties(path)
|
||||
}
|
||||
|
||||
pub fn get_empty_by_name(
|
||||
gltf_path: &str,
|
||||
name: &str,
|
||||
) -> anyhow::Result<Option<crate::empty::EmptyNode>>
|
||||
pub fn get_empty_by_name(gltf_path: &str, name: &str) -> anyhow::Result<Option<EmptyNode>>
|
||||
{
|
||||
let empties = Self::load_empties(gltf_path)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to load empty nodes: {}", e))?;
|
||||
@@ -1,11 +1,7 @@
|
||||
use glam::{Mat4, Vec3};
|
||||
use gltf::json::Extras;
|
||||
use std::{ops::Deref, path::Path};
|
||||
use std::path::Path;
|
||||
|
||||
use crate::{
|
||||
components::lights::spot::SpotlightComponent,
|
||||
world::{Transform, World},
|
||||
};
|
||||
use crate::components::lights::spot::SpotlightComponent;
|
||||
|
||||
pub struct LightData
|
||||
{
|
||||
@@ -104,7 +100,7 @@ impl Lights
|
||||
transform: global_transform,
|
||||
tag,
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,28 +122,4 @@ impl Lights
|
||||
{
|
||||
crate::render::with_device(|_device| Lights::load_gltf_lights(path))
|
||||
}
|
||||
|
||||
pub fn spawn_lights(world: &mut World, spotlights: Vec<LightData>)
|
||||
{
|
||||
use crate::components::RotateComponent;
|
||||
|
||||
for light_data in spotlights
|
||||
{
|
||||
let entity = world.spawn();
|
||||
let transform = Transform::from_matrix(light_data.transform);
|
||||
|
||||
world.transforms.insert(entity, transform);
|
||||
world.spotlights.insert(entity, light_data.component);
|
||||
|
||||
if let Some(tag) = light_data.tag
|
||||
{
|
||||
if tag == "lighthouse"
|
||||
{
|
||||
world
|
||||
.rotates
|
||||
.insert(entity, RotateComponent::new(Vec3::Y, 1.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use glam::{Mat4, Quat, Vec3};
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::utility::transform::Transform;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
6
src/loaders/mod.rs
Normal file
6
src/loaders/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub mod empty;
|
||||
pub mod heightmap;
|
||||
pub mod lights;
|
||||
pub mod mesh;
|
||||
pub mod scene;
|
||||
pub mod terrain;
|
||||
@@ -1,12 +1,10 @@
|
||||
use anyhow::Result;
|
||||
use glam::Vec3;
|
||||
|
||||
use crate::{
|
||||
empty::Empties,
|
||||
light::{LightData, Lights},
|
||||
mesh::{InstanceData, Mesh},
|
||||
render,
|
||||
};
|
||||
use crate::loaders::empty::Empties;
|
||||
use crate::loaders::lights::{LightData, Lights};
|
||||
use crate::loaders::mesh::{InstanceData, Mesh};
|
||||
use crate::render;
|
||||
|
||||
pub const CAMERA_SPAWN_OFFSET: Vec3 = Vec3::new(15.0, 15.0, 15.0);
|
||||
|
||||
@@ -21,9 +19,8 @@ impl Space
|
||||
{
|
||||
pub fn load_space(gltf_path: &str) -> Result<Space>
|
||||
{
|
||||
let mesh_data = render::with_device(|device| {
|
||||
Mesh::load_gltf_with_instances(device, gltf_path)
|
||||
})?;
|
||||
let mesh_data =
|
||||
render::with_device(|device| Mesh::load_gltf_with_instances(device, gltf_path))?;
|
||||
|
||||
let lights = Lights::load_lights(gltf_path)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to load lights: {}", e))?;
|
||||
@@ -45,7 +42,8 @@ impl Space
|
||||
|
||||
if let Some(empty_node) = empty
|
||||
{
|
||||
let (_scale, _rotation, translation) = empty_node.transform.to_scale_rotation_translation();
|
||||
let (_scale, _rotation, translation) =
|
||||
empty_node.transform.to_scale_rotation_translation();
|
||||
Ok(translation)
|
||||
}
|
||||
else
|
||||
23
src/loaders/terrain.rs
Normal file
23
src/loaders/terrain.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use exr::prelude::{ReadChannels, ReadLayers};
|
||||
use nalgebra::DMatrix;
|
||||
|
||||
pub fn load_heightfield_from_exr(path: &str) -> anyhow::Result<DMatrix<f32>>
|
||||
{
|
||||
let image = exr::prelude::read()
|
||||
.no_deep_data()
|
||||
.largest_resolution_level()
|
||||
.all_channels()
|
||||
.all_layers()
|
||||
.all_attributes()
|
||||
.from_file(path)?;
|
||||
|
||||
let layer = &image.layer_data[0];
|
||||
let channel = &layer.channel_data.list[0];
|
||||
|
||||
let width = layer.size.width();
|
||||
let height = layer.size.height();
|
||||
|
||||
let heights: Vec<f32> = channel.sample_data.values_as_f32().collect();
|
||||
|
||||
Ok(DMatrix::from_row_slice(height, width, &heights))
|
||||
}
|
||||
148
src/main.rs
148
src/main.rs
@@ -1,24 +1,17 @@
|
||||
mod camera;
|
||||
mod bundles;
|
||||
mod components;
|
||||
mod debug;
|
||||
mod draw;
|
||||
mod empty;
|
||||
mod entity;
|
||||
mod event;
|
||||
mod heightmap;
|
||||
mod light;
|
||||
mod mesh;
|
||||
mod loaders;
|
||||
mod physics;
|
||||
mod player;
|
||||
mod postprocess;
|
||||
mod render;
|
||||
mod shader;
|
||||
mod snow;
|
||||
mod snow_light;
|
||||
mod space;
|
||||
mod state;
|
||||
mod systems;
|
||||
mod terrain;
|
||||
mod texture;
|
||||
mod utility;
|
||||
mod world;
|
||||
@@ -28,24 +21,22 @@ use std::time::{Duration, Instant};
|
||||
use glam::Vec3;
|
||||
use render::Renderer;
|
||||
use utility::input::InputState;
|
||||
use world::{Transform, World};
|
||||
use world::World;
|
||||
|
||||
use crate::camera::Camera;
|
||||
use crate::components::CameraComponent;
|
||||
use crate::debug::render_collider_debug;
|
||||
use crate::entity::EntityHandle;
|
||||
use crate::light::Lights;
|
||||
use crate::bundles::camera::CameraBundle;
|
||||
use crate::bundles::player::PlayerBundle;
|
||||
use crate::bundles::spotlight::spawn_spotlights;
|
||||
use crate::bundles::terrain::{TerrainBundle, TerrainConfig};
|
||||
use crate::bundles::Bundle;
|
||||
use crate::loaders::scene::Space;
|
||||
use crate::physics::PhysicsManager;
|
||||
use crate::player::Player;
|
||||
use crate::space::Space;
|
||||
use crate::systems::{
|
||||
camera_follow_system, camera_input_system, camera_noclip_system, physics_sync_system,
|
||||
player_input_system, render_system, rotate_system, spotlight_sync_system,
|
||||
start_camera_following, state_machine_physics_system, state_machine_system,
|
||||
stop_camera_following,
|
||||
};
|
||||
use crate::snow::{SnowConfig, SnowLayer};
|
||||
use crate::terrain::{Terrain, TerrainConfig};
|
||||
use crate::systems::{
|
||||
camera_follow_system, camera_input_system, camera_noclip_system, camera_view_matrix,
|
||||
noclip_toggle_system, physics_sync_system, player_input_system, render_system, rotate_system,
|
||||
snow_system, spotlight_sync_system, start_camera_following, state_machine_physics_system,
|
||||
state_machine_system,
|
||||
};
|
||||
use crate::utility::time::Time;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
@@ -68,11 +59,21 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
let player_spawn = space.player_spawn;
|
||||
let camera_spawn = space.camera_spawn_position();
|
||||
|
||||
let tree_positions: Vec<Vec3> = space
|
||||
.mesh_data
|
||||
.iter()
|
||||
.flat_map(|(_, instances)| instances.iter().map(|inst| inst.position))
|
||||
.collect();
|
||||
|
||||
let mut world = World::new();
|
||||
|
||||
let _player_entity = Player::spawn(&mut world, player_spawn);
|
||||
let _terrain_entity = Terrain::spawn(&mut world, space.mesh_data, &terrain_config)?;
|
||||
Lights::spawn_lights(&mut world, space.spotlights);
|
||||
let _player_entity = PlayerBundle {
|
||||
position: player_spawn,
|
||||
}
|
||||
.spawn(&mut world)
|
||||
.unwrap();
|
||||
let _terrain_entity = TerrainBundle::spawn(&mut world, space.mesh_data, &terrain_config)?;
|
||||
spawn_spotlights(&mut world, space.spotlights);
|
||||
|
||||
render::set_terrain_data();
|
||||
|
||||
@@ -83,15 +84,23 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
);
|
||||
|
||||
let snow_config = SnowConfig::default();
|
||||
let snow_layer = SnowLayer::load(&mut world, &snow_config)?;
|
||||
let mut snow_layer = SnowLayer::load(&snow_config)?;
|
||||
for pos in &tree_positions
|
||||
{
|
||||
snow_layer.deform_at_position(*pos, 5.0, 50.0);
|
||||
}
|
||||
println!("Snow layer loaded successfully");
|
||||
|
||||
render::set_snow_depth(&snow_layer.depth_texture_view);
|
||||
|
||||
let mut noclip_mode = true;
|
||||
|
||||
let camera_entity = Camera::spawn(&mut world, camera_spawn);
|
||||
if noclip_mode == false
|
||||
let camera_entity = CameraBundle {
|
||||
position: camera_spawn,
|
||||
}
|
||||
.spawn(&mut world)
|
||||
.unwrap();
|
||||
if !noclip_mode
|
||||
{
|
||||
start_camera_following(&mut world, camera_entity);
|
||||
}
|
||||
@@ -135,19 +144,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
|
||||
input_state.process_post_events();
|
||||
|
||||
if input_state.noclip_just_pressed
|
||||
{
|
||||
noclip_mode = !noclip_mode;
|
||||
|
||||
if noclip_mode
|
||||
{
|
||||
stop_camera_following(&mut world, camera_entity);
|
||||
}
|
||||
else
|
||||
{
|
||||
start_camera_following(&mut world, camera_entity);
|
||||
}
|
||||
}
|
||||
noclip_toggle_system(&mut world, &input_state, camera_entity, &mut noclip_mode);
|
||||
|
||||
camera_input_system(&mut world, &input_state);
|
||||
|
||||
@@ -181,34 +178,31 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
let spotlights = spotlight_sync_system(&world);
|
||||
render::update_spotlights(spotlights);
|
||||
|
||||
let mut draw_calls = render_system(&world);
|
||||
draw_calls.extend(render_collider_debug());
|
||||
snow_system(&world, &mut snow_layer, noclip_mode);
|
||||
|
||||
if let Some((camera_entity, camera_component)) = world.cameras.get_active()
|
||||
let mut draw_calls = render_system(&world);
|
||||
draw_calls.extend(snow_layer.get_draw_calls());
|
||||
|
||||
if let Some((camera_entity, camera_component)) = world.active_camera()
|
||||
{
|
||||
if let Some(camera_transform) = world.transforms.get(camera_entity)
|
||||
{
|
||||
let view =
|
||||
get_view_matrix(&world, camera_entity, camera_transform, camera_component);
|
||||
let projection = camera_component.projection_matrix();
|
||||
let player_pos = world.player_position();
|
||||
|
||||
let player_pos = world
|
||||
.player_tags
|
||||
.all()
|
||||
.first()
|
||||
.and_then(|e| world.transforms.get(*e))
|
||||
.map(|t| t.position)
|
||||
.unwrap_or(Vec3::ZERO);
|
||||
if let Some(view) = camera_view_matrix(&world)
|
||||
{
|
||||
let projection = camera_component.projection_matrix();
|
||||
|
||||
render::render(
|
||||
&view,
|
||||
&projection,
|
||||
camera_transform.position,
|
||||
player_pos,
|
||||
&draw_calls,
|
||||
time,
|
||||
delta,
|
||||
);
|
||||
render::render(
|
||||
&view,
|
||||
&projection,
|
||||
camera_transform.position,
|
||||
player_pos,
|
||||
&draw_calls,
|
||||
time,
|
||||
delta,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,27 +217,3 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_view_matrix(
|
||||
world: &World,
|
||||
camera_entity: EntityHandle,
|
||||
camera_transform: &Transform,
|
||||
camera_component: &CameraComponent,
|
||||
) -> glam::Mat4
|
||||
{
|
||||
if let Some(follow) = world.follows.get(camera_entity)
|
||||
{
|
||||
if let Some(target_transform) = world.transforms.get(follow.target)
|
||||
{
|
||||
return glam::Mat4::look_at_rh(
|
||||
camera_transform.position,
|
||||
target_transform.position,
|
||||
Vec3::Y,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let forward = camera_component.get_forward();
|
||||
let target = camera_transform.position + forward;
|
||||
glam::Mat4::look_at_rh(camera_transform.position, target, Vec3::Y)
|
||||
}
|
||||
|
||||
1495
src/render.rs
1495
src/render.rs
File diff suppressed because it is too large
Load Diff
113
src/render/bind_group.rs
Normal file
113
src/render/bind_group.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use crate::texture::{DitherTextures, FlowmapTexture};
|
||||
|
||||
use super::Renderer;
|
||||
|
||||
impl Renderer
|
||||
{
|
||||
pub(super) fn create_bind_group_from_parts(
|
||||
device: &wgpu::Device,
|
||||
layout: &wgpu::BindGroupLayout,
|
||||
uniform_buffer: &wgpu::Buffer,
|
||||
shadow_map_view: &wgpu::TextureView,
|
||||
shadow_map_sampler: &wgpu::Sampler,
|
||||
dither_textures: &DitherTextures,
|
||||
flowmap_texture: &FlowmapTexture,
|
||||
blue_noise_view: &wgpu::TextureView,
|
||||
blue_noise_sampler: &wgpu::Sampler,
|
||||
snow_light_view: &wgpu::TextureView,
|
||||
snow_light_sampler: &wgpu::Sampler,
|
||||
) -> wgpu::BindGroup
|
||||
{
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("Bind Group"),
|
||||
layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: uniform_buffer.as_entire_binding(),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::TextureView(shadow_map_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: wgpu::BindingResource::Sampler(shadow_map_sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 3,
|
||||
resource: wgpu::BindingResource::TextureView(&dither_textures.view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 4,
|
||||
resource: wgpu::BindingResource::Sampler(&dither_textures.sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 5,
|
||||
resource: wgpu::BindingResource::TextureView(&flowmap_texture.view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 6,
|
||||
resource: wgpu::BindingResource::Sampler(&flowmap_texture.sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 7,
|
||||
resource: wgpu::BindingResource::TextureView(blue_noise_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 8,
|
||||
resource: wgpu::BindingResource::Sampler(blue_noise_sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 9,
|
||||
resource: wgpu::BindingResource::TextureView(snow_light_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 10,
|
||||
resource: wgpu::BindingResource::Sampler(snow_light_sampler),
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn rebuild_bind_group_with_snow_light(&mut self)
|
||||
{
|
||||
let dither_textures = self
|
||||
.dither_textures
|
||||
.as_ref()
|
||||
.expect("Dither textures required");
|
||||
let flowmap_texture = self
|
||||
.flowmap_texture
|
||||
.as_ref()
|
||||
.expect("Flowmap texture required");
|
||||
|
||||
let snow_light_sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("Snow Light Sampler"),
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let snow_light_view = self
|
||||
.snow_light_accumulation
|
||||
.as_ref()
|
||||
.map(|s| s.read_view())
|
||||
.unwrap_or(&self.dummy_snow_light_view);
|
||||
|
||||
self.bind_group = Self::create_bind_group_from_parts(
|
||||
&self.device,
|
||||
&self.bind_group_layout,
|
||||
&self.uniform_buffer,
|
||||
&self.shadow_map_view,
|
||||
&self.shadow_map_sampler,
|
||||
dither_textures,
|
||||
flowmap_texture,
|
||||
&self.blue_noise_view,
|
||||
&self.blue_noise_sampler,
|
||||
snow_light_view,
|
||||
&snow_light_sampler,
|
||||
);
|
||||
}
|
||||
}
|
||||
885
src/render/mod.rs
Normal file
885
src/render/mod.rs
Normal file
@@ -0,0 +1,885 @@
|
||||
mod bind_group;
|
||||
mod pipeline;
|
||||
mod shadow;
|
||||
mod types;
|
||||
|
||||
pub use types::{DrawCall, Pipeline, Spotlight, SpotlightRaw, Uniforms, MAX_SPOTLIGHTS};
|
||||
|
||||
use crate::postprocess::{create_blit_pipeline, create_fullscreen_quad, LowResFramebuffer};
|
||||
use crate::texture::{DitherTextures, FlowmapTexture};
|
||||
use std::cell::RefCell;
|
||||
|
||||
use pipeline::{create_main_pipeline, create_snow_clipmap_pipeline};
|
||||
|
||||
pub struct Renderer
|
||||
{
|
||||
pub device: wgpu::Device,
|
||||
pub queue: wgpu::Queue,
|
||||
pub surface: wgpu::Surface<'static>,
|
||||
pub config: wgpu::SurfaceConfiguration,
|
||||
|
||||
framebuffer: LowResFramebuffer,
|
||||
|
||||
standard_pipeline: wgpu::RenderPipeline,
|
||||
snow_clipmap_pipeline: wgpu::RenderPipeline,
|
||||
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
bind_group_layout: wgpu::BindGroupLayout,
|
||||
bind_group: wgpu::BindGroup,
|
||||
|
||||
quad_vb: wgpu::Buffer,
|
||||
quad_ib: wgpu::Buffer,
|
||||
quad_num_indices: u32,
|
||||
blit_pipeline: wgpu::RenderPipeline,
|
||||
blit_bind_group: wgpu::BindGroup,
|
||||
|
||||
shadow_pipeline: Option<wgpu::RenderPipeline>,
|
||||
shadow_bind_group_layout: wgpu::BindGroupLayout,
|
||||
shadow_bind_group: Option<wgpu::BindGroup>,
|
||||
|
||||
terrain_height_scale: f32,
|
||||
|
||||
pub spotlights: Vec<Spotlight>,
|
||||
pub shadow_bias: f32,
|
||||
|
||||
shadow_map_texture: wgpu::Texture,
|
||||
shadow_map_view: wgpu::TextureView,
|
||||
shadow_map_sampler: wgpu::Sampler,
|
||||
|
||||
dither_textures: Option<DitherTextures>,
|
||||
flowmap_texture: Option<FlowmapTexture>,
|
||||
blue_noise_view: wgpu::TextureView,
|
||||
blue_noise_sampler: wgpu::Sampler,
|
||||
|
||||
dummy_snow_light_view: wgpu::TextureView,
|
||||
dummy_snow_light_sampler: wgpu::Sampler,
|
||||
|
||||
snow_light_accumulation: Option<crate::snow_light::SnowLightAccumulation>,
|
||||
snow_light_bound: bool,
|
||||
}
|
||||
|
||||
impl Renderer
|
||||
{
|
||||
pub async fn new(
|
||||
window: &sdl3::video::Window,
|
||||
render_scale: u32,
|
||||
) -> Result<Self, Box<dyn std::error::Error>>
|
||||
{
|
||||
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
|
||||
backends: wgpu::Backends::VULKAN,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let surface = unsafe {
|
||||
let target = wgpu::SurfaceTargetUnsafe::from_window(window)?;
|
||||
instance.create_surface_unsafe(target)?
|
||||
};
|
||||
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::HighPerformance,
|
||||
compatible_surface: Some(&surface),
|
||||
force_fallback_adapter: false,
|
||||
})
|
||||
.await
|
||||
.map_err(|_| "Failed to find adapter")?;
|
||||
|
||||
let (device, queue) = adapter
|
||||
.request_device(&wgpu::DeviceDescriptor::default())
|
||||
.await?;
|
||||
|
||||
let size = window.size();
|
||||
let config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: surface.get_capabilities(&adapter).formats[0],
|
||||
width: size.0,
|
||||
height: size.1,
|
||||
present_mode: wgpu::PresentMode::Fifo,
|
||||
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
||||
view_formats: vec![],
|
||||
desired_maximum_frame_latency: 2,
|
||||
};
|
||||
|
||||
surface.configure(&device, &config);
|
||||
|
||||
let low_res_width = config.width / render_scale;
|
||||
let low_res_height = config.height / render_scale;
|
||||
|
||||
let framebuffer =
|
||||
LowResFramebuffer::new(&device, low_res_width, low_res_height, config.format);
|
||||
|
||||
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("Uniform Buffer"),
|
||||
size: std::mem::size_of::<Uniforms>() as wgpu::BufferAddress,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let dither_textures = match DitherTextures::load_octaves(&device, &queue)
|
||||
{
|
||||
Ok(textures) =>
|
||||
{
|
||||
println!("Loaded dither textures successfully");
|
||||
Some(textures)
|
||||
}
|
||||
Err(e) =>
|
||||
{
|
||||
eprintln!(
|
||||
"Warning: Could not load dither textures: {}. Rendering may look incorrect.",
|
||||
e
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let flowmap_texture =
|
||||
match FlowmapTexture::load(&device, &queue, "textures/terrain_flowmap.exr")
|
||||
{
|
||||
Ok(texture) =>
|
||||
{
|
||||
println!("Loaded terrain flowmap successfully");
|
||||
Some(texture)
|
||||
}
|
||||
Err(e) =>
|
||||
{
|
||||
eprintln!(
|
||||
"Warning: Could not load terrain flowmap: {}. Path lighting will not work.",
|
||||
e
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let blue_noise_data = image::open("textures/blue_noise.png")
|
||||
.expect("Failed to load blue noise texture")
|
||||
.to_luma8();
|
||||
let blue_noise_size = blue_noise_data.dimensions();
|
||||
|
||||
let blue_noise_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("Blue Noise"),
|
||||
size: wgpu::Extent3d {
|
||||
width: blue_noise_size.0,
|
||||
height: blue_noise_size.1,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::R8Unorm,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
queue.write_texture(
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture: &blue_noise_texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
&blue_noise_data,
|
||||
wgpu::TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(blue_noise_size.0),
|
||||
rows_per_image: Some(blue_noise_size.1),
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: blue_noise_size.0,
|
||||
height: blue_noise_size.1,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
|
||||
let blue_noise_view =
|
||||
blue_noise_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let blue_noise_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("Blue Noise Sampler"),
|
||||
address_mode_u: wgpu::AddressMode::Repeat,
|
||||
address_mode_v: wgpu::AddressMode::Repeat,
|
||||
mag_filter: wgpu::FilterMode::Nearest,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let shadow_map_size = 4096;
|
||||
let shadow_map_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("Shadow Map"),
|
||||
size: wgpu::Extent3d {
|
||||
width: shadow_map_size,
|
||||
height: shadow_map_size,
|
||||
depth_or_array_layers: MAX_SPOTLIGHTS as u32,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
let shadow_map_view = shadow_map_texture.create_view(&wgpu::TextureViewDescriptor {
|
||||
dimension: Some(wgpu::TextureViewDimension::D2Array),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let shadow_map_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("Shadow Map Sampler"),
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
compare: Some(wgpu::CompareFunction::LessEqual),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let dummy_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("Dummy Snow Light"),
|
||||
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::R16Float,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
queue.write_texture(
|
||||
wgpu::TexelCopyTextureInfo {
|
||||
texture: &dummy_texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
&[0u8; 2],
|
||||
wgpu::TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(2),
|
||||
rows_per_image: Some(1),
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: 1,
|
||||
height: 1,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
let dummy_snow_light_view =
|
||||
dummy_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let dummy_snow_light_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("Snow Light Sampler"),
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("Bind Group Layout"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX | 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::Depth,
|
||||
view_dimension: wgpu::TextureViewDimension::D2Array,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 3,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||
view_dimension: wgpu::TextureViewDimension::D2Array,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 4,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 5,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 6,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 7,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 8,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 9,
|
||||
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: 10,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let bind_group = Self::create_bind_group_from_parts(
|
||||
&device,
|
||||
&bind_group_layout,
|
||||
&uniform_buffer,
|
||||
&shadow_map_view,
|
||||
&shadow_map_sampler,
|
||||
dither_textures.as_ref().expect("Dither textures required"),
|
||||
flowmap_texture.as_ref().expect("Flowmap texture required"),
|
||||
&blue_noise_view,
|
||||
&blue_noise_sampler,
|
||||
&dummy_snow_light_view,
|
||||
&dummy_snow_light_sampler,
|
||||
);
|
||||
|
||||
let standard_pipeline = create_main_pipeline(
|
||||
&device,
|
||||
config.format,
|
||||
&bind_group_layout,
|
||||
wgpu::DepthBiasState::default(),
|
||||
"Standard Pipeline",
|
||||
);
|
||||
|
||||
let snow_clipmap_pipeline =
|
||||
create_snow_clipmap_pipeline(&device, config.format, &bind_group_layout);
|
||||
|
||||
let (quad_vb, quad_ib, quad_num_indices) = create_fullscreen_quad(&device);
|
||||
|
||||
let blit_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("Blit 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 blit_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("Blit Bind Group"),
|
||||
layout: &blit_bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&framebuffer.view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&framebuffer.sampler),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let blit_pipeline = create_blit_pipeline(&device, config.format, &blit_bind_group_layout);
|
||||
|
||||
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::VERTEX | wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
device,
|
||||
queue,
|
||||
surface,
|
||||
config,
|
||||
framebuffer,
|
||||
standard_pipeline,
|
||||
snow_clipmap_pipeline,
|
||||
uniform_buffer,
|
||||
bind_group_layout,
|
||||
bind_group,
|
||||
quad_vb,
|
||||
quad_ib,
|
||||
quad_num_indices,
|
||||
blit_pipeline,
|
||||
blit_bind_group,
|
||||
shadow_pipeline: None,
|
||||
shadow_bind_group_layout,
|
||||
shadow_bind_group: None,
|
||||
terrain_height_scale: 10.0,
|
||||
spotlights: vec![Spotlight::new(
|
||||
glam::Vec3::new(0.0, 50.0, 0.0),
|
||||
glam::Vec3::new(-0.5, -1.0, 1.0).normalize(),
|
||||
0.4,
|
||||
1.0,
|
||||
100.0,
|
||||
)],
|
||||
shadow_bias: 0.005,
|
||||
shadow_map_texture,
|
||||
shadow_map_view,
|
||||
shadow_map_sampler,
|
||||
dither_textures,
|
||||
flowmap_texture,
|
||||
blue_noise_view,
|
||||
blue_noise_sampler,
|
||||
dummy_snow_light_view,
|
||||
dummy_snow_light_sampler,
|
||||
snow_light_accumulation: None,
|
||||
snow_light_bound: false,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&mut self,
|
||||
view: &glam::Mat4,
|
||||
projection: &glam::Mat4,
|
||||
camera_position: glam::Vec3,
|
||||
player_position: glam::Vec3,
|
||||
draw_calls: &[DrawCall],
|
||||
time: f32,
|
||||
delta_time: f32,
|
||||
)
|
||||
{
|
||||
let light_view_projections = self.calculate_light_view_projections();
|
||||
|
||||
if let Some(ref mut snow_light_accum) = self.snow_light_accumulation
|
||||
{
|
||||
let mut encoder = self
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Snow Light Accumulation Encoder"),
|
||||
});
|
||||
|
||||
snow_light_accum.render(
|
||||
&mut encoder,
|
||||
&self.queue,
|
||||
&self.spotlights,
|
||||
delta_time,
|
||||
&light_view_projections,
|
||||
self.shadow_bias,
|
||||
self.terrain_height_scale,
|
||||
);
|
||||
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
}
|
||||
|
||||
if self.snow_light_accumulation.is_some() && !self.snow_light_bound
|
||||
{
|
||||
self.rebuild_bind_group_with_snow_light();
|
||||
self.snow_light_bound = true;
|
||||
}
|
||||
|
||||
self.render_shadow_pass(draw_calls, &light_view_projections, player_position, time);
|
||||
|
||||
let mut encoder = self
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Render Encoder"),
|
||||
});
|
||||
|
||||
for (i, draw_call) in draw_calls.iter().enumerate()
|
||||
{
|
||||
let uniforms = Uniforms::new(
|
||||
draw_call.model,
|
||||
*view,
|
||||
*projection,
|
||||
&light_view_projections,
|
||||
camera_position,
|
||||
player_position,
|
||||
self.terrain_height_scale,
|
||||
time,
|
||||
self.shadow_bias,
|
||||
draw_call.tile_scale,
|
||||
draw_call.enable_dissolve,
|
||||
draw_call.enable_snow_light,
|
||||
&self.spotlights,
|
||||
);
|
||||
self.queue
|
||||
.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
|
||||
|
||||
{
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("3D Render Pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &self.framebuffer.view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: if i == 0
|
||||
{
|
||||
wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: 0.2,
|
||||
g: 0.2,
|
||||
b: 0.2,
|
||||
a: 1.0,
|
||||
})
|
||||
}
|
||||
else
|
||||
{
|
||||
wgpu::LoadOp::Load
|
||||
},
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
depth_slice: None,
|
||||
})],
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.framebuffer.depth_view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: if i == 0
|
||||
{
|
||||
wgpu::LoadOp::Clear(1.0)
|
||||
}
|
||||
else
|
||||
{
|
||||
wgpu::LoadOp::Load
|
||||
},
|
||||
store: wgpu::StoreOp::Store,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
|
||||
let pipeline = match draw_call.pipeline
|
||||
{
|
||||
Pipeline::Standard => &self.standard_pipeline,
|
||||
Pipeline::SnowClipmap => &self.snow_clipmap_pipeline,
|
||||
};
|
||||
render_pass.set_pipeline(pipeline);
|
||||
render_pass.set_bind_group(0, &self.bind_group, &[]);
|
||||
|
||||
if let Some(ref displacement_bind_group) = draw_call.displacement_bind_group
|
||||
{
|
||||
render_pass.set_bind_group(1, displacement_bind_group, &[]);
|
||||
}
|
||||
|
||||
render_pass.set_vertex_buffer(0, draw_call.vertex_buffer.slice(..));
|
||||
|
||||
if let Some(ref instance_buffer) = draw_call.instance_buffer
|
||||
{
|
||||
render_pass.set_vertex_buffer(1, instance_buffer.slice(..));
|
||||
}
|
||||
|
||||
render_pass
|
||||
.set_index_buffer(draw_call.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
|
||||
render_pass.draw_indexed(0..draw_call.num_indices, 0, 0..draw_call.num_instances);
|
||||
}
|
||||
}
|
||||
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
|
||||
let frame = match self.surface.get_current_texture()
|
||||
{
|
||||
Ok(frame) => frame,
|
||||
Err(_) =>
|
||||
{
|
||||
self.surface.configure(&self.device, &self.config);
|
||||
self.surface
|
||||
.get_current_texture()
|
||||
.expect("Failed to acquire next surface texture")
|
||||
}
|
||||
};
|
||||
|
||||
let screen_view = frame
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let mut blit_encoder =
|
||||
self.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Blit Encoder"),
|
||||
});
|
||||
|
||||
{
|
||||
let mut blit_pass = blit_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Blit Pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &screen_view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
depth_slice: None,
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
|
||||
blit_pass.set_pipeline(&self.blit_pipeline);
|
||||
blit_pass.set_bind_group(0, &self.blit_bind_group, &[]);
|
||||
blit_pass.set_vertex_buffer(0, self.quad_vb.slice(..));
|
||||
blit_pass.set_index_buffer(self.quad_ib.slice(..), wgpu::IndexFormat::Uint16);
|
||||
blit_pass.draw_indexed(0..self.quad_num_indices, 0, 0..1);
|
||||
}
|
||||
|
||||
self.queue.submit(std::iter::once(blit_encoder.finish()));
|
||||
frame.present();
|
||||
}
|
||||
|
||||
pub fn render_scale(&self) -> (u32, u32)
|
||||
{
|
||||
(
|
||||
self.config.width / self.framebuffer.width,
|
||||
self.config.height / self.framebuffer.height,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_terrain_data(&mut self)
|
||||
{
|
||||
let shadow_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("Shadow Bind Group"),
|
||||
layout: &self.shadow_bind_group_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: self.uniform_buffer.as_entire_binding(),
|
||||
}],
|
||||
});
|
||||
|
||||
let shadow_pipeline =
|
||||
pipeline::create_shadow_pipeline(&self.device, &self.shadow_bind_group_layout);
|
||||
|
||||
self.shadow_bind_group = Some(shadow_bind_group);
|
||||
self.shadow_pipeline = Some(shadow_pipeline);
|
||||
self.terrain_height_scale = 1.0;
|
||||
}
|
||||
|
||||
pub fn init_snow_light_accumulation(&mut self, terrain_min: glam::Vec2, terrain_max: glam::Vec2)
|
||||
{
|
||||
let snow_light_accumulation = crate::snow_light::SnowLightAccumulation::new(
|
||||
&self.device,
|
||||
terrain_min,
|
||||
terrain_max,
|
||||
512,
|
||||
);
|
||||
|
||||
self.snow_light_accumulation = Some(snow_light_accumulation);
|
||||
}
|
||||
|
||||
pub fn set_snow_depth(&mut self, snow_depth_view: &wgpu::TextureView)
|
||||
{
|
||||
println!("set_snow_depth() called");
|
||||
if let Some(ref mut snow_light_accum) = self.snow_light_accumulation
|
||||
{
|
||||
println!("Snow light accumulation exists, setting up bind groups");
|
||||
let snow_depth_sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("Snow Depth Sampler"),
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Nearest,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
match crate::texture::HeightmapTexture::load(
|
||||
&self.device,
|
||||
&self.queue,
|
||||
"textures/terrain_heightmap.exr",
|
||||
)
|
||||
{
|
||||
Ok(heightmap) =>
|
||||
{
|
||||
snow_light_accum.set_heightmap(
|
||||
&self.device,
|
||||
&heightmap.view,
|
||||
&heightmap.sampler,
|
||||
&self.shadow_map_view,
|
||||
&self.shadow_map_sampler,
|
||||
snow_depth_view,
|
||||
&snow_depth_sampler,
|
||||
);
|
||||
}
|
||||
Err(e) =>
|
||||
{
|
||||
eprintln!("Failed to load heightmap for snow depth setup: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn aspect_ratio(&self) -> f32
|
||||
{
|
||||
self.config.width as f32 / self.config.height as f32
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static GLOBAL_RENDERER: RefCell<Option<Renderer>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
pub fn init(renderer: Renderer)
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| *r.borrow_mut() = Some(renderer));
|
||||
}
|
||||
|
||||
pub fn with_device<F, R>(f: F) -> R
|
||||
where
|
||||
F: FnOnce(&wgpu::Device) -> R,
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let renderer = r.borrow();
|
||||
let renderer = renderer.as_ref().expect("Renderer not set");
|
||||
f(&renderer.device)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_queue<F, R>(f: F) -> R
|
||||
where
|
||||
F: FnOnce(&wgpu::Queue) -> R,
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let renderer = r.borrow();
|
||||
let renderer = renderer.as_ref().expect("Renderer not set");
|
||||
f(&renderer.queue)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_terrain_data()
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let mut renderer = r.borrow_mut();
|
||||
let renderer = renderer.as_mut().expect("Renderer not set");
|
||||
renderer.set_terrain_data();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn init_snow_light_accumulation(terrain_min: glam::Vec2, terrain_max: glam::Vec2)
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let mut renderer = r.borrow_mut();
|
||||
let renderer = renderer.as_mut().expect("Renderer not set");
|
||||
renderer.init_snow_light_accumulation(terrain_min, terrain_max);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_snow_depth(snow_depth_view: &wgpu::TextureView)
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let mut renderer = r.borrow_mut();
|
||||
let renderer = renderer.as_mut().expect("Renderer not set");
|
||||
renderer.set_snow_depth(snow_depth_view);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn aspect_ratio() -> f32
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let renderer = r.borrow();
|
||||
let renderer = renderer.as_ref().expect("Renderer not set");
|
||||
renderer.aspect_ratio()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
view: &glam::Mat4,
|
||||
projection: &glam::Mat4,
|
||||
camera_position: glam::Vec3,
|
||||
player_position: glam::Vec3,
|
||||
draw_calls: &[DrawCall],
|
||||
time: f32,
|
||||
delta_time: f32,
|
||||
)
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let mut renderer = r.borrow_mut();
|
||||
let renderer = renderer.as_mut().expect("Renderer not set");
|
||||
renderer.render(
|
||||
view,
|
||||
projection,
|
||||
camera_position,
|
||||
player_position,
|
||||
draw_calls,
|
||||
time,
|
||||
delta_time,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_shadow_bias(bias: f32)
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let mut renderer = r.borrow_mut();
|
||||
let renderer = renderer.as_mut().expect("Renderer not set");
|
||||
renderer.shadow_bias = bias;
|
||||
});
|
||||
}
|
||||
|
||||
pub fn update_spotlights(spotlights: Vec<Spotlight>)
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let mut renderer = r.borrow_mut();
|
||||
let renderer = renderer.as_mut().expect("Renderer not set");
|
||||
renderer.spotlights = spotlights;
|
||||
});
|
||||
}
|
||||
252
src/render/pipeline.rs
Normal file
252
src/render/pipeline.rs
Normal file
@@ -0,0 +1,252 @@
|
||||
use wesl::Wesl;
|
||||
|
||||
pub fn create_shadow_pipeline(
|
||||
device: &wgpu::Device,
|
||||
bind_group_layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::RenderPipeline
|
||||
{
|
||||
let compiler = Wesl::new("src/shaders");
|
||||
let shader_source = compiler
|
||||
.compile(&"package::shadow".parse().unwrap())
|
||||
.inspect_err(|e| eprintln!("WESL error: {e}"))
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Shadow Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
|
||||
});
|
||||
|
||||
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Shadow Pipeline Layout"),
|
||||
bind_group_layouts: &[bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Shadow Pipeline"),
|
||||
layout: Some(&render_pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[
|
||||
crate::loaders::mesh::Vertex::desc(),
|
||||
crate::loaders::mesh::InstanceRaw::desc(),
|
||||
],
|
||||
compilation_options: Default::default(),
|
||||
},
|
||||
fragment: None,
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: Some(wgpu::Face::Back),
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: wgpu::CompareFunction::Less,
|
||||
stencil: wgpu::StencilState::default(),
|
||||
bias: wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
cache: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_main_pipeline(
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
bind_group_layout: &wgpu::BindGroupLayout,
|
||||
depth_bias: wgpu::DepthBiasState,
|
||||
label: &str,
|
||||
) -> wgpu::RenderPipeline
|
||||
{
|
||||
let compiler = Wesl::new("src/shaders");
|
||||
let shader_source = compiler
|
||||
.compile(&"package::main".parse().unwrap())
|
||||
.inspect_err(|e| eprintln!("WESL error: {e}"))
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some(label),
|
||||
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
|
||||
});
|
||||
|
||||
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Main Pipeline Layout"),
|
||||
bind_group_layouts: &[bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some(label),
|
||||
layout: Some(&render_pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[
|
||||
crate::loaders::mesh::Vertex::desc(),
|
||||
crate::loaders::mesh::InstanceRaw::desc(),
|
||||
],
|
||||
compilation_options: Default::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format,
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: Default::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: Some(wgpu::Face::Back),
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: wgpu::CompareFunction::Less,
|
||||
stencil: wgpu::StencilState::default(),
|
||||
bias: depth_bias,
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
cache: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_snow_clipmap_pipeline(
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
main_bind_group_layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::RenderPipeline
|
||||
{
|
||||
let compiler = Wesl::new("src/shaders");
|
||||
let shader_source = compiler
|
||||
.compile(&"package::main".parse().unwrap())
|
||||
.inspect_err(|e| eprintln!("WESL error: {e}"))
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Snow Clipmap Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
|
||||
});
|
||||
|
||||
let displacement_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("Snow Displacement Bind Group Layout"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 3,
|
||||
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Snow Clipmap Pipeline Layout"),
|
||||
bind_group_layouts: &[main_bind_group_layout, &displacement_bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Snow Clipmap Pipeline"),
|
||||
layout: Some(&render_pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_snow_main"),
|
||||
buffers: &[
|
||||
crate::loaders::mesh::Vertex::desc(),
|
||||
crate::loaders::mesh::InstanceRaw::desc(),
|
||||
],
|
||||
compilation_options: Default::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format,
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: Default::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: Some(wgpu::Face::Back),
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: wgpu::CompareFunction::Less,
|
||||
stencil: wgpu::StencilState::default(),
|
||||
bias: wgpu::DepthBiasState {
|
||||
constant: -1,
|
||||
slope_scale: -1.0,
|
||||
clamp: 0.0,
|
||||
},
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
cache: None,
|
||||
})
|
||||
}
|
||||
139
src/render/shadow.rs
Normal file
139
src/render/shadow.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
use glam::Mat4;
|
||||
|
||||
use super::types::{DrawCall, Uniforms, MAX_SPOTLIGHTS};
|
||||
use super::Renderer;
|
||||
|
||||
impl Renderer
|
||||
{
|
||||
pub(super) fn render_shadow_pass(
|
||||
&mut self,
|
||||
draw_calls: &[DrawCall],
|
||||
light_view_projections: &[Mat4],
|
||||
player_position: glam::Vec3,
|
||||
time: f32,
|
||||
)
|
||||
{
|
||||
let spotlight_count = self.spotlights.len().min(MAX_SPOTLIGHTS);
|
||||
|
||||
for layer in 0..spotlight_count
|
||||
{
|
||||
let shadow_uniforms = Uniforms::new(
|
||||
Mat4::IDENTITY,
|
||||
Mat4::IDENTITY,
|
||||
light_view_projections[layer],
|
||||
light_view_projections,
|
||||
glam::Vec3::ZERO,
|
||||
player_position,
|
||||
self.terrain_height_scale,
|
||||
time,
|
||||
self.shadow_bias,
|
||||
2.0,
|
||||
false,
|
||||
false,
|
||||
&self.spotlights,
|
||||
);
|
||||
self.queue.write_buffer(
|
||||
&self.uniform_buffer,
|
||||
0,
|
||||
bytemuck::cast_slice(&[shadow_uniforms]),
|
||||
);
|
||||
|
||||
let layer_view = self
|
||||
.shadow_map_texture
|
||||
.create_view(&wgpu::TextureViewDescriptor {
|
||||
dimension: Some(wgpu::TextureViewDimension::D2),
|
||||
base_array_layer: layer as u32,
|
||||
array_layer_count: Some(1),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mut encoder = self
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Shadow Pass Encoder"),
|
||||
});
|
||||
|
||||
{
|
||||
let mut shadow_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Shadow Pass"),
|
||||
color_attachments: &[],
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &layer_view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(1.0),
|
||||
store: wgpu::StoreOp::Store,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
|
||||
shadow_pass.set_pipeline(
|
||||
self.shadow_pipeline
|
||||
.as_ref()
|
||||
.expect("shadow pipeline missing"),
|
||||
);
|
||||
shadow_pass.set_bind_group(
|
||||
0,
|
||||
self.shadow_bind_group
|
||||
.as_ref()
|
||||
.expect("shadow bind group missing"),
|
||||
&[],
|
||||
);
|
||||
|
||||
for draw_call in draw_calls.iter()
|
||||
{
|
||||
shadow_pass.set_vertex_buffer(0, draw_call.vertex_buffer.slice(..));
|
||||
|
||||
if let Some(ref instance_buffer) = draw_call.instance_buffer
|
||||
{
|
||||
shadow_pass.set_vertex_buffer(1, instance_buffer.slice(..));
|
||||
}
|
||||
|
||||
shadow_pass.set_index_buffer(
|
||||
draw_call.index_buffer.slice(..),
|
||||
wgpu::IndexFormat::Uint32,
|
||||
);
|
||||
shadow_pass.draw_indexed(
|
||||
0..draw_call.num_indices,
|
||||
0,
|
||||
0..draw_call.num_instances,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn calculate_light_view_projections(&self) -> Vec<Mat4>
|
||||
{
|
||||
self.spotlights
|
||||
.iter()
|
||||
.take(MAX_SPOTLIGHTS)
|
||||
.map(|spotlight| {
|
||||
let light_position = spotlight.position;
|
||||
let light_target = spotlight.position + spotlight.direction;
|
||||
|
||||
let up_vector = if spotlight.direction.y.abs() > 0.99
|
||||
{
|
||||
glam::Vec3::Z
|
||||
}
|
||||
else
|
||||
{
|
||||
glam::Vec3::Y
|
||||
};
|
||||
|
||||
let light_view = Mat4::look_at_rh(light_position, light_target, up_vector);
|
||||
|
||||
let fov = spotlight.outer_angle * 2.0;
|
||||
let near = 0.1;
|
||||
let far = 150.0;
|
||||
let light_projection = Mat4::perspective_rh(fov, 1.0, near, far);
|
||||
|
||||
light_projection * light_view
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
157
src/render/types.rs
Normal file
157
src/render/types.rs
Normal file
@@ -0,0 +1,157 @@
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use glam::Mat4;
|
||||
|
||||
pub const MAX_SPOTLIGHTS: usize = 4;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Pod, Zeroable, Default)]
|
||||
pub struct SpotlightRaw
|
||||
{
|
||||
pub position: [f32; 3],
|
||||
pub inner_angle: f32,
|
||||
pub direction: [f32; 3],
|
||||
pub outer_angle: f32,
|
||||
pub range: f32,
|
||||
pub _padding: [f32; 7],
|
||||
}
|
||||
|
||||
pub struct Spotlight
|
||||
{
|
||||
pub position: glam::Vec3,
|
||||
pub direction: glam::Vec3,
|
||||
pub inner_angle: f32,
|
||||
pub outer_angle: f32,
|
||||
pub range: f32,
|
||||
}
|
||||
|
||||
impl Spotlight
|
||||
{
|
||||
pub fn new(
|
||||
position: glam::Vec3,
|
||||
direction: glam::Vec3,
|
||||
inner_angle: f32,
|
||||
outer_angle: f32,
|
||||
range: f32,
|
||||
) -> Self
|
||||
{
|
||||
Self {
|
||||
position,
|
||||
direction: direction.normalize(),
|
||||
inner_angle,
|
||||
outer_angle,
|
||||
range,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_raw(&self) -> SpotlightRaw
|
||||
{
|
||||
SpotlightRaw {
|
||||
position: self.position.to_array(),
|
||||
inner_angle: self.inner_angle,
|
||||
direction: self.direction.to_array(),
|
||||
outer_angle: self.outer_angle,
|
||||
range: self.range,
|
||||
_padding: [0.0; 7],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
pub struct Uniforms
|
||||
{
|
||||
pub model: [[f32; 4]; 4],
|
||||
pub view: [[f32; 4]; 4],
|
||||
pub projection: [[f32; 4]; 4],
|
||||
pub light_view_projections: [[[f32; 4]; 4]; MAX_SPOTLIGHTS],
|
||||
pub camera_position: [f32; 3],
|
||||
pub height_scale: f32,
|
||||
pub player_position: [f32; 3],
|
||||
pub time: f32,
|
||||
pub shadow_bias: f32,
|
||||
pub tile_scale: f32,
|
||||
pub enable_dissolve: u32,
|
||||
pub enable_snow_light: u32,
|
||||
pub spotlight_count: u32,
|
||||
pub _padding1: u32,
|
||||
pub _padding2: u32,
|
||||
pub _padding3: u32,
|
||||
pub spotlights: [SpotlightRaw; MAX_SPOTLIGHTS],
|
||||
}
|
||||
|
||||
impl Uniforms
|
||||
{
|
||||
pub fn new(
|
||||
model: Mat4,
|
||||
view: Mat4,
|
||||
projection: Mat4,
|
||||
light_view_projections: &[Mat4],
|
||||
camera_position: glam::Vec3,
|
||||
player_position: glam::Vec3,
|
||||
height_scale: f32,
|
||||
time: f32,
|
||||
shadow_bias: f32,
|
||||
tile_scale: f32,
|
||||
enable_dissolve: bool,
|
||||
enable_snow_light: bool,
|
||||
spotlights: &[Spotlight],
|
||||
) -> Self
|
||||
{
|
||||
let mut spotlight_array = [SpotlightRaw::default(); MAX_SPOTLIGHTS];
|
||||
for (i, spotlight) in spotlights.iter().take(MAX_SPOTLIGHTS).enumerate()
|
||||
{
|
||||
spotlight_array[i] = spotlight.to_raw();
|
||||
}
|
||||
|
||||
let mut lvp_array = [[[0.0f32; 4]; 4]; MAX_SPOTLIGHTS];
|
||||
for (i, mat) in light_view_projections
|
||||
.iter()
|
||||
.take(MAX_SPOTLIGHTS)
|
||||
.enumerate()
|
||||
{
|
||||
lvp_array[i] = mat.to_cols_array_2d();
|
||||
}
|
||||
|
||||
Self {
|
||||
model: model.to_cols_array_2d(),
|
||||
view: view.to_cols_array_2d(),
|
||||
projection: projection.to_cols_array_2d(),
|
||||
light_view_projections: lvp_array,
|
||||
camera_position: camera_position.to_array(),
|
||||
height_scale,
|
||||
player_position: player_position.to_array(),
|
||||
time,
|
||||
shadow_bias,
|
||||
tile_scale,
|
||||
enable_dissolve: if enable_dissolve { 1 } else { 0 },
|
||||
enable_snow_light: if enable_snow_light { 1 } else { 0 },
|
||||
spotlight_count: spotlights.len().min(MAX_SPOTLIGHTS) as u32,
|
||||
_padding1: 0,
|
||||
_padding2: 0,
|
||||
_padding3: 0,
|
||||
spotlights: spotlight_array,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Pipeline
|
||||
{
|
||||
Standard,
|
||||
SnowClipmap,
|
||||
}
|
||||
|
||||
pub struct DrawCall
|
||||
{
|
||||
pub vertex_buffer: wgpu::Buffer,
|
||||
pub index_buffer: wgpu::Buffer,
|
||||
pub num_indices: u32,
|
||||
pub model: Mat4,
|
||||
pub pipeline: Pipeline,
|
||||
pub instance_buffer: Option<wgpu::Buffer>,
|
||||
pub num_instances: u32,
|
||||
pub tile_scale: f32,
|
||||
pub enable_dissolve: bool,
|
||||
pub enable_snow_light: bool,
|
||||
pub displacement_bind_group: Option<wgpu::BindGroup>,
|
||||
}
|
||||
140
src/shader.rs
140
src/shader.rs
@@ -1,140 +0,0 @@
|
||||
use crate::mesh::{InstanceRaw, Vertex};
|
||||
use wesl::{include_wesl, Wesl};
|
||||
|
||||
pub fn create_render_pipeline(
|
||||
device: &wgpu::Device,
|
||||
config: &wgpu::SurfaceConfiguration,
|
||||
bind_group_layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::RenderPipeline
|
||||
{
|
||||
let compiler = Wesl::new("src/shaders");
|
||||
let shader_source = compiler
|
||||
.compile(&"package::standard".parse().unwrap())
|
||||
.inspect_err(|e| eprintln!("WESL error: {e}"))
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
|
||||
});
|
||||
|
||||
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Render Pipeline Layout"),
|
||||
bind_group_layouts: &[bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Render Pipeline"),
|
||||
layout: Some(&render_pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[Vertex::desc(), InstanceRaw::desc()],
|
||||
compilation_options: Default::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: config.format,
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: Default::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: Some(wgpu::Face::Back),
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: wgpu::CompareFunction::Less,
|
||||
stencil: wgpu::StencilState::default(),
|
||||
bias: wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
cache: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_environment_pipeline(
|
||||
device: &wgpu::Device,
|
||||
config: &wgpu::SurfaceConfiguration,
|
||||
bind_group_layout: &wgpu::BindGroupLayout,
|
||||
) -> wgpu::RenderPipeline
|
||||
{
|
||||
let compiler = Wesl::new("src/shaders");
|
||||
let shader_source = compiler
|
||||
.compile(&"package::environment".parse().unwrap())
|
||||
.inspect_err(|e| eprintln!("WESL error: {e}"))
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Environment Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
|
||||
});
|
||||
|
||||
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Environment Pipeline Layout"),
|
||||
bind_group_layouts: &[bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Environment Pipeline"),
|
||||
layout: Some(&render_pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[Vertex::desc(), InstanceRaw::desc()],
|
||||
compilation_options: Default::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: config.format,
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: Default::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: Some(wgpu::Face::Back),
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: wgpu::CompareFunction::Less,
|
||||
stencil: wgpu::StencilState::default(),
|
||||
bias: wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
cache: None,
|
||||
})
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
import package::shared::{VertexInput, VertexOutput, sample_shadow_map, flowmap_path_lighting_with_shadow, all_spotlights_lighting, uniforms, blue_noise_texture, blue_noise_sampler};
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
|
||||
let instance_model = mat4x4<f32>(
|
||||
input.instance_model_0,
|
||||
input.instance_model_1,
|
||||
input.instance_model_2,
|
||||
input.instance_model_3
|
||||
);
|
||||
|
||||
let world_pos = instance_model * vec4<f32>(input.position, 1.0);
|
||||
output.world_position = world_pos.xyz;
|
||||
output.clip_position = uniforms.projection * uniforms.view * world_pos;
|
||||
|
||||
let normal_matrix = mat3x3<f32>(
|
||||
instance_model[0].xyz,
|
||||
instance_model[1].xyz,
|
||||
instance_model[2].xyz
|
||||
);
|
||||
output.world_normal = normalize(normal_matrix * input.normal);
|
||||
|
||||
output.light_space_position = uniforms.light_view_projection * world_pos;
|
||||
|
||||
let instance_position = vec3<f32>(
|
||||
instance_model[3][0],
|
||||
instance_model[3][1],
|
||||
instance_model[3][2]
|
||||
);
|
||||
|
||||
let to_player = uniforms.player_position - uniforms.camera_position;
|
||||
let distance_to_player = length(to_player);
|
||||
|
||||
var dissolve_amount = 0.0;
|
||||
|
||||
if distance_to_player > 0.01 {
|
||||
let ray_dir = to_player / distance_to_player;
|
||||
let ray_origin = uniforms.camera_position;
|
||||
|
||||
let tree_height = 16.0;
|
||||
let occlusion_radius = 6.5;
|
||||
|
||||
let w = instance_position - ray_origin;
|
||||
let projection_t = dot(w, ray_dir);
|
||||
|
||||
if projection_t > 0.0 && projection_t < distance_to_player {
|
||||
let closest_on_ray = ray_origin + ray_dir * projection_t;
|
||||
|
||||
let diff = closest_on_ray - instance_position;
|
||||
let height_on_trunk = clamp(diff.y, 0.0, tree_height);
|
||||
let closest_on_trunk = instance_position + vec3<f32>(0.0, height_on_trunk, 0.0);
|
||||
|
||||
let perp_distance = length(closest_on_trunk - closest_on_ray);
|
||||
|
||||
if perp_distance < occlusion_radius {
|
||||
let dissolve_t = pow(perp_distance / occlusion_radius, 0.5);
|
||||
dissolve_amount = 1.0 - clamp(dissolve_t, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output.dissolve_amount = dissolve_amount;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
|
||||
|
||||
let debug = 0u;
|
||||
|
||||
if debug == 1u {
|
||||
return vec4<f32>(input.dissolve_amount);
|
||||
}
|
||||
|
||||
if input.dissolve_amount > 0.0 {
|
||||
let screen_pos = input.clip_position.xy;
|
||||
let noise_uv = fract(screen_pos / 128.0);
|
||||
let noise_value = textureSampleLevel(blue_noise_texture, blue_noise_sampler, noise_uv, 0.0).r;
|
||||
|
||||
if noise_value < input.dissolve_amount {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
|
||||
let shadow = sample_shadow_map(input.light_space_position);
|
||||
|
||||
let tile_scale = 4.0;
|
||||
let spotlight_strokes = all_spotlights_lighting(input.world_position, input.clip_position, input.world_normal, tile_scale, shadow);
|
||||
let brightness = spotlight_strokes;
|
||||
|
||||
return vec4<f32>(brightness, brightness, brightness, 1.0);
|
||||
}
|
||||
177
src/shaders/main.wesl
Normal file
177
src/shaders/main.wesl
Normal file
@@ -0,0 +1,177 @@
|
||||
import package::util::{
|
||||
VertexInput,
|
||||
VertexOutput,
|
||||
uniforms,
|
||||
all_spotlights_lighting,
|
||||
blue_noise_texture,
|
||||
blue_noise_sampler,
|
||||
snow_light_texture,
|
||||
snow_light_sampler,
|
||||
TERRAIN_BOUNDS
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
|
||||
let instance_model = mat4x4<f32>(
|
||||
input.instance_model_0,
|
||||
input.instance_model_1,
|
||||
input.instance_model_2,
|
||||
input.instance_model_3
|
||||
);
|
||||
|
||||
let world_pos = instance_model * vec4<f32>(input.position, 1.0);
|
||||
output.world_position = world_pos.xyz;
|
||||
output.clip_position = uniforms.projection * uniforms.view * world_pos;
|
||||
|
||||
let normal_matrix = mat3x3<f32>(
|
||||
instance_model[0].xyz,
|
||||
instance_model[1].xyz,
|
||||
instance_model[2].xyz
|
||||
);
|
||||
output.world_normal = normalize(normal_matrix * input.normal);
|
||||
|
||||
var dissolve_amount = 0.0;
|
||||
|
||||
if uniforms.enable_dissolve == 1u {
|
||||
let instance_position = vec3<f32>(
|
||||
instance_model[3][0],
|
||||
instance_model[3][1],
|
||||
instance_model[3][2]
|
||||
);
|
||||
|
||||
let to_player = uniforms.player_position - uniforms.camera_position;
|
||||
let distance_to_player = length(to_player);
|
||||
|
||||
if distance_to_player > 0.01 {
|
||||
let ray_dir = to_player / distance_to_player;
|
||||
let ray_origin = uniforms.camera_position;
|
||||
|
||||
let tree_height = 16.0;
|
||||
let occlusion_radius = 6.5;
|
||||
|
||||
let w = instance_position - ray_origin;
|
||||
let projection_t = dot(w, ray_dir);
|
||||
|
||||
if projection_t > 0.0 && projection_t < distance_to_player {
|
||||
let closest_on_ray = ray_origin + ray_dir * projection_t;
|
||||
|
||||
let diff = closest_on_ray - instance_position;
|
||||
let height_on_trunk = clamp(diff.y, 0.0, tree_height);
|
||||
let closest_on_trunk = instance_position + vec3<f32>(0.0, height_on_trunk, 0.0);
|
||||
|
||||
let perp_distance = length(closest_on_trunk - closest_on_ray);
|
||||
|
||||
if perp_distance < occlusion_radius {
|
||||
let dissolve_t = pow(perp_distance / occlusion_radius, 0.5);
|
||||
dissolve_amount = 1.0 - clamp(dissolve_t, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output.dissolve_amount = dissolve_amount;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
if uniforms.enable_dissolve == 1u && in.dissolve_amount > 0.0 {
|
||||
let screen_pos = in.clip_position.xy;
|
||||
let noise_uv = fract(screen_pos / 128.0);
|
||||
let noise_value = textureSampleLevel(blue_noise_texture, blue_noise_sampler, noise_uv, 0.0).r;
|
||||
|
||||
if noise_value < in.dissolve_amount {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
|
||||
let tile_scale = uniforms.tile_scale;
|
||||
let spotlight_strokes = all_spotlights_lighting(in.world_position, in.clip_position, in.world_normal, tile_scale);
|
||||
|
||||
var brightness = spotlight_strokes;
|
||||
|
||||
if uniforms.enable_snow_light == 1u {
|
||||
let terrain_uv = (vec2<f32>(in.world_position.x, in.world_position.z) + TERRAIN_BOUNDS * 0.5) / TERRAIN_BOUNDS;
|
||||
let snow_light = textureSample(snow_light_texture, snow_light_sampler, terrain_uv).r;
|
||||
|
||||
if snow_light > 0.05 {
|
||||
let screen_pos = in.clip_position.xy / in.clip_position.w;
|
||||
let blue_noise_uv = screen_pos * 0.5 + 0.5;
|
||||
let blue_noise = textureSample(blue_noise_texture, blue_noise_sampler, blue_noise_uv * 10.0).r;
|
||||
let blue_step = step(blue_noise, snow_light / 30.0);
|
||||
|
||||
brightness = max(brightness, blue_step);
|
||||
}
|
||||
}
|
||||
|
||||
return vec4<f32>(brightness, brightness, brightness, 1.0);
|
||||
}
|
||||
|
||||
@group(1) @binding(0)
|
||||
var heightmap_texture: texture_2d<f32>;
|
||||
|
||||
@group(1) @binding(1)
|
||||
var heightmap_sampler: sampler;
|
||||
|
||||
@group(1) @binding(2)
|
||||
var snow_depth_texture: texture_2d<f32>;
|
||||
|
||||
@group(1) @binding(3)
|
||||
var snow_depth_sampler: sampler;
|
||||
|
||||
fn sample_snow_depth_bilinear(uv: vec2<f32>) -> f32 {
|
||||
let tex_size = vec2<f32>(textureDimensions(snow_depth_texture, 0));
|
||||
let texel_coord = uv * tex_size - 0.5;
|
||||
let base = vec2<i32>(floor(texel_coord));
|
||||
let f = fract(texel_coord);
|
||||
|
||||
let s00 = textureLoad(snow_depth_texture, clamp(base, vec2<i32>(0), vec2<i32>(tex_size) - 1), 0).r;
|
||||
let s10 = textureLoad(snow_depth_texture, clamp(base + vec2<i32>(1, 0), vec2<i32>(0), vec2<i32>(tex_size) - 1), 0).r;
|
||||
let s01 = textureLoad(snow_depth_texture, clamp(base + vec2<i32>(0, 1), vec2<i32>(0), vec2<i32>(tex_size) - 1), 0).r;
|
||||
let s11 = textureLoad(snow_depth_texture, clamp(base + vec2<i32>(1, 1), vec2<i32>(0), vec2<i32>(tex_size) - 1), 0).r;
|
||||
|
||||
return mix(mix(s00, s10, f.x), mix(s01, s11, f.x), f.y);
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_snow_main(in: VertexInput) -> VertexOutput {
|
||||
let instance_model = mat4x4<f32>(
|
||||
in.instance_model_0,
|
||||
in.instance_model_1,
|
||||
in.instance_model_2,
|
||||
in.instance_model_3,
|
||||
);
|
||||
|
||||
let instance_world = instance_model * vec4<f32>(in.position, 1.0);
|
||||
let world_xz = instance_world.xz;
|
||||
let level_bias = instance_world.y;
|
||||
|
||||
let uv = (world_xz + TERRAIN_BOUNDS * 0.5) / TERRAIN_BOUNDS;
|
||||
|
||||
let terrain_height = textureSampleLevel(heightmap_texture, heightmap_sampler, uv, 0.0).r;
|
||||
let snow_depth = sample_snow_depth_bilinear(uv) * 5.0;
|
||||
|
||||
let world_y = terrain_height + snow_depth + 0.2 + level_bias;
|
||||
let world_position = vec3<f32>(world_xz.x, world_y, world_xz.y);
|
||||
|
||||
let texel_size = 1.0 / vec2<f32>(textureDimensions(heightmap_texture, 0));
|
||||
let h_right = textureSampleLevel(heightmap_texture, heightmap_sampler, uv + vec2<f32>(texel_size.x, 0.0), 0.0).r + sample_snow_depth_bilinear(uv + vec2<f32>(texel_size.x, 0.0));
|
||||
let h_left = textureSampleLevel(heightmap_texture, heightmap_sampler, uv - vec2<f32>(texel_size.x, 0.0), 0.0).r + sample_snow_depth_bilinear(uv - vec2<f32>(texel_size.x, 0.0));
|
||||
let h_up = textureSampleLevel(heightmap_texture, heightmap_sampler, uv + vec2<f32>(0.0, texel_size.y), 0.0).r + sample_snow_depth_bilinear(uv + vec2<f32>(0.0, texel_size.y));
|
||||
let h_down = textureSampleLevel(heightmap_texture, heightmap_sampler, uv - vec2<f32>(0.0, texel_size.y), 0.0).r + sample_snow_depth_bilinear(uv - vec2<f32>(0.0, texel_size.y));
|
||||
|
||||
let dx = h_right - h_left;
|
||||
let dz = h_up - h_down;
|
||||
let normal = normalize(vec3<f32>(-dx, 2.0 * texel_size.x * TERRAIN_BOUNDS.x, -dz));
|
||||
|
||||
var out: VertexOutput;
|
||||
out.world_position = world_position;
|
||||
out.world_normal = normal;
|
||||
out.clip_position = uniforms.projection * uniforms.view * vec4<f32>(world_position, 1.0);
|
||||
out.dissolve_amount = in.instance_dissolve;
|
||||
|
||||
return out;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import package::shared::{ VertexInput, uniforms };
|
||||
import package::util::{ VertexInput, uniforms };
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> @builtin(position) vec4<f32> {
|
||||
@@ -10,5 +10,5 @@ fn vs_main(input: VertexInput) -> @builtin(position) vec4<f32> {
|
||||
);
|
||||
|
||||
let world_pos = instance_model * vec4<f32>(input.position, 1.0);
|
||||
return uniforms.light_view_projection * world_pos;
|
||||
return uniforms.projection * world_pos;
|
||||
}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import package::shared::{
|
||||
VertexInput,
|
||||
VertexOutput,
|
||||
uniforms,
|
||||
sample_shadow_map,
|
||||
all_spotlights_lighting,
|
||||
is_in_any_spotlight_light_area,
|
||||
blue_noise_texture,
|
||||
blue_noise_sampler,
|
||||
TERRAIN_BOUNDS,
|
||||
Spotlight
|
||||
};
|
||||
|
||||
@group(1) @binding(0)
|
||||
var persistent_light_texture: texture_2d<f32>;
|
||||
|
||||
@group(1) @binding(1)
|
||||
var persistent_light_sampler: sampler;
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
|
||||
let instance_model = mat4x4<f32>(
|
||||
input.instance_model_0,
|
||||
input.instance_model_1,
|
||||
input.instance_model_2,
|
||||
input.instance_model_3
|
||||
);
|
||||
|
||||
let world_pos = instance_model * vec4<f32>(input.position, 1.0);
|
||||
output.world_position = world_pos.xyz;
|
||||
output.clip_position = uniforms.projection * uniforms.view * world_pos;
|
||||
|
||||
let normal_matrix = mat3x3<f32>(
|
||||
instance_model[0].xyz,
|
||||
instance_model[1].xyz,
|
||||
instance_model[2].xyz
|
||||
);
|
||||
output.world_normal = normalize(normal_matrix * input.normal);
|
||||
|
||||
output.light_space_position = uniforms.light_view_projection * world_pos;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let shadow = sample_shadow_map(in.light_space_position);
|
||||
|
||||
let tile_scale = 2.0;
|
||||
let in_spotlight_light_area = is_in_any_spotlight_light_area(in.world_position, in.world_normal, tile_scale, shadow);
|
||||
let spotlight_strokes = all_spotlights_lighting(in.world_position, in.clip_position, in.world_normal, tile_scale, shadow);
|
||||
|
||||
var brightness = spotlight_strokes;
|
||||
|
||||
if !in_spotlight_light_area {
|
||||
let terrain_uv = (vec2<f32>(in.world_position.x, in.world_position.z) + TERRAIN_BOUNDS * 0.5) / TERRAIN_BOUNDS;
|
||||
let persistent_light = textureSample(persistent_light_texture, persistent_light_sampler, terrain_uv).r;
|
||||
|
||||
if persistent_light > 0.05 {
|
||||
let screen_pos = in.clip_position.xy / in.clip_position.w;
|
||||
let blue_noise_uv = screen_pos * 0.5 + 0.5;
|
||||
let blue_noise = textureSample(blue_noise_texture, blue_noise_sampler, blue_noise_uv * 10.0).r;
|
||||
let blue_step = step(blue_noise, persistent_light / 30.0);
|
||||
|
||||
brightness = max(brightness, blue_step);
|
||||
}
|
||||
}
|
||||
|
||||
return vec4<f32>(brightness, brightness, brightness, 1.0);
|
||||
}
|
||||
@@ -9,6 +9,10 @@ struct DeformParams {
|
||||
position_z: f32,
|
||||
radius: f32,
|
||||
depth: f32,
|
||||
terrain_width: f32,
|
||||
terrain_height: f32,
|
||||
_padding1: f32,
|
||||
_padding2: f32,
|
||||
}
|
||||
|
||||
@compute @workgroup_size(16, 16, 1)
|
||||
@@ -21,7 +25,7 @@ fn deform(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
||||
|
||||
let coords = vec2<i32>(i32(global_id.x), i32(global_id.y));
|
||||
|
||||
let terrain_size = vec2<f32>(1000.0, 1000.0);
|
||||
let terrain_size = vec2<f32>(params.terrain_width, params.terrain_height);
|
||||
let half_size = terrain_size / 2.0;
|
||||
|
||||
let uv = vec2<f32>(f32(global_id.x) / f32(texture_size.x), f32(global_id.y) / f32(texture_size.y));
|
||||
@@ -34,9 +38,7 @@ fn deform(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
||||
let current_depth = textureLoad(snow_depth, coords).r;
|
||||
|
||||
let falloff = 1.0 - (distance / params.radius);
|
||||
let falloff_smooth = falloff * falloff;
|
||||
|
||||
let deform_amount = params.depth * falloff_smooth;
|
||||
let deform_amount = params.depth * falloff * falloff;
|
||||
let new_depth = max(0.0, current_depth - deform_amount);
|
||||
|
||||
textureStore(snow_depth, coords, vec4<f32>(new_depth, 0.0, 0.0, 0.0));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import package::shared::{Spotlight, MAX_SPOTLIGHTS, calculate_spotlight_data};
|
||||
import package::util::{Spotlight, MAX_SPOTLIGHTS, calculate_spotlight_data};
|
||||
|
||||
struct AccumulationUniforms {
|
||||
terrain_min_xz: vec2<f32>,
|
||||
@@ -7,12 +7,12 @@ struct AccumulationUniforms {
|
||||
delta_time: f32,
|
||||
spotlight_count: u32,
|
||||
_padding: u32,
|
||||
light_view_projection: mat4x4<f32>,
|
||||
light_view_projections: array<mat4x4<f32>, MAX_SPOTLIGHTS>,
|
||||
shadow_bias: f32,
|
||||
terrain_height_scale: f32,
|
||||
_padding3: f32,
|
||||
_padding4: f32,
|
||||
spotlights: array<Spotlight, 4>,
|
||||
spotlights: array<Spotlight, MAX_SPOTLIGHTS>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0)
|
||||
@@ -31,7 +31,7 @@ var heightmap: texture_2d<f32>;
|
||||
var heightmap_sampler: sampler;
|
||||
|
||||
@group(0) @binding(5)
|
||||
var shadow_map: texture_depth_2d;
|
||||
var shadow_map: texture_depth_2d_array;
|
||||
|
||||
@group(0) @binding(6)
|
||||
var shadow_sampler: sampler_comparison;
|
||||
@@ -59,7 +59,8 @@ fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
|
||||
return out;
|
||||
}
|
||||
|
||||
fn sample_shadow_map(light_space_pos: vec4<f32>) -> f32 {
|
||||
fn sample_shadow_map_local(world_pos: vec3<f32>, spotlight_index: u32) -> f32 {
|
||||
let light_space_pos = uniforms.light_view_projections[spotlight_index] * vec4<f32>(world_pos, 1.0);
|
||||
let proj_coords = light_space_pos.xyz / light_space_pos.w;
|
||||
let ndc_coords = proj_coords * vec3<f32>(0.5, -0.5, 1.0) + vec3<f32>(0.5, 0.5, 0.0);
|
||||
|
||||
@@ -70,7 +71,7 @@ fn sample_shadow_map(light_space_pos: vec4<f32>) -> f32 {
|
||||
}
|
||||
|
||||
let depth = ndc_coords.z - uniforms.shadow_bias;
|
||||
let shadow = textureSampleCompare(shadow_map, shadow_sampler, ndc_coords.xy, depth);
|
||||
let shadow = textureSampleCompare(shadow_map, shadow_sampler, ndc_coords.xy, spotlight_index, depth);
|
||||
|
||||
return shadow;
|
||||
}
|
||||
@@ -85,15 +86,13 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let snow_surface_height = terrain_height + depth;
|
||||
let snow_surface_pos = vec3<f32>(world_xz.x, snow_surface_height, world_xz.y);
|
||||
|
||||
let light_space_position = uniforms.light_view_projection * vec4<f32>(snow_surface_pos, 1.0);
|
||||
let shadow = sample_shadow_map(light_space_position);
|
||||
|
||||
var current_light = 0.0;
|
||||
if shadow > 0.0 {
|
||||
let tile_scale = 2.0;
|
||||
let surface_normal = vec3<f32>(0.0, 1.0, 0.0);
|
||||
let tile_scale = 2.0;
|
||||
let surface_normal = vec3<f32>(0.0, 1.0, 0.0);
|
||||
|
||||
for (var i = 0u; i < uniforms.spotlight_count; i++) {
|
||||
for (var i = 0u; i < uniforms.spotlight_count; i++) {
|
||||
let shadow = sample_shadow_map_local(snow_surface_pos, i);
|
||||
if shadow > 0.0 {
|
||||
let spotlight = uniforms.spotlights[i];
|
||||
let data = calculate_spotlight_data(snow_surface_pos, surface_normal, spotlight, tile_scale, shadow);
|
||||
let light = f32(data.is_lit);
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import package::shared::{VertexInput, VertexOutput, sample_shadow_map, flowmap_path_lighting_with_shadow, all_spotlights_lighting, uniforms, blue_noise_texture, blue_noise_sampler};
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
|
||||
let instance_model = mat4x4<f32>(
|
||||
input.instance_model_0,
|
||||
input.instance_model_1,
|
||||
input.instance_model_2,
|
||||
input.instance_model_3
|
||||
);
|
||||
|
||||
let world_pos = instance_model * vec4<f32>(input.position, 1.0);
|
||||
output.world_position = world_pos.xyz;
|
||||
output.clip_position = uniforms.projection * uniforms.view * world_pos;
|
||||
|
||||
let normal_matrix = mat3x3<f32>(
|
||||
instance_model[0].xyz,
|
||||
instance_model[1].xyz,
|
||||
instance_model[2].xyz
|
||||
);
|
||||
output.world_normal = normalize(normal_matrix * input.normal);
|
||||
|
||||
output.light_space_position = uniforms.light_view_projection * world_pos;
|
||||
output.dissolve_amount = 0.0;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let shadow = sample_shadow_map(input.light_space_position);
|
||||
|
||||
let debug = 0u;
|
||||
|
||||
if debug == 3u {
|
||||
return vec4<f32>(shadow, shadow, shadow, 1.0);
|
||||
}
|
||||
|
||||
if debug == 2u {
|
||||
let proj_coords = input.light_space_position.xyz / input.light_space_position.w;
|
||||
return vec4<f32>(proj_coords.x, proj_coords.y, proj_coords.z, 1.0);
|
||||
}
|
||||
|
||||
if debug == 1u {
|
||||
let proj_coords = input.light_space_position.xyz / input.light_space_position.w;
|
||||
let ndc_coords = proj_coords * vec3<f32>(0.5, -0.5, 1.0) + vec3<f32>(0.5, 0.5, 0.0);
|
||||
let in_bounds = ndc_coords.x >= 0.0 && ndc_coords.x <= 1.0 &&
|
||||
ndc_coords.y >= 0.0 && ndc_coords.y <= 1.0 &&
|
||||
ndc_coords.z >= 0.0 && ndc_coords.z <= 1.0;
|
||||
if in_bounds {
|
||||
return vec4<f32>(ndc_coords.x, ndc_coords.y, ndc_coords.z, 1.0);
|
||||
} else {
|
||||
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
let tile_scale = 4.0;
|
||||
let flowmap_strokes = flowmap_path_lighting_with_shadow(input.world_position, input.clip_position, input.world_normal, tile_scale, shadow);
|
||||
let spotlight_strokes = all_spotlights_lighting(input.world_position, input.clip_position, input.world_normal, tile_scale, shadow);
|
||||
// let brightness = max(flowmap_strokes, spotlight_strokes);
|
||||
let brightness = 0.0;
|
||||
|
||||
return vec4<f32>(brightness, brightness, brightness, 1.0);
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
import package::shared::{VertexInput, VertexOutput, sample_shadow_map, flowmap_path_lighting_with_shadow, all_spotlights_lighting, uniforms, TERRAIN_BOUNDS, flowmap_texture, flowmap_sampler};
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var output: VertexOutput;
|
||||
|
||||
let instance_model = mat4x4<f32>(
|
||||
input.instance_model_0,
|
||||
input.instance_model_1,
|
||||
input.instance_model_2,
|
||||
input.instance_model_3
|
||||
);
|
||||
|
||||
let world_pos = instance_model * vec4<f32>(input.position, 1.0);
|
||||
output.world_position = world_pos.xyz;
|
||||
output.clip_position = uniforms.projection * uniforms.view * world_pos;
|
||||
|
||||
let normal_matrix = mat3x3<f32>(
|
||||
instance_model[0].xyz,
|
||||
instance_model[1].xyz,
|
||||
instance_model[2].xyz
|
||||
);
|
||||
output.world_normal = normalize(normal_matrix * input.normal);
|
||||
|
||||
output.light_space_position = uniforms.light_view_projection * world_pos;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let debug = 0u;
|
||||
|
||||
if debug == 4u {
|
||||
let proj_coords = input.light_space_position.xyz / input.light_space_position.w;
|
||||
let ndc_coords = proj_coords * vec3<f32>(0.5, -0.5, 1.0) + vec3<f32>(0.5, 0.5, 0.0);
|
||||
let in_bounds = ndc_coords.x >= 0.0 && ndc_coords.x <= 1.0 &&
|
||||
ndc_coords.y >= 0.0 && ndc_coords.y <= 1.0 &&
|
||||
ndc_coords.z >= 0.0 && ndc_coords.z <= 1.0;
|
||||
if in_bounds {
|
||||
return vec4<f32>(ndc_coords.x, ndc_coords.y, ndc_coords.z, 1.0);
|
||||
} else {
|
||||
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
if debug == 3u {
|
||||
let shadow = sample_shadow_map(input.light_space_position);
|
||||
return vec4<f32>(shadow, shadow, shadow, 1.0);
|
||||
}
|
||||
|
||||
if debug == 1u {
|
||||
let flowmap_uv = (vec2<f32>(input.world_position.x, input.world_position.z) + TERRAIN_BOUNDS * 0.5) / TERRAIN_BOUNDS;
|
||||
let flowmap_sample = textureSampleLevel(flowmap_texture, flowmap_sampler, flowmap_uv, 0.0).rgb;
|
||||
return vec4<f32>(flowmap_sample, 1.0);
|
||||
}
|
||||
|
||||
if debug == 2u {
|
||||
let world_pos_2d = vec2<f32>(input.world_position.x, input.world_position.z);
|
||||
let tile_size = 10.0;
|
||||
let tile_center = floor(world_pos_2d / tile_size) * tile_size + tile_size * 0.5;
|
||||
let flowmap_uv = (tile_center + TERRAIN_BOUNDS * 0.5) / TERRAIN_BOUNDS;
|
||||
|
||||
let flowmap_sample = textureSampleLevel(flowmap_texture, flowmap_sampler, flowmap_uv, 0.0).rgb;
|
||||
let x = (flowmap_sample.r) * 2.0 - 1.0;
|
||||
let y = flowmap_sample.g * 2.0 - 1.0;
|
||||
let direction_to_path = normalize(vec2<f32>(x, y));
|
||||
let perpendicular_to_path = normalize(vec2<f32>(-direction_to_path.y, direction_to_path.x));
|
||||
|
||||
let local_pos = world_pos_2d - tile_center;
|
||||
|
||||
let arrow_scale = 0.05;
|
||||
let parallel_coord = dot(local_pos, direction_to_path) * arrow_scale;
|
||||
let perpendicular_coord = dot(local_pos, perpendicular_to_path) * arrow_scale;
|
||||
|
||||
let to_path = step(0.95, fract(parallel_coord));
|
||||
let to_perp = step(0.95, fract(perpendicular_coord));
|
||||
|
||||
return vec4<f32>(to_perp, to_perp, to_perp, 1.0);
|
||||
}
|
||||
|
||||
let shadow = sample_shadow_map(input.light_space_position);
|
||||
|
||||
let tile_scale = 2.0;
|
||||
let spotlight_strokes = all_spotlights_lighting(input.world_position, input.clip_position, input.world_normal, tile_scale, shadow);
|
||||
let brightness = spotlight_strokes;
|
||||
|
||||
return vec4<f32>(brightness, brightness, brightness, 1.0);
|
||||
}
|
||||
@@ -13,8 +13,7 @@ struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) world_position: vec3<f32>,
|
||||
@location(1) world_normal: vec3<f32>,
|
||||
@location(2) light_space_position: vec4<f32>,
|
||||
@location(3) dissolve_amount: f32,
|
||||
@location(2) dissolve_amount: f32,
|
||||
}
|
||||
|
||||
const MAX_SPOTLIGHTS: u32 = 4u;
|
||||
@@ -38,15 +37,19 @@ struct Uniforms {
|
||||
model: mat4x4<f32>,
|
||||
view: mat4x4<f32>,
|
||||
projection: mat4x4<f32>,
|
||||
light_view_projection: mat4x4<f32>,
|
||||
light_view_projections: array<mat4x4<f32>, 4>,
|
||||
camera_position: vec3<f32>,
|
||||
height_scale: f32,
|
||||
player_position: vec3<f32>,
|
||||
time: f32,
|
||||
shadow_bias: f32,
|
||||
tile_scale: f32,
|
||||
enable_dissolve: u32,
|
||||
enable_snow_light: u32,
|
||||
spotlight_count: u32,
|
||||
_padding1: u32,
|
||||
_padding2: u32,
|
||||
_padding3: u32,
|
||||
spotlights: array<Spotlight, 4>,
|
||||
}
|
||||
|
||||
@@ -54,29 +57,23 @@ struct Uniforms {
|
||||
var<uniform> uniforms: Uniforms;
|
||||
|
||||
@group(0) @binding(1)
|
||||
var shadow_map: texture_depth_2d;
|
||||
var shadow_map: texture_depth_2d_array;
|
||||
|
||||
@group(0) @binding(2)
|
||||
var shadow_sampler: sampler_comparison;
|
||||
|
||||
@group(0) @binding(3)
|
||||
var dither_texture_array: texture_2d_array<f32>;
|
||||
|
||||
@group(0) @binding(4)
|
||||
var dither_sampler: sampler;
|
||||
|
||||
@group(0) @binding(5)
|
||||
var flowmap_texture: texture_2d<f32>;
|
||||
|
||||
@group(0) @binding(6)
|
||||
var flowmap_sampler: sampler;
|
||||
|
||||
@group(0) @binding(7)
|
||||
var blue_noise_texture: texture_2d<f32>;
|
||||
|
||||
@group(0) @binding(8)
|
||||
var blue_noise_sampler: sampler;
|
||||
|
||||
@group(0) @binding(9)
|
||||
var snow_light_texture: texture_2d<f32>;
|
||||
|
||||
@group(0) @binding(10)
|
||||
var snow_light_sampler: sampler;
|
||||
|
||||
const PI: f32 = 3.14159265359;
|
||||
const TERRAIN_BOUNDS: vec2<f32> = vec2<f32>(1000.0, 1000.0);
|
||||
const LINE_THICKNESS: f32 = 0.1;
|
||||
@@ -116,7 +113,8 @@ fn compute_rotation_t(distance_to_light: f32) -> f32 {
|
||||
return pow(min(max(distance_to_light - 1.0 / OCTAVE_STEPS, 0.0) * OCTAVE_STEPS, 1.0), 2.0);
|
||||
}
|
||||
|
||||
fn sample_shadow_map(light_space_pos: vec4<f32>) -> f32 {
|
||||
fn sample_shadow_map(world_pos: vec3<f32>, spotlight_index: u32) -> f32 {
|
||||
let light_space_pos = uniforms.light_view_projections[spotlight_index] * vec4<f32>(world_pos, 1.0);
|
||||
let proj_coords = light_space_pos.xyz / light_space_pos.w;
|
||||
let ndc_coords = proj_coords * vec3<f32>(0.5, -0.5, 1.0) + vec3<f32>(0.5, 0.5, 0.0);
|
||||
|
||||
@@ -125,7 +123,7 @@ fn sample_shadow_map(light_space_pos: vec4<f32>) -> f32 {
|
||||
}
|
||||
|
||||
let depth = ndc_coords.z - uniforms.shadow_bias;
|
||||
let shadow = textureSampleCompare(shadow_map, shadow_sampler, ndc_coords.xy, depth);
|
||||
let shadow = textureSampleCompare(shadow_map, shadow_sampler, ndc_coords.xy, spotlight_index, depth);
|
||||
|
||||
return shadow;
|
||||
}
|
||||
@@ -270,36 +268,18 @@ fn calculate_spotlight_data(world_pos: vec3<f32>, normal: vec3<f32>, spotlight:
|
||||
return data;
|
||||
}
|
||||
|
||||
fn is_in_spotlight_light_area(world_pos: vec3<f32>, normal: vec3<f32>, spotlight: Spotlight, tile_scale: f32, shadow: f32) -> bool {
|
||||
return calculate_spotlight_data(world_pos, normal, spotlight, tile_scale, shadow).is_lit;
|
||||
}
|
||||
|
||||
fn is_in_any_spotlight_light_area(world_pos: vec3<f32>, normal: vec3<f32>, tile_scale: f32, shadow: f32) -> bool {
|
||||
for (var i = 0u; i < uniforms.spotlight_count; i++) {
|
||||
if is_in_spotlight_light_area(world_pos, normal, uniforms.spotlights[i], tile_scale, shadow) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn spotlight_lighting(world_pos: vec3<f32>, clip_pos: vec4<f32>, normal: vec3<f32>, spotlight: Spotlight, tile_scale: f32, shadow: f32) -> f32 {
|
||||
let data = calculate_spotlight_data(world_pos, normal, spotlight, tile_scale, shadow);
|
||||
|
||||
if !data.is_lit {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return hatching_lighting(world_pos, clip_pos, tile_scale, data.direction_normalized, data.combined_distance);
|
||||
}
|
||||
|
||||
fn all_spotlights_lighting(world_pos: vec3<f32>, clip_pos: vec4<f32>, normal: vec3<f32>, tile_scale: f32, shadow: f32) -> f32 {
|
||||
fn all_spotlights_lighting(world_pos: vec3<f32>, clip_pos: vec4<f32>, normal: vec3<f32>, tile_scale: f32) -> f32 {
|
||||
var max_lighting = 0.0;
|
||||
|
||||
for (var i = 0u; i < uniforms.spotlight_count; i++) {
|
||||
let spotlight = uniforms.spotlights[i];
|
||||
let lighting = spotlight_lighting(world_pos, clip_pos, normal, spotlight, tile_scale, shadow);
|
||||
max_lighting = max(max_lighting, lighting);
|
||||
let shadow = sample_shadow_map(world_pos, i);
|
||||
let data = calculate_spotlight_data(world_pos, normal, spotlight, tile_scale, shadow);
|
||||
|
||||
if data.is_lit {
|
||||
let lighting = hatching_lighting(world_pos, clip_pos, tile_scale, data.direction_normalized, data.combined_distance);
|
||||
max_lighting = max(max_lighting, lighting);
|
||||
}
|
||||
}
|
||||
|
||||
return max_lighting;
|
||||
@@ -344,40 +324,3 @@ fn line_stroke_lighting(data: StrokeData, clip_pos: vec4<f32>) -> f32 {
|
||||
|
||||
return step(line_half_width, effective_distance);
|
||||
}
|
||||
|
||||
fn flowmap_path_lighting(world_pos: vec3<f32>, clip_pos: vec4<f32>, tile_scale: f32) -> f32 {
|
||||
let world_pos_2d = vec2<f32>(world_pos.x, world_pos.z);
|
||||
let tile_size = 1.0 / tile_scale;
|
||||
let tile_center = floor(world_pos_2d / tile_size) * tile_size + tile_size * 0.5;
|
||||
|
||||
let flowmap_uv = (tile_center + TERRAIN_BOUNDS * 0.5) / TERRAIN_BOUNDS;
|
||||
let flowmap_sample = textureSampleLevel(flowmap_texture, flowmap_sampler, flowmap_uv, 0.0);
|
||||
let x = flowmap_sample.r * 2.0 - 1.0;
|
||||
let y = flowmap_sample.g * 2.0 - 1.0;
|
||||
let direction_to_path = normalize(vec2<f32>(x, y));
|
||||
let distance_to_path = flowmap_sample.b;
|
||||
|
||||
return hatching_lighting(world_pos, clip_pos, tile_scale, direction_to_path, distance_to_path);
|
||||
}
|
||||
|
||||
fn flowmap_path_lighting_with_shadow(world_pos: vec3<f32>, clip_pos: vec4<f32>, normal: vec3<f32>, tile_scale: f32, shadow: f32) -> f32 {
|
||||
let world_pos_2d = vec2<f32>(world_pos.x, world_pos.z);
|
||||
let tile_size = 1.0 / tile_scale;
|
||||
let tile_center = floor(world_pos_2d / tile_size) * tile_size + tile_size * 0.5;
|
||||
|
||||
let flowmap_uv = (tile_center + TERRAIN_BOUNDS * 0.5) / TERRAIN_BOUNDS;
|
||||
let flowmap_sample = textureSampleLevel(flowmap_texture, flowmap_sampler, flowmap_uv, 0.0);
|
||||
let x = flowmap_sample.r * 2.0 - 1.0;
|
||||
let y = flowmap_sample.g * 2.0 - 1.0;
|
||||
let direction_to_path = normalize(vec2<f32>(x, y));
|
||||
let distance_to_path = flowmap_sample.b;
|
||||
|
||||
let light_dir_3d = normalize(vec3<f32>(-x, 100.0, -y));
|
||||
let diffuse = max(0.0, dot(normalize(normal), light_dir_3d));
|
||||
|
||||
let lighting_intensity = diffuse * shadow;
|
||||
let darkness = 1.0 - lighting_intensity;
|
||||
let combined_distance = min(distance_to_path + darkness * 0.5, 1.0);
|
||||
|
||||
return hatching_lighting(world_pos, clip_pos, tile_scale, direction_to_path, combined_distance);
|
||||
}
|
||||
465
src/snow.rs
465
src/snow.rs
@@ -5,11 +5,9 @@ use glam::{Vec2, Vec3};
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
use crate::{
|
||||
components::MeshComponent,
|
||||
entity::EntityHandle,
|
||||
mesh::{Mesh, Vertex},
|
||||
render,
|
||||
world::{Transform, World},
|
||||
loaders::mesh::{InstanceRaw, Mesh, Vertex},
|
||||
render::{self, DrawCall, Pipeline},
|
||||
texture::HeightmapTexture,
|
||||
};
|
||||
|
||||
pub struct SnowConfig
|
||||
@@ -22,16 +20,6 @@ pub struct SnowConfig
|
||||
|
||||
impl SnowConfig
|
||||
{
|
||||
pub fn new(depth_map_path: &str, heightmap_path: &str, terrain_size: Vec2, resolution: (u32, u32)) -> Self
|
||||
{
|
||||
Self {
|
||||
depth_map_path: depth_map_path.to_string(),
|
||||
heightmap_path: heightmap_path.to_string(),
|
||||
terrain_size,
|
||||
resolution,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default() -> Self
|
||||
{
|
||||
Self {
|
||||
@@ -43,9 +31,33 @@ impl SnowConfig
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClipmapConfig
|
||||
{
|
||||
pub num_levels: u32,
|
||||
pub grid_size: u32,
|
||||
pub base_cell_size: f32,
|
||||
}
|
||||
|
||||
impl Default for ClipmapConfig
|
||||
{
|
||||
fn default() -> Self
|
||||
{
|
||||
Self {
|
||||
num_levels: 3,
|
||||
grid_size: 192,
|
||||
base_cell_size: 0.25,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ClipmapLevel
|
||||
{
|
||||
mesh: Rc<Mesh>,
|
||||
instance_buffer: wgpu::Buffer,
|
||||
}
|
||||
|
||||
pub struct SnowLayer
|
||||
{
|
||||
pub entity: EntityHandle,
|
||||
pub depth_texture: wgpu::Texture,
|
||||
pub depth_texture_view: wgpu::TextureView,
|
||||
pub depth_bind_group: wgpu::BindGroup,
|
||||
@@ -54,11 +66,30 @@ pub struct SnowLayer
|
||||
pub deform_bind_group: wgpu::BindGroup,
|
||||
pub deform_pipeline: wgpu::ComputePipeline,
|
||||
pub deform_params_buffer: wgpu::Buffer,
|
||||
pub terrain_size: Vec2,
|
||||
|
||||
clipmap_config: ClipmapConfig,
|
||||
levels: Vec<ClipmapLevel>,
|
||||
displacement_bind_group: wgpu::BindGroup,
|
||||
heightmap_texture: HeightmapTexture,
|
||||
depth_sampler: wgpu::Sampler,
|
||||
}
|
||||
|
||||
fn create_instance_buffer() -> wgpu::Buffer
|
||||
{
|
||||
render::with_device(|device| {
|
||||
device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("Snow Clipmap Instance Buffer"),
|
||||
size: std::mem::size_of::<InstanceRaw>() as u64,
|
||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
impl SnowLayer
|
||||
{
|
||||
pub fn load(world: &mut World, config: &SnowConfig) -> anyhow::Result<Self>
|
||||
pub fn load(config: &SnowConfig) -> anyhow::Result<Self>
|
||||
{
|
||||
println!("\n=== Loading Snow Layer ===");
|
||||
println!("Depth map path: {}", config.depth_map_path);
|
||||
@@ -66,44 +97,42 @@ impl SnowLayer
|
||||
println!("Terrain size: {:?}", config.terrain_size);
|
||||
|
||||
let (depth_data, width, height) = Self::load_depth_map(&config.depth_map_path)?;
|
||||
let (heightmap_data, hm_width, hm_height) = Self::load_depth_map(&config.heightmap_path)?;
|
||||
|
||||
if width != hm_width || height != hm_height {
|
||||
anyhow::bail!("Snow depth map ({}×{}) and heightmap ({}×{}) dimensions don't match!",
|
||||
width, height, hm_width, hm_height);
|
||||
}
|
||||
|
||||
println!("Using EXR dimensions: {}×{}", width, height);
|
||||
|
||||
let (depth_texture, depth_texture_view, depth_bind_group) =
|
||||
Self::create_depth_texture(&depth_data, width, height);
|
||||
|
||||
let mesh = Self::generate_snow_mesh(&depth_data, &heightmap_data, width, height, config.terrain_size);
|
||||
let num_indices = mesh.num_indices;
|
||||
|
||||
let entity = world.spawn();
|
||||
world.transforms.insert(entity, Transform::IDENTITY);
|
||||
|
||||
if num_indices > 0 {
|
||||
world.meshes.insert(
|
||||
entity,
|
||||
MeshComponent {
|
||||
mesh: Rc::new(mesh),
|
||||
pipeline: render::Pipeline::Snow,
|
||||
instance_buffer: None,
|
||||
num_instances: 1,
|
||||
},
|
||||
);
|
||||
println!("Snow mesh created with {} indices", num_indices);
|
||||
} else {
|
||||
println!("⚠️ No snow mesh created - all depth values are zero");
|
||||
}
|
||||
|
||||
let (deform_pipeline, deform_bind_group, deform_params_buffer) =
|
||||
Self::create_deform_pipeline(&depth_texture_view);
|
||||
|
||||
let heightmap_texture = render::with_device(|device| {
|
||||
render::with_queue(|queue| {
|
||||
HeightmapTexture::load(device, queue, &config.heightmap_path)
|
||||
})
|
||||
})?;
|
||||
|
||||
let depth_sampler = render::with_device(|device| {
|
||||
device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("Snow Depth Sampler"),
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Nearest,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
..Default::default()
|
||||
})
|
||||
});
|
||||
|
||||
let displacement_bind_group = Self::create_displacement_bind_group(
|
||||
&heightmap_texture,
|
||||
&depth_texture_view,
|
||||
&depth_sampler,
|
||||
);
|
||||
|
||||
let clipmap_config = ClipmapConfig::default();
|
||||
let levels = Vec::new();
|
||||
|
||||
Ok(Self {
|
||||
entity,
|
||||
depth_texture,
|
||||
depth_texture_view,
|
||||
depth_bind_group,
|
||||
@@ -112,6 +141,12 @@ impl SnowLayer
|
||||
deform_bind_group,
|
||||
deform_pipeline,
|
||||
deform_params_buffer,
|
||||
terrain_size: config.terrain_size,
|
||||
clipmap_config,
|
||||
levels,
|
||||
displacement_bind_group,
|
||||
heightmap_texture,
|
||||
depth_sampler,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -132,9 +167,20 @@ impl SnowLayer
|
||||
let height = layer.size.height() as u32;
|
||||
|
||||
println!(" Layer size: {}×{}", width, height);
|
||||
println!(" Available channels: {:?}", layer.channel_data.list.iter().map(|c| &c.name).collect::<Vec<_>>());
|
||||
println!(
|
||||
" Available channels: {:?}",
|
||||
layer
|
||||
.channel_data
|
||||
.list
|
||||
.iter()
|
||||
.map(|c| &c.name)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
let channel = layer.channel_data.list.iter()
|
||||
let channel = layer
|
||||
.channel_data
|
||||
.list
|
||||
.iter()
|
||||
.find(|c| format!("{:?}", c.name).contains("\"R\""))
|
||||
.or_else(|| layer.channel_data.list.first())
|
||||
.ok_or_else(|| anyhow::anyhow!("No channels found in EXR"))?;
|
||||
@@ -149,12 +195,24 @@ impl SnowLayer
|
||||
let non_zero_count = depths.iter().filter(|&&v| v > 0.0001).count();
|
||||
|
||||
println!(" Total values: {}", depths.len());
|
||||
println!(" Min: {:.6}, Max: {:.6}, Avg: {:.6}", min_value, max_value, avg_value);
|
||||
println!(" Non-zero values: {} ({:.1}%)", non_zero_count, (non_zero_count as f32 / depths.len() as f32) * 100.0);
|
||||
println!(
|
||||
" Min: {:.6}, Max: {:.6}, Avg: {:.6}",
|
||||
min_value, max_value, avg_value
|
||||
);
|
||||
println!(
|
||||
" Non-zero values: {} ({:.1}%)",
|
||||
non_zero_count,
|
||||
(non_zero_count as f32 / depths.len() as f32) * 100.0
|
||||
);
|
||||
|
||||
if max_value < 0.0001 {
|
||||
println!(" ⚠️ WARNING: All values are effectively zero! Snow depth map may be invalid.");
|
||||
} else {
|
||||
if max_value < 0.0001
|
||||
{
|
||||
println!(
|
||||
" ⚠️ WARNING: All values are effectively zero! Snow depth map may be invalid."
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
println!(" ✓ Snow depth data loaded successfully");
|
||||
}
|
||||
|
||||
@@ -237,96 +295,6 @@ impl SnowLayer
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_snow_mesh(
|
||||
depth_data: &[f32],
|
||||
heightmap_data: &[f32],
|
||||
width: u32,
|
||||
height: u32,
|
||||
terrain_size: Vec2,
|
||||
) -> Mesh
|
||||
{
|
||||
let mut vertices = Vec::new();
|
||||
let mut indices = Vec::new();
|
||||
|
||||
let cell_size_x = terrain_size.x / (width - 1) as f32;
|
||||
let cell_size_z = terrain_size.y / (height - 1) as f32;
|
||||
let half_width = terrain_size.x / 2.0;
|
||||
let half_height = terrain_size.y / 2.0;
|
||||
|
||||
for z in 0..height
|
||||
{
|
||||
for x in 0..width
|
||||
{
|
||||
let index = (z * width + x) as usize;
|
||||
let snow_depth = depth_data.get(index).copied().unwrap_or(0.0);
|
||||
let terrain_height = heightmap_data.get(index).copied().unwrap_or(0.0);
|
||||
|
||||
let world_x = x as f32 * cell_size_x - half_width;
|
||||
let world_z = z as f32 * cell_size_z - half_height;
|
||||
let world_y = terrain_height + snow_depth;
|
||||
|
||||
vertices.push(Vertex {
|
||||
position: [world_x, world_y, world_z],
|
||||
normal: [0.0, 1.0, 0.0],
|
||||
uv: [x as f32 / width as f32, z as f32 / height as f32],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for z in 0..(height - 1)
|
||||
{
|
||||
for x in 0..(width - 1)
|
||||
{
|
||||
let index = (z * width + x) as usize;
|
||||
let depth_tl = depth_data.get(index).copied().unwrap_or(0.0);
|
||||
let depth_tr = depth_data.get(index + 1).copied().unwrap_or(0.0);
|
||||
let depth_bl = depth_data.get(index + width as usize).copied().unwrap_or(0.0);
|
||||
let depth_br = depth_data
|
||||
.get(index + width as usize + 1)
|
||||
.copied()
|
||||
.unwrap_or(0.0);
|
||||
|
||||
if depth_tl > 0.001
|
||||
|| depth_tr > 0.001
|
||||
|| depth_bl > 0.001
|
||||
|| depth_br > 0.001
|
||||
{
|
||||
let vertex_index = (z * width + x) as u32;
|
||||
|
||||
indices.push(vertex_index);
|
||||
indices.push(vertex_index + width);
|
||||
indices.push(vertex_index + 1);
|
||||
|
||||
indices.push(vertex_index + 1);
|
||||
indices.push(vertex_index + width);
|
||||
indices.push(vertex_index + width + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let vertex_buffer = render::with_device(|device| {
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Snow Vertex Buffer"),
|
||||
contents: bytemuck::cast_slice(&vertices),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
})
|
||||
});
|
||||
|
||||
let index_buffer = render::with_device(|device| {
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Snow Index Buffer"),
|
||||
contents: bytemuck::cast_slice(&indices),
|
||||
usage: wgpu::BufferUsages::INDEX,
|
||||
})
|
||||
});
|
||||
|
||||
Mesh {
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
num_indices: indices.len() as u32,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_deform_pipeline(
|
||||
depth_texture_view: &wgpu::TextureView,
|
||||
) -> (wgpu::ComputePipeline, wgpu::BindGroup, wgpu::Buffer)
|
||||
@@ -408,10 +376,215 @@ impl SnowLayer
|
||||
})
|
||||
}
|
||||
|
||||
fn create_displacement_bind_group(
|
||||
heightmap: &HeightmapTexture,
|
||||
depth_view: &wgpu::TextureView,
|
||||
depth_sampler: &wgpu::Sampler,
|
||||
) -> wgpu::BindGroup
|
||||
{
|
||||
render::with_device(|device| {
|
||||
let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("Snow Displacement Bind Group Layout"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 3,
|
||||
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("Snow Displacement Bind Group"),
|
||||
layout: &layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&heightmap.view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&heightmap.sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: wgpu::BindingResource::TextureView(depth_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 3,
|
||||
resource: wgpu::BindingResource::Sampler(depth_sampler),
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_clipmap_grid(&self, level: u32) -> Mesh
|
||||
{
|
||||
let cell_size = self.clipmap_config.base_cell_size * (1 << level) as f32;
|
||||
let grid_size = self.clipmap_config.grid_size;
|
||||
let half_extent = (grid_size as f32 * cell_size) / 2.0;
|
||||
|
||||
let mut vertices = Vec::new();
|
||||
let mut indices = Vec::new();
|
||||
|
||||
for z in 0..=grid_size
|
||||
{
|
||||
for x in 0..=grid_size
|
||||
{
|
||||
vertices.push(Vertex {
|
||||
position: [
|
||||
-half_extent + x as f32 * cell_size,
|
||||
0.0,
|
||||
-half_extent + z as f32 * cell_size,
|
||||
],
|
||||
normal: [0.0, 1.0, 0.0],
|
||||
uv: [0.0, 0.0],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let row = grid_size + 1;
|
||||
for z in 0..grid_size
|
||||
{
|
||||
for x in 0..grid_size
|
||||
{
|
||||
let i0 = z * row + x;
|
||||
let i1 = i0 + 1;
|
||||
let i2 = i0 + row;
|
||||
let i3 = i2 + 1;
|
||||
indices.push(i0);
|
||||
indices.push(i2);
|
||||
indices.push(i1);
|
||||
indices.push(i1);
|
||||
indices.push(i2);
|
||||
indices.push(i3);
|
||||
}
|
||||
}
|
||||
|
||||
let vertex_buffer = render::with_device(|device| {
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some(&format!("Snow Clipmap Level {} Vertex Buffer", level)),
|
||||
contents: bytemuck::cast_slice(&vertices),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
})
|
||||
});
|
||||
|
||||
let index_buffer = render::with_device(|device| {
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some(&format!("Snow Clipmap Level {} Index Buffer", level)),
|
||||
contents: bytemuck::cast_slice(&indices),
|
||||
usage: wgpu::BufferUsages::INDEX,
|
||||
})
|
||||
});
|
||||
|
||||
Mesh {
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
num_indices: indices.len() as u32,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, center: Vec3)
|
||||
{
|
||||
if self.levels.is_empty()
|
||||
{
|
||||
for level in 0..self.clipmap_config.num_levels
|
||||
{
|
||||
let mesh = self.generate_clipmap_grid(level);
|
||||
let instance_buffer = create_instance_buffer();
|
||||
|
||||
self.levels.push(ClipmapLevel {
|
||||
mesh: Rc::new(mesh),
|
||||
instance_buffer,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render::with_queue(|queue| {
|
||||
for (level, clipmap_level) in self.levels.iter().enumerate()
|
||||
{
|
||||
let cell_size = self.clipmap_config.base_cell_size * (1u32 << level) as f32;
|
||||
let snapped_x = (center.x / cell_size).round() * cell_size;
|
||||
let snapped_z = (center.z / cell_size).round() * cell_size;
|
||||
let y_bias = -(level as f32) * 0.005;
|
||||
let model =
|
||||
glam::Mat4::from_translation(glam::Vec3::new(snapped_x, y_bias, snapped_z));
|
||||
let instance_data = InstanceRaw {
|
||||
model: model.to_cols_array_2d(),
|
||||
dissolve_amount: 0.0,
|
||||
_padding: [0.0; 3],
|
||||
};
|
||||
queue.write_buffer(
|
||||
&clipmap_level.instance_buffer,
|
||||
0,
|
||||
bytemuck::cast_slice(&[instance_data]),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn get_draw_calls(&self) -> Vec<DrawCall>
|
||||
{
|
||||
self.levels
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|level| DrawCall {
|
||||
vertex_buffer: level.mesh.vertex_buffer.clone(),
|
||||
index_buffer: level.mesh.index_buffer.clone(),
|
||||
num_indices: level.mesh.num_indices,
|
||||
model: glam::Mat4::IDENTITY,
|
||||
pipeline: Pipeline::SnowClipmap,
|
||||
instance_buffer: Some(level.instance_buffer.clone()),
|
||||
num_instances: 1,
|
||||
tile_scale: 2.0,
|
||||
enable_dissolve: false,
|
||||
enable_snow_light: true,
|
||||
displacement_bind_group: Some(self.displacement_bind_group.clone()),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn deform_at_position(&self, position: Vec3, radius: f32, depth: f32)
|
||||
{
|
||||
render::with_queue(|queue| {
|
||||
let params_data = [position.x, position.z, radius, depth];
|
||||
let params_data = [
|
||||
position.x,
|
||||
position.z,
|
||||
radius,
|
||||
depth,
|
||||
self.terrain_size.x,
|
||||
self.terrain_size.y,
|
||||
0.0,
|
||||
0.0,
|
||||
];
|
||||
let params_bytes: &[u8] = bytemuck::cast_slice(¶ms_data);
|
||||
|
||||
queue.write_buffer(&self.deform_params_buffer, 0, params_bytes);
|
||||
@@ -442,10 +615,4 @@ impl SnowLayer
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn regenerate_mesh(&mut self, _world: &mut World, _config: &SnowConfig)
|
||||
{
|
||||
todo!("Implement regenerate_mesh with correct wgpu types for texture-to-buffer copy");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ struct AccumulationUniforms
|
||||
delta_time: f32,
|
||||
spotlight_count: u32,
|
||||
_padding: u32,
|
||||
light_view_projection: [[f32; 4]; 4],
|
||||
light_view_projections: [[[f32; 4]; 4]; crate::render::MAX_SPOTLIGHTS],
|
||||
shadow_bias: f32,
|
||||
terrain_height_scale: f32,
|
||||
_padding3: f32,
|
||||
@@ -48,12 +48,8 @@ pub struct SnowLightAccumulation
|
||||
|
||||
impl SnowLightAccumulation
|
||||
{
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
terrain_min: Vec2,
|
||||
terrain_max: Vec2,
|
||||
resolution: u32,
|
||||
) -> Self
|
||||
pub fn new(device: &wgpu::Device, terrain_min: Vec2, terrain_max: Vec2, resolution: u32)
|
||||
-> Self
|
||||
{
|
||||
let size = wgpu::Extent3d {
|
||||
width: resolution,
|
||||
@@ -68,8 +64,7 @@ impl SnowLightAccumulation
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::R16Float,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
|
||||
| wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
};
|
||||
|
||||
@@ -145,7 +140,7 @@ impl SnowLightAccumulation
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Depth,
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
view_dimension: wgpu::TextureViewDimension::D2Array,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
@@ -232,9 +227,7 @@ impl SnowLightAccumulation
|
||||
});
|
||||
|
||||
let vertices: &[f32] = &[
|
||||
-1.0, -1.0, 0.0, 1.0,
|
||||
3.0, -1.0, 2.0, 1.0,
|
||||
-1.0, 3.0, 0.0, -1.0,
|
||||
-1.0, -1.0, 0.0, 1.0, 3.0, -1.0, 2.0, 1.0, -1.0, 3.0, 0.0, -1.0,
|
||||
];
|
||||
|
||||
let quad_vb = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
@@ -419,7 +412,7 @@ impl SnowLightAccumulation
|
||||
queue: &wgpu::Queue,
|
||||
spotlights: &[crate::render::Spotlight],
|
||||
delta_time: f32,
|
||||
light_view_projection: &glam::Mat4,
|
||||
light_view_projections: &[glam::Mat4],
|
||||
shadow_bias: f32,
|
||||
terrain_height_scale: f32,
|
||||
)
|
||||
@@ -430,8 +423,12 @@ impl SnowLightAccumulation
|
||||
self.needs_clear = false;
|
||||
}
|
||||
|
||||
let mut spotlight_array = [crate::render::SpotlightRaw::default(); crate::render::MAX_SPOTLIGHTS];
|
||||
for (i, spotlight) in spotlights.iter().take(crate::render::MAX_SPOTLIGHTS).enumerate()
|
||||
let mut spotlight_array =
|
||||
[crate::render::SpotlightRaw::default(); crate::render::MAX_SPOTLIGHTS];
|
||||
for (i, spotlight) in spotlights
|
||||
.iter()
|
||||
.take(crate::render::MAX_SPOTLIGHTS)
|
||||
.enumerate()
|
||||
{
|
||||
spotlight_array[i] = spotlight.to_raw();
|
||||
}
|
||||
@@ -443,7 +440,17 @@ impl SnowLightAccumulation
|
||||
delta_time,
|
||||
spotlight_count: spotlights.len().min(crate::render::MAX_SPOTLIGHTS) as u32,
|
||||
_padding: 0,
|
||||
light_view_projection: light_view_projection.to_cols_array_2d(),
|
||||
light_view_projections: {
|
||||
let mut arr = [[[0.0f32; 4]; 4]; crate::render::MAX_SPOTLIGHTS];
|
||||
for (i, mat) in light_view_projections
|
||||
.iter()
|
||||
.take(crate::render::MAX_SPOTLIGHTS)
|
||||
.enumerate()
|
||||
{
|
||||
arr[i] = mat.to_cols_array_2d();
|
||||
}
|
||||
arr
|
||||
},
|
||||
shadow_bias,
|
||||
terrain_height_scale,
|
||||
_padding3: 0.0,
|
||||
@@ -453,10 +460,26 @@ impl SnowLightAccumulation
|
||||
|
||||
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
|
||||
|
||||
let write_view = if self.current { &self.view_ping } else { &self.view_pong };
|
||||
let bind_group = if self.current { self.bind_group_ping.as_ref() } else { self.bind_group_pong.as_ref() };
|
||||
let write_view = if self.current
|
||||
{
|
||||
&self.view_ping
|
||||
}
|
||||
else
|
||||
{
|
||||
&self.view_pong
|
||||
};
|
||||
let bind_group = if self.current
|
||||
{
|
||||
self.bind_group_ping.as_ref()
|
||||
}
|
||||
else
|
||||
{
|
||||
self.bind_group_pong.as_ref()
|
||||
};
|
||||
|
||||
let Some(bind_group) = bind_group else {
|
||||
let Some(bind_group) = bind_group
|
||||
else
|
||||
{
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -489,7 +512,14 @@ impl SnowLightAccumulation
|
||||
|
||||
pub fn read_view(&self) -> &wgpu::TextureView
|
||||
{
|
||||
if self.current { &self.view_pong } else { &self.view_ping }
|
||||
if self.current
|
||||
{
|
||||
&self.view_pong
|
||||
}
|
||||
else
|
||||
{
|
||||
&self.view_ping
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout
|
||||
|
||||
@@ -4,6 +4,32 @@ use crate::components::FollowComponent;
|
||||
use crate::utility::input::InputState;
|
||||
use crate::world::{Transform, World};
|
||||
|
||||
pub fn camera_view_matrix(world: &World) -> Option<glam::Mat4>
|
||||
{
|
||||
let (camera_entity, camera_component) = world.active_camera()?;
|
||||
let camera_transform = world.transforms.get(camera_entity)?;
|
||||
|
||||
if let Some(follow) = world.follows.get(camera_entity)
|
||||
{
|
||||
if let Some(target_transform) = world.transforms.get(follow.target)
|
||||
{
|
||||
return Some(glam::Mat4::look_at_rh(
|
||||
camera_transform.position,
|
||||
target_transform.position,
|
||||
glam::Vec3::Y,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let forward = camera_component.get_forward();
|
||||
let target = camera_transform.position + forward;
|
||||
Some(glam::Mat4::look_at_rh(
|
||||
camera_transform.position,
|
||||
target,
|
||||
glam::Vec3::Y,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn camera_input_system(world: &mut World, input_state: &InputState)
|
||||
{
|
||||
let cameras: Vec<_> = world.cameras.all();
|
||||
|
||||
@@ -5,14 +5,11 @@ use crate::world::World;
|
||||
|
||||
pub fn player_input_system(world: &mut World, input_state: &InputState)
|
||||
{
|
||||
let active_camera = world.cameras.get_active();
|
||||
|
||||
if active_camera.is_none()
|
||||
let Some((_, camera)) = world.active_camera()
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let (_, camera) = active_camera.unwrap();
|
||||
};
|
||||
|
||||
let forward = camera.get_forward_horizontal();
|
||||
let right = camera.get_right_horizontal();
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
pub mod camera;
|
||||
pub mod follow;
|
||||
pub mod input;
|
||||
pub mod noclip;
|
||||
pub mod physics_sync;
|
||||
pub mod player_states;
|
||||
pub mod render;
|
||||
pub mod rotate;
|
||||
pub mod snow;
|
||||
pub mod spotlight_sync;
|
||||
pub mod state_machine;
|
||||
pub mod tree_dissolve;
|
||||
|
||||
pub use camera::{
|
||||
camera_follow_system, camera_input_system, camera_noclip_system, start_camera_following,
|
||||
stop_camera_following,
|
||||
camera_follow_system, camera_input_system, camera_noclip_system, camera_view_matrix,
|
||||
start_camera_following,
|
||||
};
|
||||
pub use follow::follow_system;
|
||||
pub use input::player_input_system;
|
||||
pub use noclip::noclip_toggle_system;
|
||||
pub use physics_sync::physics_sync_system;
|
||||
pub use render::render_system;
|
||||
pub use rotate::rotate_system;
|
||||
pub use snow::snow_system;
|
||||
pub use spotlight_sync::spotlight_sync_system;
|
||||
pub use state_machine::{state_machine_physics_system, state_machine_system};
|
||||
pub use tree_dissolve::{tree_dissolve_update_system, tree_occlusion_system};
|
||||
|
||||
25
src/systems/noclip.rs
Normal file
25
src/systems/noclip.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use crate::entity::EntityHandle;
|
||||
use crate::systems::camera::{start_camera_following, stop_camera_following};
|
||||
use crate::utility::input::InputState;
|
||||
use crate::world::World;
|
||||
|
||||
pub fn noclip_toggle_system(
|
||||
world: &mut World,
|
||||
input_state: &InputState,
|
||||
camera_entity: EntityHandle,
|
||||
noclip_mode: &mut bool,
|
||||
)
|
||||
{
|
||||
if input_state.noclip_just_pressed
|
||||
{
|
||||
*noclip_mode = !*noclip_mode;
|
||||
if *noclip_mode
|
||||
{
|
||||
stop_camera_following(world, camera_entity);
|
||||
}
|
||||
else
|
||||
{
|
||||
start_camera_following(world, camera_entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,216 +1,16 @@
|
||||
use std::{f32::consts::PI, rc::Rc};
|
||||
|
||||
use glam::Vec3;
|
||||
use kurbo::ParamCurve;
|
||||
use rapier3d::{
|
||||
control::{CharacterAutostep, KinematicCharacterController},
|
||||
math::Vector,
|
||||
prelude::{ColliderBuilder, RigidBodyBuilder},
|
||||
};
|
||||
use rapier3d::math::Vector;
|
||||
|
||||
use crate::{
|
||||
components::{
|
||||
jump::JumpComponent, lights::spot::SpotlightComponent, InputComponent, MeshComponent,
|
||||
MovementComponent, PhysicsComponent,
|
||||
},
|
||||
entity::EntityHandle,
|
||||
mesh::Mesh,
|
||||
physics::PhysicsManager,
|
||||
render::Pipeline,
|
||||
state::{State, StateMachine},
|
||||
world::{Transform, World},
|
||||
};
|
||||
|
||||
pub struct Player;
|
||||
|
||||
impl Player
|
||||
{
|
||||
pub fn spawn(world: &mut World, position: Vec3) -> EntityHandle
|
||||
{
|
||||
let entity = world.spawn();
|
||||
|
||||
let spawn_transform = Transform::from_position(position);
|
||||
|
||||
let rigidbody = RigidBodyBuilder::kinematic_position_based()
|
||||
.translation(spawn_transform.position.into())
|
||||
.build();
|
||||
let collider = ColliderBuilder::capsule_y(0.5, 0.5).build();
|
||||
let _controller = KinematicCharacterController {
|
||||
slide: true,
|
||||
autostep: Some(CharacterAutostep::default()),
|
||||
max_slope_climb_angle: 45.0,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let rigidbody_handle = PhysicsManager::add_rigidbody(rigidbody);
|
||||
let collider_handle = PhysicsManager::add_collider(collider, Some(rigidbody_handle));
|
||||
|
||||
let mesh = Mesh::load_mesh("meshes/player_mesh.glb").expect("missing player mesh");
|
||||
|
||||
let falling_state = PlayerFallingState { entity };
|
||||
let idle_state = PlayerIdleState { entity };
|
||||
let walking_state = PlayerWalkingState {
|
||||
entity,
|
||||
enter_time_stamp: 0.0,
|
||||
};
|
||||
let jumping_state = PlayerJumpingState {
|
||||
entity,
|
||||
enter_time_stamp: 0.0,
|
||||
};
|
||||
|
||||
let mut state_machine = StateMachine::new(Box::new(falling_state));
|
||||
state_machine.add_state(walking_state);
|
||||
state_machine.add_state(idle_state);
|
||||
state_machine.add_state(jumping_state);
|
||||
|
||||
let entity_id = entity;
|
||||
|
||||
state_machine.add_transition::<PlayerFallingState, PlayerIdleState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
let has_input = world
|
||||
.inputs
|
||||
.with(entity_id, |i| i.move_direction.length() > 0.01)
|
||||
.unwrap_or(false);
|
||||
is_grounded && !has_input
|
||||
});
|
||||
|
||||
state_machine.add_transition::<PlayerFallingState, PlayerWalkingState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
let has_input = world
|
||||
.inputs
|
||||
.with(entity_id, |i| i.move_direction.length() > 0.01)
|
||||
.unwrap_or(false);
|
||||
is_grounded && has_input
|
||||
});
|
||||
|
||||
state_machine.add_transition::<PlayerIdleState, PlayerWalkingState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
let has_input = world
|
||||
.inputs
|
||||
.with(entity_id, |i| i.move_direction.length() > 0.01)
|
||||
.unwrap_or(false);
|
||||
is_grounded && has_input
|
||||
});
|
||||
|
||||
state_machine.add_transition::<PlayerWalkingState, PlayerIdleState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
let has_input = world
|
||||
.inputs
|
||||
.with(entity_id, |i| i.move_direction.length() > 0.01)
|
||||
.unwrap_or(false);
|
||||
is_grounded && !has_input
|
||||
});
|
||||
|
||||
state_machine.add_transition::<PlayerIdleState, PlayerFallingState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
!is_grounded
|
||||
});
|
||||
|
||||
state_machine.add_transition::<PlayerWalkingState, PlayerFallingState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
!is_grounded
|
||||
});
|
||||
|
||||
state_machine.add_transition::<PlayerIdleState, PlayerJumpingState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
let jump_pressed = world
|
||||
.inputs
|
||||
.with(entity_id, |i| i.jump_just_pressed)
|
||||
.unwrap_or(false);
|
||||
is_grounded && jump_pressed
|
||||
});
|
||||
|
||||
state_machine.add_transition::<PlayerWalkingState, PlayerJumpingState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
let jump_pressed = world
|
||||
.inputs
|
||||
.with(entity_id, |i| i.jump_just_pressed)
|
||||
.unwrap_or(false);
|
||||
is_grounded && jump_pressed
|
||||
});
|
||||
|
||||
state_machine.add_transition::<PlayerJumpingState, PlayerFallingState>(move |world| {
|
||||
world
|
||||
.jumps
|
||||
.with(entity_id, |jump| {
|
||||
jump.jump_config.jump_context.duration >= jump.jump_config.jump_duration
|
||||
})
|
||||
.unwrap_or(true)
|
||||
});
|
||||
|
||||
world
|
||||
.transforms
|
||||
.insert(entity, spawn_transform);
|
||||
world.movements.insert(entity, MovementComponent::new());
|
||||
world.jumps.insert(entity, JumpComponent::new());
|
||||
world.inputs.insert(entity, InputComponent::default());
|
||||
world.physics.insert(
|
||||
entity,
|
||||
PhysicsComponent {
|
||||
rigidbody: rigidbody_handle,
|
||||
collider: Some(collider_handle),
|
||||
},
|
||||
);
|
||||
world.meshes.insert(
|
||||
entity,
|
||||
MeshComponent {
|
||||
mesh: Rc::new(mesh),
|
||||
pipeline: Pipeline::Standard,
|
||||
instance_buffer: None,
|
||||
num_instances: 1,
|
||||
},
|
||||
);
|
||||
world.player_tags.insert(entity);
|
||||
world.state_machines.insert(entity, state_machine);
|
||||
|
||||
let outer_angle = PI / 2.0 * 0.9;
|
||||
world.spotlights.insert(
|
||||
entity,
|
||||
SpotlightComponent::new(
|
||||
Vec3::new(1.0, 2.0, 1.0),
|
||||
Vec3::new(0.0, -1.0, 0.0),
|
||||
100.0,
|
||||
outer_angle * 0.5,
|
||||
outer_angle,
|
||||
),
|
||||
);
|
||||
|
||||
entity
|
||||
}
|
||||
|
||||
pub fn despawn(world: &mut World, entity: EntityHandle)
|
||||
{
|
||||
world.despawn(entity);
|
||||
}
|
||||
}
|
||||
use crate::entity::EntityHandle;
|
||||
use crate::physics::PhysicsManager;
|
||||
use crate::state::State;
|
||||
use crate::utility::time::Time;
|
||||
use crate::world::World;
|
||||
|
||||
pub struct PlayerFallingState
|
||||
{
|
||||
entity: EntityHandle,
|
||||
pub entity: EntityHandle,
|
||||
}
|
||||
|
||||
impl State for PlayerFallingState
|
||||
@@ -220,14 +20,14 @@ impl State for PlayerFallingState
|
||||
"PlayerFallingState"
|
||||
}
|
||||
|
||||
fn on_state_enter(&mut self, world: &mut World)
|
||||
fn on_state_enter(&mut self, _world: &mut World)
|
||||
{
|
||||
println!("entered falling");
|
||||
}
|
||||
|
||||
fn on_state_exit(&mut self, world: &mut World) {}
|
||||
fn on_state_exit(&mut self, _world: &mut World) {}
|
||||
|
||||
fn on_state_update(&mut self, world: &mut World, delta: f32) {}
|
||||
fn on_state_update(&mut self, _world: &mut World, _delta: f32) {}
|
||||
|
||||
fn on_state_physics_update(&mut self, world: &mut World, delta: f32)
|
||||
{
|
||||
@@ -297,7 +97,7 @@ impl State for PlayerFallingState
|
||||
|
||||
pub struct PlayerIdleState
|
||||
{
|
||||
entity: EntityHandle,
|
||||
pub entity: EntityHandle,
|
||||
}
|
||||
|
||||
impl State for PlayerIdleState
|
||||
@@ -338,7 +138,7 @@ impl State for PlayerIdleState
|
||||
|
||||
fn on_state_update(&mut self, _world: &mut World, _delta: f32) {}
|
||||
|
||||
fn on_state_physics_update(&mut self, world: &mut World, delta: f32)
|
||||
fn on_state_physics_update(&mut self, world: &mut World, _delta: f32)
|
||||
{
|
||||
const GROUND_CHECK_DISTANCE: f32 = 0.6;
|
||||
|
||||
@@ -380,8 +180,8 @@ impl State for PlayerIdleState
|
||||
|
||||
pub struct PlayerWalkingState
|
||||
{
|
||||
entity: EntityHandle,
|
||||
enter_time_stamp: f32,
|
||||
pub entity: EntityHandle,
|
||||
pub enter_time_stamp: f32,
|
||||
}
|
||||
|
||||
impl State for PlayerWalkingState
|
||||
@@ -393,19 +193,16 @@ impl State for PlayerWalkingState
|
||||
|
||||
fn on_state_enter(&mut self, _world: &mut World)
|
||||
{
|
||||
use crate::utility::time::Time;
|
||||
self.enter_time_stamp = Time::get_time_elapsed();
|
||||
println!("entered walking");
|
||||
}
|
||||
|
||||
fn on_state_exit(&mut self, _world: &mut World) {}
|
||||
|
||||
fn on_state_update(&mut self, world: &mut World, delta: f32) {}
|
||||
fn on_state_update(&mut self, _world: &mut World, _delta: f32) {}
|
||||
|
||||
fn on_state_physics_update(&mut self, world: &mut World, delta: f32)
|
||||
{
|
||||
use crate::utility::time::Time;
|
||||
|
||||
let (movement_input, walking_config) = world
|
||||
.movements
|
||||
.with(self.entity, |movement| {
|
||||
@@ -519,8 +316,8 @@ impl State for PlayerWalkingState
|
||||
|
||||
pub struct PlayerJumpingState
|
||||
{
|
||||
entity: EntityHandle,
|
||||
enter_time_stamp: f32,
|
||||
pub entity: EntityHandle,
|
||||
pub enter_time_stamp: f32,
|
||||
}
|
||||
|
||||
impl State for PlayerJumpingState
|
||||
@@ -532,7 +329,6 @@ impl State for PlayerJumpingState
|
||||
|
||||
fn on_state_enter(&mut self, world: &mut World)
|
||||
{
|
||||
use crate::utility::time::Time;
|
||||
self.enter_time_stamp = Time::get_time_elapsed();
|
||||
|
||||
let current_position = world.transforms.get(self.entity).unwrap().position;
|
||||
@@ -562,8 +358,6 @@ impl State for PlayerJumpingState
|
||||
|
||||
fn on_state_physics_update(&mut self, world: &mut World, delta: f32)
|
||||
{
|
||||
use crate::utility::time::Time;
|
||||
|
||||
let current_time = Time::get_time_elapsed();
|
||||
|
||||
world.jumps.with_mut(self.entity, |jump| {
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::mesh::InstanceRaw;
|
||||
use crate::loaders::mesh::InstanceRaw;
|
||||
use crate::render::DrawCall;
|
||||
use crate::world::World;
|
||||
use bytemuck::cast_slice;
|
||||
@@ -56,6 +56,10 @@ pub fn render_system(world: &World) -> Vec<DrawCall>
|
||||
pipeline: mesh_component.pipeline,
|
||||
instance_buffer,
|
||||
num_instances,
|
||||
tile_scale: mesh_component.tile_scale,
|
||||
enable_dissolve: mesh_component.enable_dissolve,
|
||||
enable_snow_light: mesh_component.enable_snow_light,
|
||||
displacement_bind_group: None,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
|
||||
13
src/systems/snow.rs
Normal file
13
src/systems/snow.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use crate::snow::SnowLayer;
|
||||
use crate::world::World;
|
||||
|
||||
pub fn snow_system(world: &World, snow_layer: &mut SnowLayer, noclip: bool)
|
||||
{
|
||||
let camera_pos = world.active_camera_position();
|
||||
let player_pos = world.player_position();
|
||||
if !noclip
|
||||
{
|
||||
snow_layer.deform_at_position(player_pos, 1.5, 10.0);
|
||||
}
|
||||
snow_layer.update(camera_pos);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::components::DissolveComponent;
|
||||
use crate::world::World;
|
||||
use glam::Vec3;
|
||||
|
||||
pub fn tree_dissolve_update_system(world: &mut World, delta: f32)
|
||||
{
|
||||
@@ -22,22 +21,9 @@ pub fn tree_occlusion_system(world: &mut World)
|
||||
|
||||
if let Some(player_pos) = player_pos
|
||||
{
|
||||
let camera_entity = world.cameras.get_active().map(|(e, _)| e);
|
||||
let camera_entity = world.active_camera().map(|(e, _)| e);
|
||||
let camera_pos = camera_entity.and_then(|e| world.transforms.get(e).map(|t| t.position));
|
||||
|
||||
let tree_count = world.tree_tags.all().len();
|
||||
if tree_count > 0
|
||||
{
|
||||
static mut FRAME_COUNT: u32 = 0;
|
||||
unsafe {
|
||||
FRAME_COUNT += 1;
|
||||
if FRAME_COUNT % 60 == 0
|
||||
{
|
||||
println!("Tree occlusion system: {} trees detected", tree_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(camera_pos) = camera_pos
|
||||
{
|
||||
let to_player = player_pos - camera_pos;
|
||||
@@ -74,18 +60,6 @@ pub fn tree_occlusion_system(world: &mut World)
|
||||
let dissolve_amount =
|
||||
1.0 - (perp_distance / occlusion_radius).clamp(0.0, 1.0);
|
||||
|
||||
static mut DEBUG_FRAME: u32 = 0;
|
||||
unsafe {
|
||||
DEBUG_FRAME += 1;
|
||||
if DEBUG_FRAME % 60 == 0
|
||||
{
|
||||
println!(
|
||||
"Tree occluding! perp_dist: {:.2}, dissolve: {:.2}",
|
||||
perp_distance, dissolve_amount
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(dissolve) = world.dissolves.get_mut(tree_entity)
|
||||
{
|
||||
dissolve.target_amount = dissolve_amount;
|
||||
|
||||
630
src/world.rs
630
src/world.rs
@@ -13,12 +13,12 @@ use crate::state::StateMachine;
|
||||
|
||||
pub use crate::utility::transform::Transform;
|
||||
|
||||
pub struct TransformStorage
|
||||
pub struct Storage<T>
|
||||
{
|
||||
pub components: HashMap<EntityHandle, Transform>,
|
||||
pub components: HashMap<EntityHandle, T>,
|
||||
}
|
||||
|
||||
impl TransformStorage
|
||||
impl<T> Storage<T>
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
@@ -27,31 +27,31 @@ impl TransformStorage
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, entity: EntityHandle, component: Transform)
|
||||
pub fn insert(&mut self, entity: EntityHandle, component: T)
|
||||
{
|
||||
self.components.insert(entity, component);
|
||||
}
|
||||
|
||||
pub fn get(&self, entity: EntityHandle) -> Option<&Transform>
|
||||
pub fn get(&self, entity: EntityHandle) -> Option<&T>
|
||||
{
|
||||
self.components.get(&entity)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, entity: EntityHandle) -> Option<&mut Transform>
|
||||
pub fn get_mut(&mut self, entity: EntityHandle) -> Option<&mut T>
|
||||
{
|
||||
self.components.get_mut(&entity)
|
||||
}
|
||||
|
||||
pub fn with<F, R>(&self, entity: EntityHandle, f: F) -> Option<R>
|
||||
where
|
||||
F: FnOnce(&Transform) -> R,
|
||||
F: FnOnce(&T) -> R,
|
||||
{
|
||||
self.components.get(&entity).map(f)
|
||||
}
|
||||
|
||||
pub fn with_mut<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
|
||||
where
|
||||
F: FnOnce(&mut Transform) -> R,
|
||||
F: FnOnce(&mut T) -> R,
|
||||
{
|
||||
self.components.get_mut(&entity).map(f)
|
||||
}
|
||||
@@ -67,554 +67,23 @@ impl TransformStorage
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MeshStorage
|
||||
{
|
||||
pub components: HashMap<EntityHandle, MeshComponent>,
|
||||
}
|
||||
|
||||
impl MeshStorage
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
components: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, entity: EntityHandle, component: MeshComponent)
|
||||
{
|
||||
self.components.insert(entity, component);
|
||||
}
|
||||
|
||||
pub fn get(&self, entity: EntityHandle) -> Option<&MeshComponent>
|
||||
{
|
||||
self.components.get(&entity)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, entity: EntityHandle)
|
||||
{
|
||||
self.components.remove(&entity);
|
||||
}
|
||||
|
||||
pub fn all(&self) -> Vec<EntityHandle>
|
||||
{
|
||||
self.components.keys().copied().collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PhysicsStorage
|
||||
{
|
||||
pub components: HashMap<EntityHandle, PhysicsComponent>,
|
||||
}
|
||||
|
||||
impl PhysicsStorage
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
components: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, entity: EntityHandle, component: PhysicsComponent)
|
||||
{
|
||||
self.components.insert(entity, component);
|
||||
}
|
||||
|
||||
pub fn get(&self, entity: EntityHandle) -> Option<PhysicsComponent>
|
||||
{
|
||||
self.components.get(&entity).copied()
|
||||
}
|
||||
|
||||
pub fn with<F, R>(&self, entity: EntityHandle, f: F) -> Option<R>
|
||||
where
|
||||
F: FnOnce(&PhysicsComponent) -> R,
|
||||
{
|
||||
self.components.get(&entity).map(f)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, entity: EntityHandle)
|
||||
{
|
||||
self.components.remove(&entity);
|
||||
}
|
||||
|
||||
pub fn all(&self) -> Vec<EntityHandle>
|
||||
{
|
||||
self.components.keys().copied().collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MovementStorage
|
||||
{
|
||||
pub components: HashMap<EntityHandle, MovementComponent>,
|
||||
}
|
||||
|
||||
impl MovementStorage
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
components: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, entity: EntityHandle, component: MovementComponent)
|
||||
{
|
||||
self.components.insert(entity, component);
|
||||
}
|
||||
|
||||
pub fn get(&self, entity: EntityHandle) -> Option<&MovementComponent>
|
||||
{
|
||||
self.components.get(&entity)
|
||||
}
|
||||
|
||||
pub fn with<F, R>(&self, entity: EntityHandle, f: F) -> Option<R>
|
||||
where
|
||||
F: FnOnce(&MovementComponent) -> R,
|
||||
{
|
||||
self.components.get(&entity).map(f)
|
||||
}
|
||||
|
||||
pub fn with_mut<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
|
||||
where
|
||||
F: FnOnce(&mut MovementComponent) -> R,
|
||||
{
|
||||
self.components.get_mut(&entity).map(f)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, entity: EntityHandle)
|
||||
{
|
||||
self.components.remove(&entity);
|
||||
}
|
||||
|
||||
pub fn all(&self) -> Vec<EntityHandle>
|
||||
{
|
||||
self.components.keys().copied().collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct JumpStorage
|
||||
{
|
||||
pub components: HashMap<EntityHandle, JumpComponent>,
|
||||
}
|
||||
|
||||
impl JumpStorage
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
components: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, entity: EntityHandle, component: JumpComponent)
|
||||
{
|
||||
self.components.insert(entity, component);
|
||||
}
|
||||
|
||||
pub fn get(&self, entity: EntityHandle) -> Option<&JumpComponent>
|
||||
{
|
||||
self.components.get(&entity)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, entity: EntityHandle) -> Option<&mut JumpComponent>
|
||||
{
|
||||
self.components.get_mut(&entity)
|
||||
}
|
||||
|
||||
pub fn with<F, R>(&self, entity: EntityHandle, f: F) -> Option<R>
|
||||
where
|
||||
F: FnOnce(&JumpComponent) -> R,
|
||||
{
|
||||
self.components.get(&entity).map(f)
|
||||
}
|
||||
|
||||
pub fn with_mut<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
|
||||
where
|
||||
F: FnOnce(&mut JumpComponent) -> R,
|
||||
{
|
||||
self.components.get_mut(&entity).map(f)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, entity: EntityHandle)
|
||||
{
|
||||
self.components.remove(&entity);
|
||||
}
|
||||
|
||||
pub fn all(&self) -> Vec<EntityHandle>
|
||||
{
|
||||
self.components.keys().copied().collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InputStorage
|
||||
{
|
||||
pub components: HashMap<EntityHandle, InputComponent>,
|
||||
}
|
||||
|
||||
impl InputStorage
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
components: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, entity: EntityHandle, component: InputComponent)
|
||||
{
|
||||
self.components.insert(entity, component);
|
||||
}
|
||||
|
||||
pub fn get(&self, entity: EntityHandle) -> Option<&InputComponent>
|
||||
{
|
||||
self.components.get(&entity)
|
||||
}
|
||||
|
||||
pub fn with<F, R>(&self, entity: EntityHandle, f: F) -> Option<R>
|
||||
where
|
||||
F: FnOnce(&InputComponent) -> R,
|
||||
{
|
||||
self.components.get(&entity).map(f)
|
||||
}
|
||||
|
||||
pub fn with_mut<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
|
||||
where
|
||||
F: FnOnce(&mut InputComponent) -> R,
|
||||
{
|
||||
self.components.get_mut(&entity).map(f)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, entity: EntityHandle)
|
||||
{
|
||||
self.components.remove(&entity);
|
||||
}
|
||||
|
||||
pub fn all(&self) -> Vec<EntityHandle>
|
||||
{
|
||||
self.components.keys().copied().collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PlayerTagStorage
|
||||
{
|
||||
pub components: HashMap<EntityHandle, ()>,
|
||||
}
|
||||
|
||||
impl PlayerTagStorage
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
components: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, entity: EntityHandle)
|
||||
{
|
||||
self.components.insert(entity, ());
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, entity: EntityHandle)
|
||||
{
|
||||
self.components.remove(&entity);
|
||||
}
|
||||
|
||||
pub fn all(&self) -> Vec<EntityHandle>
|
||||
{
|
||||
self.components.keys().copied().collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StateMachineStorage
|
||||
{
|
||||
pub components: HashMap<EntityHandle, StateMachine>,
|
||||
}
|
||||
|
||||
impl StateMachineStorage
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
components: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, entity: EntityHandle, component: StateMachine)
|
||||
{
|
||||
self.components.insert(entity, component);
|
||||
}
|
||||
|
||||
pub fn with_mut<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
|
||||
where
|
||||
F: FnOnce(&mut StateMachine) -> R,
|
||||
{
|
||||
self.components.get_mut(&entity).map(f)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, entity: EntityHandle)
|
||||
{
|
||||
self.components.remove(&entity);
|
||||
}
|
||||
|
||||
pub fn all(&self) -> Vec<EntityHandle>
|
||||
{
|
||||
self.components.keys().copied().collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CameraStorage
|
||||
{
|
||||
pub components: HashMap<EntityHandle, CameraComponent>,
|
||||
}
|
||||
|
||||
impl CameraStorage
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
components: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, entity: EntityHandle, component: CameraComponent)
|
||||
{
|
||||
self.components.insert(entity, component);
|
||||
}
|
||||
|
||||
pub fn get(&self, entity: EntityHandle) -> Option<&CameraComponent>
|
||||
{
|
||||
self.components.get(&entity)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, entity: EntityHandle) -> Option<&mut CameraComponent>
|
||||
{
|
||||
self.components.get_mut(&entity)
|
||||
}
|
||||
|
||||
pub fn with_mut<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
|
||||
where
|
||||
F: FnOnce(&mut CameraComponent) -> R,
|
||||
{
|
||||
self.components.get_mut(&entity).map(f)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, entity: EntityHandle)
|
||||
{
|
||||
self.components.remove(&entity);
|
||||
}
|
||||
|
||||
pub fn all(&self) -> Vec<EntityHandle>
|
||||
{
|
||||
self.components.keys().copied().collect()
|
||||
}
|
||||
|
||||
pub fn get_active(&self) -> Option<(EntityHandle, &CameraComponent)>
|
||||
{
|
||||
self.components
|
||||
.iter()
|
||||
.find(|(_, cam)| cam.is_active)
|
||||
.map(|(e, c)| (*e, c))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SpotlightStorage
|
||||
{
|
||||
pub components: HashMap<EntityHandle, SpotlightComponent>,
|
||||
}
|
||||
|
||||
impl SpotlightStorage
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
components: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, entity: EntityHandle, component: SpotlightComponent)
|
||||
{
|
||||
self.components.insert(entity, component);
|
||||
}
|
||||
|
||||
pub fn get(&self, entity: EntityHandle) -> Option<&SpotlightComponent>
|
||||
{
|
||||
self.components.get(&entity)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, entity: EntityHandle) -> Option<&mut SpotlightComponent>
|
||||
{
|
||||
self.components.get_mut(&entity)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, entity: EntityHandle)
|
||||
{
|
||||
self.components.remove(&entity);
|
||||
}
|
||||
|
||||
pub fn all(&self) -> Vec<EntityHandle>
|
||||
{
|
||||
self.components.keys().copied().collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TreeTagStorage
|
||||
{
|
||||
pub components: HashMap<EntityHandle, ()>,
|
||||
}
|
||||
|
||||
impl TreeTagStorage
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
components: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, entity: EntityHandle)
|
||||
{
|
||||
self.components.insert(entity, ());
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, entity: EntityHandle)
|
||||
{
|
||||
self.components.remove(&entity);
|
||||
}
|
||||
|
||||
pub fn all(&self) -> Vec<EntityHandle>
|
||||
{
|
||||
self.components.keys().copied().collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DissolveStorage
|
||||
{
|
||||
pub components: HashMap<EntityHandle, DissolveComponent>,
|
||||
}
|
||||
|
||||
impl DissolveStorage
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
components: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, entity: EntityHandle, component: DissolveComponent)
|
||||
{
|
||||
self.components.insert(entity, component);
|
||||
}
|
||||
|
||||
pub fn get(&self, entity: EntityHandle) -> Option<&DissolveComponent>
|
||||
{
|
||||
self.components.get(&entity)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, entity: EntityHandle) -> Option<&mut DissolveComponent>
|
||||
{
|
||||
self.components.get_mut(&entity)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, entity: EntityHandle)
|
||||
{
|
||||
self.components.remove(&entity);
|
||||
}
|
||||
|
||||
pub fn all(&self) -> Vec<EntityHandle>
|
||||
{
|
||||
self.components.keys().copied().collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FollowStorage
|
||||
{
|
||||
pub components: HashMap<EntityHandle, FollowComponent>,
|
||||
}
|
||||
|
||||
impl FollowStorage
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
components: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, entity: EntityHandle, component: FollowComponent)
|
||||
{
|
||||
self.components.insert(entity, component);
|
||||
}
|
||||
|
||||
pub fn get(&self, entity: EntityHandle) -> Option<&FollowComponent>
|
||||
{
|
||||
self.components.get(&entity)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, entity: EntityHandle)
|
||||
{
|
||||
self.components.remove(&entity);
|
||||
}
|
||||
|
||||
pub fn all(&self) -> Vec<EntityHandle>
|
||||
{
|
||||
self.components.keys().copied().collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RotateStorage
|
||||
{
|
||||
pub components: HashMap<EntityHandle, RotateComponent>,
|
||||
}
|
||||
|
||||
impl RotateStorage
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
components: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, entity: EntityHandle, component: RotateComponent)
|
||||
{
|
||||
self.components.insert(entity, component);
|
||||
}
|
||||
|
||||
pub fn get(&self, entity: EntityHandle) -> Option<&RotateComponent>
|
||||
{
|
||||
self.components.get(&entity)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, entity: EntityHandle)
|
||||
{
|
||||
self.components.remove(&entity);
|
||||
}
|
||||
|
||||
pub fn all(&self) -> Vec<EntityHandle>
|
||||
{
|
||||
self.components.keys().copied().collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct World
|
||||
{
|
||||
pub entities: EntityManager,
|
||||
pub transforms: TransformStorage,
|
||||
pub meshes: MeshStorage,
|
||||
pub physics: PhysicsStorage,
|
||||
pub movements: MovementStorage,
|
||||
pub jumps: JumpStorage,
|
||||
pub inputs: InputStorage,
|
||||
pub player_tags: PlayerTagStorage,
|
||||
pub state_machines: StateMachineStorage,
|
||||
pub cameras: CameraStorage,
|
||||
pub spotlights: SpotlightStorage,
|
||||
pub tree_tags: TreeTagStorage,
|
||||
pub dissolves: DissolveStorage,
|
||||
pub follows: FollowStorage,
|
||||
pub rotates: RotateStorage,
|
||||
pub transforms: Storage<Transform>,
|
||||
pub meshes: Storage<MeshComponent>,
|
||||
pub physics: Storage<PhysicsComponent>,
|
||||
pub movements: Storage<MovementComponent>,
|
||||
pub jumps: Storage<JumpComponent>,
|
||||
pub inputs: Storage<InputComponent>,
|
||||
pub player_tags: Storage<()>,
|
||||
pub state_machines: Storage<StateMachine>,
|
||||
pub cameras: Storage<CameraComponent>,
|
||||
pub spotlights: Storage<SpotlightComponent>,
|
||||
pub tree_tags: Storage<()>,
|
||||
pub dissolves: Storage<DissolveComponent>,
|
||||
pub follows: Storage<FollowComponent>,
|
||||
pub rotates: Storage<RotateComponent>,
|
||||
}
|
||||
|
||||
impl World
|
||||
@@ -623,20 +92,20 @@ impl World
|
||||
{
|
||||
Self {
|
||||
entities: EntityManager::new(),
|
||||
transforms: TransformStorage::new(),
|
||||
meshes: MeshStorage::new(),
|
||||
physics: PhysicsStorage::new(),
|
||||
movements: MovementStorage::new(),
|
||||
jumps: JumpStorage::new(),
|
||||
inputs: InputStorage::new(),
|
||||
player_tags: PlayerTagStorage::new(),
|
||||
state_machines: StateMachineStorage::new(),
|
||||
cameras: CameraStorage::new(),
|
||||
spotlights: SpotlightStorage::new(),
|
||||
tree_tags: TreeTagStorage::new(),
|
||||
dissolves: DissolveStorage::new(),
|
||||
follows: FollowStorage::new(),
|
||||
rotates: RotateStorage::new(),
|
||||
transforms: Storage::new(),
|
||||
meshes: Storage::new(),
|
||||
physics: Storage::new(),
|
||||
movements: Storage::new(),
|
||||
jumps: Storage::new(),
|
||||
inputs: Storage::new(),
|
||||
player_tags: Storage::new(),
|
||||
state_machines: Storage::new(),
|
||||
cameras: Storage::new(),
|
||||
spotlights: Storage::new(),
|
||||
tree_tags: Storage::new(),
|
||||
dissolves: Storage::new(),
|
||||
follows: Storage::new(),
|
||||
rotates: Storage::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -663,4 +132,31 @@ impl World
|
||||
self.rotates.remove(entity);
|
||||
self.entities.despawn(entity);
|
||||
}
|
||||
|
||||
pub fn active_camera(&self) -> Option<(EntityHandle, &CameraComponent)>
|
||||
{
|
||||
self.cameras
|
||||
.components
|
||||
.iter()
|
||||
.find(|(_, cam)| cam.is_active)
|
||||
.map(|(e, c)| (*e, c))
|
||||
}
|
||||
|
||||
pub fn active_camera_position(&self) -> glam::Vec3
|
||||
{
|
||||
self.active_camera()
|
||||
.and_then(|(entity, _)| self.transforms.get(entity))
|
||||
.map(|t| t.position)
|
||||
.unwrap_or(glam::Vec3::ZERO)
|
||||
}
|
||||
|
||||
pub fn player_position(&self) -> glam::Vec3
|
||||
{
|
||||
self.player_tags
|
||||
.all()
|
||||
.first()
|
||||
.and_then(|e| self.transforms.get(*e))
|
||||
.map(|t| t.position)
|
||||
.unwrap_or(glam::Vec3::ZERO)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user