Compare commits

...

11 Commits

Author SHA1 Message Date
Jonas H
c37a9fd5dd tree dissolve + instances 2026-03-05 15:06:29 +01:00
Jonas H
350fddc2af trigger system 2026-03-05 15:05:49 +01:00
Jonas H
bab54b6f21 picking + entity names 2026-03-05 15:05:19 +01:00
Jonas H
5e5c6a47e7 dynamic uniform offsets 2026-03-05 15:03:56 +01:00
Jonas H
28f8c65571 flatten component configs 2026-03-05 15:03:55 +01:00
Jonas H
d888dc077e cargo imgui and sdl update 2026-03-04 09:08:19 +01:00
Jonas H
adeb181d17 ignored cargo.lock 2026-03-04 09:08:01 +01:00
Jonas H
0ce4e813ab draw/event cleanup and gitignore 2026-03-04 09:06:49 +01:00
Jonas H
322b1f7b43 editor+1 2026-03-04 09:05:39 +01:00
Jonas H
1925ebea0c sdl->28 2026-03-04 09:04:58 +01:00
Jonas H
5f8d924a02 editor with imgui and sdl->28 2026-03-04 09:04:37 +01:00
40 changed files with 1239 additions and 2941 deletions

4
.gitignore vendored
View File

@@ -1,2 +1,6 @@
/target /target
target target
Cargo.lock
imgui.ini

2436
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,8 +4,12 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
sdl3 = { version = "0.16", features = ["raw-window-handle"] } sdl3 = { version = "0.17", features = ["raw-window-handle"] }
wgpu = "27" sdl3-sys = "0.6"
dear-imgui-rs = "0.10"
dear-imgui-wgpu = "0.10"
dear-imgui-sdl3 = "0.10"
wgpu = "28"
pollster = "0.3" pollster = "0.3"
glam = "0.30" glam = "0.30"
anyhow = "1.0" anyhow = "1.0"

View File

@@ -20,6 +20,7 @@ impl Bundle for CameraBundle
let transform = Transform::from_position(self.position); let transform = Transform::from_position(self.position);
world.cameras.insert(camera_entity, camera_component); world.cameras.insert(camera_entity, camera_component);
world.transforms.insert(camera_entity, transform); world.transforms.insert(camera_entity, transform);
world.names.insert(camera_entity, "Camera".to_string());
Ok(camera_entity) Ok(camera_entity)
} }
} }

View File

