use crate::ecs::EditorWorld;
use nightshade::ecs::light::components::LightType;
use nightshade::ecs::lines::components::{Line, Lines};
use nightshade::ecs::world::{GLOBAL_TRANSFORM, LINES, VISIBILITY};
use nightshade::prelude::*;
const SPHERE_SEGMENTS: usize = 32;
const CONE_SEGMENTS: usize = 24;
const ARROW_LENGTH: f32 = 1.5;
const ARROW_HEAD_SIZE: f32 = 0.25;
pub fn update(editor_world: &mut EditorWorld, world: &mut World) {
let gizmo_entity = ensure_gizmo_entity(editor_world, world);
let lines = build_lines_for_selection(editor_world, world);
if lines.is_empty() {
if let Some(data) = world.core.get_lines(gizmo_entity)
&& data.lines.is_empty()
{
return;
}
world.core.set_lines(gizmo_entity, Lines::new(Vec::new()));
return;
}
world.core.set_lines(gizmo_entity, Lines::new(lines));
}
fn ensure_gizmo_entity(editor_world: &mut EditorWorld, world: &mut World) -> Entity {
if let Some(entity) = editor_world.resources.light_gizmos.entity
&& 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.light_gizmos.entity = Some(entity);
entity
}
fn build_lines_for_selection(editor_world: &EditorWorld, world: &World) -> Vec<Line> {
let Some(selected) = editor_world.resources.ui.selected_entity else {
return Vec::new();
};
let Some(light) = world.core.get_light(selected) else {
return Vec::new();
};
let Some(transform) = world.core.get_global_transform(selected) else {
return Vec::new();
};
let position = transform.translation();
let forward = transform.forward_vector();
let right = transform.right_vector();
let up = transform.up_vector();
let color = vec4(light.color.x, light.color.y, light.color.z, 1.0);
match light.light_type {
LightType::Point => point_light_lines(position, light.range.max(0.05), color),
LightType::Directional => directional_light_lines(position, forward, right, up, color),
LightType::Spot => spot_light_lines(
position,
forward,
right,
up,
light.outer_cone_angle.max(0.01),
light.range.max(0.05),
color,
),
}
}
fn point_light_lines(center: Vec3, radius: f32, color: Vec4) -> Vec<Line> {
let mut lines = Vec::with_capacity(SPHERE_SEGMENTS * 3);
let axis_x = Vec3::new(1.0, 0.0, 0.0);
let axis_y = Vec3::new(0.0, 1.0, 0.0);
let axis_z = Vec3::new(0.0, 0.0, 1.0);
push_circle(&mut lines, center, axis_x, axis_y, radius, color);
push_circle(&mut lines, center, axis_y, axis_z, radius, color);
push_circle(&mut lines, center, axis_x, axis_z, radius, color);
lines.push(Line {
start: center - axis_x * radius,
end: center + axis_x * radius,
color,
});
lines.push(Line {
start: center - axis_y * radius,
end: center + axis_y * radius,
color,
});
lines.push(Line {
start: center - axis_z * radius,
end: center + axis_z * radius,
color,
});
lines
}
fn directional_light_lines(
position: Vec3,
forward: Vec3,
right: Vec3,
up: Vec3,
color: Vec4,
) -> Vec<Line> {
let mut lines = Vec::with_capacity(16);
let direction = nalgebra_glm::normalize(&forward);
let arrow_end = position + direction * ARROW_LENGTH;
lines.push(Line {
start: position,
end: arrow_end,
color,
});
let head_back = arrow_end - direction * ARROW_HEAD_SIZE;
let right_normal = nalgebra_glm::normalize(&right) * (ARROW_HEAD_SIZE * 0.5);
let up_normal = nalgebra_glm::normalize(&up) * (ARROW_HEAD_SIZE * 0.5);
lines.push(Line {
start: arrow_end,
end: head_back + right_normal,
color,
});
lines.push(Line {
start: arrow_end,
end: head_back - right_normal,
color,
});
lines.push(Line {
start: arrow_end,
end: head_back + up_normal,
color,
});
lines.push(Line {
start: arrow_end,
end: head_back - up_normal,
color,
});
let parallel_offset = 0.6;
for (offset_right, offset_up) in [
(parallel_offset, 0.0),
(-parallel_offset, 0.0),
(0.0, parallel_offset),
(0.0, -parallel_offset),
] {
let offset = right * offset_right + up * offset_up;
lines.push(Line {
start: position + offset,
end: position + offset + direction * ARROW_LENGTH,
color,
});
}
lines
}
fn spot_light_lines(
apex: Vec3,
forward: Vec3,
right: Vec3,
up: Vec3,
outer_angle: f32,
range: f32,
color: Vec4,
) -> Vec<Line> {
let mut lines = Vec::with_capacity(CONE_SEGMENTS + 8);
let direction = nalgebra_glm::normalize(&forward);
let cone_height = range;
let cone_radius = cone_height * outer_angle.tan();
let cone_center = apex + direction * cone_height;
let mut prev = cone_perimeter_point(cone_center, right, up, cone_radius, 0.0);
for index in 1..=CONE_SEGMENTS {
let theta = (index as f32 / CONE_SEGMENTS as f32) * std::f32::consts::TAU;
let next = cone_perimeter_point(cone_center, right, up, cone_radius, theta);
lines.push(Line {
start: prev,
end: next,
color,
});
prev = next;
}
let spokes = 4;
for index in 0..spokes {
let theta = (index as f32 / spokes as f32) * std::f32::consts::TAU;
let perimeter = cone_perimeter_point(cone_center, right, up, cone_radius, theta);
lines.push(Line {
start: apex,
end: perimeter,
color,
});
}
lines.push(Line {
start: apex,
end: cone_center,
color,
});
lines
}
fn push_circle(
lines: &mut Vec<Line>,
center: Vec3,
axis_a: Vec3,
axis_b: Vec3,
radius: f32,
color: Vec4,
) {
let mut prev = center + axis_a * radius;
for index in 1..=SPHERE_SEGMENTS {
let theta = (index as f32 / SPHERE_SEGMENTS as f32) * std::f32::consts::TAU;
let next = center + axis_a * (theta.cos() * radius) + axis_b * (theta.sin() * radius);
lines.push(Line {
start: prev,
end: next,
color,
});
prev = next;
}
}
fn cone_perimeter_point(center: Vec3, right: Vec3, up: Vec3, radius: f32, theta: f32) -> Vec3 {
center + right * (theta.cos() * radius) + up * (theta.sin() * radius)
}