use std::collections::HashMap;
use crate::interaction::select::selection::Selection;
use crate::renderer::PickId;
use crate::renderer::{GlyphItem, GlyphType, PolylineItem, sphere_wireframe_polyline};
use crate::scene::LayerId;
use crate::scene::material::ItemSettings;
use crate::scene::scene::Scene;
use crate::{LightKind, LightSource};
const GLYPH_SIZE: f32 = 0.28;
pub fn build_light_glyphs(
scene: &Scene,
selection: &Selection,
) -> (Vec<GlyphItem>, Vec<PolylineItem>) {
let layer_visible: HashMap<LayerId, bool> =
scene.layers().iter().map(|l| (l.id, l.visible)).collect();
let mut glyphs: Vec<GlyphItem> = Vec::new();
let mut polylines: Vec<PolylineItem> = Vec::new();
for node in scene.nodes() {
let Some(src) = node.light.as_ref() else {
continue;
};
if !node.is_visible() {
continue;
}
if !layer_visible.get(&node.layer()).copied().unwrap_or(true) {
continue;
}
let id = node.id();
let world = node.world_transform();
let translation = world.col(3).truncate();
let is_selected = selection.contains(id);
let colour_rgba = [src.colour[0], src.colour[1], src.colour[2], 1.0];
let mut settings = ItemSettings::default();
settings.pick_id = PickId(id);
settings.selected = is_selected;
settings.unlit = true;
settings.cast_shadows = false;
settings.receive_shadows = false;
let (glyph_type, vector) = match &src.kind {
LightKind::Directional { direction } => {
let d = glam::Vec3::from(*direction);
let d = if d.length_squared() > 1.0e-12 {
d.normalize()
} else {
glam::Vec3::Z
};
let v = world.transform_vector3(d).normalize_or_zero();
(GlyphType::Arrow, v)
}
LightKind::Point { .. } => (GlyphType::Sphere, glam::Vec3::Z),
LightKind::Spot { direction, .. } => {
let d = glam::Vec3::from(*direction);
let d = if d.length_squared() > 1.0e-12 {
d.normalize()
} else {
glam::Vec3::NEG_Z
};
let v = world.transform_vector3(d).normalize_or_zero();
(GlyphType::Arrow, v)
}
};
let mut g = GlyphItem::default();
g.glyph_type = glyph_type;
g.positions.push(translation.into());
let vec_scaled: [f32; 3] = (vector * GLYPH_SIZE).into();
g.vectors.push(vec_scaled);
g.scale = GLYPH_SIZE;
g.scale_by_magnitude = matches!(glyph_type, GlyphType::Arrow);
g.use_default_colour = true;
g.default_colour = colour_rgba;
g.settings = settings;
glyphs.push(g);
if is_selected {
let world_src = resolve_light_for_glyph(src, world);
let outline_colour = [colour_rgba[0], colour_rgba[1], colour_rgba[2], 0.8];
match world_src.kind {
LightKind::Point { position, range } => {
let mut pl = sphere_wireframe_polyline(position, range, 48, outline_colour);
pl.line_width = 1.5;
pl.settings.pick_id = PickId(id);
pl.settings.selected = true;
pl.settings.unlit = true;
polylines.push(pl);
}
LightKind::Spot {
position,
direction,
range,
outer_angle,
..
} => {
let mut pl = spot_cone_polyline(
position,
direction,
range,
outer_angle,
24,
outline_colour,
);
pl.settings.pick_id = PickId(id);
pl.settings.selected = true;
pl.settings.unlit = true;
polylines.push(pl);
}
LightKind::Directional { .. } => {}
}
}
}
(glyphs, polylines)
}
fn resolve_light_for_glyph(src: &LightSource, world: glam::Mat4) -> LightSource {
let translation = world.col(3).truncate();
let kind = match &src.kind {
LightKind::Directional { direction } => {
let rotated = world
.transform_vector3(glam::Vec3::from(*direction))
.normalize_or_zero();
LightKind::Directional {
direction: rotated.into(),
}
}
LightKind::Point { range, .. } => LightKind::Point {
position: translation.into(),
range: *range,
},
LightKind::Spot {
direction,
range,
inner_angle,
outer_angle,
..
} => {
let rotated = world
.transform_vector3(glam::Vec3::from(*direction))
.normalize_or_zero();
LightKind::Spot {
position: translation.into(),
direction: rotated.into(),
range: *range,
inner_angle: *inner_angle,
outer_angle: *outer_angle,
}
}
};
LightSource {
kind,
colour: src.colour,
intensity: src.intensity,
importance: src.importance,
cast_shadows: src.cast_shadows,
}
}
fn spot_cone_polyline(
apex: [f32; 3],
direction: [f32; 3],
range: f32,
outer_angle: f32,
segments: u32,
colour: [f32; 4],
) -> PolylineItem {
let n = segments.max(8) as usize;
let apex_v = glam::Vec3::from(apex);
let dir = glam::Vec3::from(direction).normalize_or_zero();
let dir = if dir.length_squared() > 1.0e-8 {
dir
} else {
glam::Vec3::NEG_Z
};
let up_ref = if dir.z.abs() > 0.95 {
glam::Vec3::X
} else {
glam::Vec3::Z
};
let right = dir.cross(up_ref).normalize_or_zero();
let up = right.cross(dir).normalize_or_zero();
let rim_radius = range * outer_angle.sin();
let rim_center = apex_v + dir * range * outer_angle.cos();
let mut positions: Vec<[f32; 3]> = Vec::with_capacity(n + 1 + 4 * 2);
let mut strips: Vec<u32> = Vec::with_capacity(5);
for i in 0..=n {
let t = i as f32 / n as f32 * std::f32::consts::TAU;
let p = rim_center + right * (rim_radius * t.cos()) + up * (rim_radius * t.sin());
positions.push(p.into());
}
strips.push((n + 1) as u32);
for k in 0..4 {
let t = k as f32 / 4.0 * std::f32::consts::TAU;
let rim = rim_center + right * (rim_radius * t.cos()) + up * (rim_radius * t.sin());
positions.push(apex);
positions.push(rim.into());
strips.push(2);
}
PolylineItem {
positions,
strip_lengths: strips,
default_colour: colour,
line_width: 1.5,
..Default::default()
}
}