@@ -6,9 +6,10 @@ use rapier3d::control::{CharacterAutostep, KinematicCharacterController};
use rapier3d::prelude::{ColliderBuilder, RigidBodyBuilder}; use rapier3d::prelude::{ColliderBuilder, RigidBodyBuilder};
use crate::bundles::Bundle; use crate::bundles::Bundle;
use crate::components::jump::JumpComponent;
use crate::components::lights::spot::SpotlightComponent; 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::entity::EntityHandle;
use crate::loaders::mesh::Mesh; use crate::loaders::mesh::Mesh;
use crate::physics::PhysicsManager; use crate::physics::PhysicsManager;
@@ -70,7 +71,7 @@ impl Bundle for PlayerBundle
state_machine.add_transition::<PlayerFallingState, PlayerIdleState>(move |world| { state_machine.add_transition::<PlayerFallingState, PlayerIdleState>(move |world| {
let is_grounded = world let is_grounded = world
.movements .movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored) .with(entity_id, |m| m.movement_context.is_floored)
.unwrap_or(false); .unwrap_or(false);
let has_input = world let has_input = world
.inputs .inputs
@@ -82,7 +83,7 @@ impl Bundle for PlayerBundle
state_machine.add_transition::<PlayerFallingState, PlayerWalkingState>(move |world| { state_machine.add_transition::<PlayerFallingState, PlayerWalkingState>(move |world| {
let is_grounded = world let is_grounded = world
.movements .movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored) .with(entity_id, |m| m.movement_context.is_floored)
.unwrap_or(false); .unwrap_or(false);
let has_input = world let has_input = world
.inputs .inputs
@@ -94,7 +95,7 @@ impl Bundle for PlayerBundle
state_machine.add_transition::<PlayerIdleState, PlayerWalkingState>(move |world| { state_machine.add_transition::<PlayerIdleState, PlayerWalkingState>(move |world| {
let is_grounded = world let is_grounded = world
.movements .movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored) .with(entity_id, |m| m.movement_context.is_floored)
.unwrap_or(false); .unwrap_or(false);
let has_input = world let has_input = world
.inputs .inputs
@@ -106,7 +107,7 @@ impl Bundle for PlayerBundle
state_machine.add_transition::<PlayerWalkingState, PlayerIdleState>(move |world| { state_machine.add_transition::<PlayerWalkingState, PlayerIdleState>(move |world| {
let is_grounded = world let is_grounded = world
.movements .movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored) .with(entity_id, |m| m.movement_context.is_floored)
.unwrap_or(false); .unwrap_or(false);
let has_input = world let has_input = world
.inputs .inputs
@@ -118,7 +119,7 @@ impl Bundle for PlayerBundle
state_machine.add_transition::<PlayerIdleState, PlayerFallingState>(move |world| { state_machine.add_transition::<PlayerIdleState, PlayerFallingState>(move |world| {
let is_grounded = world let is_grounded = world
.movements .movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored) .with(entity_id, |m| m.movement_context.is_floored)
.unwrap_or(false); .unwrap_or(false);
!is_grounded !is_grounded
}); });
@@ -126,7 +127,7 @@ impl Bundle for PlayerBundle
state_machine.add_transition::<PlayerWalkingState, PlayerFallingState>(move |world| { state_machine.add_transition::<PlayerWalkingState, PlayerFallingState>(move |world| {
let is_grounded = world let is_grounded = world
.movements .movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored) .with(entity_id, |m| m.movement_context.is_floored)
.unwrap_or(false); .unwrap_or(false);
!is_grounded !is_grounded
}); });
@@ -134,7 +135,7 @@ impl Bundle for PlayerBundle
state_machine.add_transition::<PlayerIdleState, PlayerJumpingState>(move |world| { state_machine.add_transition::<PlayerIdleState, PlayerJumpingState>(move |world| {
let is_grounded = world let is_grounded = world
.movements .movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored) .with(entity_id, |m| m.movement_context.is_floored)
.unwrap_or(false); .unwrap_or(false);
let jump_pressed = world let jump_pressed = world
.inputs .inputs
@@ -146,7 +147,7 @@ impl Bundle for PlayerBundle
state_machine.add_transition::<PlayerWalkingState, PlayerJumpingState>(move |world| { state_machine.add_transition::<PlayerWalkingState, PlayerJumpingState>(move |world| {
let is_grounded = world let is_grounded = world
.movements .movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored) .with(entity_id, |m| m.movement_context.is_floored)
.unwrap_or(false); .unwrap_or(false);
let jump_pressed = world let jump_pressed = world
.inputs .inputs
@@ -159,14 +160,14 @@ impl Bundle for PlayerBundle
world world
.jumps .jumps
.with(entity_id, |jump| { .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) .unwrap_or(true)
}); });
world.transforms.insert(entity, spawn_transform); world.transforms.insert(entity, spawn_transform);
world.movements.insert(entity, MovementComponent::new()); 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.inputs.insert(entity, InputComponent::default());
world.physics.insert( world.physics.insert(
entity, entity,
@@ -189,6 +190,7 @@ impl Bundle for PlayerBundle
); );
world.player_tags.insert(entity, ()); world.player_tags.insert(entity, ());
world.state_machines.insert(entity, state_machine); world.state_machines.insert(entity, state_machine);
world.names.insert(entity, "Player".to_string());
let outer_angle = PI / 2.0 * 0.9; let outer_angle = PI / 2.0 * 0.9;
world.spotlights.insert( world.spotlights.insert(

View File

@@ -19,6 +19,7 @@ impl Bundle for SpotlightBundle
let transform = Transform::from_matrix(self.light_data.transform); let transform = Transform::from_matrix(self.light_data.transform);
world.transforms.insert(entity, transform); world.transforms.insert(entity, transform);
world.spotlights.insert(entity, self.light_data.component); world.spotlights.insert(entity, self.light_data.component);
world.names.insert(entity, "Spotlight".to_string());
if let Some(tag) = self.light_data.tag if let Some(tag) = self.light_data.tag
{ {
if tag == "lighthouse" if tag == "lighthouse"
@@ -34,12 +35,13 @@ impl Bundle for SpotlightBundle
pub fn spawn_spotlights(world: &mut World, spotlights: Vec<LightData>) 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 entity = world.spawn();
let transform = Transform::from_matrix(light_data.transform); let transform = Transform::from_matrix(light_data.transform);
world.transforms.insert(entity, transform); world.transforms.insert(entity, transform);
world.spotlights.insert(entity, light_data.component); world.spotlights.insert(entity, light_data.component);
world.names.insert(entity, format!("Spotlight_{}", index));
if let Some(tag) = light_data.tag if let Some(tag) = light_data.tag
{ {
if tag == "lighthouse" if tag == "lighthouse"

View File

@@ -4,7 +4,7 @@ use glam::Vec2;
use nalgebra::vector; use nalgebra::vector;
use rapier3d::prelude::{ColliderBuilder, RigidBodyBuilder}; use rapier3d::prelude::{ColliderBuilder, RigidBodyBuilder};
use crate::components::{MeshComponent, PhysicsComponent}; use crate::components::{MeshComponent, PhysicsComponent, TreeInstancesComponent};
use crate::entity::EntityHandle; use crate::entity::EntityHandle;
use crate::loaders::mesh::{InstanceData, InstanceRaw, Mesh}; use crate::loaders::mesh::{InstanceData, InstanceRaw, Mesh};
use crate::loaders::terrain::load_heightfield_from_exr; use crate::loaders::terrain::load_heightfield_from_exr;
@@ -78,6 +78,7 @@ impl TerrainBundle
enable_snow_light: true, enable_snow_light: true,
}, },
); );
world.names.insert(entity, "Terrain".to_string());
if !physics_added if !physics_added
{ {
@@ -125,7 +126,7 @@ impl TerrainBundle
device.create_buffer_init(&wgpu::util::BufferInitDescriptor { device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Tree Instance Buffer"), label: Some("Tree Instance Buffer"),
contents: bytemuck::cast_slice(&instance_raw), 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 { MeshComponent {
mesh: Rc::new(mesh), mesh: Rc::new(mesh),
pipeline: render::Pipeline::Standard, pipeline: render::Pipeline::Standard,
instance_buffer: Some(instance_buffer), instance_buffer: Some(instance_buffer.clone()),
num_instances: num_instances as u32, num_instances: num_instances as u32,
tile_scale: 4.0, tile_scale: 4.0,
enable_dissolve: true, enable_dissolve: true,
enable_snow_light: false, enable_snow_light: false,
}, },
); );
world.tree_instances.insert(
entity,
TreeInstancesComponent::new(instances, Rc::new(instance_buffer)),
);
world.names.insert(entity, "Trees".to_string());
} }
} }

View File

@@ -3,22 +3,6 @@ use kurbo::CubicBez;
#[derive(Clone)] #[derive(Clone)]
pub struct JumpComponent 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_height: f32,
pub jump_duration: f32, pub jump_duration: f32,
@@ -30,7 +14,7 @@ pub struct JumpConfig
pub jump_context: JumpContext, pub jump_context: JumpContext,
} }
impl Default for JumpConfig impl Default for JumpComponent
{ {
fn default() -> Self fn default() -> Self
{ {
@@ -56,22 +40,3 @@ pub struct JumpContext
pub origin_height: f32, pub origin_height: f32,
pub normal: Vec3, 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;
}
}

View File

@@ -9,12 +9,17 @@ pub mod movement;
pub mod noclip; pub mod noclip;
pub mod physics; pub mod physics;
pub mod rotate; pub mod rotate;
pub mod tree_instances;
pub mod trigger;
pub use camera::CameraComponent; pub use camera::CameraComponent;
pub use dissolve::DissolveComponent; pub use dissolve::DissolveComponent;
pub use follow::FollowComponent; pub use follow::FollowComponent;
pub use input::InputComponent; pub use input::InputComponent;
pub use jump::JumpComponent;
pub use mesh::MeshComponent; pub use mesh::MeshComponent;
pub use movement::MovementComponent; pub use movement::MovementComponent;
pub use physics::PhysicsComponent; pub use physics::PhysicsComponent;
pub use rotate::RotateComponent; pub use rotate::RotateComponent;
pub use tree_instances::TreeInstancesComponent;
pub use trigger::{TriggerComponent, TriggerEvent, TriggerEventKind, TriggerFilter, TriggerShape, TriggerState};

View File

@@ -3,22 +3,6 @@ use kurbo::CubicBez;
#[derive(Clone)] #[derive(Clone)]
pub struct MovementComponent 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: f32,
pub walking_acceleration_duration: f32, pub walking_acceleration_duration: f32,
@@ -29,7 +13,7 @@ pub struct MovementConfig
pub movement_context: MovementContext, pub movement_context: MovementContext,
} }
impl MovementConfig impl MovementComponent
{ {
pub fn new() -> Self pub fn new() -> Self
{ {

View 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
View 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,
}

View File

@@ -150,6 +150,7 @@ pub fn render_collider_debug() -> Vec<DrawCall>
enable_dissolve: false, enable_dissolve: false,
enable_snow_light: false, enable_snow_light: false,
displacement_bind_group: None, displacement_bind_group: None,
entity: None,
}); });
} }
}); });
@@ -183,6 +184,7 @@ pub fn render_collider_debug() -> Vec<DrawCall>
enable_dissolve: false, enable_dissolve: false,
enable_snow_light: false, enable_snow_light: false,
displacement_bind_group: None, displacement_bind_group: None,
entity: None,
}); });
} }
}); });

