render iteration

This commit is contained in:
Jonas H
2026-02-08 14:06:35 +01:00
parent 2422106725
commit 82c3e1e3b0
67 changed files with 6381 additions and 1564 deletions

View File

@@ -1,6 +1,11 @@
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
@@ -26,147 +31,21 @@ impl CameraUniforms
}
}
pub struct Camera
{
pub position: Vec3,
pub target: Vec3,
pub up: Vec3,
pub fov: f32,
pub aspect: f32,
pub near: f32,
pub far: f32,
pub yaw: f32,
pub pitch: f32,
pub is_following: bool,
pub follow_offset: Vec3,
}
pub struct Camera;
impl Camera
{
pub fn init(aspect: f32) -> Self
pub fn spawn(world: &mut World, position: Vec3) -> EntityHandle
{
Self {
position: Vec3::new(15.0, 15.0, 15.0),
target: Vec3::ZERO,
up: Vec3::Y,
fov: 45.0_f32.to_radians(),
aspect,
near: 0.1,
far: 100.0,
yaw: -135.0_f32.to_radians(),
pitch: -30.0_f32.to_radians(),
is_following: true,
follow_offset: Vec3::ZERO,
}
}
let camera_entity = world.spawn();
pub fn view_matrix(&self) -> Mat4
{
Mat4::look_at_rh(self.position, self.target, self.up)
}
let camera_component = CameraComponent::new(render::aspect_ratio());
pub fn projection_matrix(&self) -> Mat4
{
Mat4::perspective_rh(self.fov, self.aspect, self.near, self.far)
}
let transform = Transform::from_position(position);
pub fn update_rotation(&mut self, mouse_delta: (f32, f32), sensitivity: f32)
{
self.yaw += mouse_delta.0 * sensitivity;
self.pitch -= mouse_delta.1 * sensitivity;
world.cameras.insert(camera_entity, camera_component);
world.transforms.insert(camera_entity, transform);
self.pitch = self
.pitch
.clamp(-89.0_f32.to_radians(), 89.0_f32.to_radians());
}
pub fn get_forward(&self) -> Vec3
{
Vec3::new(
self.yaw.cos() * self.pitch.cos(),
self.pitch.sin(),
self.yaw.sin() * self.pitch.cos(),
)
.normalize()
}
pub fn get_right(&self) -> Vec3
{
self.get_forward().cross(Vec3::Y).normalize()
}
pub fn get_forward_horizontal(&self) -> Vec3
{
Vec3::new(self.yaw.cos(), 0.0, self.yaw.sin()).normalize()
}
pub fn get_right_horizontal(&self) -> Vec3
{
self.get_forward_horizontal().cross(Vec3::Y).normalize()
}
pub fn update_noclip(&mut self, input: Vec3, speed: f32)
{
let forward = self.get_forward();
let right = self.get_right();
self.position += forward * input.z * speed;
self.position += right * input.x * speed;
self.position += Vec3::Y * input.y * speed;
self.target = self.position + forward;
}
pub fn start_following(&mut self, target_position: Vec3)
{
self.is_following = true;
self.follow_offset = self.position - target_position;
let distance = self.follow_offset.length();
if distance > 0.0
{
self.pitch = (self.follow_offset.y / distance).asin();
self.yaw = self.follow_offset.z.atan2(self.follow_offset.x) + std::f32::consts::PI;
}
}
pub fn stop_following(&mut self)
{
self.is_following = false;
let look_direction = (self.target - self.position).normalize();
self.yaw = look_direction.z.atan2(look_direction.x);
self.pitch = look_direction.y.asin();
}
pub fn update_follow(&mut self, target_position: Vec3, mouse_delta: (f32, f32), sensitivity: f32)
{
if !self.is_following
{
return;
}
if mouse_delta.0.abs() > 0.0 || mouse_delta.1.abs() > 0.0
{
self.yaw += mouse_delta.0 * sensitivity;
self.pitch += mouse_delta.1 * sensitivity;
self.pitch = self
.pitch
.clamp(-89.0_f32.to_radians(), 89.0_f32.to_radians());
}
let distance = self.follow_offset.length();
let orbit_yaw = self.yaw + std::f32::consts::PI;
let offset_x = distance * orbit_yaw.cos() * self.pitch.cos();
let offset_y = distance * self.pitch.sin();
let offset_z = distance * orbit_yaw.sin() * self.pitch.cos();
self.follow_offset = Vec3::new(offset_x, offset_y, offset_z);
self.position = target_position + self.follow_offset;
self.target = target_position;
camera_entity
}
}

View File

@@ -1,32 +0,0 @@
use glam::Vec3;
use crate::entity::EntityHandle;
#[derive(Clone, Copy)]
pub struct CameraFollowComponent
{
pub target_entity: EntityHandle,
pub offset: Vec3,
pub is_following: bool,
}
impl CameraFollowComponent
{
pub fn new(target_entity: EntityHandle) -> Self
{
Self {
target_entity,
offset: Vec3::ZERO,
is_following: false,
}
}
pub fn with_offset(target_entity: EntityHandle, offset: Vec3) -> Self
{
Self {
target_entity,
offset,
is_following: true,
}
}
}

View File

@@ -0,0 +1,27 @@
pub struct DissolveComponent
{
pub amount: f32,
pub target_amount: f32,
pub transition_speed: f32,
}
impl DissolveComponent
{
pub fn new() -> Self
{
Self {
amount: 0.0,
target_amount: 0.0,
transition_speed: 3.0,
}
}
pub fn with_speed(transition_speed: f32) -> Self
{
Self {
amount: 0.0,
target_amount: 0.0,
transition_speed,
}
}
}

10
src/components/follow.rs Normal file
View File

@@ -0,0 +1,10 @@
use crate::entity::EntityHandle;
use crate::utility::transform::Transform;
pub struct FollowComponent
{
pub target: EntityHandle,
pub offset: Transform,
pub inherit_rotation: bool,
pub inherit_scale: bool,
}

View File

@@ -41,12 +41,7 @@ impl Default for JumpConfig
max_air_momentum: 8.0,
air_damping_active: 0.4,
air_damping_passive: 0.9,
jump_curve: CubicBez::new(
(0.0, 0.0),
(0.4, 0.75),
(0.7, 0.9),
(1.0, 1.0),
),
jump_curve: CubicBez::new((0.0, 0.0), (0.4, 0.75), (0.7, 0.9), (1.0, 1.0)),
jump_context: JumpContext::default(),
}
}

View File

@@ -0,0 +1,18 @@
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(),
}
}
}

View File

@@ -0,0 +1,3 @@
pub mod directional;
pub mod point;
pub mod spot;

View File

@@ -0,0 +1,14 @@
use glam::Vec3;
pub struct PointlightComponent
{
pub offset: Vec3,
}
impl PointlightComponent
{
pub fn new(offset: Vec3) -> Self
{
Self { offset }
}
}

View File

@@ -0,0 +1,25 @@
use glam::Vec3;
#[derive(Debug, Copy, Clone)]
pub struct SpotlightComponent
{
pub offset: Vec3,
pub direction: Vec3,
pub range: f32,
pub inner_angle: f32,
pub outer_angle: f32,
}
impl SpotlightComponent
{
pub fn new(offset: Vec3, direction: Vec3, range: f32, inner_angle: f32, outer_angle: f32) -> Self
{
Self {
offset,
direction: direction.normalize(),
range,
inner_angle,
outer_angle,
}
}
}

View File

@@ -1,16 +1,22 @@
pub mod camera;
pub mod camera_follow;
pub mod dissolve;
pub mod follow;
pub mod input;
pub mod jump;
pub mod lights;
pub mod mesh;
pub mod movement;
pub mod physics;
pub mod player_tag;
pub mod rotate;
pub mod state_machine;
pub mod tree_tag;
pub use camera::CameraComponent;
pub use camera_follow::CameraFollowComponent;
pub use dissolve::DissolveComponent;
pub use follow::FollowComponent;
pub use input::InputComponent;
pub use mesh::MeshComponent;
pub use movement::MovementComponent;
pub use physics::PhysicsComponent;
pub use rotate::RotateComponent;

View File

@@ -43,7 +43,7 @@ impl MovementConfig
(1.0, 1.0),
),
walking_damping: 0.8,
max_walking_speed: 6.0,
max_walking_speed: 12.0,
idle_damping: 0.1,
movement_context: MovementContext::new(),
}

15
src/components/rotate.rs Normal file
View File

@@ -0,0 +1,15 @@
use glam::Vec3;
pub struct RotateComponent
{
pub axis: Vec3,
pub speed: f32,
}
impl RotateComponent
{
pub fn new(axis: Vec3, speed: f32) -> Self
{
Self { axis, speed }
}
}

View File

@@ -0,0 +1 @@
pub struct TreeTag;

View File

