Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quadtree sketch example #91

Merged
merged 8 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions examples/dune
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@
(modules donut_with_scale)
(libraries joy))

(executable
(name quadtree)
(modules quadtree)
(libraries joy))

(executable
(name flowfield)
(modules flowfield noise)
Expand Down
127 changes: 127 additions & 0 deletions examples/quadtree.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
type point = float Joy.point

(* Constants *)
let size = 800.
let half_size = size /. 2.
let max_leaf_points = 4
let clusters = 32
let point_size = 1

let box_color = (0, 0, 0)
let point_color = (255, 1, 1)

(* Init rng *)
let _ = Random.self_init ()

(* Point utils *)
let splat n : point = { x = n; y = n }

let pmap2 f ({ x = x1; y = y1 } : point) ({ x = x2; y = y2 } : point) : point =
{ x = f x1 x2; y = f y1 y2 }

let ( +~ ) (p1 : point) (p2 : point) : point =
{ x = p1.x +. p2.x; y = p1.y +. p2.y }

let ( /! ) ({ x; y } : point) scalar : point =
{ x = x /. scalar; y = y /. scalar }

(* Random utils for creating random clustered points *)
let rand_point () : point =
{ x = Random.float size -. half_size; y = Random.float size -. half_size }

(* Creates a point within 50 units of a center *)
let centered_point (center : point) _: point =
let offset () = Random.float 100. -. 50. in
center +~ { x = offset (); y = offset () }

(* Creates a list of random points clustered around a center point *)
let cluster _ =
let center = rand_point () in
List.init (8 + Random.int 24) (centered_point center)

(* Box and utils *)

(* Axis aligned bounding box *)
type box = { min : point; max : point }


let box min max = { min; max }

(* Returns the middle point of the box *)
let midpoint { min; max } = pmap2 ( +. ) min max /! 2.

(* Subdivides a box into four even axis-aligned boxes *)
let quarters ({ min; max } as box) =
let mid = midpoint box in
let lu = { min = { x = min.x; y = mid.y }; max = { x = mid.x; y = max.y } } in
let ru = { min = { x = mid.x; y = mid.y }; max = { x = max.x; y = max.y } } in
let rd = { min = { x = mid.x; y = min.y }; max = { x = max.x; y = mid.y } } in
let ld = { min; max = mid } in
(lu, ru, rd, ld)

(* Checks whether point is within bounds of box *)
let contains { min; max } ({ x; y }: point) =
x > min.x && x < max.x && y > min.y && y < max.y

(* Quadtree and utils *)

(* 2-tuple of bounding box * 'a list of elts whose positions are within that box *)
type 'a leaf = box * 'a list

type 'a tree = Leaf of 'a leaf | Node of 'a tree list

(* Constructs tree from root *)
let split_root box points =
(* Groups points with the boxes that contain them *)
let partition (lu, ru, rd, ld) es =
let belongs box = List.filter (contains box) in
( (lu, belongs lu es),
(ru, belongs ru es),
(rd, belongs rd es),
(ld, belongs ld es) )
in
(* Splits and converts to Node if leaf has too many points,
otherwise returns leaf *)
let rec split (box, es) =
if List.length es > max_leaf_points then
let quarters' = quarters box in
let lu, ru, rd, ld = partition quarters' points in
Node (List.map split [ lu; ru; rd; ld ])
else Leaf (box, es)
in
split (box, points)

(* Builds our float point tree *)
let build () =
let root = box (splat (-.half_size)) (splat half_size) in
let points = List.flatten (List.init clusters cluster) in
split_root root points

(* Converts our constructed tree into a flat list of shapes for rendering *)
let to_flat_shapes tree =
let open Joy in
(* Converts box into rectangle *)
let rect_of_bb bb =
rectangle ~c:(midpoint bb)
(int_of_float (bb.max.x -. bb.min.x))
(int_of_float (bb.max.y -. bb.min.y))
|> with_stroke box_color
in
(* Converts point into circle of radius 1 *)
let circle_of_point pt = circle ~c:pt point_size |> with_stroke point_color in
(* Traverses tree recursively *)
let rec convert xs = function
| Node children -> List.concat_map (convert xs) children
| Leaf (aabb, es) ->
let b = rect_of_bb aabb in
List.map circle_of_point es @ (b :: xs)
in
convert [] tree

let () =
let open Joy in
init ();
let tree = build () in
let shapes = to_flat_shapes tree in
show shapes;
write ~filename:"quadtree.png" ()
Loading