View File

@@ -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
View 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
View 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);
}

View File

@@ -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 {}

View File

@@ -59,7 +59,7 @@ pub fn load_exr_heightmap(
address_mode_w: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Nearest, mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest, min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest, mipmap_filter: wgpu::MipmapFilterMode::Nearest,
..Default::default() ..Default::default()
}); });

View File

@@ -2,6 +2,26 @@ use bytemuck::{Pod, Zeroable};
use glam::{Mat4, Quat, Vec3}; use glam::{Mat4, Quat, Vec3};
use std::path::Path; 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)] #[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)] #[derive(Clone, Copy, Pod, Zeroable)]
pub struct Vertex pub struct Vertex
@@ -113,6 +133,10 @@ pub struct Mesh
pub vertex_buffer: wgpu::Buffer, pub vertex_buffer: wgpu::Buffer,
pub index_buffer: wgpu::Buffer, pub index_buffer: wgpu::Buffer,
pub num_indices: u32, 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 impl Mesh
@@ -121,6 +145,8 @@ impl Mesh
{ {
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
let (aabb_min, aabb_max) = compute_aabb(vertices);
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"), label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(vertices), contents: bytemuck::cast_slice(vertices),
@@ -137,6 +163,10 @@ impl Mesh
vertex_buffer, vertex_buffer,
index_buffer, index_buffer,
num_indices: indices.len() as u32, num_indices: indices.len() as u32,
aabb_min,
aabb_max,
cpu_positions: vertices.iter().map(|v| v.position).collect(),
cpu_indices: indices.to_vec(),
} }
} }

View File

@@ -1,11 +1,11 @@
mod bundles; mod bundles;
mod components; mod components;
mod debug; mod debug;
mod draw; mod editor;
mod entity; mod entity;
mod event;
mod loaders; mod loaders;
mod physics; mod physics;
mod picking;
mod postprocess; mod postprocess;
mod render; mod render;
mod snow; mod snow;
@@ -17,11 +17,15 @@ mod utility;
mod world; mod world;
use crate::debug::{collider_debug, DebugMode}; use crate::debug::{collider_debug, DebugMode};
use crate::editor::{editor_loop, EditorState, FrameStats};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use glam::Vec3; use glam::Vec3;
use render::Renderer; use render::Renderer;
use sdl3::event::Event;
use sdl3::keyboard::Keycode;
use sdl3::mouse::MouseButton;
use utility::input::InputState; use utility::input::InputState;
use world::World; use world::World;
@@ -34,11 +38,13 @@ use crate::loaders::scene::Space;
use crate::physics::PhysicsManager; use crate::physics::PhysicsManager;
use crate::snow::{SnowConfig, SnowLayer}; use crate::snow::{SnowConfig, SnowLayer};
use crate::systems::{ use crate::systems::{
camera_follow_system, camera_input_system, camera_noclip_system, camera_view_matrix, camera_follow_system, camera_input_system, camera_view_matrix, physics_sync_system,
noclip_toggle_system, physics_sync_system, player_input_system, render_system, rotate_system, player_input_system, render_system, rotate_system, snow_system, spotlight_sync_system,
snow_system, spotlight_sync_system, start_camera_following, state_machine_physics_system, start_camera_following, state_machine_physics_system, state_machine_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; use crate::utility::time::Time;
fn main() -> Result<(), Box<dyn std::error::Error>> 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))?; let renderer = pollster::block_on(Renderer::new(&window, 2))?;
render::init(renderer); 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 space = Space::load_space("meshes/terrain.gltf")?;
let terrain_config = TerrainConfig::default(); 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); render::set_snow_depth(&snow_layer.depth_texture_view);
let mut noclip_mode = true;
let mut debug_mode = DebugMode::default(); let mut debug_mode = DebugMode::default();
let camera_entity = CameraBundle { let camera_entity = CameraBundle {
@@ -103,12 +115,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
} }
.spawn(&mut world) .spawn(&mut world)
.unwrap(); .unwrap();
if !noclip_mode
{
start_camera_following(&mut world, camera_entity); 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(); let mut input_state = InputState::new();
sdl_context.mouse().set_relative_mouse_mode(&window, true); 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; const FIXED_TIMESTEP: f32 = 1.0 / 60.0;
let mut physics_accumulator = 0.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 'running: loop
{ {
let frame_start = Instant::now(); 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(); let delta = (frame_start - last_frame).as_secs_f32();
last_frame = frame_start; last_frame = frame_start;
for event in event_pump.poll_iter() editor.begin_frame();
{
let mouse_capture_changed = input_state.handle_event(&event);
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 sdl_context
.mouse() .mouse()
@@ -145,10 +256,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
break 'running; 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 if input_state.debug_cycle_just_pressed
{ {
debug_mode = debug_mode.cycle(); debug_mode = debug_mode.cycle();
@@ -157,9 +264,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
camera_input_system(&mut world, &input_state); 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 else
{ {
@@ -167,6 +274,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
player_input_system(&mut world, &input_state); player_input_system(&mut world, &input_state);
} }
let physics_start = Instant::now();
physics_accumulator += delta; physics_accumulator += delta;
while physics_accumulator >= FIXED_TIMESTEP while physics_accumulator >= FIXED_TIMESTEP
@@ -176,18 +285,25 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
PhysicsManager::physics_step(); PhysicsManager::physics_step();
physics_sync_system(&mut world); physics_sync_system(&mut world);
trigger_system(&mut world);
physics_accumulator -= FIXED_TIMESTEP; physics_accumulator -= FIXED_TIMESTEP;
} }
stats.physics_budget_ms = physics_start.elapsed().as_secs_f32() * 1000.0;
state_machine_system(&mut world, delta); state_machine_system(&mut world, delta);
rotate_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); let spotlights = spotlight_sync_system(&world);
render::update_spotlights(spotlights); 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); let mut draw_calls = render_system(&world);
draw_calls.extend(snow_layer.get_draw_calls()); 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(); 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, &view,
&projection, &projection,
camera_transform.position, camera_transform.position,
@@ -217,6 +337,22 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
delta, delta,
debug_mode, 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();
} }
} }
} }

View File