@@ -126,6 +126,8 @@ pub fn render_collider_debug() -> Vec<DrawCall>
let instance_data = InstanceRaw {
model: model.to_cols_array_2d(),
dissolve_amount: 0.0,
_padding: [0.0; 3],
};
let instance_buffer = render::with_device(|device| {
@@ -153,6 +155,8 @@ pub fn render_collider_debug() -> Vec<DrawCall>
{
let instance_data = InstanceRaw {
model: Mat4::IDENTITY.to_cols_array_2d(),
dissolve_amount: 0.0,
_padding: [0.0; 3],
};
let instance_buffer = render::with_device(|device| {

View File

@@ -1,5 +1,3 @@
pub mod collider_debug;
pub mod noclip;
pub use collider_debug::{render_collider_debug, set_debug_heightfield};
pub use noclip::{update_follow_camera, update_noclip_camera};
pub use collider_debug::render_collider_debug;

101
src/empty.rs Normal file
View File

@@ -0,0 +1,101 @@
use glam::Mat4;
use std::path::Path;
pub struct EmptyNode
{
pub name: String,
pub transform: Mat4,
}
impl EmptyNode
{
pub fn new(name: String, transform: Mat4) -> Self
{
Self { name, transform }
}
}
pub struct Empties
{
nodes: Vec<EmptyNode>,
}
impl Empties
{
fn new(nodes: Vec<EmptyNode>) -> Self
{
Self { nodes }
}
pub fn into_nodes(self) -> Vec<EmptyNode>
{
self.nodes
}
pub fn load_gltf_empties(path: impl AsRef<Path>)
-> Result<Empties, Box<dyn std::error::Error>>
{
let (gltf, _buffers, _images) = gltf::import(path)?;
let mut all_empties = Vec::new();
for scene in gltf.scenes()
{
for node in scene.nodes()
{
Self::process_node(&node, Mat4::IDENTITY, &mut all_empties)?;
}
}
Ok(Empties::new(all_empties))
}
fn process_node(
node: &gltf::Node,
parent_transform: Mat4,
all_empties: &mut Vec<EmptyNode>,
) -> Result<(), Box<dyn std::error::Error>>
{
let local_transform = Mat4::from_cols_array_2d(&node.transform().matrix());
let global_transform = parent_transform * local_transform;
let is_empty = node.mesh().is_none() && node.light().is_none() && node.camera().is_none();
if is_empty
{
let name = node.name().unwrap_or("Unnamed").to_string();
all_empties.push(EmptyNode::new(name, global_transform));
}
for child in node.children()
{
Self::process_node(&child, global_transform, all_empties)?;
}
Ok(())
}
pub fn load_empties(path: impl AsRef<Path>) -> Result<Empties, Box<dyn std::error::Error>>
{
Self::load_gltf_empties(path)
}
pub fn get_empty_by_name(
gltf_path: &str,
name: &str,
) -> anyhow::Result<Option<crate::empty::EmptyNode>>
{
let empties = Self::load_empties(gltf_path)
.map_err(|e| anyhow::anyhow!("Failed to load empty nodes: {}", e))?;
for empty_node in empties.into_nodes()
{
if empty_node.name == name
{
return Ok(Some(empty_node));
}
}
Ok(None)
}
}

153
src/light.rs Normal file
View File

@@ -0,0 +1,153 @@
use glam::{Mat4, Vec3};
use gltf::json::Extras;
use std::{ops::Deref, path::Path};
use crate::{
components::lights::spot::SpotlightComponent,
world::{Transform, World},
};
pub struct LightData
{
pub component: SpotlightComponent,
pub transform: Mat4,
pub tag: Option<String>,
}
pub struct Lights
{
spotlights: Vec<LightData>,
}
impl Lights
{
fn new(spotlights: Vec<LightData>) -> Self
{
Self { spotlights }
}
pub fn into_spotlights(self) -> Vec<LightData>
{
self.spotlights
}
pub fn load_gltf_lights(path: impl AsRef<Path>) -> Result<Lights, Box<dyn std::error::Error>>
{
let (gltf, _buffers, _images) = gltf::import(path)?;
let mut all_directional = Vec::new();
let mut all_point = Vec::new();
let mut all_spot = Vec::new();
for scene in gltf.scenes()
{
for node in scene.nodes()
{
Self::process_node(
&node,
Mat4::IDENTITY,
&mut all_directional,
&mut all_point,
&mut all_spot,
)?;
}
}
Ok(Lights::new(all_spot))
}
fn process_node(
node: &gltf::Node,
parent_transform: Mat4,
all_directional: &mut Vec<LightData>,
all_point: &mut Vec<LightData>,
all_spot: &mut Vec<LightData>,
) -> Result<(), Box<dyn std::error::Error>>
{
let local_transform = Mat4::from_cols_array_2d(&node.transform().matrix());
let global_transform = parent_transform * local_transform;
if let Some(light) = node.light()
{
let local_transform = Mat4::from_cols_array_2d(&node.transform().matrix());
let global_transform = parent_transform * local_transform;
let (_scale, rotation, _translation) = global_transform.to_scale_rotation_translation();
let tag = serde_json::to_value(light.extras())
.ok()
.and_then(|extras| {
extras
.get("light_tag")
.and_then(|v| v.as_str())
.map(String::from)
});
match light.kind()
{
gltf::khr_lights_punctual::Kind::Directional => todo!(),
gltf::khr_lights_punctual::Kind::Point => todo!(),
gltf::khr_lights_punctual::Kind::Spot {
inner_cone_angle,
outer_cone_angle,
} =>
{
let range = light.range().unwrap_or(100.0);
let spotlight = SpotlightComponent::new(
Vec3::ZERO,
rotation * -Vec3::Z,
range,
inner_cone_angle,
outer_cone_angle,
);
all_spot.push(LightData {
component: spotlight,
transform: global_transform,
tag,
});
},
}
}
for child in node.children()
{
Self::process_node(
&child,
global_transform,
all_directional,
all_point,
all_spot,
)?;
}
Ok(())
}
pub fn load_lights(path: impl AsRef<Path>) -> Result<Lights, Box<dyn std::error::Error>>
{
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));
}
}
}
}
}

View File

