use crate::graph_constructor::GraphConstructor;
use astar_lib::a_star::{NavGraph, NodeState};
use glume::gl;
use glume::gl::types::*;
use glume::window::{Event, MouseButton};
pub struct InteractionCore {
screen_extension: (f32, f32),
cursor_pos: [f32; 2],
shader_program: u32,
translation: i32,
color: i32,
line_vbo_vba: (u32, u32),
circle_vba: u32,
graph_constructor: GraphConstructor,
num_of_points: usize,
num_of_links: usize,
graph: NavGraph,
circle_radius: f32,
node_selected: Option<usize>,
}
const POINTS_IN_CIRCLE: usize = 20;
impl InteractionCore {
pub fn new(
circle_radius: f32,
circle_exclusion_radius: f32,
max_line_length: f32,
edge_distance: f32,
num_of_points: usize,
num_of_links: usize,
) -> InteractionCore {
let shader_program = Self::create_shader_program();
let (translation, color) = Self::get_translation_colors_parameters(shader_program);
let line_vbo_vba = Self::create_line_vbo_and_vba();
let circle_vba = Self::create_circle_vba(circle_radius);
let mut graph_constructor =
GraphConstructor::new(1.0, max_line_length, circle_exclusion_radius, edge_distance);
graph_constructor.add_random_points(num_of_points);
graph_constructor.add_random_links(num_of_links);
let graph = graph_constructor.generate_graph();
InteractionCore {
screen_extension: (100.0, 100.0),
cursor_pos: [0.0, 0.0],
shader_program,
translation,
color,
line_vbo_vba,
circle_vba,
graph_constructor,
num_of_points,
num_of_links,
graph,
circle_radius,
node_selected: None,
}
}
fn create_vbo_vba(vertex_data: &[f32], buffer_type: GLenum) -> (u32, u32) {
let mut vbo: u32 = 0;
let mut vba: u32 = 0;
unsafe {
gl::CreateBuffers(1, &mut vbo);
gl::CreateVertexArrays(1, &mut vba);
gl::BindVertexArray(vba);
gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
gl::BufferData(
gl::ARRAY_BUFFER,
(size_of_val(vertex_data)) as GLsizeiptr,
vertex_data.as_ptr() as *const GLvoid,
buffer_type,
);
gl::VertexAttribPointer(
0,
2,
gl::FLOAT,
gl::FALSE,
(2 * size_of::<f32>()) as GLint,
std::ptr::null(),
);
gl::EnableVertexAttribArray(0);
gl::BindVertexArray(0);
gl::BindBuffer(gl::ARRAY_BUFFER, 0);
}
(vbo, vba)
}
fn create_line_vbo_and_vba() -> (u32, u32) {
let vertices: Vec<f32> = vec![0.0, 0.0, 0.0, 0.0];
Self::create_vbo_vba(&vertices, gl::DYNAMIC_DRAW)
}
fn create_circle_vba(radius: f32) -> u32 {
let mut vertices: Vec<f32> = Vec::with_capacity(POINTS_IN_CIRCLE * 2);
for i in 0..POINTS_IN_CIRCLE {
vertices.push(
radius * (i as f32 * 2.0 * std::f32::consts::PI / POINTS_IN_CIRCLE as f32).cos(),
);
vertices.push(
radius * (i as f32 * 2.0 * std::f32::consts::PI / POINTS_IN_CIRCLE as f32).sin(),
);
}
Self::create_vbo_vba(&vertices, gl::STATIC_DRAW).1
}
fn get_translation_colors_parameters(program: u32) -> (i32, i32) {
let color_str = std::ffi::CString::new("PaintColor").unwrap();
let translation_str = std::ffi::CString::new("translation").unwrap();
let translation;
let color;
unsafe {
translation = gl::GetUniformLocation(program, translation_str.as_ptr());
color = gl::GetUniformLocation(program, color_str.as_ptr());
}
(translation, color)
}
fn compile_shader(source: &str, shader_type: u32) -> u32 {
let shader = unsafe { gl::CreateShader(shader_type) };
let c_str = std::ffi::CString::new(source).unwrap();
unsafe {
gl::ShaderSource(shader, 1, &c_str.as_ptr(), std::ptr::null());
gl::CompileShader(shader);
}
shader
}
fn create_shader_program() -> u32 {
let v_code = r#"
#version 330
uniform vec2 translation;
layout(location = 0) in vec2 position;
void main()
{
gl_Position = vec4(position + translation, 0.0, 1.0);
}
"#;
let f_code = r#"
#version 330
uniform vec3 PaintColor;
out vec4 color;
void main()
{
color = vec4(PaintColor, 1.0);
}
"#;
let v_shader = Self::compile_shader(v_code, gl::VERTEX_SHADER);
let f_shader = Self::compile_shader(f_code, gl::FRAGMENT_SHADER);
unsafe {
let program = gl::CreateProgram();
gl::AttachShader(program, v_shader);
gl::AttachShader(program, f_shader);
gl::LinkProgram(program);
gl::DetachShader(program, v_shader);
gl::DetachShader(program, f_shader);
gl::DeleteShader(v_shader);
gl::DeleteShader(f_shader);
program
}
}
fn draw_circle(&self, center: [f32; 2], color: &[f32]) {
let color_ptr = color.as_ptr();
let position_ptr = center.as_ptr();
unsafe {
gl::Uniform3fv(self.color, 1, color_ptr);
gl::Uniform2fv(self.translation, 1, position_ptr);
gl::DrawArrays(gl::TRIANGLE_FAN, 0, POINTS_IN_CIRCLE as i32);
}
}
fn draw_line(&self, start: [f32; 2], end: [f32; 2], color: &[f32]) {
let vertices = [start[0], start[1], end[0], end[1]];
let color_ptr = color.as_ptr();
let zero_vec = [0.0_f32, 0.0_f32];
let position_ptr = zero_vec.as_ptr();
unsafe {
gl::Uniform3fv(self.color, 1, color_ptr);
gl::Uniform2fv(self.translation, 1, position_ptr);
gl::BufferData(
gl::ARRAY_BUFFER,
(vertices.len() * size_of::<f32>()) as GLsizeiptr,
vertices.as_ptr() as *const GLvoid,
gl::DYNAMIC_DRAW,
);
gl::DrawArrays(gl::LINES, 0, 2);
}
}
fn get_color(state: &NodeState) -> [f32; 3] {
match state {
NodeState::Clear => [0.1_f32, 0.5_f32, 0.1_f32],
NodeState::Visited => [1.0_f32, 1.0_f32, 0.0_f32],
NodeState::Closed => [1.0_f32, 0.0_f32, 0.0_f32],
NodeState::Solution => [0.0_f32, 0.0_f32, 1.0_f32],
}
}
pub fn redraw(&self) {
unsafe {
gl::UseProgram(self.shader_program);
gl::BindVertexArray(self.line_vbo_vba.1);
gl::BindBuffer(gl::ARRAY_BUFFER, self.line_vbo_vba.0);
}
for (start, end, solution) in self.graph.get_all_links_with_solution_hint() {
let color_state = if solution {
NodeState::Solution
} else {
NodeState::Clear
};
self.draw_line(start, end, &Self::get_color(&color_state))
}
unsafe {
gl::BindVertexArray(self.circle_vba);
}
for (position, state) in self.graph.get_all_nodes_with_state() {
self.draw_circle(position, &Self::get_color(state));
}
unsafe {
gl::BindVertexArray(0);
gl::BindBuffer(gl::ARRAY_BUFFER, 0);
gl::UseProgram(0);
}
}
pub fn set_window_extension(&mut self, width: u32, height: u32) {
self.screen_extension = (width as f32, height as f32);
}
pub fn set_cursor_pos(&mut self, (x, y): (f32, f32)) {
self.cursor_pos = [
2.0_f32 * x / self.screen_extension.0 - 1.0_f32,
1.0 - 2.0_f32 * y / self.screen_extension.1,
];
if let Some(start) = self.node_selected
&& let Some(destination) = self
.graph
.find_nearest_node_with_radius(self.cursor_pos, self.circle_radius)
{
self.graph.search_graph(start, destination);
}
}
pub fn generate_graph(&mut self) {
self.node_selected = None;
self.graph_constructor.add_random_points(self.num_of_points);
self.graph_constructor.add_random_links(self.num_of_links);
self.graph = self.graph_constructor.generate_graph();
}
pub fn pick_node(&mut self) {
if let Some(hit_node) = self
.graph
.find_nearest_node_with_radius(self.cursor_pos, self.circle_radius)
{
self.node_selected = Some(hit_node);
self.graph.search_graph(hit_node, hit_node);
}
}
}
pub fn run_prog(
circle_radius: f32,
circle_exclusion_radius: f32,
max_line_length: f32,
edge_distance: f32,
num_of_points: usize,
num_of_links: usize,
) {
assert!(
circle_radius < circle_exclusion_radius,
"The inner radius has to be smaller."
);
let window_config = glume::window::WindowConfiguration {
title: "A star".to_string(),
size: (800, 800),
gl_version: (3, 3),
};
let window = window_config.build_window();
unsafe {
gl::Enable(gl::DEBUG_OUTPUT);
}
let mut core = InteractionCore::new(
circle_radius,
circle_exclusion_radius,
max_line_length,
edge_distance,
num_of_points,
num_of_links,
);
window.run(move |wc, event| {
match event {
Event::Resized(width, height) => {
core.set_window_extension(width, height);
unsafe {
gl::Viewport(0, 0, width as i32, height as i32);
}
}
Event::RedrawRequested => {
unsafe {
gl::ClearColor(0.0, 0.0, 0.0, 1.0);
gl::Clear(gl::COLOR_BUFFER_BIT);
core.redraw();
};
}
Event::CursorMoved(x, y) => {
core.set_cursor_pos((x, y));
wc.request_redraw();
}
Event::KeyPressed(key) => {
use glume::window::VirtualKeyCode as Vk;
if key == Vk::Escape {
wc.close()
}
}
Event::MouseButtonPressed(button) => {
wc.request_redraw();
match button {
MouseButton::Left => {
core.pick_node();
}
MouseButton::Right => core.generate_graph(),
_ => {}
}
}
_ => {}
}
Ok(())
})
}