@@ -1,7 +1,10 @@
use crate::camera::Camera;
use crate::mesh::Mesh;
use glam::{Mat4, Vec3, Vec4}; 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 struct Ray
{ {
pub origin: Vec3, pub origin: Vec3,
@@ -15,7 +18,8 @@ impl Ray
screen_y: f32, screen_y: f32,
screen_width: u32, screen_width: u32,
screen_height: u32, screen_height: u32,
camera: &Camera, view: &Mat4,
projection: &Mat4,
) -> Self ) -> Self
{ {
let ndc_x = (2.0 * screen_x) / screen_width as f32 - 1.0; 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 clip_coords = Vec4::new(ndc_x, ndc_y, -1.0, 1.0);
let view_matrix = camera.view_matrix(); let inv_projection = projection.inverse();
let projection_matrix = camera.projection_matrix(); let inv_view = view.inverse();
let inv_projection = projection_matrix.inverse();
let inv_view = view_matrix.inverse();
let eye_coords = inv_projection * clip_coords; let eye_coords = inv_projection * clip_coords;
let eye_coords = Vec4::new(eye_coords.x, eye_coords.y, -1.0, 0.0); let eye_coords = Vec4::new(eye_coords.x, eye_coords.y, -1.0, 0.0);
@@ -34,39 +36,90 @@ impl Ray
let world_coords = inv_view * eye_coords; let world_coords = inv_view * eye_coords;
let direction = Vec3::new(world_coords.x, world_coords.y, world_coords.z).normalize(); let direction = Vec3::new(world_coords.x, world_coords.y, world_coords.z).normalize();
Ray { let origin = inv_view.col(3).truncate();
origin: camera.position,
direction, 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 inv_dir = Vec3::new(
let local_origin = inv_transform.transform_point3(self.origin); 1.0 / self.direction.x,
let local_direction = inv_transform.transform_vector3(self.direction).normalize(); 1.0 / self.direction.y,
1.0 / self.direction.z,
);
let mut closest_distance = f32::MAX; let t1 = (min - self.origin) * inv_dir;
let mut hit = false; let t2 = (max - self.origin) * inv_dir;
for triangle_idx in (0..mesh.num_indices).step_by(3) let tmin = t1.min(t2);
{ let tmax = t1.max(t2);
let distance =
self.intersects_triangle_local(local_origin, local_direction, mesh, triangle_idx);
if let Some(d) = distance 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
{ {
if d < closest_distance Some(tenter.max(0.0))
}
else
{ {
closest_distance = d; None
hit = true;
} }
} }
} }
if hit fn transform_aabb(aabb_min: Vec3, aabb_max: Vec3, model: &Mat4) -> (Vec3, Vec3)
{ {
Some(closest_distance) 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 else
{ {
@@ -74,14 +127,124 @@ impl Ray
} }
} }
fn intersects_triangle_local( /// Raymesh triangle intersection in local space, returning world-space distance.
&self, ///
local_origin: Vec3, /// Transforms the ray into local mesh space, tests every triangle with
local_direction: Vec3, /// MöllerTrumbore, then maps the closest local hit back to a world-space
_mesh: &Mesh, /// distance so results can be compared across entities.
_triangle_idx: u32, fn intersect_mesh_triangles(ray: &Ray, mesh: &Mesh, model: &Mat4) -> Option<f32>
) -> 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 tri in mesh.cpu_indices.chunks_exact(3)
{
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(t) = intersect_triangle(local_origin, local_dir, v0, v1, v2)
{
if t < closest_t
{
closest_t = t;
hit = true;
}
}
}
if hit
{
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 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);
}
}
}
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
}

View File

@@ -85,7 +85,7 @@ impl LowResFramebuffer
address_mode_w: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Nearest, mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest, min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest, mipmap_filter: wgpu::MipmapFilterMode::Nearest,
..Default::default() ..Default::default()
}); });
@@ -157,7 +157,7 @@ pub fn create_blit_pipeline(
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Blit Pipeline Layout"), label: Some("Blit Pipeline Layout"),
bind_group_layouts: &[bind_group_layout], bind_group_layouts: &[bind_group_layout],
push_constant_ranges: &[], immediate_size: 0,
}); });
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
@@ -194,7 +194,7 @@ pub fn create_blit_pipeline(
mask: !0, mask: !0,
alpha_to_coverage_enabled: false, alpha_to_coverage_enabled: false,
}, },
multiview: None, multiview_mask: None,
cache: None, cache: None,
}) })
} }

View File

@@ -1,5 +1,7 @@
use crate::texture::{DitherTextures, FlowmapTexture}; use crate::texture::{DitherTextures, FlowmapTexture};
use std::num::NonZeroU64;
use super::types::Uniforms;
use super::Renderer; use super::Renderer;
impl Renderer impl Renderer
@@ -24,7 +26,11 @@ impl Renderer
entries: &[ entries: &[
wgpu::BindGroupEntry { wgpu::BindGroupEntry {
binding: 0, 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 { wgpu::BindGroupEntry {
binding: 1, binding: 1,

View File

@@ -61,13 +61,13 @@ impl DebugOverlay
let shadow_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let shadow_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Shadow Debug Pipeline Layout"), label: Some("Shadow Debug Pipeline Layout"),
bind_group_layouts: &[&shadow_bind_group_layout], bind_group_layouts: &[&shadow_bind_group_layout],
push_constant_ranges: &[], immediate_size: 0,
}); });
let snow_light_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let snow_light_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Snow Light Debug Pipeline Layout"), label: Some("Snow Light Debug Pipeline Layout"),
bind_group_layouts: &[&snow_light_bind_group_layout], bind_group_layouts: &[&snow_light_bind_group_layout],
push_constant_ranges: &[], immediate_size: 0,
}); });
let color_target = wgpu::ColorTargetState { let color_target = wgpu::ColorTargetState {
@@ -98,12 +98,11 @@ impl DebugOverlay
}, },
depth_stencil: None, depth_stencil: None,
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None, multiview_mask: None,
cache: None, cache: None,
}); });
let pipeline_snow_light = let pipeline_snow_light = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Snow Light Debug Pipeline"), label: Some("Snow Light Debug Pipeline"),
layout: Some(&snow_light_layout), layout: Some(&snow_light_layout),
vertex: wgpu::VertexState { vertex: wgpu::VertexState {
@@ -125,7 +124,7 @@ impl DebugOverlay
}, },
depth_stencil: None, depth_stencil: None,
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None, multiview_mask: None,
cache: None, cache: None,
}); });
@@ -171,6 +170,7 @@ impl DebugOverlay
depth_stencil_attachment: None, depth_stencil_attachment: None,
timestamp_writes: None, timestamp_writes: None,
occlusion_query_set: None, occlusion_query_set: None,
multiview_mask: None,
}); });
pass.set_pipeline(&self.pipeline_shadow); pass.set_pipeline(&self.pipeline_shadow);
@@ -221,6 +221,7 @@ impl DebugOverlay
depth_stencil_attachment: None, depth_stencil_attachment: None,
timestamp_writes: None, timestamp_writes: None,
occlusion_query_set: None, occlusion_query_set: None,
multiview_mask: None,
}); });
pass.set_pipeline(&self.pipeline_snow_light); pass.set_pipeline(&self.pipeline_snow_light);

View File