@@ -2,20 +2,24 @@ mod camera;
mod components;
mod debug;
mod draw;
mod empty;
mod entity;
mod event;
mod heightmap;
mod light;
mod mesh;
mod physics;
mod picking;
mod player;
mod postprocess;
mod render;
mod shader;
mod snow;
mod snow_light;
mod space;
mod state;
mod systems;
mod terrain;
mod texture_loader;
mod texture;
mod utility;
mod world;
@@ -26,16 +30,21 @@ use render::Renderer;
use utility::input::InputState;
use world::{Transform, World};
use crate::components::{CameraComponent, CameraFollowComponent};
use crate::camera::Camera;
use crate::components::CameraComponent;
use crate::debug::render_collider_debug;
use crate::entity::EntityHandle;
use crate::light::Lights;
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, start_camera_following, state_machine_physics_system,
state_machine_system, stop_camera_following,
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::utility::time::Time;
@@ -45,26 +54,43 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
let video_subsystem = sdl_context.video()?;
let window = video_subsystem
.window("snow_trail", 800, 600)
.window("snow_trail", 1200, 900)
.position_centered()
.resizable()
.vulkan()
.build()?;
let renderer = pollster::block_on(Renderer::new(&window, 1))?;
let renderer = pollster::block_on(Renderer::new(&window, 2))?;
render::init(renderer);
let space = Space::load_space("meshes/terrain.gltf")?;
let terrain_config = TerrainConfig::default();
let player_spawn = space.player_spawn;
let camera_spawn = space.camera_spawn_position();
let mut world = World::new();
let player_entity = Player::spawn(&mut world);
let _terrain_entity = Terrain::spawn(&mut world, &terrain_config)?;
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);
render::set_terrain_data();
let terrain_half_size = terrain_config.size / 2.0;
render::init_snow_light_accumulation(
glam::Vec2::new(-terrain_half_size.x, -terrain_half_size.y),
glam::Vec2::new(terrain_half_size.x, terrain_half_size.y),
);
let snow_config = SnowConfig::default();
let snow_layer = SnowLayer::load(&mut world, &snow_config)?;
println!("Snow layer loaded successfully");
render::set_snow_depth(&snow_layer.depth_texture_view);
let mut noclip_mode = true;
let camera_entity = spawn_camera(&mut world, player_entity);
let camera_entity = Camera::spawn(&mut world, camera_spawn);
if noclip_mode == false
{
start_camera_following(&mut world, camera_entity);
@@ -150,6 +176,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
state_machine_system(&mut world, delta);
rotate_system(&mut world, delta);
let spotlights = spotlight_sync_system(&world);
render::update_spotlights(spotlights);
let mut draw_calls = render_system(&world);
draw_calls.extend(render_collider_debug());
@@ -161,12 +192,22 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
get_view_matrix(&world, camera_entity, camera_transform, camera_component);
let projection = camera_component.projection_matrix();
render::render_with_matrices(
let player_pos = world
.player_tags
.all()
.first()
.and_then(|e| world.transforms.get(*e))
.map(|t| t.position)
.unwrap_or(Vec3::ZERO);
render::render(
&view,
&projection,
camera_transform.position,
player_pos,
&draw_calls,
time,
delta,
);
}
}
@@ -183,27 +224,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
Ok(())
}
fn spawn_camera(world: &mut World, target_entity: EntityHandle) -> EntityHandle
{
let camera_entity = world.spawn();
let camera_component = CameraComponent::new(render::aspect_ratio());
let camera_follow = CameraFollowComponent::new(target_entity);
let initial_position = Vec3::new(15.0, 15.0, 15.0);
let transform = Transform {
position: initial_position,
rotation: glam::Quat::IDENTITY,
scale: Vec3::ONE,
};
world.cameras.insert(camera_entity, camera_component);
world.camera_follows.insert(camera_entity, camera_follow);
world.transforms.insert(camera_entity, transform);
camera_entity
}
fn get_view_matrix(
world: &World,
camera_entity: EntityHandle,
@@ -211,18 +231,15 @@ fn get_view_matrix(
camera_component: &CameraComponent,
) -> glam::Mat4
{
if let Some(follow) = world.camera_follows.get(camera_entity)
if let Some(follow) = world.follows.get(camera_entity)
{
if follow.is_following
if let Some(target_transform) = world.transforms.get(follow.target)
{
if let Some(target_transform) = world.transforms.get(follow.target_entity)
{
return glam::Mat4::look_at_rh(
camera_transform.position,
target_transform.position,
Vec3::Y,
);
}
return glam::Mat4::look_at_rh(
camera_transform.position,
target_transform.position,
Vec3::Y,
);
}
}

View File

@@ -48,6 +48,7 @@ pub struct InstanceData
pub position: Vec3,
pub rotation: Quat,
pub scale: Vec3,
pub dissolve_amount: f32,
}
impl InstanceData
@@ -57,6 +58,8 @@ impl InstanceData
let model = Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.position);
InstanceRaw {
model: model.to_cols_array_2d(),
dissolve_amount: self.dissolve_amount,
_padding: [0.0; 3],
}
}
}
@@ -66,6 +69,8 @@ impl InstanceData
pub struct InstanceRaw
{
pub model: [[f32; 4]; 4],
pub dissolve_amount: f32,
pub _padding: [f32; 3],
}
impl InstanceRaw
@@ -96,6 +101,11 @@ impl InstanceRaw
shader_location: 6,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: (std::mem::size_of::<[f32; 4]>() * 4) as wgpu::BufferAddress,
shader_location: 7,
format: wgpu::VertexFormat::Float32,
},
],
}
}
@@ -506,88 +516,92 @@ impl Mesh
{
let extensions = json_node.get("extensions").unwrap();
let instancing_ext = extensions.get("EXT_mesh_gpu_instancing").unwrap();
let mut mesh_vertices = Vec::new();
let mut mesh_indices = Vec::new();
let mut mesh_vertices = Vec::new();
let mut mesh_indices = Vec::new();
for primitive in mesh_data.primitives()
for primitive in mesh_data.primitives()
{
let reader = primitive
.reader(|buffer| buffers.get(buffer.index()).map(|data| &data[..]));
let positions = reader
.read_positions()
.ok_or_else(|| anyhow::anyhow!("Missing position data"))?
.collect::<Vec<[f32; 3]>>();
let normals = reader
.read_normals()
.ok_or_else(|| anyhow::anyhow!("Missing normal data"))?
.collect::<Vec<[f32; 3]>>();
let uvs = reader
.read_tex_coords(0)
.map(|iter| iter.into_f32().collect::<Vec<[f32; 2]>>())
.unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]);
let base_index = mesh_vertices.len() as u32;
for ((pos, normal), uv) in
positions.iter().zip(normals.iter()).zip(uvs.iter())
{
let reader = primitive
.reader(|buffer| buffers.get(buffer.index()).map(|data| &data[..]));
let positions = reader
.read_positions()
.ok_or_else(|| anyhow::anyhow!("Missing position data"))?
.collect::<Vec<[f32; 3]>>();
let normals = reader
.read_normals()
.ok_or_else(|| anyhow::anyhow!("Missing normal data"))?
.collect::<Vec<[f32; 3]>>();
let uvs = reader
.read_tex_coords(0)
.map(|iter| iter.into_f32().collect::<Vec<[f32; 2]>>())
.unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]);
let base_index = mesh_vertices.len() as u32;
for ((pos, normal), uv) in
positions.iter().zip(normals.iter()).zip(uvs.iter())
{
mesh_vertices.push(Vertex {
position: *pos,
normal: *normal,
uv: *uv,
});
}
if let Some(indices_reader) = reader.read_indices()
{
mesh_indices
.extend(indices_reader.into_u32().map(|i| i + base_index));
}
mesh_vertices.push(Vertex {
position: *pos,
normal: *normal,
uv: *uv,
});
}
let attributes = instancing_ext
.get("attributes")
.and_then(|v| v.as_object())
.ok_or_else(|| anyhow::anyhow!("Missing attributes in EXT_mesh_gpu_instancing"))?;
if let Some(indices_reader) = reader.read_indices()
{
mesh_indices.extend(indices_reader.into_u32().map(|i| i + base_index));
}
}
let translation_accessor_index = attributes
.get("TRANSLATION")
.and_then(|v| v.as_u64())
.ok_or_else(|| anyhow::anyhow!("Missing TRANSLATION in instancing extension"))? as usize;
let attributes = instancing_ext
.get("attributes")
.and_then(|v| v.as_object())
.ok_or_else(|| {
anyhow::anyhow!("Missing attributes in EXT_mesh_gpu_instancing")
})?;
let rotation_accessor_index = attributes
.get("ROTATION")
.and_then(|v| v.as_u64())
.ok_or_else(|| anyhow::anyhow!("Missing ROTATION in instancing extension"))? as usize;
let translation_accessor_index = attributes
.get("TRANSLATION")
.and_then(|v| v.as_u64())
.ok_or_else(|| {
anyhow::anyhow!("Missing TRANSLATION in instancing extension")
})? as usize;
let scale_accessor_index = attributes
.get("SCALE")
.and_then(|v| v.as_u64())
.ok_or_else(|| anyhow::anyhow!("Missing SCALE in instancing extension"))? as usize;
let rotation_accessor_index = attributes
.get("ROTATION")
.and_then(|v| v.as_u64())
.ok_or_else(|| {
anyhow::anyhow!("Missing ROTATION in instancing extension")
})? as usize;
let translations = Self::read_vec3_accessor(
&document,
&buffers,
translation_accessor_index,
)?;
let rotations =
Self::read_quat_accessor(&document, &buffers, rotation_accessor_index)?;
let scales =
Self::read_vec3_accessor(&document, &buffers, scale_accessor_index)?;
let scale_accessor_index = attributes
.get("SCALE")
.and_then(|v| v.as_u64())
.ok_or_else(|| anyhow::anyhow!("Missing SCALE in instancing extension"))?
as usize;
let instances: Vec<InstanceData> = translations
.into_iter()
.zip(rotations.into_iter())
.zip(scales.into_iter())
.map(|((position, rotation), scale)| InstanceData {
position,
rotation,
scale,
})
.collect();
let translations =
Self::read_vec3_accessor(&document, &buffers, translation_accessor_index)?;
let rotations =
Self::read_quat_accessor(&document, &buffers, rotation_accessor_index)?;
let scales =
Self::read_vec3_accessor(&document, &buffers, scale_accessor_index)?;
let instances: Vec<InstanceData> = translations
.into_iter()
.zip(rotations.into_iter())
.zip(scales.into_iter())
.map(|((position, rotation), scale)| InstanceData {
position,
rotation,
scale,
dissolve_amount: 0.0,
})
.collect();
let mesh = Mesh::new(device, &mesh_vertices, &mesh_indices);
result.push((mesh, instances));
@@ -655,7 +669,9 @@ impl Mesh
.nth(accessor_index)
.ok_or_else(|| anyhow::anyhow!("Invalid accessor index"))?;
let buffer_view = accessor.view().ok_or_else(|| anyhow::anyhow!("Missing buffer view"))?;
let buffer_view = accessor
.view()
.ok_or_else(|| anyhow::anyhow!("Missing buffer view"))?;
let buffer = &buffers[buffer_view.buffer().index()];
let start = buffer_view.offset() + accessor.offset();
let stride = buffer_view.stride().unwrap_or(12);
@@ -699,7 +715,9 @@ impl Mesh
.nth(accessor_index)
.ok_or_else(|| anyhow::anyhow!("Invalid accessor index"))?;
let buffer_view = accessor.view().ok_or_else(|| anyhow::anyhow!("Missing buffer view"))?;
let buffer_view = accessor
.view()
.ok_or_else(|| anyhow::anyhow!("Missing buffer view"))?;
let buffer = &buffers[buffer_view.buffer().index()];
let start = buffer_view.offset() + accessor.offset();
let stride = buffer_view.stride().unwrap_or(16);

View File

@@ -1,4 +1,4 @@
use std::rc::Rc;
use std::{f32::consts::PI, rc::Rc};
use glam::Vec3;
use kurbo::ParamCurve;
@@ -10,7 +10,8 @@ use rapier3d::{
use crate::{
components::{
jump::JumpComponent, InputComponent, MeshComponent, MovementComponent, PhysicsComponent,
jump::JumpComponent, lights::spot::SpotlightComponent, InputComponent, MeshComponent,
MovementComponent, PhysicsComponent,
},
entity::EntityHandle,
mesh::Mesh,
@@ -24,14 +25,14 @@ pub struct Player;
impl Player
{
pub fn spawn(world: &mut World) -> EntityHandle
pub fn spawn(world: &mut World, position: Vec3) -> EntityHandle
{
let entity = world.spawn();
let initial_position = Vec3::new(0.0, 5.0, 0.0);
let spawn_transform = Transform::from_position(position);
let rigidbody = RigidBodyBuilder::kinematic_position_based()
.translation(initial_position.into())
.translation(spawn_transform.position.into())
.build();
let collider = ColliderBuilder::capsule_y(0.5, 0.5).build();
let _controller = KinematicCharacterController {
@@ -163,7 +164,7 @@ impl Player
world
.transforms
.insert(entity, Transform::from_position(initial_position));
.insert(entity, spawn_transform);
world.movements.insert(entity, MovementComponent::new());
world.jumps.insert(entity, JumpComponent::new());
world.inputs.insert(entity, InputComponent::default());
@@ -178,7 +179,7 @@ impl Player
entity,
MeshComponent {
mesh: Rc::new(mesh),
pipeline: Pipeline::Render,
pipeline: Pipeline::Standard,
instance_buffer: None,
num_instances: 1,
},
@@ -186,6 +187,18 @@ impl Player
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
}
@@ -233,7 +246,8 @@ impl State for PlayerFallingState
.flatten()
.unwrap();
let terrain_height = PhysicsManager::get_terrain_height_at(current_pos.x, current_pos.z);
let next_pos = current_pos + velocity;
let terrain_height = PhysicsManager::get_terrain_height_at(next_pos.x, next_pos.z);
let is_grounded = if let Some(height) = terrain_height
{
@@ -553,7 +567,8 @@ impl State for PlayerJumpingState
let current_time = Time::get_time_elapsed();
world.jumps.with_mut(self.entity, |jump| {
jump.jump_config.jump_context.duration = current_time - jump.jump_config.jump_context.execution_time;
jump.jump_config.jump_context.duration =
current_time - jump.jump_config.jump_context.execution_time;
});
let jump_config = world

View File

@@ -147,7 +147,7 @@ pub fn create_blit_pipeline(
) -> wgpu::RenderPipeline
{
let shader_source =
std::fs::read_to_string("shaders/blit.wgsl").expect("Failed to read blit shader");
std::fs::read_to_string("src/shaders/blit.wgsl").expect("Failed to read blit shader");
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Blit Shader"),

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
use crate::mesh::{InstanceRaw, Vertex};
use wesl::{include_wesl, Wesl};
pub fn create_render_pipeline(
device: &wgpu::Device,
@@ -6,11 +7,12 @@ pub fn create_render_pipeline(
bind_group_layout: &wgpu::BindGroupLayout,
) -> wgpu::RenderPipeline
{
let shared_source =
std::fs::read_to_string("shaders/shared.wgsl").expect("Failed to read shared shader");
let standard_source =
std::fs::read_to_string("shaders/standard.wgsl").expect("Failed to read standard shader");
let shader_source = format!("{}\n{}", shared_source, standard_source);
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"),
@@ -67,3 +69,72 @@ pub fn create_render_pipeline(
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,
})
}

28
src/shaders/blit.wgsl Normal file
View File

@@ -0,0 +1,28 @@
struct VertexInput {
@location(0) position: vec2<f32>,
@location(1) uv: vec2<f32>,
}
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) uv: vec2<f32>,
}
@group(0) @binding(0)
var t_texture: texture_2d<f32>;
@group(0) @binding(1)
var t_sampler: sampler;
@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
var output: VertexOutput;
output.clip_position = vec4<f32>(input.position, 0.0, 1.0);
output.uv = input.uv;
return output;
}
@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
return textureSample(t_texture, t_sampler, input.uv);
}

View File

@@ -0,0 +1,95 @@
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);
}

