use crate::ecs::EditorWorld;
use nightshade::ecs::lines::components::{Line, Lines};
use nightshade::ecs::world::{CAMERA, GLOBAL_TRANSFORM, LINES, RENDER_MESH, VISIBILITY};
use nightshade::prelude::*;
const FRUSTUM_DEPTH: f32 = 1.4;
const FRUSTUM_HALF_WIDTH: f32 = 0.5;
const FRUSTUM_HALF_HEIGHT: f32 = 0.34;
const PROXY_SIZE: f32 = 0.34;
const PROXY_MATERIAL: &str = "editor_camera_gizmo";
const COLOR_IDLE: Vec4 = Vec4::new(0.28, 0.72, 1.0, 1.0);
const COLOR_SELECTED: Vec4 = Vec4::new(1.0, 0.82, 0.2, 1.0);
pub fn update(editor_world: &mut EditorWorld, world: &mut World) {
let editor_camera = editor_world.resources.camera.camera_entity;
let cameras: Vec<Entity> = {
let editor_scene = &editor_world.resources.editor_scene;
world
.core
.query_entities(CAMERA)
.filter(|entity| {
Some(*entity) != editor_camera && !editor_scene.is_scaffolding(*entity)
})
.collect()
};
despawn_stale_proxies(editor_world, world, &cameras);
ensure_proxy_material(world);
sync_proxies(editor_world, world, &cameras);
let selected = editor_world.resources.ui.selected_entity;
let lines_entity = ensure_lines_entity(editor_world, world);
let mut lines = Vec::new();
for &camera in &cameras {
if let Some(transform) = world.core.get_global_transform(camera) {
push_frustum(&mut lines, transform, selected == Some(camera));
}
}
world.core.set_lines(lines_entity, Lines::new(lines));
}
fn despawn_stale_proxies(editor_world: &mut EditorWorld, world: &mut World, cameras: &[Entity]) {
let stale: Vec<Entity> = editor_world
.resources
.camera_gizmos
.camera_to_proxy
.keys()
.filter(|camera| !cameras.contains(camera))
.copied()
.collect();
for camera in stale {
if let Some(proxy) = editor_world
.resources
.camera_gizmos
.camera_to_proxy
.remove(&camera)
{
editor_world
.resources
.editor_scene
.unregister_scaffolding(proxy);
despawn_recursive_immediate(world, proxy);
}
}
}
fn sync_proxies(editor_world: &mut EditorWorld, world: &mut World, cameras: &[Entity]) {
for &camera in cameras {
let position = world
.core
.get_global_transform(camera)
.map(|transform| transform.translation())
.unwrap_or_else(Vec3::zeros);
let existing = editor_world
.resources
.camera_gizmos
.camera_to_proxy
.get(&camera)
.copied()
.filter(|proxy| world.core.entity_has_components(*proxy, RENDER_MESH));
let proxy = match existing {
Some(proxy) => proxy,
None => {
let proxy = spawn_mesh(
world,
"Cube",
position,
Vec3::new(PROXY_SIZE, PROXY_SIZE, PROXY_SIZE),
);
world
.core
.set_material_ref(proxy, MaterialRef::new(PROXY_MATERIAL));
editor_world
.resources
.editor_scene
.register_scaffolding(proxy);
editor_world
.resources
.camera_gizmos
.camera_to_proxy
.insert(camera, proxy);
proxy
}
};
if let Some(transform) = world.core.get_local_transform_mut(proxy) {
transform.translation = position;
}
mark_local_transform_dirty(world, proxy);
}
}
fn ensure_lines_entity(editor_world: &mut EditorWorld, world: &mut World) -> Entity {
if let Some(entity) = editor_world.resources.camera_gizmos.lines
&& world.core.entity_has_components(entity, LINES)
{
return entity;
}
let entity = spawn_entities(world, LINES | VISIBILITY | GLOBAL_TRANSFORM, 1)[0];
world.core.set_lines(entity, Lines::default());
world
.core
.set_visibility(entity, Visibility { visible: true });
world
.core
.set_global_transform(entity, GlobalTransform::default());
editor_world
.resources
.editor_scene
.register_scaffolding(entity);
editor_world.resources.camera_gizmos.lines = Some(entity);
entity
}
fn ensure_proxy_material(world: &mut World) {
if world
.resources
.assets
.material_registry
.registry
.name_to_index
.contains_key(PROXY_MATERIAL)
{
return;
}
material_registry_insert(
&mut world.resources.assets.material_registry,
PROXY_MATERIAL.to_string(),
Material {
base_color: [0.28, 0.72, 1.0, 1.0],
emissive_factor: [0.12, 0.36, 0.55],
emissive_strength: 2.0,
unlit: true,
..Default::default()
},
);
if let Some(&index) = world
.resources
.assets
.material_registry
.registry
.name_to_index
.get(PROXY_MATERIAL)
{
registry_add_reference(
&mut world.resources.assets.material_registry.registry,
index,
);
}
}
fn push_frustum(lines: &mut Vec<Line>, transform: &GlobalTransform, selected: bool) {
let position = transform.translation();
let forward = nalgebra_glm::normalize(&transform.forward_vector());
let right = nalgebra_glm::normalize(&transform.right_vector());
let up = nalgebra_glm::normalize(&transform.up_vector());
let color = if selected { COLOR_SELECTED } else { COLOR_IDLE };
let far_center = position + forward * FRUSTUM_DEPTH;
let half_width = right * FRUSTUM_HALF_WIDTH;
let half_height = up * FRUSTUM_HALF_HEIGHT;
let corners = [
far_center + half_width + half_height,
far_center - half_width + half_height,
far_center - half_width - half_height,
far_center + half_width - half_height,
];
for corner in corners {
lines.push(Line {
start: position,
end: corner,
color,
});
}
for index in 0..4 {
lines.push(Line {
start: corners[index],
end: corners[(index + 1) % 4],
color,
});
}
let top_mid = (corners[0] + corners[1]) * 0.5;
let peak = top_mid + up * (FRUSTUM_HALF_HEIGHT * 0.7);
lines.push(Line {
start: corners[0],
end: peak,
color,
});
lines.push(Line {
start: corners[1],
end: peak,
color,
});
}