diff --git a/examples/quadtree_demo/quadtree_demo.v b/examples/quadtree_demo/quadtree_demo.v new file mode 100644 index 0000000000..557e98ad3f --- /dev/null +++ b/examples/quadtree_demo/quadtree_demo.v @@ -0,0 +1,191 @@ +module main + +import datatypes +import gg +import gx +import os +import time +import math +import rand + +const ( + win_width = 1340 + win_height = 640 + timer_period = 40 * time.millisecond // defaulted at 25 fps + font_small = gx.TextCfg{ + color: gx.black + size: 20 + } + font_large = gx.TextCfg{ + color: gx.black + size: 40 + } +) + +struct App { +mut: + gg &gg.Context + qt datatypes.Quadtree + players []datatypes.AABB + particles []Particle + retrieveds []datatypes.AABB + nodes []datatypes.Quadtree + width f64 = 1340 + height f64 = 640 +} + +struct Particle { +mut: + pmt datatypes.AABB + speed f64 + angle f64 +} + +fn (mut p Particle) update() { + p.pmt.x += p.speed * math.cos(p.angle * math.pi / 180) + p.pmt.y += p.speed * math.sin(p.angle * math.pi / 180) + + if p.pmt.x < 0 { + p.pmt.x = 0 + p.speed = -p.speed + p.angle = -p.angle + } + if p.pmt.x > 1340 { + p.pmt.x = 1340 + p.speed = -p.speed + p.angle = -p.angle + } + if p.pmt.y < 0 { + p.pmt.y = 0 + p.speed = -p.speed + p.angle = 180 - p.angle + } + if p.pmt.y > 640 { + p.pmt.y = 640 + p.speed = -p.speed + p.angle = 180 - p.angle + } +} + +fn (mut app App) start() { + app.players << datatypes.AABB{1200 * rand.f64(), 500 * rand.f64(), 20, 20} + app.insert_particles() + for mut particle in app.particles { + particle.speed = 10 * rand.f64() + particle.angle = 200 * rand.f64() + } + app.nodes << app.qt.get_nodes() +} + +fn (mut app App) update() { + app.qt.clear() + app.nodes = [] + for mut particle in app.particles { + particle.update() + app.qt.insert(particle.pmt) + } + app.find_particles() + app.nodes << app.qt.get_nodes() +} + +fn (mut app App) insert_particles() { + mut grid := 10.0 + mut gridh := app.qt.perimeter.width / grid + mut gridv := app.qt.perimeter.height / grid + num_particles := 100 + for _ in 0 .. num_particles { + mut x := rand_minmax(0, gridh) * grid + mut y := rand_minmax(0, gridv) * grid + mut random_particle := datatypes.AABB{ + x: x + y: y + width: rand_minmax(1, 4) * grid + height: rand_minmax(1, 4) * grid + } + app.particles << Particle{random_particle, 0.0, 0.0} + } +} + +fn (mut app App) find_particles() { + app.retrieveds = [] + app.retrieveds << app.qt.retrieve(app.players[0]) +} + +fn main() { + mut app := &App{ + gg: 0 + } + app.gg = gg.new_context( + bg_color: gx.white + width: win_width + height: win_height + use_ortho: true + create_window: true + window_title: 'Quadtree Demo' + frame_fn: frame + event_fn: on_event + user_data: app + font_path: os.resource_abs_path('../assets/fonts/RobotoMono-Regular.ttf') + ) + app.qt = app.qt.create(0, 0, 1340, 640, 8, 4, 0) + app.start() + go app.run() + app.gg.run() +} + +fn (mut app App) on_mouse_move(mouse_x f32, mouse_y f32) { + for mut player in app.players { + player.x = (mouse_x / gg.window_size_real_pixels().width) * 1340 + player.y = (mouse_y / gg.window_size_real_pixels().height) * 640 + } +} + +fn on_event(mut e gg.Event, mut app App) { + match e.typ { + .mouse_move { app.on_mouse_move(e.mouse_x, e.mouse_y) } + else {} + } +} + +fn (mut app App) run() { + for { + app.update() + time.sleep(timer_period) + } +} + +fn frame(app &App) { + app.gg.begin() + app.draw() + app.gg.end() +} + +fn (app &App) display() { + for player in app.players { + app.gg.draw_rect_filled(f32(player.x), f32(player.y), f32(player.width), f32(player.height), + gx.black) + } + for particle in app.particles { + app.gg.draw_rect_empty(f32(particle.pmt.x), f32(particle.pmt.y), f32(particle.pmt.width), + f32(particle.pmt.height), gx.blue) + } + for node in app.nodes { + app.gg.draw_rect_empty(f32(node.perimeter.x), f32(node.perimeter.y), f32(node.perimeter.width), + f32(node.perimeter.height), gx.red) + } + for retrieved in app.retrieveds { + app.gg.draw_rect_filled(f32(retrieved.x + 1), f32(retrieved.y + 1), f32(retrieved.width - 2), + f32(retrieved.height - 2), gx.green) + } + app.gg.draw_text(1200, 25, 'Nodes: $app.nodes.len', font_small) + app.gg.draw_text(1200, 50, 'Particles: $app.particles.len', font_small) +} + +fn (app &App) draw() { + app.display() +} + +fn rand_minmax(min f64, max f64) f64 { + mut val := min + (rand.f64() * (max - min)) + return val +} diff --git a/vlib/datatypes/README.md b/vlib/datatypes/README.md index 32c9427d6a..bece6af48f 100644 --- a/vlib/datatypes/README.md +++ b/vlib/datatypes/README.md @@ -27,4 +27,5 @@ println(stack) - [x] Queue (FIFO) - [x] Min heap (priority queue) - [x] Set +- [x] Quadtree - [ ] ... diff --git a/vlib/datatypes/quadtree.v b/vlib/datatypes/quadtree.v new file mode 100644 index 0000000000..a40c452942 --- /dev/null +++ b/vlib/datatypes/quadtree.v @@ -0,0 +1,205 @@ +module datatypes + +pub struct AABB { +pub mut: + x f64 + y f64 + width f64 + height f64 +} + +pub struct Quadtree { +pub mut: + perimeter AABB + capacity int + depth int + level int + particles []AABB + nodes []Quadtree +} + +// create returns a new configurable root node for the tree. +pub fn (mut q Quadtree) create(x f64, y f64, width f64, height f64, capacity int, depth int, level int) Quadtree { + return Quadtree{ + perimeter: AABB{ + x: x + y: y + width: width + height: height + } + capacity: capacity + depth: depth + level: level + particles: []AABB{} + nodes: []Quadtree{len: 0, cap: 4} + } +} + +// insert recursevely adds a particle in the correct index of the tree. +pub fn (mut q Quadtree) insert(p AABB) { + mut indexes := []int{} + + if q.nodes.len > 0 { + indexes = q.get_index(p) + for k in 0 .. indexes.len { + q.nodes[indexes[k]].insert(p) + } + return + } + + q.particles << p + + if (q.particles.len > q.capacity) && (q.level < q.depth) { + if q.nodes.len == 0 { + q.split() + } + + for j in 0 .. q.particles.len { + indexes = q.get_index(q.particles[j]) + for k in 0 .. indexes.len { + q.nodes[indexes[k]].insert(q.particles[j]) + } + } + q.particles = [] + } +} + +// retrieve recursevely checks if a particle is in a specific index of the tree. +pub fn (mut q Quadtree) retrieve(p AABB) []AABB { + mut indexes := q.get_index(p) + mut detected_particles := q.particles.clone() + + if q.nodes.len > 0 { + for j in 0 .. indexes.len { + detected_particles << q.nodes[indexes[j]].retrieve(p) + } + } + return detected_particles +} + +// clear flushes out nodes and partcles from the tree. +pub fn (mut q Quadtree) clear() { + q.particles = [] + for j in 0 .. q.nodes.len { + if q.nodes.len > 0 { + q.nodes[j].clear() + } + } + q.nodes = [] +} + +// get_nodes recursevely returns the subdivisions the tree has. +pub fn (q Quadtree) get_nodes() []Quadtree { + mut nodes := []Quadtree{} + if q.nodes.len > 0 { + for j in 0 .. q.nodes.len { + nodes << q.nodes[j] + nodes << q.nodes[j].get_nodes() + } + } + return nodes +} + +fn (mut q Quadtree) split() { + if q.nodes.len == 4 { + return + } + + next_level := q.level + 1 + child_width := q.perimeter.width / 2 + child_height := q.perimeter.height / 2 + x := q.perimeter.x + y := q.perimeter.y + + //(0) + q.nodes << Quadtree{ + perimeter: AABB{ + x: x + child_width + y: y + width: child_width + height: child_height + } + capacity: q.capacity + depth: q.depth + level: next_level + particles: []AABB{} + nodes: []Quadtree{len: 0, cap: 4} + } + + //(1) + q.nodes << Quadtree{ + perimeter: AABB{ + x: x + y: y + width: child_width + height: child_height + } + capacity: q.capacity + depth: q.depth + level: next_level + particles: []AABB{} + nodes: []Quadtree{len: 0, cap: 4} + } + + //(2) + q.nodes << Quadtree{ + perimeter: AABB{ + x: x + y: y + child_height + width: child_width + height: child_height + } + capacity: q.capacity + depth: q.depth + level: next_level + particles: []AABB{} + nodes: []Quadtree{len: 0, cap: 4} + } + + //(3) + q.nodes << Quadtree{ + perimeter: AABB{ + x: x + child_width + y: y + child_height + width: child_width + height: child_height + } + capacity: q.capacity + depth: q.depth + level: next_level + particles: []AABB{} + nodes: []Quadtree{len: 0, cap: 4} + } +} + +fn (mut q Quadtree) get_index(p AABB) []int { + mut indexes := []int{} + mut v_midpoint := q.perimeter.x + (q.perimeter.width / 2) + mut h_midpoint := q.perimeter.y + (q.perimeter.height / 2) + + mut north := p.y < h_midpoint + mut south := p.y + p.height > h_midpoint + mut west := p.x < v_midpoint + mut east := p.x + p.width > v_midpoint + + // top-right quad + if north && east { + indexes << 0 + } + + // top-left quad + if north && west { + indexes << 1 + } + + // bottom-left quad + if south && west { + indexes << 2 + } + + // bottom-right quad + if south && east { + indexes << 3 + } + return indexes +} diff --git a/vlib/datatypes/quadtree_test.v b/vlib/datatypes/quadtree_test.v new file mode 100644 index 0000000000..1c062f51d2 --- /dev/null +++ b/vlib/datatypes/quadtree_test.v @@ -0,0 +1,83 @@ +module datatypes + +fn test_create() { + mut qt := Quadtree{} + test := qt.create(0, 0, 1340, 640, 8, 4, 0) + test_clone := qt.create(0, 0, 1340, 640, 8, 4, 0) + assert test == test_clone +} + +fn test_insert() { + mut qt := Quadtree{} + mut test := qt.create(0, 0, 1340, 640, 8, 4, 0) + mut pt := AABB{ + x: 100 + y: 50 + width: 60 + height: 100 + } + assert test.particles == [] + test.insert(pt) + assert test.particles[0] == pt +} + +fn test_retrieve() { + mut qt := Quadtree{} + mut test := qt.create(0, 0, 1340, 640, 8, 4, 0) + mut pt := AABB{ + x: 100 + y: 50 + width: 60 + height: 100 + } + test.insert(pt) + t := test.retrieve(pt) + assert t[0] == pt +} + +fn test_clear() { + mut qt := Quadtree{} + mut test := qt.create(0, 0, 1340, 640, 8, 4, 0) + mut test_clone := qt.create(0, 0, 1340, 640, 8, 4, 0) + mut pt := AABB{ + x: 100 + y: 50 + width: 60 + height: 100 + } + test.split() + test.insert(pt) + assert test != test_clone + test.clear() + assert test == test_clone +} + +fn test_get_nodes() { + mut qt := Quadtree{} + mut test := qt.create(0, 0, 1340, 640, 8, 4, 0) + test.split() + t := test.get_nodes() + assert t.len == 4 +} + +fn test_split() { + mut qt := Quadtree{} + mut test := qt.create(0, 0, 1340, 640, 8, 4, 0) + test.split() + t := test.get_nodes() + assert t.len == 4 +} + +fn test_get_index() { + mut qt := Quadtree{} + mut test := qt.create(0, 0, 1340, 640, 8, 4, 0) + mut pt := AABB{ + x: 100 + y: 50 + width: 60 + height: 100 + } + test.particles << pt + t := test.get_index(pt) + assert t == [1] +}