14
src/shaders/shadow.wesl Normal file
View File

@@ -0,0 +1,14 @@
import package::shared::{ VertexInput, uniforms };
@vertex
fn vs_main(input: VertexInput) -> @builtin(position) vec4<f32> {
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);
return uniforms.light_view_projection * world_pos;
}

383
src/shaders/shared.wesl Normal file
View File

@@ -0,0 +1,383 @@
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>,
@location(2) uv: vec2<f32>,
@location(3) instance_model_0: vec4<f32>,
@location(4) instance_model_1: vec4<f32>,
@location(5) instance_model_2: vec4<f32>,
@location(6) instance_model_3: vec4<f32>,
@location(7) instance_dissolve: f32,
}
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,
}
const MAX_SPOTLIGHTS: u32 = 4u;
struct Spotlight {
position: vec3<f32>,
inner_angle: f32,
direction: vec3<f32>,
outer_angle: f32,
range: f32,
_padding: f32,
_padding2: f32,
_padding3: f32,
_padding4: f32,
_padding5: f32,
_padding6: f32,
_padding7: f32,
}
struct Uniforms {
model: mat4x4<f32>,
view: mat4x4<f32>,
projection: mat4x4<f32>,
light_view_projection: mat4x4<f32>,
camera_position: vec3<f32>,
height_scale: f32,
player_position: vec3<f32>,
time: f32,
shadow_bias: f32,
spotlight_count: u32,
_padding1: u32,
_padding2: u32,
spotlights: array<Spotlight, 4>,
}
@group(0) @binding(0)
var<uniform> uniforms: Uniforms;
@group(0) @binding(1)
var shadow_map: texture_depth_2d;
@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;
const PI: f32 = 3.14159265359;
const TERRAIN_BOUNDS: vec2<f32> = vec2<f32>(1000.0, 1000.0);
const LINE_THICKNESS: f32 = 0.1;
const OCTAVE_STEPS: f32 = 4.0;
const STROKE_LENGTH: f32 = 0.8;
fn hash2(p: vec2<f32>) -> f32 {
let p3 = fract(vec3<f32>(p.x, p.y, p.x) * 0.13);
let p3_dot = dot(p3, vec3<f32>(p3.y + 3.333, p3.z + 3.333, p3.x + 3.333));
return fract((p3.x + p3.y) * p3_dot);
}
fn rand(p: vec2f) -> f32 {
return fract(sin(dot(p, vec2f(12.9898, 78.233))) * 43758.5453);
}
struct StrokeData {
world_pos_2d: vec2<f32>,
tile_center: vec2<f32>,
tile_size: f32,
direction_to_light: vec2<f32>,
distance_to_light: f32,
perpendicular_to_light: vec2<f32>,
rotation_t: f32,
line_direction: vec2<f32>,
perpendicular_to_line: vec2<f32>,
octave_index: f32,
offset: vec2<f32>,
local_pos: vec2<f32>,
}
fn compute_perpendicular(dir: vec2<f32>) -> vec2<f32> {
return normalize(vec2<f32>(-dir.y, dir.x));
}
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 {
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);
if 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 {
return 1.0;
}
let depth = ndc_coords.z - uniforms.shadow_bias;
let shadow = textureSampleCompare(shadow_map, shadow_sampler, ndc_coords.xy, depth);
return shadow;
}
fn hatching_lighting(world_pos: vec3<f32>, clip_pos: vec4<f32>, tile_scale: f32, direction: vec2<f32>, distance: f32) -> f32 {
let octave_index = round((1.0 - pow(distance, 2.0)) * OCTAVE_STEPS);
let octave_normalized = octave_index / OCTAVE_STEPS;
if octave_index > 3.0 {
return 1.0;
} else if octave_index < 1.0 {
let screen_pos = clip_pos.xy / clip_pos.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;
return step(blue_noise, 0.05);
}
let world_pos_2d = vec2<f32>(world_pos.x, world_pos.z);
let tile_size = 1.0 / tile_scale;
let base_tile_center = floor(world_pos_2d / tile_size) * tile_size + tile_size * 0.5;
var min_lighting = 1.0;
for (var tile_y: i32 = -1; tile_y <= 1; tile_y++) {
for (var tile_x: i32 = -1; tile_x <= 1; tile_x++) {
let tile_center = base_tile_center + vec2<f32>(f32(tile_x), f32(tile_y)) * tile_size;
let perpendicular_to_light = compute_perpendicular(direction);
let t = compute_rotation_t(distance);
let parallel = mix(perpendicular_to_light, direction, t / 2.0);
let perpendicular = compute_perpendicular(parallel);
let spacing = LINE_THICKNESS * 1.5;
var max_offset: i32;
switch i32(octave_index) {
case default {
max_offset = 0;
}
case 3 {
max_offset = 3;
}
case 2 {
max_offset = 9;
}
case 1 {
max_offset = 6;
}
}
for (var i: i32 = -max_offset; i <= max_offset; i++) {
let random = rand(tile_center + vec2<f32>(f32(i), f32(-i)));
var chance: f32;
switch i32(octave_index) {
case 1, default: {
chance = 1.0;
}
case 2: {
chance = 0.5;
}
case 3: {
chance = 0.8;
}
}
if random > chance {
continue;
}
let offset = perpendicular * f32(i) * tile_size / f32(max_offset);
let local_pos = world_pos_2d - tile_center - offset;
let stroke_data = StrokeData(
world_pos_2d,
tile_center,
tile_size,
direction,
distance,
perpendicular_to_light,
t,
parallel,
perpendicular,
octave_index,
offset,
local_pos,
);
let lighting = line_stroke_lighting(stroke_data, clip_pos);
min_lighting = min(min_lighting, lighting);
}
}
}
return min_lighting;
}
struct SpotlightData {
direction_normalized: vec2<f32>,
combined_distance: f32,
is_lit: bool,
}
fn calculate_spotlight_data(world_pos: vec3<f32>, normal: vec3<f32>, spotlight: Spotlight, tile_scale: f32, shadow: f32) -> SpotlightData {
var data: SpotlightData;
data.is_lit = false;
data.direction_normalized = vec2<f32>(0.0, 0.0);
data.combined_distance = 2.0;
let to_fragment = normalize(world_pos - spotlight.position);
let angle_to_fragment = acos(dot(to_fragment, spotlight.direction));
if angle_to_fragment > spotlight.outer_angle {
return data;
}
let cube_size = 1.0 / tile_scale;
let cube_center = vec3<f32>(
floor(world_pos.x / cube_size) * cube_size + cube_size * 0.5,
floor(world_pos.y / cube_size) * cube_size + cube_size * 0.5,
floor(world_pos.z / cube_size) * cube_size + cube_size * 0.5
);
let tile_center = vec2<f32>(cube_center.x, cube_center.z);
let angular_falloff = smoothstep(spotlight.inner_angle, spotlight.outer_angle, angle_to_fragment);
let direction_to_point_3d = normalize(spotlight.position - cube_center);
let diffuse_raw = max(0.0, dot(normalize(normal), direction_to_point_3d));
let diffuse_res = 4.0;
let diffuse = floor(diffuse_raw * diffuse_res) / diffuse_res;
let t = (cube_center.y - spotlight.position.y) / spotlight.direction.y;
let hit_point = spotlight.position + spotlight.direction * t;
let hit_point_2d = vec2<f32>(hit_point.x, hit_point.z);
let direction_to_hit = hit_point_2d - tile_center;
let distance_to_hit = min(length(direction_to_hit) / spotlight.range, 1.0);
data.direction_normalized = normalize(direction_to_hit);
let lighting_intensity = shadow * pow(diffuse, 0.5) * (1.0 - angular_falloff);
let darkness = 1.0 - lighting_intensity;
data.combined_distance = distance_to_hit + darkness;
data.is_lit = data.combined_distance <= 1.0;
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 {
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);
}
return max_lighting;
}
fn line_stroke_lighting(data: StrokeData, clip_pos: vec4<f32>) -> f32 {
let octave_normalized = data.octave_index / OCTAVE_STEPS;
let noise = hash2(data.tile_center + data.offset) * 2.0 - 1.0;
var noise_at_octave = noise;
var jitter: f32;
switch i32(data.octave_index) {
case default {
noise_at_octave = noise;
jitter = 1.0;
}
case 2 {
noise_at_octave = noise / 10.0;
jitter = 1.0;
}
case 3 {
noise_at_octave = noise / 20.0;
jitter = 0.5;
}
}
let line = mix(data.perpendicular_to_light, data.direction_to_light, data.rotation_t / (2.0 + noise_at_octave));
let perpendicular_to_line = compute_perpendicular(line);
let parallel_coord = dot(data.local_pos, line);
let perpendicular_coord = dot(data.local_pos, perpendicular_to_line);
let line_half_width = LINE_THICKNESS * (1.0 - octave_normalized * 0.5) * data.tile_size;
let straight_section_half_length = max(0.0, data.tile_size * 0.4 - line_half_width);
let parallel_jitter = (rand(data.tile_center + data.offset * 123.456) * 2.0 - 1.0) * data.tile_size * jitter;
let jittered_parallel_coord = parallel_coord - parallel_jitter;
let overhang = max(0.0, abs(jittered_parallel_coord) - straight_section_half_length);
let effective_distance = sqrt(overhang * overhang + perpendicular_coord * perpendicular_coord);
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);
}

72
src/shaders/snow.wesl Normal file
View File

@@ -0,0 +1,72 @@
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);
}

View File

