picking + entity names
This commit is contained in:
@@ -20,6 +20,7 @@ impl Bundle for CameraBundle
|
||||
let transform = Transform::from_position(self.position);
|
||||
world.cameras.insert(camera_entity, camera_component);
|
||||
world.transforms.insert(camera_entity, transform);
|
||||
world.names.insert(camera_entity, "Camera".to_string());
|
||||
Ok(camera_entity)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ impl Bundle for SpotlightBundle
|
||||
let transform = Transform::from_matrix(self.light_data.transform);
|
||||
world.transforms.insert(entity, transform);
|
||||
world.spotlights.insert(entity, self.light_data.component);
|
||||
world.names.insert(entity, "Spotlight".to_string());
|
||||
if let Some(tag) = self.light_data.tag
|
||||
{
|
||||
if tag == "lighthouse"
|
||||
@@ -34,12 +35,13 @@ impl Bundle for SpotlightBundle
|
||||
|
||||
pub fn spawn_spotlights(world: &mut World, spotlights: Vec<LightData>)
|
||||
{
|
||||
for light_data in spotlights
|
||||
for (index, light_data) in spotlights.into_iter().enumerate()
|
||||
{
|
||||
let entity = world.spawn();
|
||||
let transform = Transform::from_matrix(light_data.transform);
|
||||
world.transforms.insert(entity, transform);
|
||||
world.spotlights.insert(entity, light_data.component);
|
||||
world.names.insert(entity, format!("Spotlight_{}", index));
|
||||
if let Some(tag) = light_data.tag
|
||||
{
|
||||
if tag == "lighthouse"
|
||||
|
||||
@@ -9,12 +9,15 @@ pub mod movement;
|
||||
pub mod noclip;
|
||||
pub mod physics;
|
||||
pub mod rotate;
|
||||
pub mod tree_instances;
|
||||
|
||||
pub use camera::CameraComponent;
|
||||
pub use dissolve::DissolveComponent;
|
||||
pub use follow::FollowComponent;
|
||||
pub use input::InputComponent;
|
||||
pub use jump::JumpComponent;
|
||||
pub use mesh::MeshComponent;
|
||||
pub use movement::MovementComponent;
|
||||
pub use physics::PhysicsComponent;
|
||||
pub use rotate::RotateComponent;
|
||||
pub use tree_instances::TreeInstancesComponent;
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
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,
|
||||
@@ -57,7 +61,12 @@ impl Inspector
|
||||
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();
|
||||
ui.window("Inspector")
|
||||
@@ -68,6 +77,113 @@ impl Inspector
|
||||
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)
|
||||
|
||||
@@ -78,5 +78,6 @@ pub fn editor_loop(
|
||||
{
|
||||
camera_noclip_system(world, input_state, delta);
|
||||
}
|
||||
editor.inspector.build_ui(stats);
|
||||
let selected = editor.selected_entity;
|
||||
editor.inspector.build_ui(stats, world, selected);
|
||||
}
|
||||
|
||||
37
src/main.rs
37
src/main.rs
@@ -5,6 +5,7 @@ mod editor;
|
||||
mod entity;
|
||||
mod loaders;
|
||||
mod physics;
|
||||
mod picking;
|
||||
mod postprocess;
|
||||
mod render;
|
||||
mod snow;
|
||||
@@ -165,12 +166,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
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);
|
||||
}
|
||||
@@ -196,11 +199,38 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
{
|
||||
editor.right_mouse_held = false;
|
||||
input_state.mouse_captured = false;
|
||||
start_camera_following(&mut world, camera_entity);
|
||||
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;
|
||||
}
|
||||
|
||||
_ =>
|
||||
{}
|
||||
}
|
||||
@@ -303,8 +333,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
|
||||
|
||||
if editor.active
|
||||
{
|
||||
let screen_view =
|
||||
frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
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"),
|
||||
|
||||
229
src/picking.rs
229
src/picking.rs
@@ -1,7 +1,10 @@
|
||||
use crate::camera::Camera;
|
||||
use crate::mesh::Mesh;
|
||||
use glam::{Mat4, Vec3, Vec4};
|
||||
|
||||
use crate::components::TreeInstancesComponent;
|
||||
use crate::entity::EntityHandle;
|
||||
use crate::loaders::mesh::Mesh;
|
||||
use crate::world::World;
|
||||
|
||||
pub struct Ray
|
||||
{
|
||||
pub origin: Vec3,
|
||||
@@ -15,7 +18,8 @@ impl Ray
|
||||
screen_y: f32,
|
||||
screen_width: u32,
|
||||
screen_height: u32,
|
||||
camera: &Camera,
|
||||
view: &Mat4,
|
||||
projection: &Mat4,
|
||||
) -> Self
|
||||
{
|
||||
let ndc_x = (2.0 * screen_x) / screen_width as f32 - 1.0;
|
||||
@@ -23,10 +27,8 @@ impl Ray
|
||||
|
||||
let clip_coords = Vec4::new(ndc_x, ndc_y, -1.0, 1.0);
|
||||
|
||||
let view_matrix = camera.view_matrix();
|
||||
let projection_matrix = camera.projection_matrix();
|
||||
let inv_projection = projection_matrix.inverse();
|
||||
let inv_view = view_matrix.inverse();
|
||||
let inv_projection = projection.inverse();
|
||||
let inv_view = view.inverse();
|
||||
|
||||
let eye_coords = inv_projection * clip_coords;
|
||||
let eye_coords = Vec4::new(eye_coords.x, eye_coords.y, -1.0, 0.0);
|
||||
@@ -34,31 +36,127 @@ impl Ray
|
||||
let world_coords = inv_view * eye_coords;
|
||||
let direction = Vec3::new(world_coords.x, world_coords.y, world_coords.z).normalize();
|
||||
|
||||
Ray {
|
||||
origin: camera.position,
|
||||
direction,
|
||||
}
|
||||
let origin = inv_view.col(3).truncate();
|
||||
|
||||
Ray { origin, direction }
|
||||
}
|
||||
|
||||
pub fn intersects_mesh(&self, mesh: &Mesh, transform: &Mat4) -> Option<f32>
|
||||
pub fn intersects_aabb(&self, min: Vec3, max: Vec3) -> Option<f32>
|
||||
{
|
||||
let inv_transform = transform.inverse();
|
||||
let local_origin = inv_transform.transform_point3(self.origin);
|
||||
let local_direction = inv_transform.transform_vector3(self.direction).normalize();
|
||||
let inv_dir = Vec3::new(
|
||||
1.0 / self.direction.x,
|
||||
1.0 / self.direction.y,
|
||||
1.0 / self.direction.z,
|
||||
);
|
||||
|
||||
let mut closest_distance = f32::MAX;
|
||||
let t1 = (min - self.origin) * inv_dir;
|
||||
let t2 = (max - self.origin) * inv_dir;
|
||||
|
||||
let tmin = t1.min(t2);
|
||||
let tmax = t1.max(t2);
|
||||
|
||||
let tenter = tmin.x.max(tmin.y).max(tmin.z);
|
||||
let texit = tmax.x.min(tmax.y).min(tmax.z);
|
||||
|
||||
if tenter <= texit && texit >= 0.0
|
||||
{
|
||||
Some(tenter.max(0.0))
|
||||
}
|
||||
else
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn transform_aabb(aabb_min: Vec3, aabb_max: Vec3, model: &Mat4) -> (Vec3, Vec3)
|
||||
{
|
||||
let corners = [
|
||||
Vec3::new(aabb_min.x, aabb_min.y, aabb_min.z),
|
||||
Vec3::new(aabb_max.x, aabb_min.y, aabb_min.z),
|
||||
Vec3::new(aabb_min.x, aabb_max.y, aabb_min.z),
|
||||
Vec3::new(aabb_max.x, aabb_max.y, aabb_min.z),
|
||||
Vec3::new(aabb_min.x, aabb_min.y, aabb_max.z),
|
||||
Vec3::new(aabb_max.x, aabb_min.y, aabb_max.z),
|
||||
Vec3::new(aabb_min.x, aabb_max.y, aabb_max.z),
|
||||
Vec3::new(aabb_max.x, aabb_max.y, aabb_max.z),
|
||||
];
|
||||
|
||||
let mut world_min = Vec3::splat(f32::MAX);
|
||||
let mut world_max = Vec3::splat(f32::MIN);
|
||||
for corner in &corners
|
||||
{
|
||||
let world_corner = model.transform_point3(*corner);
|
||||
world_min = world_min.min(world_corner);
|
||||
world_max = world_max.max(world_corner);
|
||||
}
|
||||
(world_min, world_max)
|
||||
}
|
||||
|
||||
fn intersect_triangle(origin: Vec3, dir: Vec3, v0: Vec3, v1: Vec3, v2: Vec3) -> Option<f32>
|
||||
{
|
||||
let edge1 = v1 - v0;
|
||||
let edge2 = v2 - v0;
|
||||
let h = dir.cross(edge2);
|
||||
let a = edge1.dot(h);
|
||||
if a.abs() < 1e-7
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let f = 1.0 / a;
|
||||
let s = origin - v0;
|
||||
let u = f * s.dot(h);
|
||||
if !(0.0..=1.0).contains(&u)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let q = s.cross(edge1);
|
||||
let v = f * dir.dot(q);
|
||||
if v < 0.0 || u + v > 1.0
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let t = f * edge2.dot(q);
|
||||
if t > 1e-7
|
||||
{
|
||||
Some(t)
|
||||
}
|
||||
else
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Ray–mesh triangle intersection in local space, returning world-space distance.
|
||||
///
|
||||
/// Transforms the ray into local mesh space, tests every triangle with
|
||||
/// Möller–Trumbore, then maps the closest local hit back to a world-space
|
||||
/// distance so results can be compared across entities.
|
||||
fn intersect_mesh_triangles(ray: &Ray, mesh: &Mesh, model: &Mat4) -> Option<f32>
|
||||
{
|
||||
if mesh.cpu_indices.is_empty()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let inv_model = model.inverse();
|
||||
let local_origin = inv_model.transform_point3(ray.origin);
|
||||
let local_dir = inv_model.transform_vector3(ray.direction);
|
||||
|
||||
let mut closest_t = f32::MAX;
|
||||
let mut hit = false;
|
||||
|
||||
for triangle_idx in (0..mesh.num_indices).step_by(3)
|
||||
for tri in mesh.cpu_indices.chunks_exact(3)
|
||||
{
|
||||
let distance =
|
||||
self.intersects_triangle_local(local_origin, local_direction, mesh, triangle_idx);
|
||||
let v0 = Vec3::from(mesh.cpu_positions[tri[0] as usize]);
|
||||
let v1 = Vec3::from(mesh.cpu_positions[tri[1] as usize]);
|
||||
let v2 = Vec3::from(mesh.cpu_positions[tri[2] as usize]);
|
||||
|
||||
if let Some(d) = distance
|
||||
if let Some(t) = intersect_triangle(local_origin, local_dir, v0, v1, v2)
|
||||
{
|
||||
if d < closest_distance
|
||||
if t < closest_t
|
||||
{
|
||||
closest_distance = d;
|
||||
closest_t = t;
|
||||
hit = true;
|
||||
}
|
||||
}
|
||||
@@ -66,22 +164,87 @@ impl Ray
|
||||
|
||||
if hit
|
||||
{
|
||||
Some(closest_distance)
|
||||
let local_hit = local_origin + local_dir * closest_t;
|
||||
let world_hit = model.transform_point3(local_hit);
|
||||
Some((world_hit - ray.origin).length())
|
||||
}
|
||||
else
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn intersect_instances(
|
||||
ray: &Ray,
|
||||
mesh: &Mesh,
|
||||
tree_instances: &TreeInstancesComponent,
|
||||
) -> Option<f32>
|
||||
{
|
||||
let mut closest: Option<f32> = None;
|
||||
|
||||
for instance in &tree_instances.instances
|
||||
{
|
||||
let instance_model = Mat4::from_scale_rotation_translation(
|
||||
instance.scale,
|
||||
instance.rotation,
|
||||
instance.position,
|
||||
);
|
||||
let (world_min, world_max) = transform_aabb(mesh.aabb_min, mesh.aabb_max, &instance_model);
|
||||
|
||||
if let Some(d) = ray.intersects_aabb(world_min, world_max)
|
||||
{
|
||||
if closest.map_or(true, |c| d < c)
|
||||
{
|
||||
closest = Some(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn intersects_triangle_local(
|
||||
&self,
|
||||
local_origin: Vec3,
|
||||
local_direction: Vec3,
|
||||
_mesh: &Mesh,
|
||||
_triangle_idx: u32,
|
||||
) -> Option<f32>
|
||||
{
|
||||
None
|
||||
}
|
||||
closest
|
||||
}
|
||||
|
||||
pub fn pick_entity(ray: &Ray, world: &World) -> Option<EntityHandle>
|
||||
{
|
||||
let mut closest_entity = None;
|
||||
let mut closest_distance = f32::MAX;
|
||||
|
||||
for (&entity, mesh_component) in &world.meshes.components
|
||||
{
|
||||
let transform = match world.transforms.get(entity)
|
||||
{
|
||||
Some(t) => t,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let model = transform.to_matrix();
|
||||
|
||||
let distance = if let Some(tree_instances) = world.tree_instances.get(entity)
|
||||
{
|
||||
intersect_instances(ray, &mesh_component.mesh, tree_instances)
|
||||
}
|
||||
else
|
||||
{
|
||||
let (world_min, world_max) = transform_aabb(
|
||||
mesh_component.mesh.aabb_min,
|
||||
mesh_component.mesh.aabb_max,
|
||||
&model,
|
||||
);
|
||||
if ray.intersects_aabb(world_min, world_max).is_none()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
intersect_mesh_triangles(ray, &mesh_component.mesh, &model)
|
||||
};
|
||||
|
||||
if let Some(d) = distance
|
||||
{
|
||||
if d < closest_distance
|
||||
{
|
||||
closest_distance = d;
|
||||
closest_entity = Some(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closest_entity
|
||||
}
|
||||
|
||||
12
src/world.rs
12
src/world.rs
@@ -2,11 +2,11 @@ use std::collections::HashMap;
|
||||
|
||||
use crate::components::dissolve::DissolveComponent;
|
||||
use crate::components::follow::FollowComponent;
|
||||
use crate::components::jump::JumpComponent;
|
||||
use crate::components::lights::spot::SpotlightComponent;
|
||||
use crate::components::tree_instances::TreeInstancesComponent;
|
||||
use crate::components::{
|
||||
CameraComponent, InputComponent, MeshComponent, MovementComponent, PhysicsComponent,
|
||||
RotateComponent,
|
||||
CameraComponent, InputComponent, JumpComponent, MeshComponent, MovementComponent,
|
||||
PhysicsComponent, RotateComponent,
|
||||
};
|
||||
use crate::entity::{EntityHandle, EntityManager};
|
||||
use crate::state::StateMachine;
|
||||
@@ -84,6 +84,8 @@ pub struct World
|
||||
pub dissolves: Storage<DissolveComponent>,
|
||||
pub follows: Storage<FollowComponent>,
|
||||
pub rotates: Storage<RotateComponent>,
|
||||
pub tree_instances: Storage<TreeInstancesComponent>,
|
||||
pub names: Storage<String>,
|
||||
}
|
||||
|
||||
impl World
|
||||
@@ -106,6 +108,8 @@ impl World
|
||||
dissolves: Storage::new(),
|
||||
follows: Storage::new(),
|
||||
rotates: Storage::new(),
|
||||
tree_instances: Storage::new(),
|
||||
names: Storage::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,6 +134,8 @@ impl World
|
||||
self.dissolves.remove(entity);
|
||||
self.follows.remove(entity);
|
||||
self.rotates.remove(entity);
|
||||
self.tree_instances.remove(entity);
|
||||
self.names.remove(entity);
|
||||
self.entities.despawn(entity);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user