@@ -6,12 +6,19 @@ mod types;
pub use types::{DrawCall, Pipeline, Spotlight, SpotlightRaw, Uniforms, MAX_SPOTLIGHTS}; pub use types::{DrawCall, Pipeline, Spotlight, SpotlightRaw, Uniforms, MAX_SPOTLIGHTS};
use crate::entity::EntityHandle;
use crate::debug::DebugMode; use crate::debug::DebugMode;
use crate::postprocess::{create_blit_pipeline, create_fullscreen_quad, LowResFramebuffer}; use crate::postprocess::{create_blit_pipeline, create_fullscreen_quad, LowResFramebuffer};
use crate::texture::{DitherTextures, FlowmapTexture}; use crate::texture::{DitherTextures, FlowmapTexture};
use pipeline::{create_debug_lines_pipeline, create_main_pipeline, create_snow_clipmap_pipeline, use pipeline::{
create_wireframe_pipeline}; create_debug_lines_pipeline, create_main_pipeline, create_snow_clipmap_pipeline,
create_wireframe_pipeline,
};
use std::cell::RefCell; use std::cell::RefCell;
use std::num::NonZeroU64;
const MAX_DRAW_CALLS: usize = 64;
pub struct Renderer pub struct Renderer
{ {
@@ -62,6 +69,8 @@ pub struct Renderer
snow_light_accumulation: Option<crate::snow_light::SnowLightAccumulation>, snow_light_accumulation: Option<crate::snow_light::SnowLightAccumulation>,
snow_light_bound: bool, snow_light_bound: bool,
pub selected_entity: Option<EntityHandle>,
} }
impl Renderer impl Renderer
@@ -90,8 +99,9 @@ impl Renderer
.await .await
.map_err(|_| "Failed to find adapter")?; .map_err(|_| "Failed to find adapter")?;
let wireframe_supported = let wireframe_supported = adapter
adapter.features().contains(wgpu::Features::POLYGON_MODE_LINE); .features()
.contains(wgpu::Features::POLYGON_MODE_LINE);
let required_features = if wireframe_supported let required_features = if wireframe_supported
{ {
wgpu::Features::POLYGON_MODE_LINE wgpu::Features::POLYGON_MODE_LINE
@@ -129,7 +139,7 @@ impl Renderer
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Uniform Buffer"), 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, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false, mapped_at_creation: false,
}); });
@@ -248,7 +258,7 @@ impl Renderer
address_mode_w: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear, mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear, min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Nearest, mipmap_filter: wgpu::MipmapFilterMode::Nearest,
compare: Some(wgpu::CompareFunction::LessEqual), compare: Some(wgpu::CompareFunction::LessEqual),
..Default::default() ..Default::default()
}); });
@@ -306,8 +316,8 @@ impl Renderer
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer { ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform, ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false, has_dynamic_offset: true,
min_binding_size: None, min_binding_size: NonZeroU64::new(std::mem::size_of::<Uniforms>() as u64),
}, },
count: None, count: None,
}, },
@@ -463,15 +473,22 @@ impl Renderer
let wireframe_pipeline = if wireframe_supported 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 else
{ {
None None
}; };
let debug_lines_pipeline = let debug_lines_pipeline = Some(create_debug_lines_pipeline(
Some(create_debug_lines_pipeline(&device, config.format, &bind_group_layout)); &device,
config.format,
&bind_group_layout,
));
let debug_overlay = Some(debug_overlay::DebugOverlay::new(&device, config.format)); let debug_overlay = Some(debug_overlay::DebugOverlay::new(&device, config.format));
@@ -533,6 +550,7 @@ impl Renderer
dummy_snow_light_sampler, dummy_snow_light_sampler,
snow_light_accumulation: None, snow_light_accumulation: None,
snow_light_bound: false, snow_light_bound: false,
selected_entity: None,
}) })
} }
@@ -546,7 +564,7 @@ impl Renderer
time: f32, time: f32,
delta_time: f32, delta_time: f32,
debug_mode: DebugMode, debug_mode: DebugMode,
) ) -> wgpu::SurfaceTexture
{ {
let light_view_projections = self.calculate_light_view_projections(); let light_view_projections = self.calculate_light_view_projections();
@@ -585,6 +603,9 @@ impl Renderer
label: Some("Render Encoder"), 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() for (i, draw_call) in draw_calls.iter().enumerate()
{ {
let uniforms = Uniforms::new( let uniforms = Uniforms::new(
@@ -603,8 +624,15 @@ impl Renderer
&self.spotlights, &self.spotlights,
debug_mode.as_u32(), 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 { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
@@ -647,6 +675,7 @@ impl Renderer
}), }),
timestamp_writes: None, timestamp_writes: None,
occlusion_query_set: None, occlusion_query_set: None,
multiview_mask: None,
}); });
let pipeline = match draw_call.pipeline let pipeline = match draw_call.pipeline
@@ -659,7 +688,7 @@ impl Renderer
.unwrap_or(&self.standard_pipeline), .unwrap_or(&self.standard_pipeline),
}; };
render_pass.set_pipeline(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 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() 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; continue;
} }
@@ -706,9 +738,11 @@ impl Renderer
&self.spotlights, &self.spotlights,
4, 4,
); );
let wire_offset = uniform_slot * uniform_size;
uniform_slot += 1;
self.queue.write_buffer( self.queue.write_buffer(
&self.uniform_buffer, &self.uniform_buffer,
0, wire_offset,
bytemuck::cast_slice(&[uniforms]), bytemuck::cast_slice(&[uniforms]),
); );
@@ -737,10 +771,11 @@ impl Renderer
), ),
timestamp_writes: None, timestamp_writes: None,
occlusion_query_set: None, occlusion_query_set: None,
multiview_mask: None,
}); });
wire_pass.set_pipeline(wireframe_pipeline); 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(..)); wire_pass.set_vertex_buffer(0, draw_call.vertex_buffer.slice(..));
if let Some(ref instance_buffer) = draw_call.instance_buffer 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 debug_mode == DebugMode::ShadowMap
{ {
if let Some(ref overlay) = self.debug_overlay if let Some(ref overlay) = self.debug_overlay
@@ -850,6 +978,7 @@ impl Renderer
depth_stencil_attachment: None, depth_stencil_attachment: None,
timestamp_writes: None, timestamp_writes: None,
occlusion_query_set: None, occlusion_query_set: None,
multiview_mask: None,
}); });
blit_pass.set_pipeline(&self.blit_pipeline); blit_pass.set_pipeline(&self.blit_pipeline);
@@ -860,7 +989,7 @@ impl Renderer
} }
self.queue.submit(std::iter::once(blit_encoder.finish())); self.queue.submit(std::iter::once(blit_encoder.finish()));
frame.present(); frame
} }
pub fn render_scale(&self) -> (u32, u32) pub fn render_scale(&self) -> (u32, u32)
@@ -1025,7 +1154,7 @@ pub fn render(
time: f32, time: f32,
delta_time: f32, delta_time: f32,
debug_mode: DebugMode, debug_mode: DebugMode,
) ) -> wgpu::SurfaceTexture
{ {
GLOBAL_RENDERER.with(|r| { GLOBAL_RENDERER.with(|r| {
let mut renderer = r.borrow_mut(); let mut renderer = r.borrow_mut();
@@ -1039,8 +1168,19 @@ pub fn render(
time, time,
delta_time, delta_time,
debug_mode, 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) pub fn set_shadow_bias(bias: f32)
@@ -1060,3 +1200,12 @@ pub fn update_spotlights(spotlights: Vec<Spotlight>)
renderer.spotlights = spotlights; 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;
});
}

View File

@@ -20,7 +20,7 @@ pub fn create_shadow_pipeline(
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Shadow Pipeline Layout"), label: Some("Shadow Pipeline Layout"),
bind_group_layouts: &[bind_group_layout], bind_group_layouts: &[bind_group_layout],
push_constant_ranges: &[], immediate_size: 0,
}); });
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
@@ -57,7 +57,7 @@ pub fn create_shadow_pipeline(
mask: !0, mask: !0,
alpha_to_coverage_enabled: false, alpha_to_coverage_enabled: false,
}, },
multiview: None, multiview_mask: None,
cache: None, cache: None,
}) })
} }
@@ -85,7 +85,7 @@ pub fn create_main_pipeline(
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Main Pipeline Layout"), label: Some("Main Pipeline Layout"),
bind_group_layouts: &[bind_group_layout], bind_group_layouts: &[bind_group_layout],
push_constant_ranges: &[], immediate_size: 0,
}); });
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
@@ -131,7 +131,7 @@ pub fn create_main_pipeline(
mask: !0, mask: !0,
alpha_to_coverage_enabled: false, alpha_to_coverage_enabled: false,
}, },
multiview: None, multiview_mask: None,
cache: None, cache: None,
}) })
} }
@@ -157,7 +157,7 @@ pub fn create_wireframe_pipeline(
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Wireframe Pipeline Layout"), label: Some("Wireframe Pipeline Layout"),
bind_group_layouts: &[bind_group_layout], bind_group_layouts: &[bind_group_layout],
push_constant_ranges: &[], immediate_size: 0,
}); });
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
@@ -203,7 +203,7 @@ pub fn create_wireframe_pipeline(
mask: !0, mask: !0,
alpha_to_coverage_enabled: false, alpha_to_coverage_enabled: false,
}, },
multiview: None, multiview_mask: None,
cache: None, cache: None,
}) })
} }
@@ -229,7 +229,7 @@ pub fn create_debug_lines_pipeline(
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Debug Lines Pipeline Layout"), label: Some("Debug Lines Pipeline Layout"),
bind_group_layouts: &[bind_group_layout], bind_group_layouts: &[bind_group_layout],
push_constant_ranges: &[], immediate_size: 0,
}); });
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
@@ -275,7 +275,7 @@ pub fn create_debug_lines_pipeline(
mask: !0, mask: !0,
alpha_to_coverage_enabled: false, alpha_to_coverage_enabled: false,
}, },
multiview: None, multiview_mask: None,
cache: None, cache: None,
}) })
} }
@@ -340,7 +340,7 @@ pub fn create_snow_clipmap_pipeline(
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Snow Clipmap Pipeline Layout"), label: Some("Snow Clipmap Pipeline Layout"),
bind_group_layouts: &[main_bind_group_layout, &displacement_bind_group_layout], bind_group_layouts: &[main_bind_group_layout, &displacement_bind_group_layout],
push_constant_ranges: &[], immediate_size: 0,
}); });
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
@@ -390,7 +390,7 @@ pub fn create_snow_clipmap_pipeline(
mask: !0, mask: !0,
alpha_to_coverage_enabled: false, alpha_to_coverage_enabled: false,
}, },
multiview: None, multiview_mask: None,
cache: None, cache: None,
}) })
} }