@@ -0,0 +1,44 @@
@group(0) @binding(0)
var snow_depth: texture_storage_2d<r32float, read_write>;
@group(0) @binding(1)
var<uniform> params: DeformParams;
struct DeformParams {
position_x: f32,
position_z: f32,
radius: f32,
depth: f32,
}
@compute @workgroup_size(16, 16, 1)
fn deform(@builtin(global_invocation_id) global_id: vec3<u32>) {
let texture_size = textureDimensions(snow_depth);
if (global_id.x >= texture_size.x || global_id.y >= texture_size.y) {
return;
}
let coords = vec2<i32>(i32(global_id.x), i32(global_id.y));
let terrain_size = vec2<f32>(1000.0, 1000.0);
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));
let world_pos = uv * terrain_size - half_size;
let deform_center = vec2<f32>(params.position_x, params.position_z);
let distance = length(world_pos - deform_center);
if (distance < params.radius) {
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 new_depth = max(0.0, current_depth - deform_amount);
textureStore(snow_depth, coords, vec4<f32>(new_depth, 0.0, 0.0, 0.0));
}
}

View File

@@ -0,0 +1,117 @@
import package::shared::{Spotlight, MAX_SPOTLIGHTS, calculate_spotlight_data};
struct AccumulationUniforms {
terrain_min_xz: vec2<f32>,
terrain_max_xz: vec2<f32>,
decay_rate: f32,
delta_time: f32,
spotlight_count: u32,
_padding: u32,
light_view_projection: mat4x4<f32>,
shadow_bias: f32,
terrain_height_scale: f32,
_padding3: f32,
_padding4: f32,
spotlights: array<Spotlight, 4>,
}
@group(0) @binding(0)
var previous_light: texture_2d<f32>;
@group(0) @binding(1)
var light_sampler: sampler;
@group(0) @binding(2)
var<uniform> uniforms: AccumulationUniforms;
@group(0) @binding(3)
var heightmap: texture_2d<f32>;
@group(0) @binding(4)
var heightmap_sampler: sampler;
@group(0) @binding(5)
var shadow_map: texture_depth_2d;
@group(0) @binding(6)
var shadow_sampler: sampler_comparison;
@group(0) @binding(7)
var snow_depth: texture_2d<f32>;
@group(0) @binding(8)
var snow_depth_sampler: sampler;
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) uv: vec2<f32>,
}
@vertex
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
var out: VertexOutput;
let x = f32((vertex_index << 1u) & 2u);
let y = f32(vertex_index & 2u);
out.position = vec4<f32>(x * 2.0 - 1.0, y * 2.0 - 1.0, 0.0, 1.0);
out.uv = vec2<f32>(x, 1.0 - y);
return out;
}
fn sample_shadow_map(light_space_pos: vec4<f32>) -> f32 {
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);
if 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 {
return 1.0;
}
let depth = ndc_coords.z - uniforms.shadow_bias;
let shadow = textureSampleCompare(shadow_map, shadow_sampler, ndc_coords.xy, depth);
return shadow;
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let prev_light = textureSample(previous_light, light_sampler, in.uv).r;
let world_xz = mix(uniforms.terrain_min_xz, uniforms.terrain_max_xz, in.uv);
let terrain_height = textureSampleLevel(heightmap, heightmap_sampler, in.uv, 0.0).r * uniforms.terrain_height_scale;
let depth = textureSampleLevel(snow_depth, snow_depth_sampler, in.uv, 0.0).r;
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);
for (var i = 0u; i < uniforms.spotlight_count; i++) {
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);
current_light = max(current_light, light);
}
}
var accumulated: f32;
if current_light > 0.01 {
accumulated = current_light;
} else {
let decay_factor = exp(-uniforms.decay_rate * uniforms.delta_time * 60.0);
accumulated = prev_light * decay_factor;
if accumulated < 0.01 {
accumulated = 0.0;
}
}
return vec4<f32>(accumulated, 0.0, 0.0, 1.0);
}

66
src/shaders/standard.wesl Normal file
View File

@@ -0,0 +1,66 @@
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);
}

89
src/shaders/terrain.wesl Normal file
View File

@@ -0,0 +1,89 @@
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);
}

451
src/snow.rs Normal file
View File

@@ -0,0 +1,451 @@
use std::rc::Rc;
use exr::prelude::{ReadChannels, ReadLayers};
use glam::{Vec2, Vec3};
use wgpu::util::DeviceExt;
use crate::{
components::MeshComponent,
entity::EntityHandle,
mesh::{Mesh, Vertex},
render,
world::{Transform, World},
};
pub struct SnowConfig
{
pub depth_map_path: String,
pub heightmap_path: String,
pub terrain_size: Vec2,
pub resolution: (u32, u32),
}
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 {
depth_map_path: "textures/snow_depth.exr".to_string(),
heightmap_path: "textures/terrain_heightmap.exr".to_string(),
terrain_size: Vec2::new(1000.0, 1000.0),
resolution: (1000, 1000),
}
}
}
pub struct SnowLayer
{
pub entity: EntityHandle,
pub depth_texture: wgpu::Texture,
pub depth_texture_view: wgpu::TextureView,
pub depth_bind_group: wgpu::BindGroup,
pub width: u32,
pub height: u32,
pub deform_bind_group: wgpu::BindGroup,
pub deform_pipeline: wgpu::ComputePipeline,
pub deform_params_buffer: wgpu::Buffer,
}
impl SnowLayer
{
pub fn load(world: &mut World, config: &SnowConfig) -> anyhow::Result<Self>
{
println!("\n=== Loading Snow Layer ===");
println!("Depth map path: {}", config.depth_map_path);
println!("Heightmap path: {}", config.heightmap_path);
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);
Ok(Self {
entity,
depth_texture,
depth_texture_view,
depth_bind_group,
width,
height,
deform_bind_group,
deform_pipeline,
deform_params_buffer,
})
}
fn load_depth_map(path: &str) -> anyhow::Result<(Vec<f32>, u32, u32)>
{
println!("Loading snow depth map from: {}", path);
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 width = layer.size.width() as u32;
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<_>>());
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"))?;
println!(" Using channel: {:?}", channel.name);
let depths: Vec<f32> = channel.sample_data.values_as_f32().collect();
let min_value = depths.iter().cloned().fold(f32::INFINITY, f32::min);
let max_value = depths.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
let avg_value = depths.iter().sum::<f32>() / depths.len() as f32;
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);
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");
}
Ok((depths, width, height))
}
fn create_depth_texture(
depth_data: &[f32],
width: u32,
height: u32,
) -> (wgpu::Texture, wgpu::TextureView, wgpu::BindGroup)
{
render::with_device(|device| {
let size = wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
};
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Snow Depth Texture"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::R32Float,
usage: wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::STORAGE_BINDING,
view_formats: &[],
});
let data_bytes: &[u8] = bytemuck::cast_slice(depth_data);
render::with_queue(|queue| {
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
data_bytes,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(width * 4),
rows_per_image: Some(height),
},
size,
);
});
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Snow Depth Bind Group Layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: false },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
}],
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Snow Depth Bind Group"),
layout: &bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&texture_view),
}],
});
(texture, texture_view, bind_group)
})
}
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)
{
render::with_device(|device| {
let shader_source = std::fs::read_to_string("src/shaders/snow_deform.wgsl")
.expect("Failed to load snow deform shader");
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Snow Deform Shader"),
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
});
let params_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Snow Deform Params"),
size: 32,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Snow Deform Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::StorageTexture {
access: wgpu::StorageTextureAccess::ReadWrite,
format: wgpu::TextureFormat::R32Float,
view_dimension: wgpu::TextureViewDimension::D2,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Snow Deform Bind Group"),
layout: &bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(depth_texture_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: params_buffer.as_entire_binding(),
},
],
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Snow Deform Pipeline Layout"),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("Snow Deform Pipeline"),
layout: Some(&pipeline_layout),
module: &shader,
entry_point: Some("deform"),
compilation_options: Default::default(),
cache: None,
});
(pipeline, bind_group, params_buffer)
})
}
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_bytes: &[u8] = bytemuck::cast_slice(&params_data);
queue.write_buffer(&self.deform_params_buffer, 0, params_bytes);
});
render::with_device(|device| {
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Snow Deform Encoder"),
});
{
let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("Snow Deform Pass"),
timestamp_writes: None,
});
compute_pass.set_pipeline(&self.deform_pipeline);
compute_pass.set_bind_group(0, &self.deform_bind_group, &[]);
let workgroup_size = 16;
let dispatch_x = (self.width + workgroup_size - 1) / workgroup_size;
let dispatch_y = (self.height + workgroup_size - 1) / workgroup_size;
compute_pass.dispatch_workgroups(dispatch_x, dispatch_y, 1);
}
render::with_queue(|queue| {
queue.submit(Some(encoder.finish()));
});
});
}
#[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");
}
}

550
src/snow_light.rs Normal file
View File

