Compare commits
5 Commits
d888dc077e
...
c37a9fd5dd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c37a9fd5dd | ||
|
|
350fddc2af | ||
|
|
bab54b6f21 | ||
|
|
5e5c6a47e7 | ||
|
|
28f8c65571 |
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
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_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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
use dear_imgui_rs::{Condition, Context};
|
use dear_imgui_rs::{Condition, Context};
|
||||||
use dear_imgui_wgpu::{WgpuInitInfo, WgpuRenderer};
|
use dear_imgui_wgpu::{WgpuInitInfo, WgpuRenderer};
|
||||||
|
use glam::EulerRot;
|
||||||
use sdl3_sys::events::SDL_Event;
|
use sdl3_sys::events::SDL_Event;
|
||||||
|
|
||||||
|
use crate::entity::EntityHandle;
|
||||||
|
use crate::world::World;
|
||||||
|
|
||||||
pub struct FrameStats
|
pub struct FrameStats
|
||||||
{
|
{
|
||||||
pub fps: f32,
|
pub fps: f32,
|
||||||
@@ -57,7 +61,12 @@ impl Inspector
|
|||||||
self.imgui.io().want_capture_mouse()
|
self.imgui.io().want_capture_mouse()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_ui(&mut self, stats: &FrameStats)
|
pub fn build_ui(
|
||||||
|
&mut self,
|
||||||
|
stats: &FrameStats,
|
||||||
|
world: &World,
|
||||||
|
selected_entity: Option<EntityHandle>,
|
||||||
|
)
|
||||||
{
|
{
|
||||||
let ui = self.imgui.frame();
|
let ui = self.imgui.frame();
|
||||||
ui.window("Inspector")
|
ui.window("Inspector")
|
||||||
@@ -68,6 +77,113 @@ impl Inspector
|
|||||||
ui.text(format!("Physics: {:.1} ms", stats.physics_budget_ms));
|
ui.text(format!("Physics: {:.1} ms", stats.physics_budget_ms));
|
||||||
ui.text(format!("Draw calls: {}", stats.draw_call_count));
|
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)
|
pub fn render(&mut self, encoder: &mut wgpu::CommandEncoder, view: &wgpu::TextureView)
|
||||||
|
|||||||
@@ -78,5 +78,6 @@ pub fn editor_loop(
|
|||||||
{
|
{
|
||||||
camera_noclip_system(world, input_state, delta);
|
camera_noclip_system(world, input_state, delta);
|
||||||
}
|
}
|
||||||
editor.inspector.build_ui(stats);
|
let selected = editor.selected_entity;
|
||||||
|
editor.inspector.build_ui(stats, world, selected);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
44
src/main.rs
44
src/main.rs
@@ -5,6 +5,7 @@ mod editor;
|
|||||||
mod entity;
|
mod entity;
|
||||||
mod loaders;
|
mod loaders;
|
||||||
mod physics;
|
mod physics;
|
||||||
|
mod picking;
|
||||||
mod postprocess;
|
mod postprocess;
|
||||||
mod render;
|
mod render;
|
||||||
mod snow;
|
mod snow;
|
||||||
@@ -40,6 +41,8 @@ use crate::systems::{
|
|||||||
camera_follow_system, camera_input_system, camera_view_matrix, physics_sync_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,
|
player_input_system, render_system, rotate_system, snow_system, spotlight_sync_system,
|
||||||
start_camera_following, state_machine_physics_system, state_machine_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::systems::camera::stop_camera_following;
|
||||||
use crate::utility::time::Time;
|
use crate::utility::time::Time;
|
||||||
@@ -165,12 +168,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
|||||||
editor.active = !editor.active;
|
editor.active = !editor.active;
|
||||||
if editor.active
|
if editor.active
|
||||||
{
|
{
|
||||||
|
stop_camera_following(&mut world, camera_entity);
|
||||||
sdl_context.mouse().set_relative_mouse_mode(&window, false);
|
sdl_context.mouse().set_relative_mouse_mode(&window, false);
|
||||||
editor.right_mouse_held = false;
|
editor.right_mouse_held = false;
|
||||||
input_state.mouse_captured = false;
|
input_state.mouse_captured = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
start_camera_following(&mut world, camera_entity);
|
||||||
input_state.mouse_captured = true;
|
input_state.mouse_captured = true;
|
||||||
sdl_context.mouse().set_relative_mouse_mode(&window, true);
|
sdl_context.mouse().set_relative_mouse_mode(&window, true);
|
||||||
}
|
}
|
||||||
@@ -196,11 +201,38 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
|||||||
{
|
{
|
||||||
editor.right_mouse_held = false;
|
editor.right_mouse_held = false;
|
||||||
input_state.mouse_captured = false;
|
input_state.mouse_captured = false;
|
||||||
start_camera_following(&mut world, camera_entity);
|
|
||||||
sdl_context.mouse().set_relative_mouse_mode(&window, false);
|
sdl_context.mouse().set_relative_mouse_mode(&window, false);
|
||||||
continue;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
_ =>
|
_ =>
|
||||||
{}
|
{}
|
||||||
}
|
}
|
||||||
@@ -253,6 +285,7 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -263,6 +296,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
|||||||
|
|
||||||
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);
|
||||||
|
|
||||||
@@ -303,8 +340,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
|||||||
|
|
||||||
if editor.active
|
if editor.active
|
||||||
{
|
{
|
||||||
let screen_view =
|
let screen_view = frame
|
||||||
frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
|
.texture
|
||||||
|
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
let mut encoder = render::with_device(|d| {
|
let mut encoder = render::with_device(|d| {
|
||||||
d.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
d.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||||
label: Some("ImGui Encoder"),
|
label: Some("ImGui Encoder"),
|
||||||
|
|||||||
231
src/picking.rs
231
src/picking.rs
@@ -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(
|
/// Ray–mesh 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öller–Trumbore, 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -102,8 +102,7 @@ impl DebugOverlay
|
|||||||
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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
@@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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 {
|
||||||
@@ -660,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
|
||||||
{
|
{
|
||||||
@@ -686,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;
|
||||||
}
|
}
|
||||||
@@ -707,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]),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -742,7 +775,7 @@ impl Renderer
|
|||||||
});
|
});
|
||||||
|
|
||||||
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
|
||||||
@@ -764,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
|
||||||
@@ -1074,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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -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>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/snow.rs
14
src/snow.rs
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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,
|
||||||
@@ -21,3 +22,7 @@ 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;
|
||||||
|
|||||||
@@ -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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
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);
|
||||||
|
}
|
||||||
18
src/world.rs
18
src/world.rs
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user