View File

@@ -68,6 +68,7 @@ impl Renderer
}), }),
timestamp_writes: None, timestamp_writes: None,
occlusion_query_set: None, occlusion_query_set: None,
multiview_mask: None,
}); });
shadow_pass.set_pipeline( shadow_pass.set_pipeline(

View File

@@ -1,6 +1,8 @@
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
use glam::Mat4; use glam::Mat4;
use crate::entity::EntityHandle;
pub const MAX_SPOTLIGHTS: usize = 4; pub const MAX_SPOTLIGHTS: usize = 4;
#[repr(C)] #[repr(C)]
@@ -156,4 +158,5 @@ pub struct DrawCall
pub enable_dissolve: bool, pub enable_dissolve: bool,
pub enable_snow_light: bool, pub enable_snow_light: bool,
pub displacement_bind_group: Option<wgpu::BindGroup>, pub displacement_bind_group: Option<wgpu::BindGroup>,
pub entity: Option<EntityHandle>,
} }

View File

@@ -32,46 +32,7 @@ fn vs_main(input: VertexInput) -> VertexOutput {
); );
output.world_normal = normalize(normal_matrix * input.normal); output.world_normal = normalize(normal_matrix * input.normal);
var dissolve_amount = 0.0; output.dissolve_amount = input.instance_dissolve;
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.uv = input.uv; output.uv = input.uv;
return output; return output;
@@ -80,11 +41,10 @@ fn vs_main(input: VertexInput) -> VertexOutput {
@fragment @fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> { fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
if uniforms.enable_dissolve == 1u && in.dissolve_amount > 0.0 { if uniforms.enable_dissolve == 1u && in.dissolve_amount > 0.0 {
let screen_pos = in.clip_position.xy; let screen_pos = in.clip_position.xy / in.clip_position.w;
let noise_uv = fract(screen_pos / 128.0); let noise_uv = screen_pos * 0.5 + 0.5;
let noise_value = textureSampleLevel(blue_noise_texture, blue_noise_sampler, noise_uv, 0.0).r; let noise = textureSample(blue_noise_texture, blue_noise_sampler, noise_uv * 10.0).r;
if in.dissolve_amount > noise {
if noise_value < in.dissolve_amount {
discard; discard;
} }
} }

View File

@@ -360,7 +360,7 @@ impl SnowLayer
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Snow Deform Pipeline Layout"), label: Some("Snow Deform Pipeline Layout"),
bind_group_layouts: &[&bind_group_layout], bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[], immediate_size: 0,
}); });
let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { 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 { Mesh {
vertex_buffer, vertex_buffer,
index_buffer, index_buffer,
num_indices: indices.len() as u32, 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_dissolve: false,
enable_snow_light: true, enable_snow_light: true,
displacement_bind_group: Some(self.displacement_bind_group.clone()), displacement_bind_group: Some(self.displacement_bind_group.clone()),
entity: None,
}) })
.collect() .collect()
} }

View File