@@ -0,0 +1,550 @@
use bytemuck::{Pod, Zeroable};
use glam::{Vec2, Vec3};
use wgpu::util::DeviceExt;
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct AccumulationUniforms
{
terrain_min_xz: [f32; 2],
terrain_max_xz: [f32; 2],
decay_rate: f32,
delta_time: f32,
spotlight_count: u32,
_padding: u32,
light_view_projection: [[f32; 4]; 4],
shadow_bias: f32,
terrain_height_scale: f32,
_padding3: f32,
_padding4: f32,
spotlights: [crate::render::SpotlightRaw; crate::render::MAX_SPOTLIGHTS],
}
pub struct SnowLightAccumulation
{
texture_ping: wgpu::Texture,
texture_pong: wgpu::Texture,
view_ping: wgpu::TextureView,
view_pong: wgpu::TextureView,
bind_group_layout: wgpu::BindGroupLayout,
bind_group_ping: Option<wgpu::BindGroup>,
bind_group_pong: Option<wgpu::BindGroup>,
uniform_buffer: wgpu::Buffer,
pipeline: wgpu::RenderPipeline,
quad_vb: wgpu::Buffer,
quad_ib: wgpu::Buffer,
quad_num_indices: u32,
current: bool,
needs_clear: bool,
terrain_min: Vec2,
terrain_max: Vec2,
pub decay_rate: f32,
}
impl SnowLightAccumulation
{
pub fn new(
device: &wgpu::Device,
terrain_min: Vec2,
terrain_max: Vec2,
resolution: u32,
) -> Self
{
let size = wgpu::Extent3d {
width: resolution,
height: resolution,
depth_or_array_layers: 1,
};
let texture_desc = wgpu::TextureDescriptor {
label: Some("Snow Light Accumulation"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::R16Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
};
let texture_ping = device.create_texture(&texture_desc);
let texture_pong = device.create_texture(&texture_desc);
let view_ping = texture_ping.create_view(&wgpu::TextureViewDescriptor::default());
let view_pong = texture_pong.create_view(&wgpu::TextureViewDescriptor::default());
let 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 uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Snow Light Accumulation Uniforms"),
size: std::mem::size_of::<AccumulationUniforms>() as wgpu::BufferAddress,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Snow Light Accumulation 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,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
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: 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::Depth,
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 6,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
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,
},
],
});
let compiler = wesl::Wesl::new("src/shaders");
let shader_source = compiler
.compile(&"package::snow_light_accumulation".parse().unwrap())
.inspect_err(|e| eprintln!("WESL error: {e}"))
.unwrap()
.to_string();
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Snow Light Accumulation Shader"),
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Snow Light Accumulation Pipeline Layout"),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Snow Light Accumulation Pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::R16Float,
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: None,
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
});
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,
];
let quad_vb = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Snow Light Quad VB"),
contents: bytemuck::cast_slice(vertices),
usage: wgpu::BufferUsages::VERTEX,
});
let indices: &[u16] = &[0, 1, 2];
let quad_ib = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Snow Light Quad IB"),
contents: bytemuck::cast_slice(indices),
usage: wgpu::BufferUsages::INDEX,
});
Self {
texture_ping,
texture_pong,
view_ping,
view_pong,
bind_group_layout,
bind_group_ping: None,
bind_group_pong: None,
uniform_buffer,
pipeline,
quad_vb,
quad_ib,
quad_num_indices: 3,
current: false,
needs_clear: true,
terrain_min,
terrain_max,
decay_rate: 0.015,
}
}
pub fn clear(&self, encoder: &mut wgpu::CommandEncoder)
{
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Clear Snow Light Ping"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &self.view_ping,
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,
});
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Clear Snow Light Pong"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &self.view_pong,
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,
});
}
pub fn set_heightmap(
&mut self,
device: &wgpu::Device,
heightmap_view: &wgpu::TextureView,
heightmap_sampler: &wgpu::Sampler,
shadow_map_view: &wgpu::TextureView,
shadow_sampler: &wgpu::Sampler,
snow_depth_view: &wgpu::TextureView,
snow_depth_sampler: &wgpu::Sampler,
)
{
let 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()
});
self.bind_group_ping = Some(device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Snow Light Accumulation Bind Group Ping"),
layout: &self.bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&self.view_pong),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: self.uniform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::TextureView(heightmap_view),
},
wgpu::BindGroupEntry {
binding: 4,
resource: wgpu::BindingResource::Sampler(heightmap_sampler),
},
wgpu::BindGroupEntry {
binding: 5,
resource: wgpu::BindingResource::TextureView(shadow_map_view),
},
wgpu::BindGroupEntry {
binding: 6,
resource: wgpu::BindingResource::Sampler(shadow_sampler),
},
wgpu::BindGroupEntry {
binding: 7,
resource: wgpu::BindingResource::TextureView(snow_depth_view),
},
wgpu::BindGroupEntry {
binding: 8,
resource: wgpu::BindingResource::Sampler(snow_depth_sampler),
},
],
}));
self.bind_group_pong = Some(device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Snow Light Accumulation Bind Group Pong"),
layout: &self.bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&self.view_ping),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: self.uniform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::TextureView(heightmap_view),
},
wgpu::BindGroupEntry {
binding: 4,
resource: wgpu::BindingResource::Sampler(heightmap_sampler),
},
wgpu::BindGroupEntry {
binding: 5,
resource: wgpu::BindingResource::TextureView(shadow_map_view),
},
wgpu::BindGroupEntry {
binding: 6,
resource: wgpu::BindingResource::Sampler(shadow_sampler),
},
wgpu::BindGroupEntry {
binding: 7,
resource: wgpu::BindingResource::TextureView(snow_depth_view),
},
wgpu::BindGroupEntry {
binding: 8,
resource: wgpu::BindingResource::Sampler(snow_depth_sampler),
},
],
}));
}
pub fn render(
&mut self,
encoder: &mut wgpu::CommandEncoder,
queue: &wgpu::Queue,
spotlights: &[crate::render::Spotlight],
delta_time: f32,
light_view_projection: &glam::Mat4,
shadow_bias: f32,
terrain_height_scale: f32,
)
{
if self.needs_clear
{
self.clear(encoder);
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()
{
spotlight_array[i] = spotlight.to_raw();
}
let uniforms = AccumulationUniforms {
terrain_min_xz: self.terrain_min.to_array(),
terrain_max_xz: self.terrain_max.to_array(),
decay_rate: self.decay_rate,
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(),
shadow_bias,
terrain_height_scale,
_padding3: 0.0,
_padding4: 0.0,
spotlights: spotlight_array,
};
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 Some(bind_group) = bind_group else {
return;
};
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Snow Light Accumulation Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: write_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
render_pass.set_pipeline(&self.pipeline);
render_pass.set_bind_group(0, bind_group, &[]);
render_pass.set_vertex_buffer(0, self.quad_vb.slice(..));
render_pass.set_index_buffer(self.quad_ib.slice(..), wgpu::IndexFormat::Uint16);
render_pass.draw_indexed(0..self.quad_num_indices, 0, 0..1);
}
self.current = !self.current;
}
pub fn read_view(&self) -> &wgpu::TextureView
{
if self.current { &self.view_pong } else { &self.view_ping }
}
pub fn create_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout
{
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Snow Persistent Light 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,
},
],
})
}
pub fn create_read_bind_group(
&self,
device: &wgpu::Device,
layout: &wgpu::BindGroupLayout,
) -> wgpu::BindGroup
{
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Snow Persistent 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()
});
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Snow Persistent Light Bind Group"),
layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(self.read_view()),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
],
})
}
}

75
src/space.rs Normal file
View File

@@ -0,0 +1,75 @@
use anyhow::Result;
use glam::Vec3;
use crate::{
empty::Empties,
light::{LightData, Lights},
mesh::{InstanceData, Mesh},
render,
};
pub const CAMERA_SPAWN_OFFSET: Vec3 = Vec3::new(15.0, 15.0, 15.0);
pub struct Space
{
pub mesh_data: Vec<(Mesh, Vec<InstanceData>)>,
pub spotlights: Vec<LightData>,
pub player_spawn: Vec3,
}
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 lights = Lights::load_lights(gltf_path)
.map_err(|e| anyhow::anyhow!("Failed to load lights: {}", e))?;
let spotlights = lights.into_spotlights();
let player_spawn = Self::get_player_spawn(gltf_path)?;
Ok(Space {
mesh_data,
spotlights,
player_spawn,
})
}
fn get_player_spawn(gltf_path: &str) -> Result<Vec3>
{
let empty = Empties::get_empty_by_name(gltf_path, "PlayerSpawn")?;
if let Some(empty_node) = empty
{
let (_scale, _rotation, translation) = empty_node.transform.to_scale_rotation_translation();
Ok(translation)
}
else
{
println!("Warning: PlayerSpawn empty not found, using default position");
Ok(Vec3::new(0.0, 5.0, 0.0))
}
}
pub fn camera_spawn_position(&self) -> Vec3
{
self.player_spawn + CAMERA_SPAWN_OFFSET
}
pub fn terrain_mesh(&self) -> Option<&Mesh>
{
self.mesh_data.first().map(|(mesh, _)| mesh)
}
pub fn tree_instances(&self) -> Option<(&Mesh, &Vec<InstanceData>)>
{
self.mesh_data
.iter()
.find(|(_, instances)| !instances.is_empty())
.map(|(mesh, instances)| (mesh, instances))
}
}

View File

