use crate::ecs::lattice::components::Lattice;
use crate::ecs::lines::components::Line;
use crate::ecs::world::{
Entity, GLOBAL_TRANSFORM, LINES, LOCAL_TRANSFORM, LOCAL_TRANSFORM_DIRTY, VISIBILITY, World,
};
use nalgebra_glm::{Vec3, Vec4, vec3};
use std::f32::consts::PI;
const BASE_COLOR: Vec4 = Vec4::new(0.4, 0.4, 0.4, 0.5);
const POINT_COLOR: Vec4 = Vec4::new(0.0, 0.8, 1.0, 1.0);
const GRID_COLOR: Vec4 = Vec4::new(0.0, 0.6, 0.8, 0.8);
const SELECTED_COLOR: Vec4 = Vec4::new(1.0, 1.0, 0.0, 1.0);
pub struct LatticeDebugState {
pub debug_entity: Option<Entity>,
pub enabled: bool,
pub selected_point: Option<(usize, usize, usize)>,
pub last_version: u32,
}
impl Default for LatticeDebugState {
fn default() -> Self {
Self {
debug_entity: None,
enabled: true,
selected_point: None,
last_version: u32::MAX,
}
}
}
pub fn lattice_debug_draw(
world: &mut World,
lattice_entity: Entity,
state: &mut LatticeDebugState,
) {
if !state.enabled {
if let Some(entity) = state.debug_entity
&& let Some(visibility) = world.core.get_visibility_mut(entity)
{
visibility.visible = false;
}
return;
}
let Some(lattice) = world.core.get_lattice(lattice_entity).cloned() else {
return;
};
let debug_entity = match state.debug_entity {
Some(entity) => entity,
None => {
let entity = world.spawn_entities(
LINES | VISIBILITY | LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY,
1,
)[0];
state.debug_entity = Some(entity);
entity
}
};
if let Some(visibility) = world.core.get_visibility_mut(debug_entity) {
visibility.visible = true;
}
if state.last_version == lattice.version && state.selected_point.is_none() {
return;
}
let mut lines = Vec::new();
draw_lattice_points(&lattice, &mut lines, state.selected_point);
draw_lattice_grid(&lattice, &mut lines);
if let Some(lines_component) = world.core.get_lines_mut(debug_entity) {
lines_component.lines = lines;
lines_component.mark_dirty();
}
state.last_version = lattice.version;
}
fn draw_lattice_points(
lattice: &Lattice,
lines: &mut Vec<Line>,
selected: Option<(usize, usize, usize)>,
) {
let [nx, ny, nz] = lattice.dimensions;
let sphere_radius = 0.08;
let segments = 12;
for z in 0..nz {
for y in 0..ny {
for x in 0..nx {
let point = lattice.get_point(x, y, z);
let is_selected = selected == Some((x, y, z));
let color = if is_selected {
SELECTED_COLOR
} else {
POINT_COLOR
};
draw_sphere_wireframe(lines, point, sphere_radius, segments, color);
let base_point = lattice.base_points[lattice.get_index(x, y, z)];
if (point - base_point).norm() > 0.001 {
draw_sphere_wireframe(lines, base_point, sphere_radius * 0.5, 8, BASE_COLOR);
}
}
}
}
}
fn draw_lattice_grid(lattice: &Lattice, lines: &mut Vec<Line>) {
let [nx, ny, nz] = lattice.dimensions;
for z in 0..nz {
for y in 0..ny {
for x in 0..nx {
let point = lattice.get_point(x, y, z);
if x + 1 < nx {
let neighbor = lattice.get_point(x + 1, y, z);
lines.push(Line {
start: point,
end: neighbor,
color: GRID_COLOR,
});
}
if y + 1 < ny {
let neighbor = lattice.get_point(x, y + 1, z);
lines.push(Line {
start: point,
end: neighbor,
color: GRID_COLOR,
});
}
if z + 1 < nz {
let neighbor = lattice.get_point(x, y, z + 1);
lines.push(Line {
start: point,
end: neighbor,
color: GRID_COLOR,
});
}
}
}
}
}
fn draw_sphere_wireframe(
lines: &mut Vec<Line>,
center: Vec3,
radius: f32,
segments: usize,
color: Vec4,
) {
draw_circle(
lines,
center,
radius,
segments,
vec3(1.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
color,
);
draw_circle(
lines,
center,
radius,
segments,
vec3(1.0, 0.0, 0.0),
vec3(0.0, 0.0, 1.0),
color,
);
draw_circle(
lines,
center,
radius,
segments,
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, 1.0),
color,
);
}
fn draw_circle(
lines: &mut Vec<Line>,
center: Vec3,
radius: f32,
segments: usize,
axis_a: Vec3,
axis_b: Vec3,
color: Vec4,
) {
for index in 0..segments {
let angle1 = (index as f32 / segments as f32) * 2.0 * PI;
let angle2 = ((index + 1) as f32 / segments as f32) * 2.0 * PI;
let p1 = center + axis_a * (angle1.cos() * radius) + axis_b * (angle1.sin() * radius);
let p2 = center + axis_a * (angle2.cos() * radius) + axis_b * (angle2.sin() * radius);
lines.push(Line {
start: p1,
end: p2,
color,
});
}
}
pub fn get_lattice_point_at_screen_position(
world: &World,
lattice_entity: Entity,
screen_pos: nalgebra_glm::Vec2,
screen_size: nalgebra_glm::Vec2,
) -> Option<(usize, usize, usize)> {
let lattice = world.core.get_lattice(lattice_entity)?;
let camera_entity = world.resources.active_camera?;
let camera = world.core.get_camera(camera_entity)?;
let camera_transform = world.core.get_global_transform(camera_entity)?;
let view_matrix = nalgebra_glm::inverse(&camera_transform.0);
let projection_matrix = camera
.projection
.matrix_with_aspect(screen_size.x / screen_size.y);
let view_projection = projection_matrix * view_matrix;
let [nx, ny, nz] = lattice.dimensions;
let mut closest_point: Option<(usize, usize, usize)> = None;
let mut closest_distance = f32::MAX;
let pick_radius = 20.0;
for z in 0..nz {
for y in 0..ny {
for x in 0..nx {
let world_pos = lattice.get_point(x, y, z);
let clip = view_projection
* nalgebra_glm::vec4(world_pos.x, world_pos.y, world_pos.z, 1.0);
if clip.w <= 0.0 {
continue;
}
let ndc = nalgebra_glm::vec3(clip.x / clip.w, clip.y / clip.w, clip.z / clip.w);
let screen_x = (ndc.x + 1.0) * 0.5 * screen_size.x;
let screen_y = (1.0 - ndc.y) * 0.5 * screen_size.y;
let dx = screen_x - screen_pos.x;
let dy = screen_y - screen_pos.y;
let dist = (dx * dx + dy * dy).sqrt();
if dist < pick_radius && dist < closest_distance {
closest_distance = dist;
closest_point = Some((x, y, z));
}
}
}
}
closest_point
}