Compare commits
11 Commits
d037fc4acd
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c37a9fd5dd | ||
|
|
350fddc2af | ||
|
|
bab54b6f21 | ||
|
|
5e5c6a47e7 | ||
|
|
28f8c65571 | ||
|
|
d888dc077e | ||
|
|
adeb181d17 | ||
|
|
0ce4e813ab | ||
|
|
322b1f7b43 | ||
|
|
1925ebea0c | ||
|
|
5f8d924a02 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,2 +1,6 @@
|
||||
/target
|
||||
target
|
||||
|
||||
Cargo.lock
|
||||
|
||||
imgui.ini
|
||||
|
||||
2436
Cargo.lock
generated
2436
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -4,8 +4,12 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
sdl3 = { version = "0.16", features = ["raw-window-handle"] }
|
||||
wgpu = "27"
|
||||
sdl3 = { version = "0.17", features = ["raw-window-handle"] }
|
||||
sdl3-sys = "0.6"
|
||||
dear-imgui-rs = "0.10"
|
||||
dear-imgui-wgpu = "0.10"
|
||||
dear-imgui-sdl3 = "0.10"
|
||||
wgpu = "28"
|
||||
pollster = "0.3"
|
||||
glam = "0.30"
|
||||
anyhow = "1.0"
|
||||
|
||||
@@ -20,6 +20,7 @@ impl Bundle for CameraBundle
|
||||
let transform = Transform::from_position(self.position);
|
||||
world.cameras.insert(camera_entity, camera_component);
|
||||
world.transforms.insert(camera_entity, transform);
|
||||
world.names.insert(camera_entity, "Camera".to_string());
|
||||
Ok(camera_entity)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,10 @@ use rapier3d::control::{CharacterAutostep, KinematicCharacterController};
|
||||
use rapier3d::prelude::{ColliderBuilder, RigidBodyBuilder};
|
||||
|
||||
use crate::bundles::Bundle;
|
||||
use crate::components::jump::JumpComponent;
|
||||
use crate::components::lights::spot::SpotlightComponent;
|
||||
use crate::components::{InputComponent, MeshComponent, MovementComponent, PhysicsComponent};
|
||||
use crate::components::{
|
||||
InputComponent, JumpComponent, MeshComponent, MovementComponent, PhysicsComponent,
|
||||
};
|
||||
use crate::entity::EntityHandle;
|
||||
use crate::loaders::mesh::Mesh;
|
||||
use crate::physics::PhysicsManager;
|
||||
@@ -70,7 +71,7 @@ impl Bundle for PlayerBundle
|
||||
state_machine.add_transition::<PlayerFallingState, PlayerIdleState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.with(entity_id, |m| m.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
let has_input = world
|
||||
.inputs
|
||||
@@ -82,7 +83,7 @@ impl Bundle for PlayerBundle
|
||||
state_machine.add_transition::<PlayerFallingState, PlayerWalkingState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.with(entity_id, |m| m.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
let has_input = world
|
||||
.inputs
|
||||
@@ -94,7 +95,7 @@ impl Bundle for PlayerBundle
|
||||
state_machine.add_transition::<PlayerIdleState, PlayerWalkingState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.with(entity_id, |m| m.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
let has_input = world
|
||||
.inputs
|
||||
@@ -106,7 +107,7 @@ impl Bundle for PlayerBundle
|
||||
state_machine.add_transition::<PlayerWalkingState, PlayerIdleState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.with(entity_id, |m| m.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
let has_input = world
|
||||
.inputs
|
||||
@@ -118,7 +119,7 @@ impl Bundle for PlayerBundle
|
||||
state_machine.add_transition::<PlayerIdleState, PlayerFallingState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.with(entity_id, |m| m.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
!is_grounded
|
||||
});
|
||||
@@ -126,7 +127,7 @@ impl Bundle for PlayerBundle
|
||||
state_machine.add_transition::<PlayerWalkingState, PlayerFallingState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.with(entity_id, |m| m.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
!is_grounded
|
||||
});
|
||||
@@ -134,7 +135,7 @@ impl Bundle for PlayerBundle
|
||||
state_machine.add_transition::<PlayerIdleState, PlayerJumpingState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.with(entity_id, |m| m.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
let jump_pressed = world
|
||||
.inputs
|
||||
@@ -146,7 +147,7 @@ impl Bundle for PlayerBundle
|
||||
state_machine.add_transition::<PlayerWalkingState, PlayerJumpingState>(move |world| {
|
||||
let is_grounded = world
|
||||
.movements
|
||||
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
|
||||
.with(entity_id, |m| m.movement_context.is_floored)
|
||||
.unwrap_or(false);
|
||||
let jump_pressed = world
|
||||
.inputs
|
||||
@@ -159,14 +160,14 @@ impl Bundle for PlayerBundle
|
||||
world
|
||||
.jumps
|
||||
.with(entity_id, |jump| {
|
||||
jump.jump_config.jump_context.duration >= jump.jump_config.jump_duration
|
||||
jump.jump_context.duration >= jump.jump_duration
|
||||
})
|
||||
.unwrap_or(true)
|
||||
});
|
||||
|
||||
world.transforms.insert(entity, spawn_transform);
|
||||
world.movements.insert(entity, MovementComponent::new());
|
||||
world.jumps.insert(entity, JumpComponent::new());
|
||||
world.jumps.insert(entity, JumpComponent::default());
|
||||
world.inputs.insert(entity, InputComponent::default());
|
||||
world.physics.insert(
|
||||
entity,
|
||||
@@ -189,6 +190,7 @@ impl Bundle for PlayerBundle
|
||||
);
|
||||
world.player_tags.insert(entity, ());
|
||||
world.state_machines.insert(entity, state_machine);
|
||||
world.names.insert(entity, "Player".to_string());
|
||||
|
||||
let outer_angle = PI / 2.0 * 0.9;
|
||||
world.spotlights.insert(
|
||||
|
||||
@@ -19,6 +19,7 @@ impl Bundle for SpotlightBundle
|
||||
let transform = Transform::from_matrix(self.light_data.transform);
|
||||
world.transforms.insert(entity, transform);
|
||||
world.spotlights.insert(entity, self.light_data.component);
|
||||
world.names.insert(entity, "Spotlight".to_string());
|
||||
if let Some(tag) = self.light_data.tag
|
||||
{
|
||||
if tag == "lighthouse"
|
||||
@@ -34,12 +35,13 @@ impl Bundle for SpotlightBundle
|
||||
|
||||
pub fn spawn_spotlights(world: &mut World, spotlights: Vec<LightData>)
|
||||
{
|
||||
for light_data in spotlights
|
||||
for (index, light_data) in spotlights.into_iter().enumerate()
|
||||
{
|
||||
let entity = world.spawn();
|
||||
let transform = Transform::from_matrix(light_data.transform);
|
||||
world.transforms.insert(entity, transform);
|
||||
world.spotlights.insert(entity, light_data.component);
|
||||
world.names.insert(entity, format!("Spotlight_{}", index));
|
||||
if let Some(tag) = light_data.tag
|
||||
{
|
||||
if tag == "lighthouse"
|
||||
|
||||
@@ -4,7 +4,7 @@ use glam::Vec2;
|
||||
use nalgebra::vector;
|
||||
use rapier3d::prelude::{ColliderBuilder, RigidBodyBuilder};
|
||||
|
||||
use crate::components::{MeshComponent, PhysicsComponent};
|
||||
use crate::components::{MeshComponent, PhysicsComponent, TreeInstancesComponent};
|
||||
use crate::entity::EntityHandle;
|
||||
use crate::loaders::mesh::{InstanceData, InstanceRaw, Mesh};
|
||||
use crate::loaders::terrain::load_heightfield_from_exr;
|
||||
@@ -78,6 +78,7 @@ impl TerrainBundle
|
||||
enable_snow_light: true,
|
||||
},
|
||||
);
|
||||
world.names.insert(entity, "Terrain".to_string());
|
||||
|
||||
if !physics_added
|
||||
{
|
||||
@@ -125,7 +126,7 @@ impl TerrainBundle
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Tree Instance Buffer"),
|
||||
contents: bytemuck::cast_slice(&instance_raw),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -135,13 +136,18 @@ impl TerrainBundle
|
||||
MeshComponent {
|
||||
mesh: Rc::new(mesh),
|
||||
pipeline: render::Pipeline::Standard,
|
||||
instance_buffer: Some(instance_buffer),
|
||||
instance_buffer: Some(instance_buffer.clone()),
|
||||
num_instances: num_instances as u32,
|
||||
tile_scale: 4.0,
|
||||
enable_dissolve: true,
|
||||
enable_snow_light: false,
|
||||
},
|
||||
);
|
||||
world.tree_instances.insert(
|
||||
entity,
|
||||
TreeInstancesComponent::new(instances, Rc::new(instance_buffer)),
|
||||
);
|
||||
world.names.insert(entity, "Trees".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,22 +3,6 @@ use kurbo::CubicBez;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct JumpComponent
|
||||
{
|
||||
pub jump_config: JumpConfig,
|
||||
}
|
||||
|
||||
impl JumpComponent
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
jump_config: JumpConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct JumpConfig
|
||||
{
|
||||
pub jump_height: f32,
|
||||
pub jump_duration: f32,
|
||||
@@ -30,7 +14,7 @@ pub struct JumpConfig
|
||||
pub jump_context: JumpContext,
|
||||
}
|
||||
|
||||
impl Default for JumpConfig
|
||||
impl Default for JumpComponent
|
||||
{
|
||||
fn default() -> Self
|
||||
{
|
||||
@@ -56,22 +40,3 @@ pub struct JumpContext
|
||||
pub origin_height: f32,
|
||||
pub normal: Vec3,
|
||||
}
|
||||
|
||||
impl JumpContext
|
||||
{
|
||||
fn start(time: f32, current_height: f32, surface_normal: Vec3) -> Self
|
||||
{
|
||||
Self {
|
||||
in_progress: false,
|
||||
duration: 0.0,
|
||||
execution_time: time,
|
||||
origin_height: current_height,
|
||||
normal: surface_normal,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop(&mut self)
|
||||
{
|
||||
self.in_progress = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,17 @@ pub mod movement;
|
||||
pub mod noclip;
|
||||
pub mod physics;
|
||||
pub mod rotate;
|
||||
pub mod tree_instances;
|
||||
pub mod trigger;
|
||||
|
||||
pub use camera::CameraComponent;
|
||||
pub use dissolve::DissolveComponent;
|
||||
pub use follow::FollowComponent;
|
||||
pub use input::InputComponent;
|
||||
pub use jump::JumpComponent;
|
||||
pub use mesh::MeshComponent;
|
||||
pub use movement::MovementComponent;
|
||||
pub use physics::PhysicsComponent;
|
||||
pub use rotate::RotateComponent;
|
||||
pub use tree_instances::TreeInstancesComponent;
|
||||
pub use trigger::{TriggerComponent, TriggerEvent, TriggerEventKind, TriggerFilter, TriggerShape, TriggerState};
|
||||
|
||||
@@ -3,22 +3,6 @@ use kurbo::CubicBez;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MovementComponent
|
||||
{
|
||||
pub movement_config: MovementConfig,
|
||||
}
|
||||
|
||||
impl MovementComponent
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
movement_config: MovementConfig::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MovementConfig
|
||||
{
|
||||
pub walking_acceleration: f32,
|
||||
pub walking_acceleration_duration: f32,
|
||||
@@ -29,7 +13,7 @@ pub struct MovementConfig
|
||||
pub movement_context: MovementContext,
|
||||
}
|
||||
|
||||
impl MovementConfig
|
||||
impl MovementComponent
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
|
||||
27
src/components/tree_instances.rs
Normal file
27
src/components/tree_instances.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use crate::loaders::mesh::InstanceData;
|
||||
use std::rc::Rc;
|
||||
use wgpu::Buffer;
|
||||
|
||||
pub struct TreeInstancesComponent
|
||||
{
|
||||
pub instances: Vec<InstanceData>,
|
||||
pub dissolve_amounts: Vec<f32>,
|
||||
pub dissolve_targets: Vec<f32>,
|
||||
pub transition_speed: f32,
|
||||
pub instance_buffer: Rc<Buffer>,
|
||||
}
|
||||
|
||||
impl TreeInstancesComponent
|
||||
{
|
||||
pub fn new(instances: Vec<InstanceData>, instance_buffer: Rc<Buffer>) -> Self
|
||||
{
|
||||
let num_instances = instances.len();
|
||||
Self {
|
||||
instances,
|
||||
dissolve_amounts: vec![0.0; num_instances],
|
||||
dissolve_targets: vec![0.0; num_instances],
|
||||
transition_speed: 5.0,
|
||||
instance_buffer,
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/components/trigger.rs
Normal file
37
src/components/trigger.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use crate::entity::EntityHandle;
|
||||
|
||||
pub enum TriggerShape
|
||||
{
|
||||
Sphere { radius: f32 },
|
||||
}
|
||||
|
||||
pub enum TriggerFilter
|
||||
{
|
||||
Player,
|
||||
}
|
||||
|
||||
pub enum TriggerState
|
||||
{
|
||||
Idle,
|
||||
Inside,
|
||||
}
|
||||
|
||||
pub struct TriggerComponent
|
||||
{
|
||||
pub shape: TriggerShape,
|
||||
pub filter: TriggerFilter,
|
||||
pub state: TriggerState,
|
||||
}
|
||||
|
||||
pub enum TriggerEventKind
|
||||
{
|
||||
Entered,
|
||||
Exited,
|
||||
}
|
||||
|
||||
pub struct TriggerEvent
|
||||
{
|
||||
pub trigger_entity: EntityHandle,
|
||||
pub activator_entity: EntityHandle,
|
||||
pub kind: TriggerEventKind,
|
||||
}
|
||||
@@ -150,6 +150,7 @@ pub fn render_collider_debug() -> Vec<DrawCall>
|
||||
enable_dissolve: false,
|
||||
enable_snow_light: false,
|
||||
displacement_bind_group: None,
|
||||
entity: None,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -183,6 +184,7 @@ pub fn render_collider_debug() -> Vec<DrawCall>
|
||||
enable_dissolve: false,
|
||||
enable_snow_light: false,
|
||||
displacement_bind_group: None,
|
||||
entity: None,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
71
src/draw.rs
71
src/draw.rs
@@ -1,71 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::entity::EntityHandle;
|
||||
use crate::loaders::mesh::Mesh;
|
||||
use crate::render::{DrawCall, Pipeline};
|
||||
|
||||
pub type DrawHandle = usize;
|
||||
|
||||
struct DrawEntry
|
||||
{
|
||||
mesh: Rc<Mesh>,
|
||||
entity: EntityHandle,
|
||||
pipeline: Pipeline,
|
||||
}
|
||||
|
||||
pub struct DrawManager
|
||||
{
|
||||
entries: HashMap<DrawHandle, DrawEntry>,
|
||||
next_handle: DrawHandle,
|
||||
}
|
||||
|
||||
impl DrawManager
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
entries: HashMap::new(),
|
||||
next_handle: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_mesh_internal(
|
||||
&mut self,
|
||||
mesh: Rc<Mesh>,
|
||||
entity: EntityHandle,
|
||||
pipeline: Pipeline,
|
||||
) -> DrawHandle
|
||||
{
|
||||
let handle = self.next_handle;
|
||||
self.next_handle += 1;
|
||||
|
||||
self.entries.insert(
|
||||
handle,
|
||||
DrawEntry {
|
||||
mesh,
|
||||
entity,
|
||||
pipeline,
|
||||
},
|
||||
);
|
||||
|
||||
handle
|
||||
}
|
||||
|
||||
pub fn clear_mesh_internal(&mut self, handle: DrawHandle)
|
||||
{
|
||||
self.entries.remove(&handle);
|
||||
}
|
||||
|
||||
pub fn collect_draw_calls(&self) -> Vec<DrawCall>
|
||||
{
|
||||
vec![]
|
||||
}
|
||||
|
||||
pub fn draw_mesh(_mesh: Rc<Mesh>, _entity: EntityHandle, _pipeline: Pipeline) -> DrawHandle
|
||||
{
|
||||
0
|
||||
}
|
||||
|
||||
pub fn clear_mesh(_handle: DrawHandle) {}
|
||||
}
|
||||
214
src/editor/inspector.rs
Normal file
214
src/editor/inspector.rs
Normal file
@@ -0,0 +1,214 @@
|
||||
use dear_imgui_rs::{Condition, Context};
|
||||
use dear_imgui_wgpu::{WgpuInitInfo, WgpuRenderer};
|
||||
use glam::EulerRot;
|
||||
use sdl3_sys::events::SDL_Event;
|
||||
|
||||
use crate::entity::EntityHandle;
|
||||
use crate::world::World;
|
||||
|
||||
pub struct FrameStats
|
||||
{
|
||||
pub fps: f32,
|
||||
pub frame_ms: f32,
|
||||
pub physics_budget_ms: f32,
|
||||
pub draw_call_count: usize,
|
||||
}
|
||||
|
||||
pub struct Inspector
|
||||
{
|
||||
imgui: Context,
|
||||
renderer: WgpuRenderer,
|
||||
}
|
||||
|
||||
impl Inspector
|
||||
{
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
surface_format: wgpu::TextureFormat,
|
||||
) -> Self
|
||||
{
|
||||
let mut imgui = Context::create();
|
||||
let init_info = WgpuInitInfo::new(device.clone(), queue.clone(), surface_format);
|
||||
let renderer =
|
||||
WgpuRenderer::new(init_info, &mut imgui).expect("Failed to create imgui wgpu renderer");
|
||||
Self { imgui, renderer }
|
||||
}
|
||||
|
||||
pub fn init_platform(&mut self, window: &sdl3::video::Window)
|
||||
{
|
||||
dear_imgui_sdl3::init_for_other(&mut self.imgui, window)
|
||||
.expect("Failed to init imgui sdl3 platform");
|
||||
}
|
||||
|
||||
pub fn begin_frame(&mut self)
|
||||
{
|
||||
dear_imgui_sdl3::sdl3_new_frame(&mut self.imgui);
|
||||
}
|
||||
|
||||
pub fn process_event(&mut self, raw_event: &SDL_Event)
|
||||
{
|
||||
dear_imgui_sdl3::process_sys_event(raw_event);
|
||||
}
|
||||
|
||||
pub fn wants_keyboard(&self) -> bool
|
||||
{
|
||||
self.imgui.io().want_capture_keyboard()
|
||||
}
|
||||
|
||||
pub fn wants_mouse(&self) -> bool
|
||||
{
|
||||
self.imgui.io().want_capture_mouse()
|
||||
}
|
||||
|
||||
pub fn build_ui(
|
||||
&mut self,
|
||||
stats: &FrameStats,
|
||||
world: &World,
|
||||
selected_entity: Option<EntityHandle>,
|
||||
)
|
||||
{
|
||||
let ui = self.imgui.frame();
|
||||
ui.window("Inspector")
|
||||
.position([10.0, 10.0], Condition::FirstUseEver)
|
||||
.build(|| {
|
||||
ui.text(format!("FPS: {:.1}", stats.fps));
|
||||
ui.text(format!("Frame: {:.1} ms", stats.frame_ms));
|
||||
ui.text(format!("Physics: {:.1} ms", stats.physics_budget_ms));
|
||||
ui.text(format!("Draw calls: {}", stats.draw_call_count));
|
||||
});
|
||||
|
||||
if let Some(entity) = selected_entity
|
||||
{
|
||||
ui.window("Entity")
|
||||
.position([10.0, 120.0], Condition::FirstUseEver)
|
||||
.build(|| {
|
||||
let name = world
|
||||
.names
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| format!("Entity #{}", entity));
|
||||
ui.text(format!("Name: {}", name));
|
||||
ui.text(format!("ID: {}", entity));
|
||||
|
||||
ui.separator();
|
||||
ui.text("Transform");
|
||||
|
||||
if let Some(transform) = world.transforms.get(entity)
|
||||
{
|
||||
let p = transform.position;
|
||||
ui.text(format!(" Pos ({:.2}, {:.2}, {:.2})", p.x, p.y, p.z));
|
||||
|
||||
let (ex, ey, ez) = transform.rotation.to_euler(EulerRot::XYZ);
|
||||
ui.text(format!(
|
||||
" Rot ({:.1}, {:.1}, {:.1}) deg",
|
||||
ex.to_degrees(),
|
||||
ey.to_degrees(),
|
||||
ez.to_degrees()
|
||||
));
|
||||
|
||||
let s = transform.scale;
|
||||
ui.text(format!(" Scale ({:.2}, {:.2}, {:.2})", s.x, s.y, s.z));
|
||||
}
|
||||
else
|
||||
{
|
||||
ui.text(" (no transform)");
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
ui.text("Components");
|
||||
|
||||
if world.meshes.get(entity).is_some()
|
||||
{
|
||||
ui.text(" Mesh");
|
||||
}
|
||||
if world.physics.get(entity).is_some()
|
||||
{
|
||||
ui.text(" Physics");
|
||||
}
|
||||
if let Some(m) = world.movements.get(entity)
|
||||
{
|
||||
ui.text(format!(
|
||||
" Movement (max_speed {:.1})",
|
||||
m.max_walking_speed
|
||||
));
|
||||
}
|
||||
if world.jumps.get(entity).is_some()
|
||||
{
|
||||
ui.text(" Jump");
|
||||
}
|
||||
if world.inputs.get(entity).is_some()
|
||||
{
|
||||
ui.text(" Input");
|
||||
}
|
||||
if world.player_tags.get(entity).is_some()
|
||||
{
|
||||
ui.text(" [Player]");
|
||||
}
|
||||
if world.tree_tags.get(entity).is_some()
|
||||
{
|
||||
ui.text(" [Tree]");
|
||||
}
|
||||
if let Some(cam) = world.cameras.get(entity)
|
||||
{
|
||||
ui.text(format!(
|
||||
" Camera fov={:.0} near={:.2} far={:.0}",
|
||||
cam.fov.to_degrees(),
|
||||
cam.near,
|
||||
cam.far
|
||||
));
|
||||
}
|
||||
if let Some(spot) = world.spotlights.get(entity)
|
||||
{
|
||||
let o = spot.offset;
|
||||
ui.text(format!(
|
||||
" Spotlight offset ({:.1}, {:.1}, {:.1})",
|
||||
o.x, o.y, o.z
|
||||
));
|
||||
}
|
||||
if let Some(ti) = world.tree_instances.get(entity)
|
||||
{
|
||||
ui.text(format!(" TreeInstances ({})", ti.instances.len()));
|
||||
}
|
||||
if world.follows.get(entity).is_some()
|
||||
{
|
||||
ui.text(" Follow");
|
||||
}
|
||||
if world.rotates.get(entity).is_some()
|
||||
{
|
||||
ui.text(" Rotate");
|
||||
}
|
||||
if let Some(d) = world.dissolves.get(entity)
|
||||
{
|
||||
ui.text(format!(" Dissolve ({:.2})", d.amount));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&mut self, encoder: &mut wgpu::CommandEncoder, view: &wgpu::TextureView)
|
||||
{
|
||||
let draw_data = self.imgui.render();
|
||||
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("ImGui Render Pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
depth_slice: None,
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
occlusion_query_set: None,
|
||||
timestamp_writes: None,
|
||||
multiview_mask: None,
|
||||
});
|
||||
|
||||
self.renderer
|
||||
.render_draw_data(draw_data, &mut render_pass)
|
||||
.expect("Failed to render imgui");
|
||||
}
|
||||
}
|
||||
83
src/editor/mod.rs
Normal file
83
src/editor/mod.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
mod inspector;
|
||||
|
||||
use sdl3_sys::events::SDL_Event;
|
||||
|
||||
use crate::entity::EntityHandle;
|
||||
use crate::systems::camera_noclip_system;
|
||||
use crate::utility::input::InputState;
|
||||
use crate::world::World;
|
||||
|
||||
pub use inspector::FrameStats;
|
||||
use inspector::Inspector;
|
||||
|
||||
pub struct EditorState
|
||||
{
|
||||
pub active: bool,
|
||||
pub right_mouse_held: bool,
|
||||
pub selected_entity: Option<EntityHandle>,
|
||||
inspector: Inspector,
|
||||
}
|
||||
|
||||
impl EditorState
|
||||
{
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
surface_format: wgpu::TextureFormat,
|
||||
) -> Self
|
||||
{
|
||||
Self {
|
||||
active: false,
|
||||
right_mouse_held: false,
|
||||
selected_entity: None,
|
||||
inspector: Inspector::new(device, queue, surface_format),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_platform(&mut self, window: &sdl3::video::Window)
|
||||
{
|
||||
self.inspector.init_platform(window);
|
||||
}
|
||||
|
||||
pub fn begin_frame(&mut self)
|
||||
{
|
||||
self.inspector.begin_frame();
|
||||
}
|
||||
|
||||
pub fn process_event(&mut self, raw_event: &SDL_Event) -> bool
|
||||
{
|
||||
self.inspector.process_event(raw_event);
|
||||
false
|
||||
}
|
||||
|
||||
pub fn wants_keyboard(&self) -> bool
|
||||
{
|
||||
self.inspector.wants_keyboard()
|
||||
}
|
||||
|
||||
pub fn wants_mouse(&self) -> bool
|
||||
{
|
||||
self.inspector.wants_mouse()
|
||||
}
|
||||
|
||||
pub fn render(&mut self, encoder: &mut wgpu::CommandEncoder, view: &wgpu::TextureView)
|
||||
{
|
||||
self.inspector.render(encoder, view);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn editor_loop(
|
||||
editor: &mut EditorState,
|
||||
world: &mut World,
|
||||
input_state: &InputState,
|
||||
stats: &FrameStats,
|
||||
delta: f32,
|
||||
)
|
||||
{
|
||||
if editor.right_mouse_held
|
||||
{
|
||||
camera_noclip_system(world, input_state, delta);
|
||||
}
|
||||
let selected = editor.selected_entity;
|
||||
editor.inspector.build_ui(stats, world, selected);
|
||||
}
|
||||
70
src/event.rs
70
src/event.rs
@@ -1,70 +0,0 @@
|
||||
use std::any::{Any, TypeId};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub trait Event: std::fmt::Debug {}
|
||||
|
||||
type EventHandler<T> = Box<dyn FnMut(&T)>;
|
||||
|
||||
pub struct EventBus
|
||||
{
|
||||
handlers: HashMap<TypeId, Box<dyn Any>>,
|
||||
}
|
||||
|
||||
impl EventBus
|
||||
{
|
||||
fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
handlers: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn subscribe_internal<T: Event + 'static, F: FnMut(&T) + 'static>(&mut self, handler: F)
|
||||
{
|
||||
let type_id = TypeId::of::<T>();
|
||||
let handlers: &mut Vec<EventHandler<T>> = self
|
||||
.handlers
|
||||
.entry(type_id)
|
||||
.or_insert_with(|| Box::new(Vec::<EventHandler<T>>::new()))
|
||||
.downcast_mut()
|
||||
.unwrap();
|
||||
|
||||
handlers.push(Box::new(handler));
|
||||
}
|
||||
|
||||
fn publish_internal<T: Event + 'static>(&mut self, event: &T)
|
||||
{
|
||||
let type_id = TypeId::of::<T>();
|
||||
|
||||
if let Some(handlers) = self.handlers.get_mut(&type_id)
|
||||
{
|
||||
let typed_handlers = handlers.downcast_mut::<Vec<EventHandler<T>>>().unwrap();
|
||||
for handler in typed_handlers
|
||||
{
|
||||
handler(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subscribe<T: Event + 'static, F: FnMut(&T) + 'static>(handler: F)
|
||||
{
|
||||
EVENT_BUS.with(|bus| bus.borrow_mut().subscribe_internal(handler));
|
||||
}
|
||||
|
||||
pub fn publish<T: Event + 'static>(event: &T)
|
||||
{
|
||||
EVENT_BUS.with(|bus| bus.borrow_mut().publish_internal(event));
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static EVENT_BUS: RefCell<EventBus> = RefCell::new(EventBus::new());
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UpdateEvent
|
||||
{
|
||||
pub delta: f32,
|
||||
}
|
||||
impl Event for UpdateEvent {}
|
||||
@@ -59,7 +59,7 @@ pub fn load_exr_heightmap(
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Nearest,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,26 @@ use bytemuck::{Pod, Zeroable};
|
||||
use glam::{Mat4, Quat, Vec3};
|
||||
use std::path::Path;
|
||||
|
||||
fn compute_aabb(vertices: &[Vertex]) -> (Vec3, Vec3)
|
||||
{
|
||||
let mut aabb_min = Vec3::splat(f32::MAX);
|
||||
let mut aabb_max = Vec3::splat(f32::MIN);
|
||||
for v in vertices
|
||||
{
|
||||
let p = Vec3::from(v.position);
|
||||
aabb_min = aabb_min.min(p);
|
||||
aabb_max = aabb_max.max(p);
|
||||
}
|
||||
if vertices.is_empty()
|
||||
{
|
||||
(Vec3::ZERO, Vec3::ZERO)
|
||||
}
|
||||
else
|
||||
{
|
||||
(aabb_min, aabb_max)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
pub struct Vertex
|
||||
@@ -113,6 +133,10 @@ pub struct Mesh
|
||||
pub vertex_buffer: wgpu::Buffer,
|
||||
pub index_buffer: wgpu::Buffer,
|
||||
pub num_indices: u32,
|
||||
pub aabb_min: Vec3,
|
||||
pub aabb_max: Vec3,
|
||||
pub cpu_positions: Vec<[f32; 3]>,
|
||||
pub cpu_indices: Vec<u32>,
|
||||
}
|
||||
|
||||
impl Mesh
|
||||
@@ -121,6 +145,8 @@ impl Mesh
|
||||
{
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
let (aabb_min, aabb_max) = compute_aabb(vertices);
|
||||
|
||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Vertex Buffer"),
|
||||
contents: bytemuck::cast_slice(vertices),
|
||||
@@ -137,6 +163,10 @@ impl Mesh
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
num_indices: indices.len() as u32,
|
||||
aabb_min,
|
||||
aabb_max,
|
||||
cpu_positions: vertices.iter().map(|v| v.position).collect(),
|
||||
cpu_indices: indices.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
182
src/main.rs
182
src/main.rs
@@ -1,11 +1,11 @@
|
||||
mod bundles;
|
||||
mod components;
|
||||
mod debug;
|
||||
mod draw;
|
||||
mod editor;
|
||||
mod entity;
|
||||
mod event;
|
||||
mod loaders;
|
||||
mod physics;
|
||||
mod picking;
|
||||
mod postprocess;
|
||||
mod render;
|
||||
mod snow;
|
||||
@@ -17,11 +17,15 @@ mod utility;
|
||||
mod world;
|
||||
|
||||
use crate::debug::{collider_debug, DebugMode};
|
||||
use crate::editor::{editor_loop, EditorState, FrameStats};
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use glam::Vec3;
|
||||
use render::Renderer;
|
||||
use sdl3::event::Event;
|
||||
use sdl3::keyboard::Keycode;
|
||||
use sdl3::mouse::MouseButton;
|
||||
use utility::input::InputState;
|
||||
use world::World;
|
||||
|
||||
@@ -34,11 +38,13 @@ use crate::loaders::scene::Space;
|
||||
use crate::physics::PhysicsManager;
|
||||
use crate::snow::{SnowConfig, SnowLayer};
|
||||
use crate::systems::{
|
||||
camera_follow_system, camera_input_system, camera_noclip_system, camera_view_matrix,
|
||||
noclip_toggle_system, physics_sync_system, player_input_system, render_system, rotate_system,
|
||||
snow_system, spotlight_sync_system, start_camera_following, state_machine_physics_system,
|
||||
state_machine_system,
|
||||
camera_follow_system, camera_input_system, camera_view_matrix, physics_sync_system,
|
||||
player_input_system, render_system, rotate_system, snow_system, spotlight_sync_system,
|
||||
start_camera_following, state_machine_physics_system, state_machine_system,
|
||||
tree_dissolve_update_system, tree_instance_buffer_update_system, tree_occlusion_system,
|
||||
trigger_system,
|
||||
};
|
||||
use crate::systems::camera::stop_camera_following;
|
||||
use crate::utility::time::Time;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
@@ -55,6 +61,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
let renderer = pollster::block_on(Renderer::new(&window, 2))?;
|
||||
render::init(renderer);
|
||||
|
||||
let mut editor = render::with_device(|device| {
|
||||
render::with_queue(|queue| {
|
||||
render::with_surface_format(|format| EditorState::new(device, queue, format))
|
||||
})
|
||||
});
|
||||
editor.init_platform(&window);
|
||||
|
||||
let space = Space::load_space("meshes/terrain.gltf")?;
|
||||
let terrain_config = TerrainConfig::default();
|
||||
|
||||
@@ -95,7 +108,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
|
||||
render::set_snow_depth(&snow_layer.depth_texture_view);
|
||||
|
||||
let mut noclip_mode = true;
|
||||
let mut debug_mode = DebugMode::default();
|
||||
|
||||
let camera_entity = CameraBundle {
|
||||
@@ -103,12 +115,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
}
|
||||
.spawn(&mut world)
|
||||
.unwrap();
|
||||
if !noclip_mode
|
||||
{
|
||||
start_camera_following(&mut world, camera_entity);
|
||||
}
|
||||
|
||||
let mut event_pump = sdl_context.event_pump()?;
|
||||
let _event_pump = sdl_context.event_pump()?;
|
||||
let mut input_state = InputState::new();
|
||||
|
||||
sdl_context.mouse().set_relative_mouse_mode(&window, true);
|
||||
@@ -121,6 +130,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
const FIXED_TIMESTEP: f32 = 1.0 / 60.0;
|
||||
let mut physics_accumulator = 0.0;
|
||||
|
||||
let mut stats = FrameStats {
|
||||
fps: 0.0,
|
||||
frame_ms: 0.0,
|
||||
physics_budget_ms: 0.0,
|
||||
draw_call_count: 0,
|
||||
};
|
||||
|
||||
'running: loop
|
||||
{
|
||||
let frame_start = Instant::now();
|
||||
@@ -128,11 +144,106 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
let delta = (frame_start - last_frame).as_secs_f32();
|
||||
last_frame = frame_start;
|
||||
|
||||
for event in event_pump.poll_iter()
|
||||
{
|
||||
let mouse_capture_changed = input_state.handle_event(&event);
|
||||
editor.begin_frame();
|
||||
|
||||
if mouse_capture_changed
|
||||
while let Some(raw_event) = dear_imgui_sdl3::sdl3_poll_event_ll()
|
||||
{
|
||||
editor.process_event(&raw_event);
|
||||
let event = Event::from_ll(raw_event);
|
||||
|
||||
match &event
|
||||
{
|
||||
Event::Quit { .. } =>
|
||||
{
|
||||
input_state.quit_requested = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
Event::KeyDown {
|
||||
keycode: Some(Keycode::I),
|
||||
repeat: false,
|
||||
..
|
||||
} =>
|
||||
{
|
||||
editor.active = !editor.active;
|
||||
if editor.active
|
||||
{
|
||||
stop_camera_following(&mut world, camera_entity);
|
||||
sdl_context.mouse().set_relative_mouse_mode(&window, false);
|
||||
editor.right_mouse_held = false;
|
||||
input_state.mouse_captured = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
start_camera_following(&mut world, camera_entity);
|
||||
input_state.mouse_captured = true;
|
||||
sdl_context.mouse().set_relative_mouse_mode(&window, true);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Event::MouseButtonDown {
|
||||
mouse_btn: MouseButton::Right,
|
||||
..
|
||||
} if editor.active =>
|
||||
{
|
||||
editor.right_mouse_held = true;
|
||||
input_state.mouse_captured = true;
|
||||
stop_camera_following(&mut world, camera_entity);
|
||||
sdl_context.mouse().set_relative_mouse_mode(&window, true);
|
||||
continue;
|
||||
}
|
||||
|
||||
Event::MouseButtonUp {
|
||||
mouse_btn: MouseButton::Right,
|
||||
..
|
||||
} if editor.active =>
|
||||
{
|
||||
editor.right_mouse_held = false;
|
||||
input_state.mouse_captured = false;
|
||||
sdl_context.mouse().set_relative_mouse_mode(&window, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
Event::MouseButtonDown {
|
||||
mouse_btn: MouseButton::Left,
|
||||
x,
|
||||
y,
|
||||
..
|
||||
} if editor.active && !editor.wants_mouse() =>
|
||||
{
|
||||
if let Some(view) = crate::systems::camera_view_matrix(&world)
|
||||
{
|
||||
if let Some((_, cam)) = world.active_camera()
|
||||
{
|
||||
let projection = cam.projection_matrix();
|
||||
let (win_w, win_h) = window.size();
|
||||
let ray = crate::picking::Ray::from_screen_position(
|
||||
*x,
|
||||
*y,
|
||||
win_w,
|
||||
win_h,
|
||||
&view,
|
||||
&projection,
|
||||
);
|
||||
editor.selected_entity = crate::picking::pick_entity(&ray, &world);
|
||||
render::set_selected_entity(editor.selected_entity);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
_ =>
|
||||
{}
|
||||
}
|
||||
|
||||
if editor.active && (editor.wants_keyboard() || editor.wants_mouse())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let capture_changed = input_state.handle_event(&event);
|
||||
if capture_changed && !editor.active
|
||||
{
|
||||
sdl_context
|
||||
.mouse()
|
||||
@@ -145,10 +256,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
break 'running;
|
||||
}
|
||||
|
||||
input_state.process_post_events();
|
||||
|
||||
noclip_toggle_system(&mut world, &input_state, camera_entity, &mut noclip_mode);
|
||||
|
||||
if input_state.debug_cycle_just_pressed
|
||||
{
|
||||
debug_mode = debug_mode.cycle();
|
||||
@@ -157,9 +264,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
|
||||
camera_input_system(&mut world, &input_state);
|
||||
|
||||
if noclip_mode
|
||||
if editor.active
|
||||
{
|
||||
camera_noclip_system(&mut world, &input_state, delta);
|
||||
editor_loop(&mut editor, &mut world, &input_state, &stats, delta);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -167,6 +274,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
player_input_system(&mut world, &input_state);
|
||||
}
|
||||
|
||||
let physics_start = Instant::now();
|
||||
|
||||
physics_accumulator += delta;
|
||||
|
||||
while physics_accumulator >= FIXED_TIMESTEP
|
||||
@@ -176,18 +285,25 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
PhysicsManager::physics_step();
|
||||
|
||||
physics_sync_system(&mut world);
|
||||
trigger_system(&mut world);
|
||||
|
||||
physics_accumulator -= FIXED_TIMESTEP;
|
||||
}
|
||||
|
||||
stats.physics_budget_ms = physics_start.elapsed().as_secs_f32() * 1000.0;
|
||||
|
||||
state_machine_system(&mut world, delta);
|
||||
|
||||
rotate_system(&mut world, delta);
|
||||
|
||||
tree_occlusion_system(&mut world);
|
||||
tree_dissolve_update_system(&mut world, delta);
|
||||
tree_instance_buffer_update_system(&mut world);
|
||||
|
||||
let spotlights = spotlight_sync_system(&world);
|
||||
render::update_spotlights(spotlights);
|
||||
|
||||
snow_system(&world, &mut snow_layer, noclip_mode);
|
||||
snow_system(&world, &mut snow_layer, editor.active);
|
||||
|
||||
let mut draw_calls = render_system(&world);
|
||||
draw_calls.extend(snow_layer.get_draw_calls());
|
||||
@@ -207,7 +323,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
{
|
||||
let projection = camera_component.projection_matrix();
|
||||
|
||||
render::render(
|
||||
stats.draw_call_count = draw_calls.len();
|
||||
stats.fps = 1.0 / delta;
|
||||
stats.frame_ms = delta * 1000.0;
|
||||
|
||||
let frame = render::render(
|
||||
&view,
|
||||
&projection,
|
||||
camera_transform.position,
|
||||
@@ -217,6 +337,22 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
delta,
|
||||
debug_mode,
|
||||
);
|
||||
|
||||
if editor.active
|
||||
{
|
||||
let screen_view = frame
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let mut encoder = render::with_device(|d| {
|
||||
d.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("ImGui Encoder"),
|
||||
})
|
||||
});
|
||||
editor.render(&mut encoder, &screen_view);
|
||||
render::with_queue(|q| q.submit(std::iter::once(encoder.finish())));
|
||||
}
|
||||
|
||||
frame.present();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
229
src/picking.rs
229
src/picking.rs
@@ -1,7 +1,10 @@
|
||||
use crate::camera::Camera;
|
||||
use crate::mesh::Mesh;
|
||||
use glam::{Mat4, Vec3, Vec4};
|
||||
|
||||
use crate::components::TreeInstancesComponent;
|
||||
use crate::entity::EntityHandle;
|
||||
use crate::loaders::mesh::Mesh;
|
||||
use crate::world::World;
|
||||
|
||||
pub struct Ray
|
||||
{
|
||||
pub origin: Vec3,
|
||||
@@ -15,7 +18,8 @@ impl Ray
|
||||
screen_y: f32,
|
||||
screen_width: u32,
|
||||
screen_height: u32,
|
||||
camera: &Camera,
|
||||
view: &Mat4,
|
||||
projection: &Mat4,
|
||||
) -> Self
|
||||
{
|
||||
let ndc_x = (2.0 * screen_x) / screen_width as f32 - 1.0;
|
||||
@@ -23,10 +27,8 @@ impl Ray
|
||||
|
||||
let clip_coords = Vec4::new(ndc_x, ndc_y, -1.0, 1.0);
|
||||
|
||||
let view_matrix = camera.view_matrix();
|
||||
let projection_matrix = camera.projection_matrix();
|
||||
let inv_projection = projection_matrix.inverse();
|
||||
let inv_view = view_matrix.inverse();
|
||||
let inv_projection = projection.inverse();
|
||||
let inv_view = view.inverse();
|
||||
|
||||
let eye_coords = inv_projection * clip_coords;
|
||||
let eye_coords = Vec4::new(eye_coords.x, eye_coords.y, -1.0, 0.0);
|
||||
@@ -34,31 +36,127 @@ impl Ray
|
||||
let world_coords = inv_view * eye_coords;
|
||||
let direction = Vec3::new(world_coords.x, world_coords.y, world_coords.z).normalize();
|
||||
|
||||
Ray {
|
||||
origin: camera.position,
|
||||
direction,
|
||||
}
|
||||
let origin = inv_view.col(3).truncate();
|
||||
|
||||
Ray { origin, direction }
|
||||
}
|
||||
|
||||
pub fn intersects_mesh(&self, mesh: &Mesh, transform: &Mat4) -> Option<f32>
|
||||
pub fn intersects_aabb(&self, min: Vec3, max: Vec3) -> Option<f32>
|
||||
{
|
||||
let inv_transform = transform.inverse();
|
||||
let local_origin = inv_transform.transform_point3(self.origin);
|
||||
let local_direction = inv_transform.transform_vector3(self.direction).normalize();
|
||||
let inv_dir = Vec3::new(
|
||||
1.0 / self.direction.x,
|
||||
1.0 / self.direction.y,
|
||||
1.0 / self.direction.z,
|
||||
);
|
||||
|
||||
let mut closest_distance = f32::MAX;
|
||||
let t1 = (min - self.origin) * inv_dir;
|
||||
let t2 = (max - self.origin) * inv_dir;
|
||||
|
||||
let tmin = t1.min(t2);
|
||||
let tmax = t1.max(t2);
|
||||
|
||||
let tenter = tmin.x.max(tmin.y).max(tmin.z);
|
||||
let texit = tmax.x.min(tmax.y).min(tmax.z);
|
||||
|
||||
if tenter <= texit && texit >= 0.0
|
||||
{
|
||||
Some(tenter.max(0.0))
|
||||
}
|
||||
else
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn transform_aabb(aabb_min: Vec3, aabb_max: Vec3, model: &Mat4) -> (Vec3, Vec3)
|
||||
{
|
||||
let corners = [
|
||||
Vec3::new(aabb_min.x, aabb_min.y, aabb_min.z),
|
||||
Vec3::new(aabb_max.x, aabb_min.y, aabb_min.z),
|
||||
Vec3::new(aabb_min.x, aabb_max.y, aabb_min.z),
|
||||
Vec3::new(aabb_max.x, aabb_max.y, aabb_min.z),
|
||||
Vec3::new(aabb_min.x, aabb_min.y, aabb_max.z),
|
||||
Vec3::new(aabb_max.x, aabb_min.y, aabb_max.z),
|
||||
Vec3::new(aabb_min.x, aabb_max.y, aabb_max.z),
|
||||
Vec3::new(aabb_max.x, aabb_max.y, aabb_max.z),
|
||||
];
|
||||
|
||||
let mut world_min = Vec3::splat(f32::MAX);
|
||||
let mut world_max = Vec3::splat(f32::MIN);
|
||||
for corner in &corners
|
||||
{
|
||||
let world_corner = model.transform_point3(*corner);
|
||||
world_min = world_min.min(world_corner);
|
||||
world_max = world_max.max(world_corner);
|
||||
}
|
||||
(world_min, world_max)
|
||||
}
|
||||
|
||||
fn intersect_triangle(origin: Vec3, dir: Vec3, v0: Vec3, v1: Vec3, v2: Vec3) -> Option<f32>
|
||||
{
|
||||
let edge1 = v1 - v0;
|
||||
let edge2 = v2 - v0;
|
||||
let h = dir.cross(edge2);
|
||||
let a = edge1.dot(h);
|
||||
if a.abs() < 1e-7
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let f = 1.0 / a;
|
||||
let s = origin - v0;
|
||||
let u = f * s.dot(h);
|
||||
if !(0.0..=1.0).contains(&u)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let q = s.cross(edge1);
|
||||
let v = f * dir.dot(q);
|
||||
if v < 0.0 || u + v > 1.0
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let t = f * edge2.dot(q);
|
||||
if t > 1e-7
|
||||
{
|
||||
Some(t)
|
||||
}
|
||||
else
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Ray–mesh triangle intersection in local space, returning world-space distance.
|
||||
///
|
||||
/// Transforms the ray into local mesh space, tests every triangle with
|
||||
/// Möller–Trumbore, then maps the closest local hit back to a world-space
|
||||
/// distance so results can be compared across entities.
|
||||
fn intersect_mesh_triangles(ray: &Ray, mesh: &Mesh, model: &Mat4) -> Option<f32>
|
||||
{
|
||||
if mesh.cpu_indices.is_empty()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let inv_model = model.inverse();
|
||||
let local_origin = inv_model.transform_point3(ray.origin);
|
||||
let local_dir = inv_model.transform_vector3(ray.direction);
|
||||
|
||||
let mut closest_t = f32::MAX;
|
||||
let mut hit = false;
|
||||
|
||||
for triangle_idx in (0..mesh.num_indices).step_by(3)
|
||||
for tri in mesh.cpu_indices.chunks_exact(3)
|
||||
{
|
||||
let distance =
|
||||
self.intersects_triangle_local(local_origin, local_direction, mesh, triangle_idx);
|
||||
let v0 = Vec3::from(mesh.cpu_positions[tri[0] as usize]);
|
||||
let v1 = Vec3::from(mesh.cpu_positions[tri[1] as usize]);
|
||||
let v2 = Vec3::from(mesh.cpu_positions[tri[2] as usize]);
|
||||
|
||||
if let Some(d) = distance
|
||||
if let Some(t) = intersect_triangle(local_origin, local_dir, v0, v1, v2)
|
||||
{
|
||||
if d < closest_distance
|
||||
if t < closest_t
|
||||
{
|
||||
closest_distance = d;
|
||||
closest_t = t;
|
||||
hit = true;
|
||||
}
|
||||
}
|
||||
@@ -66,22 +164,87 @@ impl Ray
|
||||
|
||||
if hit
|
||||
{
|
||||
Some(closest_distance)
|
||||
let local_hit = local_origin + local_dir * closest_t;
|
||||
let world_hit = model.transform_point3(local_hit);
|
||||
Some((world_hit - ray.origin).length())
|
||||
}
|
||||
else
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn intersect_instances(
|
||||
ray: &Ray,
|
||||
mesh: &Mesh,
|
||||
tree_instances: &TreeInstancesComponent,
|
||||
) -> Option<f32>
|
||||
{
|
||||
let mut closest: Option<f32> = None;
|
||||
|
||||
for instance in &tree_instances.instances
|
||||
{
|
||||
let instance_model = Mat4::from_scale_rotation_translation(
|
||||
instance.scale,
|
||||
instance.rotation,
|
||||
instance.position,
|
||||
);
|
||||
let (world_min, world_max) = transform_aabb(mesh.aabb_min, mesh.aabb_max, &instance_model);
|
||||
|
||||
if let Some(d) = ray.intersects_aabb(world_min, world_max)
|
||||
{
|
||||
if closest.map_or(true, |c| d < c)
|
||||
{
|
||||
closest = Some(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn intersects_triangle_local(
|
||||
&self,
|
||||
local_origin: Vec3,
|
||||
local_direction: Vec3,
|
||||
_mesh: &Mesh,
|
||||
_triangle_idx: u32,
|
||||
) -> Option<f32>
|
||||
{
|
||||
None
|
||||
}
|
||||
closest
|
||||
}
|
||||
|
||||
pub fn pick_entity(ray: &Ray, world: &World) -> Option<EntityHandle>
|
||||
{
|
||||
let mut closest_entity = None;
|
||||
let mut closest_distance = f32::MAX;
|
||||
|
||||
for (&entity, mesh_component) in &world.meshes.components
|
||||
{
|
||||
let transform = match world.transforms.get(entity)
|
||||
{
|
||||
Some(t) => t,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let model = transform.to_matrix();
|
||||
|
||||
let distance = if let Some(tree_instances) = world.tree_instances.get(entity)
|
||||
{
|
||||
intersect_instances(ray, &mesh_component.mesh, tree_instances)
|
||||
}
|
||||
else
|
||||
{
|
||||
let (world_min, world_max) = transform_aabb(
|
||||
mesh_component.mesh.aabb_min,
|
||||
mesh_component.mesh.aabb_max,
|
||||
&model,
|
||||
);
|
||||
if ray.intersects_aabb(world_min, world_max).is_none()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
intersect_mesh_triangles(ray, &mesh_component.mesh, &model)
|
||||
};
|
||||
|
||||
if let Some(d) = distance
|
||||
{
|
||||
if d < closest_distance
|
||||
{
|
||||
closest_distance = d;
|
||||
closest_entity = Some(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closest_entity
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ impl LowResFramebuffer
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Nearest,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
@@ -157,7 +157,7 @@ pub fn create_blit_pipeline(
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Blit Pipeline Layout"),
|
||||
bind_group_layouts: &[bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
@@ -194,7 +194,7 @@ pub fn create_blit_pipeline(
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use crate::texture::{DitherTextures, FlowmapTexture};
|
||||
use std::num::NonZeroU64;
|
||||
|
||||
use super::types::Uniforms;
|
||||
use super::Renderer;
|
||||
|
||||
impl Renderer
|
||||
@@ -24,7 +26,11 @@ impl Renderer
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: uniform_buffer.as_entire_binding(),
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: uniform_buffer,
|
||||
offset: 0,
|
||||
size: NonZeroU64::new(std::mem::size_of::<Uniforms>() as u64),
|
||||
}),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
|
||||
@@ -61,13 +61,13 @@ impl DebugOverlay
|
||||
let shadow_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Shadow Debug Pipeline Layout"),
|
||||
bind_group_layouts: &[&shadow_bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
let snow_light_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Snow Light Debug Pipeline Layout"),
|
||||
bind_group_layouts: &[&snow_light_bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
let color_target = wgpu::ColorTargetState {
|
||||
@@ -98,12 +98,11 @@ impl DebugOverlay
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview: None,
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
let pipeline_snow_light =
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
let pipeline_snow_light = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Snow Light Debug Pipeline"),
|
||||
layout: Some(&snow_light_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
@@ -125,7 +124,7 @@ impl DebugOverlay
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview: None,
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
@@ -171,6 +170,7 @@ impl DebugOverlay
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
});
|
||||
|
||||
pass.set_pipeline(&self.pipeline_shadow);
|
||||
@@ -221,6 +221,7 @@ impl DebugOverlay
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
});
|
||||
|
||||
pass.set_pipeline(&self.pipeline_snow_light);
|
||||
|
||||
@@ -6,12 +6,19 @@ mod types;
|
||||
|
||||
pub use types::{DrawCall, Pipeline, Spotlight, SpotlightRaw, Uniforms, MAX_SPOTLIGHTS};
|
||||
|
||||
use crate::entity::EntityHandle;
|
||||
|
||||
use crate::debug::DebugMode;
|
||||
use crate::postprocess::{create_blit_pipeline, create_fullscreen_quad, LowResFramebuffer};
|
||||
use crate::texture::{DitherTextures, FlowmapTexture};
|
||||
use pipeline::{create_debug_lines_pipeline, create_main_pipeline, create_snow_clipmap_pipeline,
|
||||
create_wireframe_pipeline};
|
||||
use pipeline::{
|
||||
create_debug_lines_pipeline, create_main_pipeline, create_snow_clipmap_pipeline,
|
||||
create_wireframe_pipeline,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
use std::num::NonZeroU64;
|
||||
|
||||
const MAX_DRAW_CALLS: usize = 64;
|
||||
|
||||
pub struct Renderer
|
||||
{
|
||||
@@ -62,6 +69,8 @@ pub struct Renderer
|
||||
|
||||
snow_light_accumulation: Option<crate::snow_light::SnowLightAccumulation>,
|
||||
snow_light_bound: bool,
|
||||
|
||||
pub selected_entity: Option<EntityHandle>,
|
||||
}
|
||||
|
||||
impl Renderer
|
||||
@@ -90,8 +99,9 @@ impl Renderer
|
||||
.await
|
||||
.map_err(|_| "Failed to find adapter")?;
|
||||
|
||||
let wireframe_supported =
|
||||
adapter.features().contains(wgpu::Features::POLYGON_MODE_LINE);
|
||||
let wireframe_supported = adapter
|
||||
.features()
|
||||
.contains(wgpu::Features::POLYGON_MODE_LINE);
|
||||
let required_features = if wireframe_supported
|
||||
{
|
||||
wgpu::Features::POLYGON_MODE_LINE
|
||||
@@ -129,7 +139,7 @@ impl Renderer
|
||||
|
||||
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("Uniform Buffer"),
|
||||
size: std::mem::size_of::<Uniforms>() as wgpu::BufferAddress,
|
||||
size: (std::mem::size_of::<Uniforms>() * MAX_DRAW_CALLS) as wgpu::BufferAddress,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
@@ -248,7 +258,7 @@ impl Renderer
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
|
||||
compare: Some(wgpu::CompareFunction::LessEqual),
|
||||
..Default::default()
|
||||
});
|
||||
@@ -306,8 +316,8 @@ impl Renderer
|
||||
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
has_dynamic_offset: true,
|
||||
min_binding_size: NonZeroU64::new(std::mem::size_of::<Uniforms>() as u64),
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
@@ -463,15 +473,22 @@ impl Renderer
|
||||
|
||||
let wireframe_pipeline = if wireframe_supported
|
||||
{
|
||||
Some(create_wireframe_pipeline(&device, config.format, &bind_group_layout))
|
||||
Some(create_wireframe_pipeline(
|
||||
&device,
|
||||
config.format,
|
||||
&bind_group_layout,
|
||||
))
|
||||
}
|
||||
else
|
||||
{
|
||||
None
|
||||
};
|
||||
|
||||
let debug_lines_pipeline =
|
||||
Some(create_debug_lines_pipeline(&device, config.format, &bind_group_layout));
|
||||
let debug_lines_pipeline = Some(create_debug_lines_pipeline(
|
||||
&device,
|
||||
config.format,
|
||||
&bind_group_layout,
|
||||
));
|
||||
|
||||
let debug_overlay = Some(debug_overlay::DebugOverlay::new(&device, config.format));
|
||||
|
||||
@@ -533,6 +550,7 @@ impl Renderer
|
||||
dummy_snow_light_sampler,
|
||||
snow_light_accumulation: None,
|
||||
snow_light_bound: false,
|
||||
selected_entity: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -546,7 +564,7 @@ impl Renderer
|
||||
time: f32,
|
||||
delta_time: f32,
|
||||
debug_mode: DebugMode,
|
||||
)
|
||||
) -> wgpu::SurfaceTexture
|
||||
{
|
||||
let light_view_projections = self.calculate_light_view_projections();
|
||||
|
||||
@@ -585,6 +603,9 @@ impl Renderer
|
||||
label: Some("Render Encoder"),
|
||||
});
|
||||
|
||||
let uniform_size = std::mem::size_of::<Uniforms>() as u64;
|
||||
let mut uniform_slot: u64 = 0;
|
||||
|
||||
for (i, draw_call) in draw_calls.iter().enumerate()
|
||||
{
|
||||
let uniforms = Uniforms::new(
|
||||
@@ -603,8 +624,15 @@ impl Renderer
|
||||
&self.spotlights,
|
||||
debug_mode.as_u32(),
|
||||
);
|
||||
self.queue
|
||||
.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
|
||||
|
||||
let offset = uniform_slot * uniform_size;
|
||||
uniform_slot += 1;
|
||||
|
||||
self.queue.write_buffer(
|
||||
&self.uniform_buffer,
|
||||
offset,
|
||||
bytemuck::cast_slice(&[uniforms]),
|
||||
);
|
||||
|
||||
{
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
@@ -647,6 +675,7 @@ impl Renderer
|
||||
}),
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
});
|
||||
|
||||
let pipeline = match draw_call.pipeline
|
||||
@@ -659,7 +688,7 @@ impl Renderer
|
||||
.unwrap_or(&self.standard_pipeline),
|
||||
};
|
||||
render_pass.set_pipeline(pipeline);
|
||||
render_pass.set_bind_group(0, &self.bind_group, &[]);
|
||||
render_pass.set_bind_group(0, &self.bind_group, &[offset as u32]);
|
||||
|
||||
if let Some(ref displacement_bind_group) = draw_call.displacement_bind_group
|
||||
{
|
||||
@@ -685,7 +714,10 @@ impl Renderer
|
||||
{
|
||||
for draw_call in draw_calls.iter()
|
||||
{
|
||||
if matches!(draw_call.pipeline, Pipeline::SnowClipmap | Pipeline::DebugLines)
|
||||
if matches!(
|
||||
draw_call.pipeline,
|
||||
Pipeline::SnowClipmap | Pipeline::DebugLines
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -706,9 +738,11 @@ impl Renderer
|
||||
&self.spotlights,
|
||||
4,
|
||||
);
|
||||
let wire_offset = uniform_slot * uniform_size;
|
||||
uniform_slot += 1;
|
||||
self.queue.write_buffer(
|
||||
&self.uniform_buffer,
|
||||
0,
|
||||
wire_offset,
|
||||
bytemuck::cast_slice(&[uniforms]),
|
||||
);
|
||||
|
||||
@@ -737,10 +771,11 @@ impl Renderer
|
||||
),
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
});
|
||||
|
||||
wire_pass.set_pipeline(wireframe_pipeline);
|
||||
wire_pass.set_bind_group(0, &self.bind_group, &[]);
|
||||
wire_pass.set_bind_group(0, &self.bind_group, &[wire_offset as u32]);
|
||||
wire_pass.set_vertex_buffer(0, draw_call.vertex_buffer.slice(..));
|
||||
|
||||
if let Some(ref instance_buffer) = draw_call.instance_buffer
|
||||
@@ -762,6 +797,99 @@ impl Renderer
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(selected) = self.selected_entity
|
||||
{
|
||||
if let Some(ref wireframe_pipeline) = self.wireframe_pipeline
|
||||
{
|
||||
for draw_call in draw_calls.iter()
|
||||
{
|
||||
if draw_call.entity != Some(selected)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if matches!(
|
||||
draw_call.pipeline,
|
||||
Pipeline::SnowClipmap | Pipeline::DebugLines
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let uniforms = Uniforms::new(
|
||||
draw_call.model,
|
||||
*view,
|
||||
*projection,
|
||||
&light_view_projections,
|
||||
camera_position,
|
||||
player_position,
|
||||
self.terrain_height_scale,
|
||||
time,
|
||||
self.shadow_bias,
|
||||
draw_call.tile_scale,
|
||||
draw_call.enable_dissolve,
|
||||
draw_call.enable_snow_light,
|
||||
&self.spotlights,
|
||||
4,
|
||||
);
|
||||
let sel_offset = uniform_slot * uniform_size;
|
||||
uniform_slot += 1;
|
||||
self.queue.write_buffer(
|
||||
&self.uniform_buffer,
|
||||
sel_offset,
|
||||
bytemuck::cast_slice(&[uniforms]),
|
||||
);
|
||||
|
||||
{
|
||||
let mut sel_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Selection Wireframe Pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &self.framebuffer.view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
depth_slice: None,
|
||||
})],
|
||||
depth_stencil_attachment: Some(
|
||||
wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.framebuffer.depth_view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
},
|
||||
),
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
});
|
||||
|
||||
sel_pass.set_pipeline(wireframe_pipeline);
|
||||
sel_pass.set_bind_group(0, &self.bind_group, &[sel_offset as u32]);
|
||||
sel_pass.set_vertex_buffer(0, draw_call.vertex_buffer.slice(..));
|
||||
|
||||
if let Some(ref instance_buffer) = draw_call.instance_buffer
|
||||
{
|
||||
sel_pass.set_vertex_buffer(1, instance_buffer.slice(..));
|
||||
}
|
||||
|
||||
sel_pass.set_index_buffer(
|
||||
draw_call.index_buffer.slice(..),
|
||||
wgpu::IndexFormat::Uint32,
|
||||
);
|
||||
sel_pass.draw_indexed(
|
||||
0..draw_call.num_indices,
|
||||
0,
|
||||
0..draw_call.num_instances,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if debug_mode == DebugMode::ShadowMap
|
||||
{
|
||||
if let Some(ref overlay) = self.debug_overlay
|
||||
@@ -850,6 +978,7 @@ impl Renderer
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
});
|
||||
|
||||
blit_pass.set_pipeline(&self.blit_pipeline);
|
||||
@@ -860,7 +989,7 @@ impl Renderer
|
||||
}
|
||||
|
||||
self.queue.submit(std::iter::once(blit_encoder.finish()));
|
||||
frame.present();
|
||||
frame
|
||||
}
|
||||
|
||||
pub fn render_scale(&self) -> (u32, u32)
|
||||
@@ -1025,7 +1154,7 @@ pub fn render(
|
||||
time: f32,
|
||||
delta_time: f32,
|
||||
debug_mode: DebugMode,
|
||||
)
|
||||
) -> wgpu::SurfaceTexture
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let mut renderer = r.borrow_mut();
|
||||
@@ -1039,8 +1168,19 @@ pub fn render(
|
||||
time,
|
||||
delta_time,
|
||||
debug_mode,
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_surface_format<F, R>(f: F) -> R
|
||||
where
|
||||
F: FnOnce(wgpu::TextureFormat) -> R,
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let renderer = r.borrow();
|
||||
let renderer = renderer.as_ref().expect("Renderer not set");
|
||||
f(renderer.config.format)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_shadow_bias(bias: f32)
|
||||
@@ -1060,3 +1200,12 @@ pub fn update_spotlights(spotlights: Vec<Spotlight>)
|
||||
renderer.spotlights = spotlights;
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_selected_entity(entity: Option<EntityHandle>)
|
||||
{
|
||||
GLOBAL_RENDERER.with(|r| {
|
||||
let mut renderer = r.borrow_mut();
|
||||
let renderer = renderer.as_mut().expect("Renderer not set");
|
||||
renderer.selected_entity = entity;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ pub fn create_shadow_pipeline(
|
||||
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Shadow Pipeline Layout"),
|
||||
bind_group_layouts: &[bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
@@ -57,7 +57,7 @@ pub fn create_shadow_pipeline(
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
})
|
||||
}
|
||||
@@ -85,7 +85,7 @@ pub fn create_main_pipeline(
|
||||
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Main Pipeline Layout"),
|
||||
bind_group_layouts: &[bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
@@ -131,7 +131,7 @@ pub fn create_main_pipeline(
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
})
|
||||
}
|
||||
@@ -157,7 +157,7 @@ pub fn create_wireframe_pipeline(
|
||||
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Wireframe Pipeline Layout"),
|
||||
bind_group_layouts: &[bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
@@ -203,7 +203,7 @@ pub fn create_wireframe_pipeline(
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
})
|
||||
}
|
||||
@@ -229,7 +229,7 @@ pub fn create_debug_lines_pipeline(
|
||||
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Debug Lines Pipeline Layout"),
|
||||
bind_group_layouts: &[bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
@@ -275,7 +275,7 @@ pub fn create_debug_lines_pipeline(
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
})
|
||||
}
|
||||
@@ -340,7 +340,7 @@ pub fn create_snow_clipmap_pipeline(
|
||||
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Snow Clipmap Pipeline Layout"),
|
||||
bind_group_layouts: &[main_bind_group_layout, &displacement_bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
@@ -390,7 +390,7 @@ pub fn create_snow_clipmap_pipeline(
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ impl Renderer
|
||||
}),
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
});
|
||||
|
||||
shadow_pass.set_pipeline(
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use glam::Mat4;
|
||||
|
||||
use crate::entity::EntityHandle;
|
||||
|
||||
pub const MAX_SPOTLIGHTS: usize = 4;
|
||||
|
||||
#[repr(C)]
|
||||
@@ -156,4 +158,5 @@ pub struct DrawCall
|
||||
pub enable_dissolve: bool,
|
||||
pub enable_snow_light: bool,
|
||||
pub displacement_bind_group: Option<wgpu::BindGroup>,
|
||||
pub entity: Option<EntityHandle>,
|
||||
}
|
||||
|
||||
@@ -32,46 +32,7 @@ fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
);
|
||||
output.world_normal = normalize(normal_matrix * input.normal);
|
||||
|
||||
var dissolve_amount = 0.0;
|
||||
|
||||
if uniforms.enable_dissolve == 1u {
|
||||
let instance_position = vec3<f32>(
|
||||
instance_model[3][0],
|
||||
instance_model[3][1],
|
||||
instance_model[3][2]
|
||||
);
|
||||
|
||||
let to_player = uniforms.player_position - uniforms.camera_position;
|
||||
let distance_to_player = length(to_player);
|
||||
|
||||
if distance_to_player > 0.01 {
|
||||
let ray_dir = to_player / distance_to_player;
|
||||
let ray_origin = uniforms.camera_position;
|
||||
|
||||
let tree_height = 16.0;
|
||||
let occlusion_radius = 6.5;
|
||||
|
||||
let w = instance_position - ray_origin;
|
||||
let projection_t = dot(w, ray_dir);
|
||||
|
||||
if projection_t > 0.0 && projection_t < distance_to_player {
|
||||
let closest_on_ray = ray_origin + ray_dir * projection_t;
|
||||
|
||||
let diff = closest_on_ray - instance_position;
|
||||
let height_on_trunk = clamp(diff.y, 0.0, tree_height);
|
||||
let closest_on_trunk = instance_position + vec3<f32>(0.0, height_on_trunk, 0.0);
|
||||
|
||||
let perp_distance = length(closest_on_trunk - closest_on_ray);
|
||||
|
||||
if perp_distance < occlusion_radius {
|
||||
let dissolve_t = pow(perp_distance / occlusion_radius, 0.5);
|
||||
dissolve_amount = 1.0 - clamp(dissolve_t, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output.dissolve_amount = dissolve_amount;
|
||||
output.dissolve_amount = input.instance_dissolve;
|
||||
output.uv = input.uv;
|
||||
|
||||
return output;
|
||||
@@ -80,11 +41,10 @@ fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
if uniforms.enable_dissolve == 1u && in.dissolve_amount > 0.0 {
|
||||
let screen_pos = in.clip_position.xy;
|
||||
let noise_uv = fract(screen_pos / 128.0);
|
||||
let noise_value = textureSampleLevel(blue_noise_texture, blue_noise_sampler, noise_uv, 0.0).r;
|
||||
|
||||
if noise_value < in.dissolve_amount {
|
||||
let screen_pos = in.clip_position.xy / in.clip_position.w;
|
||||
let noise_uv = screen_pos * 0.5 + 0.5;
|
||||
let noise = textureSample(blue_noise_texture, blue_noise_sampler, noise_uv * 10.0).r;
|
||||
if in.dissolve_amount > noise {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
|
||||
16
src/snow.rs
16
src/snow.rs
@@ -360,7 +360,7 @@ impl SnowLayer
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Snow Deform Pipeline Layout"),
|
||||
bind_group_layouts: &[&bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
|
||||
@@ -505,10 +505,23 @@ impl SnowLayer
|
||||
})
|
||||
});
|
||||
|
||||
let mut aabb_min = Vec3::splat(f32::MAX);
|
||||
let mut aabb_max = Vec3::splat(f32::MIN);
|
||||
for v in &vertices
|
||||
{
|
||||
let p = Vec3::from(v.position);
|
||||
aabb_min = aabb_min.min(p);
|
||||
aabb_max = aabb_max.max(p);
|
||||
}
|
||||
|
||||
Mesh {
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
num_indices: indices.len() as u32,
|
||||
aabb_min,
|
||||
aabb_max,
|
||||
cpu_positions: Vec::new(),
|
||||
cpu_indices: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,6 +581,7 @@ impl SnowLayer
|
||||
enable_dissolve: false,
|
||||
enable_snow_light: true,
|
||||
displacement_bind_group: Some(self.displacement_bind_group.clone()),
|
||||
entity: None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use glam::{Vec2, Vec3};
|
||||
use glam::Vec2;
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
#[repr(C)]
|
||||
@@ -185,7 +185,7 @@ impl SnowLightAccumulation
|
||||
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: &[],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
@@ -222,7 +222,7 @@ impl SnowLightAccumulation
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
@@ -280,6 +280,7 @@ impl SnowLightAccumulation
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
});
|
||||
|
||||
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
@@ -296,6 +297,7 @@ impl SnowLightAccumulation
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -498,6 +500,7 @@ impl SnowLightAccumulation
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
});
|
||||
|
||||
render_pass.set_pipeline(&self.pipeline);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
pub mod camera;
|
||||
pub mod follow;
|
||||
pub mod input;
|
||||
pub mod noclip;
|
||||
pub mod physics_sync;
|
||||
pub mod player_states;
|
||||
pub mod render;
|
||||
@@ -10,16 +9,20 @@ pub mod snow;
|
||||
pub mod spotlight_sync;
|
||||
pub mod state_machine;
|
||||
pub mod tree_dissolve;
|
||||
pub mod trigger;
|
||||
|
||||
pub use camera::{
|
||||
camera_follow_system, camera_input_system, camera_noclip_system, camera_view_matrix,
|
||||
start_camera_following,
|
||||
};
|
||||
pub use input::player_input_system;
|
||||
pub use noclip::noclip_toggle_system;
|
||||
pub use physics_sync::physics_sync_system;
|
||||
pub use render::render_system;
|
||||
pub use rotate::rotate_system;
|
||||
pub use snow::snow_system;
|
||||
pub use spotlight_sync::spotlight_sync_system;
|
||||
pub use state_machine::{state_machine_physics_system, state_machine_system};
|
||||
pub use tree_dissolve::{
|
||||
tree_dissolve_update_system, tree_instance_buffer_update_system, tree_occlusion_system,
|
||||
};
|
||||
pub use trigger::trigger_system;
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
use crate::entity::EntityHandle;
|
||||
use crate::systems::camera::{start_camera_following, stop_camera_following};
|
||||
use crate::utility::input::InputState;
|
||||
use crate::world::World;
|
||||
|
||||
pub fn noclip_toggle_system(
|
||||
world: &mut World,
|
||||
input_state: &InputState,
|
||||
camera_entity: EntityHandle,
|
||||
noclip_mode: &mut bool,
|
||||
)
|
||||
{
|
||||
if input_state.noclip_just_pressed
|
||||
{
|
||||
*noclip_mode = !*noclip_mode;
|
||||
if *noclip_mode
|
||||
{
|
||||
stop_camera_following(world, camera_entity);
|
||||
}
|
||||
else
|
||||
{
|
||||
start_camera_following(world, camera_entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,7 @@ impl State for PlayerFallingState
|
||||
};
|
||||
|
||||
world.movements.with_mut(self.entity, |movement| {
|
||||
movement.movement_config.movement_context.is_floored = is_grounded;
|
||||
movement.movement_context.is_floored = is_grounded;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -116,7 +116,7 @@ impl State for PlayerIdleState
|
||||
let current_velocity = *rigidbody.linvel();
|
||||
let idle_damping = world
|
||||
.movements
|
||||
.with(self.entity, |m| m.movement_config.idle_damping)
|
||||
.with(self.entity, |m| m.idle_damping)
|
||||
.unwrap_or(0.1);
|
||||
|
||||
let horizontal_velocity = Vec3::new(current_velocity.x, 0.0, current_velocity.z);
|
||||
@@ -171,7 +171,7 @@ impl State for PlayerIdleState
|
||||
});
|
||||
|
||||
world.movements.with_mut(self.entity, |movement| {
|
||||
movement.movement_config.movement_context.is_floored = true;
|
||||
movement.movement_context.is_floored = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -210,7 +210,7 @@ impl State for PlayerWalkingState
|
||||
.inputs
|
||||
.with(self.entity, |input| input.move_direction)
|
||||
.unwrap_or(Vec3::ZERO);
|
||||
(input, movement.movement_config.clone())
|
||||
(input, movement.clone())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -301,7 +301,7 @@ impl State for PlayerWalkingState
|
||||
});
|
||||
|
||||
world.movements.with_mut(self.entity, |movement| {
|
||||
movement.movement_config.movement_context.is_floored = terrain_height.is_some();
|
||||
movement.movement_context.is_floored = terrain_height.is_some();
|
||||
});
|
||||
|
||||
if movement_input.length_squared() > 0.1
|
||||
@@ -334,11 +334,11 @@ impl State for PlayerJumpingState
|
||||
let current_position = world.transforms.get(self.entity).unwrap().position;
|
||||
|
||||
world.jumps.with_mut(self.entity, |jump| {
|
||||
jump.jump_config.jump_context.in_progress = true;
|
||||
jump.jump_config.jump_context.execution_time = self.enter_time_stamp;
|
||||
jump.jump_config.jump_context.origin_height = current_position.y;
|
||||
jump.jump_config.jump_context.duration = 0.0;
|
||||
jump.jump_config.jump_context.normal = Vec3::Y;
|
||||
jump.jump_context.in_progress = true;
|
||||
jump.jump_context.execution_time = self.enter_time_stamp;
|
||||
jump.jump_context.origin_height = current_position.y;
|
||||
jump.jump_context.duration = 0.0;
|
||||
jump.jump_context.normal = Vec3::Y;
|
||||
});
|
||||
|
||||
println!("entered jumping");
|
||||
@@ -347,8 +347,8 @@ impl State for PlayerJumpingState
|
||||
fn on_state_exit(&mut self, world: &mut World)
|
||||
{
|
||||
world.jumps.with_mut(self.entity, |jump| {
|
||||
jump.jump_config.jump_context.in_progress = false;
|
||||
jump.jump_config.jump_context.duration = 0.0;
|
||||
jump.jump_context.in_progress = false;
|
||||
jump.jump_context.duration = 0.0;
|
||||
});
|
||||
|
||||
println!("exited jumping");
|
||||
@@ -361,21 +361,21 @@ 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_context.duration =
|
||||
current_time - jump.jump_context.execution_time;
|
||||
});
|
||||
|
||||
let jump_config = world
|
||||
let jump = world
|
||||
.jumps
|
||||
.with_mut(self.entity, |jump| jump.jump_config.clone())
|
||||
.with(self.entity, |jump| jump.clone())
|
||||
.unwrap();
|
||||
|
||||
let elapsed_time = jump_config.jump_context.duration;
|
||||
let normalized_time = (elapsed_time / jump_config.jump_duration).min(1.0);
|
||||
let height_progress = jump_config.jump_curve.eval(normalized_time as f64).y as f32;
|
||||
let elapsed_time = jump.jump_context.duration;
|
||||
let normalized_time = (elapsed_time / jump.jump_duration).min(1.0);
|
||||
let height_progress = jump.jump_curve.eval(normalized_time as f64).y as f32;
|
||||
|
||||
let origin_height = jump_config.jump_context.origin_height;
|
||||
let target_height = origin_height + height_progress * jump_config.jump_height;
|
||||
let origin_height = jump.jump_context.origin_height;
|
||||
let target_height = origin_height + height_progress * jump.jump_height;
|
||||
|
||||
let current_translation = world
|
||||
.physics
|
||||
@@ -409,7 +409,7 @@ impl State for PlayerJumpingState
|
||||
});
|
||||
|
||||
world.movements.with_mut(self.entity, |movement| {
|
||||
movement.movement_config.movement_context.is_floored = false;
|
||||
movement.movement_context.is_floored = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ pub fn render_system(world: &World) -> Vec<DrawCall>
|
||||
enable_dissolve: mesh_component.enable_dissolve,
|
||||
enable_snow_light: mesh_component.enable_snow_light,
|
||||
displacement_bind_group: None,
|
||||
entity: Some(entity),
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
use crate::components::DissolveComponent;
|
||||
use crate::loaders::mesh::InstanceRaw;
|
||||
use crate::world::World;
|
||||
use bytemuck::cast_slice;
|
||||
|
||||
pub fn tree_dissolve_update_system(world: &mut World, delta: f32)
|
||||
{
|
||||
for entity in world.dissolves.all()
|
||||
for entity in world.tree_instances.all()
|
||||
{
|
||||
if let Some(dissolve) = world.dissolves.get_mut(entity)
|
||||
if let Some(tree_instances) = world.tree_instances.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);
|
||||
for i in 0..tree_instances.dissolve_amounts.len()
|
||||
{
|
||||
let diff = tree_instances.dissolve_targets[i] - tree_instances.dissolve_amounts[i];
|
||||
tree_instances.dissolve_amounts[i] +=
|
||||
diff * tree_instances.transition_speed * delta;
|
||||
tree_instances.dissolve_amounts[i] =
|
||||
tree_instances.dissolve_amounts[i].clamp(0.0, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,52 +41,70 @@ pub fn tree_occlusion_system(world: &mut World)
|
||||
}
|
||||
|
||||
let to_player_normalized = to_player.normalize();
|
||||
let occlusion_radius = 10.0;
|
||||
|
||||
for tree_entity in world.tree_tags.all()
|
||||
for tree_entity in world.tree_instances.all()
|
||||
{
|
||||
if let Some(tree_transform) = world.transforms.get(tree_entity)
|
||||
if let Some(tree_instances) = world.tree_instances.get_mut(tree_entity)
|
||||
{
|
||||
let tree_pos = tree_transform.position;
|
||||
let to_tree = tree_pos - camera_pos;
|
||||
let distance_to_tree = to_tree.length();
|
||||
for (idx, instance) in tree_instances.instances.iter().enumerate()
|
||||
{
|
||||
let instance_pos = instance.position;
|
||||
let to_instance = instance_pos - camera_pos;
|
||||
let distance_to_instance = to_instance.length();
|
||||
|
||||
if distance_to_tree < distance_to_player
|
||||
if distance_to_instance < distance_to_player
|
||||
{
|
||||
let projection = to_tree.dot(to_player_normalized);
|
||||
let projection = to_instance.dot(to_player_normalized);
|
||||
|
||||
if projection > 0.0
|
||||
if projection > 0.0 && projection <= distance_to_player
|
||||
{
|
||||
let perpendicular_vec = to_tree - to_player_normalized * projection;
|
||||
let perpendicular_vec =
|
||||
to_instance - to_player_normalized * projection;
|
||||
let perp_distance = perpendicular_vec.length();
|
||||
|
||||
let occlusion_radius = 2.5;
|
||||
|
||||
if perp_distance < occlusion_radius
|
||||
if perp_distance <= occlusion_radius
|
||||
{
|
||||
let dissolve_amount =
|
||||
1.0 - (perp_distance / occlusion_radius).clamp(0.0, 1.0);
|
||||
|
||||
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);
|
||||
}
|
||||
tree_instances.dissolve_targets[idx] = dissolve_amount;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(dissolve) = world.dissolves.get_mut(tree_entity)
|
||||
{
|
||||
dissolve.target_amount = 0.0;
|
||||
tree_instances.dissolve_targets[idx] = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tree_instance_buffer_update_system(world: &mut World)
|
||||
{
|
||||
for entity in world.tree_instances.all()
|
||||
{
|
||||
if let Some(tree_instances) = world.tree_instances.get(entity)
|
||||
{
|
||||
let instance_data_vec: Vec<InstanceRaw> = tree_instances
|
||||
.instances
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, instance)| InstanceRaw {
|
||||
model: instance.to_raw().model,
|
||||
dissolve_amount: tree_instances.dissolve_amounts[idx],
|
||||
_padding: [0.0; 3],
|
||||
})
|
||||
.collect();
|
||||
|
||||
crate::render::with_queue(|queue| {
|
||||
queue.write_buffer(
|
||||
&tree_instances.instance_buffer,
|
||||
0,
|
||||
cast_slice(&instance_data_vec),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
82
src/systems/trigger.rs
Normal file
82
src/systems/trigger.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use glam::Vec3;
|
||||
|
||||
use crate::components::trigger::{TriggerEvent, TriggerEventKind, TriggerFilter, TriggerShape, TriggerState};
|
||||
use crate::entity::EntityHandle;
|
||||
use crate::world::World;
|
||||
|
||||
pub fn trigger_system(world: &mut World)
|
||||
{
|
||||
world.trigger_events.clear();
|
||||
|
||||
let trigger_entities: Vec<EntityHandle> = world.triggers.all();
|
||||
let mut pending_events: Vec<TriggerEvent> = Vec::new();
|
||||
|
||||
for trigger_entity in trigger_entities
|
||||
{
|
||||
let trigger_pos = match world.transforms.get(trigger_entity)
|
||||
{
|
||||
Some(t) => t.position,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let candidate_entities: Vec<EntityHandle> = match world.triggers.get(trigger_entity)
|
||||
{
|
||||
Some(trigger) => match &trigger.filter
|
||||
{
|
||||
TriggerFilter::Player => world.player_tags.all(),
|
||||
},
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let activator_positions: Vec<(EntityHandle, Vec3)> = candidate_entities
|
||||
.into_iter()
|
||||
.filter_map(|e| world.transforms.get(e).map(|t| (e, t.position)))
|
||||
.collect();
|
||||
|
||||
let overlapping = match world.triggers.get(trigger_entity)
|
||||
{
|
||||
Some(trigger) => activator_positions.iter().any(|(_, pos)| match &trigger.shape
|
||||
{
|
||||
TriggerShape::Sphere { radius } => (trigger_pos - *pos).length() < *radius,
|
||||
}),
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let first_activator = activator_positions.first().map(|(e, _)| *e);
|
||||
|
||||
if let Some(trigger) = world.triggers.get_mut(trigger_entity)
|
||||
{
|
||||
match (&trigger.state, overlapping)
|
||||
{
|
||||
(TriggerState::Idle, true) =>
|
||||
{
|
||||
trigger.state = TriggerState::Inside;
|
||||
if let Some(activator_entity) = first_activator
|
||||
{
|
||||
pending_events.push(TriggerEvent {
|
||||
trigger_entity,
|
||||
activator_entity,
|
||||
kind: TriggerEventKind::Entered,
|
||||
});
|
||||
}
|
||||
}
|
||||
(TriggerState::Inside, false) =>
|
||||
{
|
||||
trigger.state = TriggerState::Idle;
|
||||
if let Some(activator_entity) = first_activator
|
||||
{
|
||||
pending_events.push(TriggerEvent {
|
||||
trigger_entity,
|
||||
activator_entity,
|
||||
kind: TriggerEventKind::Exited,
|
||||
});
|
||||
}
|
||||
}
|
||||
_ =>
|
||||
{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
world.trigger_events.extend(pending_events);
|
||||
}
|
||||
@@ -123,7 +123,7 @@ impl DitherTextures
|
||||
address_mode_w: wgpu::AddressMode::Repeat,
|
||||
mag_filter: wgpu::FilterMode::Nearest,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
@@ -226,7 +226,7 @@ impl FlowmapTexture
|
||||
address_mode_w: wgpu::AddressMode::Repeat,
|
||||
mag_filter: wgpu::FilterMode::Nearest,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
|
||||
@@ -11,12 +11,10 @@ pub struct InputState
|
||||
pub shift: bool,
|
||||
|
||||
pub space_just_pressed: bool,
|
||||
pub noclip_just_pressed: bool,
|
||||
pub debug_cycle_just_pressed: bool,
|
||||
|
||||
pub mouse_delta: (f32, f32),
|
||||
pub mouse_captured: bool,
|
||||
pub noclip_mode: bool,
|
||||
pub quit_requested: bool,
|
||||
}
|
||||
|
||||
@@ -32,11 +30,9 @@ impl InputState
|
||||
space: false,
|
||||
shift: false,
|
||||
space_just_pressed: false,
|
||||
noclip_just_pressed: false,
|
||||
debug_cycle_just_pressed: false,
|
||||
mouse_delta: (0.0, 0.0),
|
||||
mouse_captured: true,
|
||||
noclip_mode: false,
|
||||
quit_requested: false,
|
||||
}
|
||||
}
|
||||
@@ -66,12 +62,6 @@ impl InputState
|
||||
self.quit_requested = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if *key == Keycode::I
|
||||
{
|
||||
self.mouse_captured = !self.mouse_captured;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +101,6 @@ impl InputState
|
||||
self.space = true;
|
||||
}
|
||||
Keycode::LShift | Keycode::RShift => self.shift = true,
|
||||
Keycode::N => self.noclip_just_pressed = true,
|
||||
Keycode::F1 => self.debug_cycle_just_pressed = true,
|
||||
_ =>
|
||||
{}
|
||||
@@ -141,22 +130,9 @@ impl InputState
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_post_events(&mut self)
|
||||
{
|
||||
if self.noclip_just_pressed
|
||||
{
|
||||
self.noclip_mode = !self.noclip_mode;
|
||||
println!(
|
||||
"Noclip mode: {}",
|
||||
if self.noclip_mode { "ON" } else { "OFF" }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_just_pressed(&mut self)
|
||||
{
|
||||
self.space_just_pressed = false;
|
||||
self.noclip_just_pressed = false;
|
||||
self.debug_cycle_just_pressed = false;
|
||||
self.mouse_delta = (0.0, 0.0);
|
||||
}
|
||||
|
||||
18
src/world.rs
18
src/world.rs
@@ -2,11 +2,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::tree_instances::TreeInstancesComponent;
|
||||
use crate::components::trigger::{TriggerComponent, TriggerEvent};
|
||||
use crate::components::{
|
||||
CameraComponent, InputComponent, MeshComponent, MovementComponent, PhysicsComponent,
|
||||
RotateComponent,
|
||||
CameraComponent, InputComponent, JumpComponent, MeshComponent, MovementComponent,
|
||||
PhysicsComponent, RotateComponent,
|
||||
};
|
||||
use crate::entity::{EntityHandle, EntityManager};
|
||||
use crate::state::StateMachine;
|
||||
@@ -84,6 +85,10 @@ pub struct World
|
||||
pub dissolves: Storage<DissolveComponent>,
|
||||
pub follows: Storage<FollowComponent>,
|
||||
pub rotates: Storage<RotateComponent>,
|
||||
pub tree_instances: Storage<TreeInstancesComponent>,
|
||||
pub names: Storage<String>,
|
||||
pub triggers: Storage<TriggerComponent>,
|
||||
pub trigger_events: Vec<TriggerEvent>,
|
||||
}
|
||||
|
||||
impl World
|
||||
@@ -106,6 +111,10 @@ impl World
|
||||
dissolves: Storage::new(),
|
||||
follows: Storage::new(),
|
||||
rotates: Storage::new(),
|
||||
tree_instances: Storage::new(),
|
||||
names: Storage::new(),
|
||||
triggers: Storage::new(),
|
||||
trigger_events: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,6 +139,9 @@ impl World
|
||||
self.dissolves.remove(entity);
|
||||
self.follows.remove(entity);
|
||||
self.rotates.remove(entity);
|
||||
self.tree_instances.remove(entity);
|
||||
self.names.remove(entity);
|
||||
self.triggers.remove(entity);
|
||||
self.entities.despawn(entity);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user