@@ -1,7 +1,8 @@
use glam::Vec3;
use crate::components::FollowComponent;
use crate::utility::input::InputState;
use crate::world::World;
use crate::world::{Transform, World};
pub fn camera_input_system(world: &mut World, input_state: &InputState)
{
@@ -18,11 +19,7 @@ pub fn camera_input_system(world: &mut World, input_state: &InputState)
if input_state.mouse_delta.0.abs() > 0.0 || input_state.mouse_delta.1.abs() > 0.0
{
let is_following = world
.camera_follows
.get(camera_entity)
.map(|f| f.is_following)
.unwrap_or(false);
let is_following = world.follows.get(camera_entity).is_some();
camera.yaw += input_state.mouse_delta.0 * 0.0008;
@@ -45,26 +42,20 @@ pub fn camera_input_system(world: &mut World, input_state: &InputState)
pub fn camera_follow_system(world: &mut World)
{
let camera_entities: Vec<_> = world.camera_follows.all();
let camera_entities: Vec<_> = world.follows.all();
for camera_entity in camera_entities
{
if let Some(follow) = world.camera_follows.get(camera_entity)
if let Some(camera) = world.cameras.get(camera_entity)
{
if !follow.is_following
if let Some(follow) = world.follows.get(camera_entity)
{
continue;
}
let target_entity = follow.target;
let offset = follow.offset.position;
let target_entity = follow.target_entity;
let offset = follow.offset;
if let Some(target_transform) = world.transforms.get(target_entity)
{
let target_position = target_transform.position;
if let Some(camera) = world.cameras.get_mut(camera_entity)
if let Some(target_transform) = world.transforms.get(target_entity)
{
let target_position = target_transform.position;
let distance = offset.length();
let orbit_yaw = camera.yaw + std::f32::consts::PI;
@@ -75,15 +66,15 @@ pub fn camera_follow_system(world: &mut World)
let new_offset = Vec3::new(offset_x, offset_y, offset_z);
if let Some(camera_transform) = world.transforms.get_mut(camera_entity)
{
camera_transform.position = target_position + new_offset;
}
world
.transforms
.with_mut(camera_entity, |camera_transform| {
camera_transform.position = target_position + new_offset;
});
if let Some(follow_mut) = world.camera_follows.get_mut(camera_entity)
{
follow_mut.offset = new_offset;
}
world.follows.components.get_mut(&camera_entity).map(|f| {
f.offset.position = new_offset;
});
}
}
}
@@ -152,19 +143,16 @@ pub fn camera_noclip_system(world: &mut World, input_state: &InputState, delta:
pub fn start_camera_following(world: &mut World, camera_entity: crate::entity::EntityHandle)
{
if let Some(follow) = world.camera_follows.get_mut(camera_entity)
if let Some(camera_transform) = world.transforms.get(camera_entity)
{
let target_entity = follow.target_entity;
if let Some(target_transform) = world.transforms.get(target_entity)
let player_entities = world.player_tags.all();
if let Some(&player_entity) = player_entities.first()
{
if let Some(camera_transform) = world.transforms.get(camera_entity)
if let Some(target_transform) = world.transforms.get(player_entity)
{
let offset = camera_transform.position - target_transform.position;
follow.offset = offset;
follow.is_following = true;
let distance = offset.length();
if distance > 0.0
{
if let Some(camera) = world.cameras.get_mut(camera_entity)
@@ -173,6 +161,16 @@ pub fn start_camera_following(world: &mut World, camera_entity: crate::entity::E
camera.yaw = offset.z.atan2(offset.x) + std::f32::consts::PI;
}
}
world.follows.insert(
camera_entity,
FollowComponent {
target: player_entity,
offset: Transform::from_position(offset),
inherit_rotation: false,
inherit_scale: false,
},
);
}
}
}
@@ -180,13 +178,13 @@ pub fn start_camera_following(world: &mut World, camera_entity: crate::entity::E
pub fn stop_camera_following(world: &mut World, camera_entity: crate::entity::EntityHandle)
{
if let Some(follow) = world.camera_follows.get_mut(camera_entity)
if let Some(follow) = world.follows.get(camera_entity)
{
follow.is_following = false;
let target_entity = follow.target;
if let Some(camera_transform) = world.transforms.get(camera_entity)
{
if let Some(target_transform) = world.transforms.get(follow.target_entity)
if let Some(target_transform) = world.transforms.get(target_entity)
{
let look_direction =
(target_transform.position - camera_transform.position).normalize();
@@ -198,5 +196,7 @@ pub fn stop_camera_following(world: &mut World, camera_entity: crate::entity::En
}
}
}
world.follows.remove(camera_entity);
}
}

48
src/systems/follow.rs Normal file
View File

@@ -0,0 +1,48 @@
use crate::world::World;
pub fn follow_system(world: &mut World)
{
let following_entities: Vec<_> = world.follows.all();
for entity in following_entities
{
if let Some(follow) = world.follows.get(entity)
{
let target = follow.target;
if let Some(target_transform) = world.transforms.get(target)
{
let target_pos = target_transform.position;
let target_rot = target_transform.rotation;
let target_scale = target_transform.scale;
let offset = follow.offset;
let inherit_rot = follow.inherit_rotation;
let inherit_scale = follow.inherit_scale;
world.transforms.with_mut(entity, |transform| {
transform.position = target_pos;
if inherit_rot
{
let rotated_offset = target_rot * offset.position;
transform.position += rotated_offset;
transform.rotation = target_rot * offset.rotation;
}
else
{
transform.position += offset.position;
transform.rotation = offset.rotation;
}
if inherit_scale
{
transform.scale = target_scale * offset.scale;
}
else
{
transform.scale = offset.scale;
}
});
}
}
}
}

View File

@@ -1,14 +1,22 @@
pub mod camera;
pub mod follow;
pub mod input;
pub mod physics_sync;
pub mod render;
pub mod rotate;
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,
};
pub use follow::follow_system;
pub use input::player_input_system;
pub use physics_sync::physics_sync_system;
pub use render::render_system;
pub use rotate::rotate_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};

View File

@@ -2,8 +2,6 @@ use crate::mesh::InstanceRaw;
use crate::render::DrawCall;
use crate::world::World;
use bytemuck::cast_slice;
use glam::Mat4;
use wgpu::util::DeviceExt;
pub fn render_system(world: &World) -> Vec<DrawCall>
{
@@ -24,16 +22,27 @@ pub fn render_system(world: &World) -> Vec<DrawCall>
}
else
{
let dissolve_amount = world.dissolves.get(entity).map(|d| d.amount).unwrap_or(0.0);
let instance_data = InstanceRaw {
model: model_matrix.to_cols_array_2d(),
dissolve_amount,
_padding: [0.0; 3],
};
let buffer = crate::render::with_device(|device| {
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Instance Buffer"),
contents: cast_slice(&[instance_data]),
usage: wgpu::BufferUsages::VERTEX,
})
size: std::mem::size_of::<InstanceRaw>() as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
crate::render::with_queue(|queue| {
queue.write_buffer(&buffer, 0, cast_slice(&[instance_data]));
});
buffer
});
(Some(buffer), 1)

20
src/systems/rotate.rs Normal file
View File

@@ -0,0 +1,20 @@
use glam::Quat;
use crate::world::World;
pub fn rotate_system(world: &mut World, delta: f32)
{
let entities = world.rotates.all();
for entity in entities
{
if let Some(rotate) = world.rotates.get(entity)
{
let rotation_delta = Quat::from_axis_angle(rotate.axis, rotate.speed * delta);
world.transforms.with_mut(entity, |transform| {
transform.rotation = rotation_delta * transform.rotation;
});
}
}
}

View File

@@ -0,0 +1,32 @@
use crate::render::Spotlight;
use crate::world::World;
pub fn spotlight_sync_system(world: &World) -> Vec<Spotlight>
{
let mut entities = world.spotlights.all();
entities.sort();
let mut spotlights = Vec::new();
for entity in entities
{
if let Some(spotlight_component) = world.spotlights.get(entity)
{
if let Some(transform) = world.transforms.get(entity)
{
let position = transform.position + spotlight_component.offset;
let direction = transform.rotation * spotlight_component.direction;
spotlights.push(Spotlight::new(
position,
direction,
spotlight_component.inner_angle,
spotlight_component.outer_angle,
spotlight_component.range,
));
}
}
}
spotlights
}

View File

@@ -0,0 +1,112 @@
use crate::components::DissolveComponent;
use crate::world::World;
use glam::Vec3;
pub fn tree_dissolve_update_system(world: &mut World, delta: f32)
{
for entity in world.dissolves.all()
{
if let Some(dissolve) = world.dissolves.get_mut(entity)
{
let diff = dissolve.target_amount - dissolve.amount;
dissolve.amount += diff * dissolve.transition_speed * delta;
dissolve.amount = dissolve.amount.clamp(0.0, 1.0);
}
}
}
pub fn tree_occlusion_system(world: &mut World)
{
let player_entity = world.player_tags.all().first().copied();
let player_pos = player_entity.and_then(|e| world.transforms.get(e).map(|t| t.position));
if let Some(player_pos) = player_pos
{
let camera_entity = world.cameras.get_active().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;
let distance_to_player = to_player.length();
if distance_to_player < 0.01
{
return;
}
let to_player_normalized = to_player.normalize();
for tree_entity in world.tree_tags.all()
{
if let Some(tree_transform) = world.transforms.get(tree_entity)
{
let tree_pos = tree_transform.position;
let to_tree = tree_pos - camera_pos;
let distance_to_tree = to_tree.length();
if distance_to_tree < distance_to_player
{
let projection = to_tree.dot(to_player_normalized);
if projection > 0.0
{
let perpendicular_vec = to_tree - to_player_normalized * projection;
let perp_distance = perpendicular_vec.length();
let occlusion_radius = 2.5;
if perp_distance < occlusion_radius
{
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;
}
else
{
let mut dissolve = DissolveComponent::new();
dissolve.target_amount = dissolve_amount;
world.dissolves.insert(tree_entity, dissolve);
}
continue;
}
}
}
if let Some(dissolve) = world.dissolves.get_mut(tree_entity)
{
dissolve.target_amount = 0.0;
}
}
}
}
}
}

View File

@@ -7,11 +7,12 @@ use rapier3d::{
math::Isometry,
prelude::{ColliderBuilder, RigidBodyBuilder},
};
use wesl::Wesl;
use crate::{
components::{MeshComponent, PhysicsComponent},
entity::EntityHandle,
mesh::{InstanceRaw, Mesh, Vertex},
mesh::{InstanceData, InstanceRaw, Mesh, Vertex},
physics::PhysicsManager,
render,
world::{Transform, World},
@@ -39,7 +40,7 @@ impl TerrainConfig
{
Self {
gltf_path: "meshes/terrain.gltf".to_string(),
heightmap_path: "textures/terrain.exr".to_string(),
heightmap_path: "textures/terrain_heightmap.exr".to_string(),
size: Vec2::new(1000.0, 1000.0),
}
}
@@ -49,111 +50,102 @@ pub struct Terrain;
impl Terrain
{
pub fn spawn(world: &mut World, config: &TerrainConfig) -> anyhow::Result<EntityHandle>
pub fn spawn(
world: &mut World,
mesh_data: Vec<(Mesh, Vec<InstanceData>)>,
config: &TerrainConfig,
) -> anyhow::Result<EntityHandle>
{
let gltf_data = render::with_device(|device| {
Mesh::load_gltf_with_instances(device, &config.gltf_path)
})?;
let terrain_entity = world.spawn();
let transform = Transform::IDENTITY;
let mut first_entity = None;
let mut physics_added = false;
let mut terrain_mesh = None;
let mut tree_mesh = None;
let mut tree_instances = None;
for (mesh, instances) in gltf_data
for (mesh, instances) in mesh_data
{
let entity = world.spawn();
if first_entity.is_none()
{
first_entity = Some(entity);
}
if instances.is_empty()
{
if terrain_mesh.is_none()
world.transforms.insert(entity, transform);
world.meshes.insert(
entity,
MeshComponent {
mesh: Rc::new(mesh),
pipeline: render::Pipeline::Terrain,
instance_buffer: None,
num_instances: 1,
},
);
if !physics_added
{
terrain_mesh = Some(mesh);
let heights = Self::load_heightfield_from_exr(&config.heightmap_path)?;
let height_scale = 1.0;
let scale = vector![config.size.x, height_scale, config.size.y];
let body = RigidBodyBuilder::fixed()
.translation(transform.get_position().into())
.build();
let rigidbody_handle = PhysicsManager::add_rigidbody(body);
let collider = ColliderBuilder::heightfield(heights.clone(), scale).build();
let collider_handle =
PhysicsManager::add_collider(collider, Some(rigidbody_handle));
PhysicsManager::set_heightfield_data(
heights,
scale,
transform.get_position().into(),
);
world.physics.insert(
entity,
PhysicsComponent {
rigidbody: rigidbody_handle,
collider: Some(collider_handle),
},
);
physics_added = true;
}
}
else
{
tree_mesh = Some(mesh);
tree_instances = Some(instances);
let num_instances = instances.len();
let instance_raw: Vec<InstanceRaw> = instances.iter().map(|i| i.to_raw()).collect();
let instance_buffer = render::with_device(|device| {
use wgpu::util::DeviceExt;
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Tree Instance Buffer"),
contents: bytemuck::cast_slice(&instance_raw),
usage: wgpu::BufferUsages::VERTEX,
})
});
world.transforms.insert(entity, Transform::IDENTITY);
world.meshes.insert(
entity,
MeshComponent {
mesh: Rc::new(mesh),
pipeline: render::Pipeline::Environment,
instance_buffer: Some(instance_buffer),
num_instances: num_instances as u32,
},
);
}
}
if let Some(terrain_mesh) = terrain_mesh
{
world.transforms.insert(terrain_entity, transform);
world.meshes.insert(
terrain_entity,
MeshComponent {
mesh: Rc::new(terrain_mesh),
pipeline: render::Pipeline::Terrain,
instance_buffer: None,
num_instances: 1,
},
);
let heights = Self::load_heightfield_from_exr(&config.heightmap_path)?;
println!(
"Loaded terrain: {} rows × {} cols heightfield from EXR",
heights.nrows(),
heights.ncols()
);
let height_scale = 1.0;
let scale = vector![config.size.x, height_scale, config.size.y];
let body = RigidBodyBuilder::fixed()
.translation(transform.get_position().into())
.build();
let rigidbody_handle = PhysicsManager::add_rigidbody(body);
let collider = ColliderBuilder::heightfield(heights.clone(), scale).build();
let collider_handle = PhysicsManager::add_collider(collider, Some(rigidbody_handle));
PhysicsManager::set_heightfield_data(heights, scale, transform.get_position().into());
world.physics.insert(
terrain_entity,
PhysicsComponent {
rigidbody: rigidbody_handle,
collider: Some(collider_handle),
},
);
}
if let (Some(tree_mesh), Some(instances)) = (tree_mesh, tree_instances)
{
let num_instances = instances.len();
println!("Loaded {} tree instances", num_instances);
let tree_entity = world.spawn();
let instance_raw: Vec<InstanceRaw> = instances.iter().map(|i| i.to_raw()).collect();
let instance_buffer = render::with_device(|device| {
use wgpu::util::DeviceExt;
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Tree Instance Buffer"),
contents: bytemuck::cast_slice(&instance_raw),
usage: wgpu::BufferUsages::VERTEX,
})
});
world.transforms.insert(tree_entity, Transform::IDENTITY);
world.meshes.insert(
tree_entity,
MeshComponent {
mesh: Rc::new(tree_mesh),
pipeline: render::Pipeline::Render,
instance_buffer: Some(instance_buffer),
num_instances: num_instances as u32,
},
);
}
Ok(terrain_entity)
first_entity.ok_or_else(|| anyhow::anyhow!("No meshes found in glTF file"))
}
fn load_heightfield_from_exr(path: &str) -> anyhow::Result<DMatrix<f32>>
@@ -184,11 +176,12 @@ pub fn create_terrain_render_pipeline(
bind_group_layout: &wgpu::BindGroupLayout,
) -> wgpu::RenderPipeline
{
let shared_source =
std::fs::read_to_string("shaders/shared.wgsl").expect("Failed to read shared shader");
let terrain_source =
std::fs::read_to_string("shaders/terrain.wgsl").expect("Failed to read terrain shader");
let shader_source = format!("{}\n{}", shared_source, terrain_source);
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"),

View File

@@ -1,6 +1,5 @@
use anyhow::Result;
use exr::prelude::{ReadChannels, ReadLayers};
use half::f16;
pub struct DitherTextures
{
@@ -16,6 +15,13 @@ pub struct FlowmapTexture
pub sampler: wgpu::Sampler,
}
pub struct HeightmapTexture
{
pub texture: wgpu::Texture,
pub view: wgpu::TextureView,
pub sampler: wgpu::Sampler,
}
impl DitherTextures
{
pub fn load_octaves(device: &wgpu::Device, queue: &wgpu::Queue) -> Result<Self>
@@ -186,16 +192,11 @@ impl FlowmapTexture
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba16Float,
format: wgpu::TextureFormat::Rgba32Float,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
let rgba_data_f16: Vec<u16> = rgba_data
.iter()
.map(|&f| f16::from_f32(f).to_bits())
.collect();
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &texture,
@@ -203,10 +204,10 @@ impl FlowmapTexture
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
bytemuck::cast_slice(&rgba_data_f16),
bytemuck::cast_slice(&rgba_data),
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(8 * width as u32),
bytes_per_row: Some(16 * width as u32),
rows_per_image: Some(height as u32),
},
wgpu::Extent3d {
@@ -223,8 +224,8 @@ impl FlowmapTexture
address_mode_u: wgpu::AddressMode::Repeat,
address_mode_v: wgpu::AddressMode::Repeat,
address_mode_w: wgpu::AddressMode::Repeat,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
@@ -236,3 +237,76 @@ impl FlowmapTexture
})
}
}
impl HeightmapTexture
{
pub fn load(device: &wgpu::Device, queue: &wgpu::Queue, path: &str) -> Result<Self>
{
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 width = layer.size.width();
let height = layer.size.height();
let channel = &layer.channel_data.list[0];
let height_data: Vec<f32> = channel.sample_data.values_as_f32().collect();
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Heightmap Texture"),
size: wgpu::Extent3d {
width: width as u32,
height: height as u32,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::R32Float,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
bytemuck::cast_slice(&height_data),
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4 * width as u32),
rows_per_image: Some(height as u32),
},
wgpu::Extent3d {
width: width as u32,
height: height as u32,
depth_or_array_layers: 1,
},
);
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Heightmap 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()
});
Ok(Self {
texture,
view,
sampler,
})
}
}

