commit a0d42e7709e30f258ef28df7bb5b4b79fd5ec49a Author: crystal heart <> Date: Tue Jun 4 12:39:07 2024 +0200 basic drawing on a quadtree no zoom yet, also no figuring out when tree nodes can merge without losing information. diff --git a/index.html b/index.html new file mode 100644 index 0000000..876d1ab --- /dev/null +++ b/index.html @@ -0,0 +1,10 @@ + + + quadtree drawer + + + + + + + diff --git a/js/canvas.js b/js/canvas.js new file mode 100644 index 0000000..a2b1004 --- /dev/null +++ b/js/canvas.js @@ -0,0 +1,13 @@ +// CORE GRAPHICS + +export const canvas = document.getElementById("canvas"); +const ctx = canvas.getContext("2d"); + +export function fill_rect(x, y, w, h, color) { + ctx.fillStyle = color; + ctx.fillRect(x, y, w, h); +} + +export function clear(color) { + fill_rect(0, 0, canvas.width, canvas.height, color); +} diff --git a/js/cursor.js b/js/cursor.js new file mode 100644 index 0000000..7fd6083 --- /dev/null +++ b/js/cursor.js @@ -0,0 +1,27 @@ +import {canvas} from "./canvas.js"; + +const state = { + x: 0, + y: 0, + buttons: 0, +}; + +function update(e) { + const elem_root = e.target.getBoundingClientRect(); + state.x = e.clientX - elem_root.left; + state.y = e.clientY - elem_root.top; + state.buttons = e.buttons; +} +canvas.addEventListener("mousedown", update); +canvas.addEventListener("mouseup", update); +canvas.addEventListener("mousemove", update); + +export function get_x() { + return state.x; +} +export function get_y() { + return state.y; +} +export function button_pressed(button) { + return ((state.buttons >>> button) & 1) !== 0; +} diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..8bed1e0 --- /dev/null +++ b/js/main.js @@ -0,0 +1,129 @@ +// IMPORTS + +import {None, Some} from "./option.js"; +import {canvas, fill_rect, clear} from "./canvas.js"; +import * as cursor from "./cursor.js"; + +// CONSTANTS + +const MIN_SCALE = 4; + +// QUADTREE + +function Tree() { + let parent = None(); + let children = None(); + let color = "#000000"; + + const node = { + set_parent: p => { parent = Some(p); }, + get_parent: () => parent, + + create_children: () => { + children.none(() => { + let _children = Array(4); + for (let i = 0; i < 4; i++) { + _children[i] = Tree(); + _children[i].set_color(color); + _children[i].set_parent(node); + } + children = Some(_children); + }); + }, + get_children: () => children, + + set_color: c => { color = c; }, + get_color: () => color, + + to_string: () => { + return children + .some(cs => { + return Some(`{color: "${color}", children: [${ + cs.map(c => c.to_string()).join(", ") + }]}`); + }) + .unwrap(`{color: "${color}"}`); + }, + }; + + return node; +} + +function draw_tree(tree, x, y, size) { + fill_rect(x, y, size, size, tree.get_color()); + + if (size < MIN_SCALE) { return; } + + tree.get_children() + .none(None) + .some(c => { + const new_size = size / 2; + draw_tree(c[0], x, y, new_size); + draw_tree(c[1], x + new_size, y, new_size); + draw_tree(c[2], x, y + new_size, new_size); + draw_tree(c[3], x + new_size, y + new_size, new_size); + }); +} + +function find_containing_node(node, root_x, root_y, x, y, scale) { + if (scale <= MIN_SCALE) { + return node; + } + + node.create_children(); + return node.get_children().none(None).some(c => { + let index = 0; + scale /= 2; + if (x >= scale) { + index += 1; + root_x += scale; + x -= scale; + } + if (y >= scale) { + index += 2; + root_y += scale; + y -= scale; + } + + return find_containing_node(c[index], root_x, root_y, x, y, scale); + }); +} + +function color_pixel(tree, root_x, root_y, x, y, scale, color) { + const node = find_containing_node(tree, 0, 0, x, y, 512); + node.set_color(color); +} + +// MAIN + +const root = Tree(); + +function render(tree) { + clear("#000000"); + draw_tree(tree, 0, 0, 512); +} + +function init() { + root.set_color("#0f0"); + root.create_children(); + root.get_children().none(None).some(c => { + c[0].set_color("#f80"); + }); + + console.log(root.to_string()); +} + +function main() { + if (cursor.button_pressed(0)) { + + color_pixel(root, 0, 0, cursor.get_x(), cursor.get_y(), 512, "#000"); + render(root); + } + + render(root); + + requestAnimationFrame(main); +} + +init(); +main(); diff --git a/js/option.js b/js/option.js new file mode 100644 index 0000000..5d16265 --- /dev/null +++ b/js/option.js @@ -0,0 +1,24 @@ +// OPTION TYPE + +/* +usage: +None().none(f1).some(f2) // returns f1() +Some(x).none(f1).some(f2) // returns f2(x) +None(x).unwrap(y) // returns y +Some(x).unwrap(y) // returns x +*/ + +export function None() { + return { + none: (f) => f(), + some: (f) => None(), + unwrap: (y) => y, + }; +} +export function Some(x) { + return { + none: (f) => Some(x), + some: (f) => f(x), + unwrap: (y) => x, + }; +} diff --git a/style.css b/style.css new file mode 100644 index 0000000..c7175cc --- /dev/null +++ b/style.css @@ -0,0 +1,4 @@ +body { + background-color: #222; + color: #fff; +}