@@ -1,5 +1,5 @@
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
use glam::{Vec2, Vec3}; use glam::Vec2;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
#[repr(C)] #[repr(C)]
@@ -185,7 +185,7 @@ impl SnowLightAccumulation
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Snow Light Accumulation Pipeline Layout"), label: Some("Snow Light Accumulation Pipeline Layout"),
bind_group_layouts: &[&bind_group_layout], bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[], immediate_size: 0,
}); });
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
@@ -222,7 +222,7 @@ impl SnowLightAccumulation
mask: !0, mask: !0,
alpha_to_coverage_enabled: false, alpha_to_coverage_enabled: false,
}, },
multiview: None, multiview_mask: None,
cache: None, cache: None,
}); });
@@ -280,6 +280,7 @@ impl SnowLightAccumulation
depth_stencil_attachment: None, depth_stencil_attachment: None,
timestamp_writes: None, timestamp_writes: None,
occlusion_query_set: None, occlusion_query_set: None,
multiview_mask: None,
}); });
encoder.begin_render_pass(&wgpu::RenderPassDescriptor { encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
@@ -296,6 +297,7 @@ impl SnowLightAccumulation
depth_stencil_attachment: None, depth_stencil_attachment: None,
timestamp_writes: None, timestamp_writes: None,
occlusion_query_set: None, occlusion_query_set: None,
multiview_mask: None,
}); });
} }
@@ -498,6 +500,7 @@ impl SnowLightAccumulation
depth_stencil_attachment: None, depth_stencil_attachment: None,
timestamp_writes: None, timestamp_writes: None,
occlusion_query_set: None, occlusion_query_set: None,
multiview_mask: None,
}); });
render_pass.set_pipeline(&self.pipeline); render_pass.set_pipeline(&self.pipeline);

View File

@@ -1,7 +1,6 @@
pub mod camera; pub mod camera;
pub mod follow; pub mod follow;
pub mod input; pub mod input;
pub mod noclip;
pub mod physics_sync; pub mod physics_sync;
pub mod player_states; pub mod player_states;
pub mod render; pub mod render;
@@ -10,16 +9,20 @@ pub mod snow;
pub mod spotlight_sync; pub mod spotlight_sync;
pub mod state_machine; pub mod state_machine;
pub mod tree_dissolve; pub mod tree_dissolve;
pub mod trigger;
pub use camera::{ pub use camera::{
camera_follow_system, camera_input_system, camera_noclip_system, camera_view_matrix, camera_follow_system, camera_input_system, camera_noclip_system, camera_view_matrix,
start_camera_following, start_camera_following,
}; };
pub use input::player_input_system; pub use input::player_input_system;
pub use noclip::noclip_toggle_system;
pub use physics_sync::physics_sync_system; pub use physics_sync::physics_sync_system;
pub use render::render_system; pub use render::render_system;
pub use rotate::rotate_system; pub use rotate::rotate_system;
pub use snow::snow_system; pub use snow::snow_system;
pub use spotlight_sync::spotlight_sync_system; pub use spotlight_sync::spotlight_sync_system;
pub use state_machine::{state_machine_physics_system, state_machine_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;

View File

@@ -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);
}
}
}

View File

@@ -90,7 +90,7 @@ impl State for PlayerFallingState
}; };
world.movements.with_mut(self.entity, |movement| { 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 current_velocity = *rigidbody.linvel();
let idle_damping = world let idle_damping = world
.movements .movements
.with(self.entity, |m| m.movement_config.idle_damping) .with(self.entity, |m| m.idle_damping)
.unwrap_or(0.1); .unwrap_or(0.1);
let horizontal_velocity = Vec3::new(current_velocity.x, 0.0, current_velocity.z); 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| { 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 .inputs
.with(self.entity, |input| input.move_direction) .with(self.entity, |input| input.move_direction)
.unwrap_or(Vec3::ZERO); .unwrap_or(Vec3::ZERO);
(input, movement.movement_config.clone()) (input, movement.clone())
}) })
.unwrap(); .unwrap();
@@ -301,7 +301,7 @@ impl State for PlayerWalkingState
}); });
world.movements.with_mut(self.entity, |movement| { 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 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; let current_position = world.transforms.get(self.entity).unwrap().position;
world.jumps.with_mut(self.entity, |jump| { world.jumps.with_mut(self.entity, |jump| {
jump.jump_config.jump_context.in_progress = true; jump.jump_context.in_progress = true;
jump.jump_config.jump_context.execution_time = self.enter_time_stamp; jump.jump_context.execution_time = self.enter_time_stamp;
jump.jump_config.jump_context.origin_height = current_position.y; jump.jump_context.origin_height = current_position.y;
jump.jump_config.jump_context.duration = 0.0; jump.jump_context.duration = 0.0;
jump.jump_config.jump_context.normal = Vec3::Y; jump.jump_context.normal = Vec3::Y;
}); });
println!("entered jumping"); println!("entered jumping");
@@ -347,8 +347,8 @@ impl State for PlayerJumpingState
fn on_state_exit(&mut self, world: &mut World) fn on_state_exit(&mut self, world: &mut World)
{ {
world.jumps.with_mut(self.entity, |jump| { world.jumps.with_mut(self.entity, |jump| {
jump.jump_config.jump_context.in_progress = false; jump.jump_context.in_progress = false;
jump.jump_config.jump_context.duration = 0.0; jump.jump_context.duration = 0.0;
}); });
println!("exited jumping"); println!("exited jumping");
@@ -361,21 +361,21 @@ impl State for PlayerJumpingState
let current_time = Time::get_time_elapsed(); let current_time = Time::get_time_elapsed();
world.jumps.with_mut(self.entity, |jump| { world.jumps.with_mut(self.entity, |jump| {
jump.jump_config.jump_context.duration = jump.jump_context.duration =
current_time - jump.jump_config.jump_context.execution_time; current_time - jump.jump_context.execution_time;
}); });
let jump_config = world let jump = world
.jumps .jumps
.with_mut(self.entity, |jump| jump.jump_config.clone()) .with(self.entity, |jump| jump.clone())
.unwrap(); .unwrap();
let elapsed_time = jump_config.jump_context.duration; let elapsed_time = jump.jump_context.duration;
let normalized_time = (elapsed_time / jump_config.jump_duration).min(1.0); let normalized_time = (elapsed_time / jump.jump_duration).min(1.0);
let height_progress = jump_config.jump_curve.eval(normalized_time as f64).y as f32; let height_progress = jump.jump_curve.eval(normalized_time as f64).y as f32;
let origin_height = jump_config.jump_context.origin_height; let origin_height = jump.jump_context.origin_height;
let target_height = origin_height + height_progress * jump_config.jump_height; let target_height = origin_height + height_progress * jump.jump_height;
let current_translation = world let current_translation = world
.physics .physics
@@ -409,7 +409,7 @@ impl State for PlayerJumpingState
}); });
world.movements.with_mut(self.entity, |movement| { world.movements.with_mut(self.entity, |movement| {
movement.movement_config.movement_context.is_floored = false; movement.movement_context.is_floored = false;
}); });
} }
} }

