picking + entity names
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -9,12 +9,15 @@ 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 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;
|
||||||
|
|||||||
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_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);
|
||||||
}
|
}
|
||||||
|
|||||||
37
src/main.rs
37
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;
|
||||||
@@ -165,12 +166,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 +199,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;
|
||||||
|
}
|
||||||
|
|
||||||
_ =>
|
_ =>
|
||||||
{}
|
{}
|
||||||
}
|
}
|
||||||
@@ -303,8 +333,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"),
|
||||||
|
|||||||
245
src/picking.rs
245
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,54 +36,215 @@ 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 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
|
||||||
{
|
{
|
||||||
let distance =
|
Some(tenter.max(0.0))
|
||||||
self.intersects_triangle_local(local_origin, local_direction, mesh, triangle_idx);
|
|
||||||
|
|
||||||
if let Some(d) = distance
|
|
||||||
{
|
|
||||||
if d < closest_distance
|
|
||||||
{
|
|
||||||
closest_distance = d;
|
|
||||||
hit = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if hit
|
|
||||||
{
|
|
||||||
Some(closest_distance)
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn intersects_triangle_local(
|
fn transform_aabb(aabb_min: Vec3, aabb_max: Vec3, model: &Mat4) -> (Vec3, Vec3)
|
||||||
&self,
|
{
|
||||||
local_origin: Vec3,
|
let corners = [
|
||||||
local_direction: Vec3,
|
Vec3::new(aabb_min.x, aabb_min.y, aabb_min.z),
|
||||||
_mesh: &Mesh,
|
Vec3::new(aabb_max.x, aabb_min.y, aabb_min.z),
|
||||||
_triangle_idx: u32,
|
Vec3::new(aabb_min.x, aabb_max.y, aabb_min.z),
|
||||||
) -> Option<f32>
|
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
|
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 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|||||||
12
src/world.rs
12
src/world.rs
@@ -2,11 +2,11 @@ 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::{
|
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 +84,8 @@ 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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl World
|
impl World
|
||||||
@@ -106,6 +108,8 @@ 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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,6 +134,8 @@ 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.entities.despawn(entity);
|
self.entities.despawn(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user