/* Generic tree structures for storage of spatial data. * Copyright (C) 2023 Alexander Pyattaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ use glium::glutin::event_loop::EventLoop; use glium::index::PrimitiveType; use glium::{ glutin, implement_vertex, program, uniform, Display, IndexBuffer, Program, Surface, VertexBuffer, }; use spatialtree::*; // the chunk struct for the tree #[allow(dead_code)] struct Chunk { visible: bool, cache_state: i32, // 0 is new, 1 is merged, 2 is cached, 3 is both selected: bool, in_bounds: bool, } fn make_shaders(display: &Display) -> Program { program!(display, 140 => { vertex: " #version 140 uniform vec2 offset; uniform float scale; in vec2 position; void main() { vec2 local_position = position * scale + 0.005; local_position.x = min(local_position.x, scale) - 0.0025; local_position.y = min(local_position.y, scale) - 0.0025; gl_Position = vec4(local_position + (offset + scale * 0.5) * 2.0 - 1.0, 0.0, 1.0); } ", fragment: " #version 140 uniform int state; uniform int selected; uniform int in_bounds; void main() { if (state == 0) gl_FragColor = vec4(0.2, 0.2, 0.2, 1.0); // new, white if (state == 1) gl_FragColor = vec4(0.0, 0.2, 0.0, 1.0); // merged, green if (state == 2) gl_FragColor = vec4(0.2, 0.0, 0.0, 1.0); // from cache, red if (state == 3) gl_FragColor = vec4(0.4, 0.2, 0.0, 1.0); // both, yellow if (selected != 0) gl_FragColor = vec4(0.0, 0.2, 0.2, 1.0); // selected, so blue if (in_bounds != 0) gl_FragColor = vec4(0.0, 0.2, 0.2, 1.0); // in bounds, so purple } " } ) .unwrap() } #[derive(Copy, Clone)] struct Vertex { // only need a 2d position position: [f32; 2], } implement_vertex!(Vertex, position); struct RenderContext { display: Display, vertex_buffer: VertexBuffer, shaders: Program, index_buffer: IndexBuffer, } impl RenderContext { pub fn new(event_loop: &EventLoop<()>) -> Self { let wb = glutin::window::WindowBuilder::new().with_title("Quadtree demo"); let cb = glutin::ContextBuilder::new().with_vsync(true); let display = Display::new(wb, cb, event_loop).unwrap(); // make a vertex buffer // we'll reuse it as we only need to draw one quad multiple times anyway let vertex_buffer = { VertexBuffer::new( &display, &[ Vertex { position: [-1.0, -1.0], }, Vertex { position: [-1.0, 1.0], }, Vertex { position: [1.0, -1.0], }, Vertex { position: [1.0, 1.0], }, ], ) .unwrap() }; // and the index buffer to form the triangle let index_buffer = IndexBuffer::new( &display, PrimitiveType::TrianglesList, &[0_u16, 1, 2, 1, 2, 3], ) .unwrap(); let shaders = make_shaders(&display); Self { display, vertex_buffer, index_buffer, shaders, } } } fn draw(mouse_pos: (f32, f32), tree: &mut QuadTree, ctx: &RenderContext) { //function for adding chunks to their respective position, and also set their properties fn chunk_creator(_position: QuadVec) -> Chunk { Chunk { visible: true, cache_state: 0, selected: false, in_bounds: false, } } let qv = QuadVec::from_float_coords([mouse_pos.0, (1.0 - mouse_pos.1)], 6); tree.lod_update(&[qv], 2, chunk_creator, |_, _| {}); // make sure there are no holes in chunks array for fast iteration tree.defragment_chunks(); // and select the chunk at the mouse position if let Some(chunk) = tree.get_chunk_by_position_mut(qv) { chunk.selected = true; } // and select a number of chunks in a region when the mouse buttons are selected // and, Redraw! let mut target = ctx.display.draw(); target.clear_color(0.6, 0.6, 0.6, 1.0); // go over all chunks, iterator version for (_, container) in tree.iter_chunks() { let (chunk, position) = (&container.chunk, container.position()); if chunk.visible { // draw it if it's visible // here we get the chunk position and size let uniforms = uniform! { offset: position.float_coords(), scale: position.float_size(), state: chunk.cache_state, selected: chunk.selected as i32, }; // draw it with glium target .draw( &ctx.vertex_buffer, &ctx.index_buffer, &ctx.shaders, &uniforms, &Default::default(), ) .unwrap(); } } target.finish().unwrap(); // deselect the chunk at the mouse position if let Some(chunk) = tree.get_chunk_by_position_mut(qv) { chunk.selected = false; } } fn main() { // set up the tree let mut tree = QuadTree::::with_capacity(32, 32); // start the glium event loop let event_loop = glutin::event_loop::EventLoop::new(); let context = RenderContext::new(&event_loop); draw((0.5, 0.5), &mut tree, &context); // the mouse cursor position let mut mouse_pos = (0.5, 0.5); // last time redraw was done let mut last_redraw = std::time::Instant::now(); // run the main loop event_loop.run(move |event, _, control_flow| { *control_flow = match event { glutin::event::Event::RedrawRequested(_) => { // and draw, if enough time elapses if last_redraw.elapsed().as_millis() > 16 { draw(mouse_pos, &mut tree, &context); last_redraw = std::time::Instant::now(); } glutin::event_loop::ControlFlow::Wait } glutin::event::Event::WindowEvent { event, .. } => match event { // stop if the window is closed glutin::event::WindowEvent::CloseRequested => glutin::event_loop::ControlFlow::Exit, glutin::event::WindowEvent::CursorMoved { position, .. } => { // get the mouse position mouse_pos = ( position.x as f32 / context.display.get_framebuffer_dimensions().0 as f32, position.y as f32 / context.display.get_framebuffer_dimensions().1 as f32, ); // request a redraw context.display.gl_window().window().request_redraw(); glutin::event_loop::ControlFlow::Wait } _ => glutin::event_loop::ControlFlow::Wait, }, _ => glutin::event_loop::ControlFlow::Wait, } }); }