View File

@@ -60,6 +60,7 @@ pub fn render_system(world: &World) -> Vec<DrawCall>
enable_dissolve: mesh_component.enable_dissolve, enable_dissolve: mesh_component.enable_dissolve,
enable_snow_light: mesh_component.enable_snow_light, enable_snow_light: mesh_component.enable_snow_light,
displacement_bind_group: None, displacement_bind_group: None,
entity: Some(entity),
}) })
}) })
.collect() .collect()

View File

@@ -1,15 +1,21 @@
use crate::components::DissolveComponent; use crate::loaders::mesh::InstanceRaw;
use crate::world::World; use crate::world::World;
use bytemuck::cast_slice;
pub fn tree_dissolve_update_system(world: &mut World, delta: f32) 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; for i in 0..tree_instances.dissolve_amounts.len()
dissolve.amount += diff * dissolve.transition_speed * delta; {
dissolve.amount = dissolve.amount.clamp(0.0, 1.0); 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 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; for (idx, instance) in tree_instances.instances.iter().enumerate()
let to_tree = tree_pos - camera_pos; {
let distance_to_tree = to_tree.length(); 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 perp_distance = perpendicular_vec.length();
let occlusion_radius = 2.5; if perp_distance <= occlusion_radius
if perp_distance < occlusion_radius
{ {
let dissolve_amount = let dissolve_amount =
1.0 - (perp_distance / occlusion_radius).clamp(0.0, 1.0); 1.0 - (perp_distance / occlusion_radius).clamp(0.0, 1.0);
tree_instances.dissolve_targets[idx] = 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; continue;
} }
} }
} }
if let Some(dissolve) = world.dissolves.get_mut(tree_entity) tree_instances.dissolve_targets[idx] = 0.0;
}
}
}
}
}
}
pub fn tree_instance_buffer_update_system(world: &mut World)
{ {
dissolve.target_amount = 0.0; 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
View 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);
}

View File

@@ -123,7 +123,7 @@ impl DitherTextures
address_mode_w: wgpu::AddressMode::Repeat, address_mode_w: wgpu::AddressMode::Repeat,
mag_filter: wgpu::FilterMode::Nearest, mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest, min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest, mipmap_filter: wgpu::MipmapFilterMode::Nearest,
..Default::default() ..Default::default()
}); });
@@ -226,7 +226,7 @@ impl FlowmapTexture
address_mode_w: wgpu::AddressMode::Repeat, address_mode_w: wgpu::AddressMode::Repeat,
mag_filter: wgpu::FilterMode::Nearest, mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest, min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest, mipmap_filter: wgpu::MipmapFilterMode::Nearest,
..Default::default() ..Default::default()
}); });

View File

@@ -11,12 +11,10 @@ pub struct InputState
pub shift: bool, pub shift: bool,
pub space_just_pressed: bool, pub space_just_pressed: bool,
pub noclip_just_pressed: bool,
pub debug_cycle_just_pressed: bool, pub debug_cycle_just_pressed: bool,
pub mouse_delta: (f32, f32), pub mouse_delta: (f32, f32),
pub mouse_captured: bool, pub mouse_captured: bool,
pub noclip_mode: bool,
pub quit_requested: bool, pub quit_requested: bool,
} }
@@ -32,11 +30,9 @@ impl InputState
space: false, space: false,
shift: false, shift: false,
space_just_pressed: false, space_just_pressed: false,
noclip_just_pressed: false,
debug_cycle_just_pressed: false, debug_cycle_just_pressed: false,
mouse_delta: (0.0, 0.0), mouse_delta: (0.0, 0.0),
mouse_captured: true, mouse_captured: true,
noclip_mode: false,
quit_requested: false, quit_requested: false,
} }
} }
@@ -66,12 +62,6 @@ impl InputState
self.quit_requested = true; self.quit_requested = true;
return true; return true;
} }
if *key == Keycode::I
{
self.mouse_captured = !self.mouse_captured;
return true;
}
} }
} }
@@ -111,7 +101,6 @@ impl InputState
self.space = true; self.space = true;
} }
Keycode::LShift | Keycode::RShift => self.shift = true, Keycode::LShift | Keycode::RShift => self.shift = true,
Keycode::N => self.noclip_just_pressed = true,
Keycode::F1 => self.debug_cycle_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) pub fn clear_just_pressed(&mut self)
{ {
self.space_just_pressed = false; self.space_just_pressed = false;
self.noclip_just_pressed = false;
self.debug_cycle_just_pressed = false; self.debug_cycle_just_pressed = false;
self.mouse_delta = (0.0, 0.0); self.mouse_delta = (0.0, 0.0);
} }

View File

@@ -2,11 +2,12 @@ use std::collections::HashMap;
use crate::components::dissolve::DissolveComponent; use crate::components::dissolve::DissolveComponent;
use crate::components::follow::FollowComponent; use crate::components::follow::FollowComponent;
use crate::components::jump::JumpComponent;
use crate::components::lights::spot::SpotlightComponent; use crate::components::lights::spot::SpotlightComponent;
use crate::components::tree_instances::TreeInstancesComponent;
use crate::components::trigger::{TriggerComponent, TriggerEvent};
use crate::components::{ use crate::components::{
CameraComponent, InputComponent, MeshComponent, MovementComponent, PhysicsComponent, CameraComponent, InputComponent, JumpComponent, MeshComponent, MovementComponent,
RotateComponent, PhysicsComponent, RotateComponent,
}; };
use crate::entity::{EntityHandle, EntityManager}; use crate::entity::{EntityHandle, EntityManager};
use crate::state::StateMachine; use crate::state::StateMachine;
@@ -84,6 +85,10 @@ pub struct World
pub dissolves: Storage<DissolveComponent>, pub dissolves: Storage<DissolveComponent>,
pub follows: Storage<FollowComponent>, pub follows: Storage<FollowComponent>,
pub rotates: Storage<RotateComponent>, pub rotates: Storage<RotateComponent>,
pub tree_instances: Storage<TreeInstancesComponent>,
pub names: Storage<String>,
pub triggers: Storage<TriggerComponent>,
pub trigger_events: Vec<TriggerEvent>,
} }
impl World impl World
@@ -106,6 +111,10 @@ impl World
dissolves: Storage::new(), dissolves: Storage::new(),
follows: Storage::new(), follows: Storage::new(),
rotates: 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.dissolves.remove(entity);
self.follows.remove(entity); self.follows.remove(entity);
self.rotates.remove(entity); self.rotates.remove(entity);
self.tree_instances.remove(entity);
self.names.remove(entity);
self.triggers.remove(entity);
self.entities.despawn(entity); self.entities.despawn(entity);
} }