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;
+}