View File

@@ -22,6 +22,16 @@ impl Transform
Self::IDENTITY.translated(position)
}
pub fn from_matrix(matrix: Mat4) -> Self
{
let (scale, rotation, position) = matrix.to_scale_rotation_translation();
Self {
position,
rotation,
scale,
}
}
pub fn to_matrix(&self) -> Mat4
{
Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.position)

View File

@@ -1,9 +1,12 @@
use std::collections::HashMap;
use crate::components::dissolve::DissolveComponent;
use crate::components::follow::FollowComponent;
use crate::components::jump::JumpComponent;
use crate::components::lights::spot::SpotlightComponent;
use crate::components::{
CameraComponent, CameraFollowComponent, InputComponent, MeshComponent, MovementComponent,
PhysicsComponent,
CameraComponent, InputComponent, MeshComponent, MovementComponent, PhysicsComponent,
RotateComponent,
};
use crate::entity::{EntityHandle, EntityManager};
use crate::state::StateMachine;
@@ -415,12 +418,12 @@ impl CameraStorage
}
}
pub struct CameraFollowStorage
pub struct SpotlightStorage
{
pub components: HashMap<EntityHandle, CameraFollowComponent>,
pub components: HashMap<EntityHandle, SpotlightComponent>,
}
impl CameraFollowStorage
impl SpotlightStorage
{
pub fn new() -> Self
{
@@ -429,26 +432,159 @@ impl CameraFollowStorage
}
}
pub fn insert(&mut self, entity: EntityHandle, component: CameraFollowComponent)
pub fn insert(&mut self, entity: EntityHandle, component: SpotlightComponent)
{
self.components.insert(entity, component);
}
pub fn get(&self, entity: EntityHandle) -> Option<&CameraFollowComponent>
pub fn get(&self, entity: EntityHandle) -> Option<&SpotlightComponent>
{
self.components.get(&entity)
}
pub fn get_mut(&mut self, entity: EntityHandle) -> Option<&mut CameraFollowComponent>
pub fn get_mut(&mut self, entity: EntityHandle) -> Option<&mut SpotlightComponent>
{
self.components.get_mut(&entity)
}
pub fn with_mut<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
where
F: FnOnce(&mut CameraFollowComponent) -> R,
pub fn remove(&mut self, entity: EntityHandle)
{
self.components.get_mut(&entity).map(f)
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)
@@ -474,7 +610,11 @@ pub struct World
pub player_tags: PlayerTagStorage,
pub state_machines: StateMachineStorage,
pub cameras: CameraStorage,
pub camera_follows: CameraFollowStorage,
pub spotlights: SpotlightStorage,
pub tree_tags: TreeTagStorage,
pub dissolves: DissolveStorage,
pub follows: FollowStorage,
pub rotates: RotateStorage,
}
impl World
@@ -492,7 +632,11 @@ impl World
player_tags: PlayerTagStorage::new(),
state_machines: StateMachineStorage::new(),
cameras: CameraStorage::new(),
camera_follows: CameraFollowStorage::new(),
spotlights: SpotlightStorage::new(),
tree_tags: TreeTagStorage::new(),
dissolves: DissolveStorage::new(),
follows: FollowStorage::new(),
rotates: RotateStorage::new(),
}
}
@@ -512,7 +656,11 @@ impl World
self.player_tags.remove(entity);
self.state_machines.remove(entity);
self.cameras.remove(entity);
self.camera_follows.remove(entity);
self.spotlights.remove(entity);
self.tree_tags.remove(entity);
self.dissolves.remove(entity);
self.follows.remove(entity);
self.rotates.remove(entity);
self.entities.despawn(entity);
}
}