(ns aphorisms.thirty-four (:require [quil.core :as q] [quil.applet :as qa] [quil.middleware :as qm] [thi.ng.geom.rect :as r] [thi.ng.geom.core :as g] [thi.ng.geom.circle :as c] [thi.ng.geom.spatialtree :as st] [thi.ng.math.core :as m] [thi.ng.geom.vector :as v] [heyarne.line-us.connection :as line-us] [heyarne.line-us.gcode :as gcode] [heyarne.line-us.helpers :as lh])) ;; attempt to implement the hyphae algorithm described in ;; https://inconvergent.net/generative/hyphae/ (def bounds (r/rect [800 -900] [1700 900])) ;; line-us drawing area: [[700;1800], [-1000;1000]] (defn pad [rect p] (let [p (v/vec2 p)] (-> (update rect :p #(g/translate % p)) (update :size #(g/translate % (m/* p -2)))))) (def canvas (pad bounds [32 32])) ;; parameters to influence how the end result looks (def start-size 15.0) (def min-size 10.0) (def shrink 0.8) ;; factor by which size decreases at each step (def spaciousness 2.5) ;; size of neighbor-check for each node (def max-path-length 100) ;; how long are the strings of nodes? (def num-paths 100) ;; how many strings should be generated max? ;; all nodes look like this: {:shape circ, :direction vec2} (defn empty-neighborhood? [quadtree node] (empty? (st/select-with-shape quadtree (g/scale-size (:shape node) spaciousness)))) (defn nice-place? [quadtree node] (and (g/contains-point? canvas (-> node :shape :p)) (empty-neighborhood? quadtree node))) (defn wobble [v] (g/rotate v (m/radians (m/random -10 10)))) (defn pick-start [quadtree] (->> (repeatedly 10 (fn [] (let [pt (g/random-point-inside #_(c/circle (g/centroid canvas) 100) (g/scale-size canvas 0.8)) dir (wobble (g/rotate (m/normalize (m/- (g/centroid canvas) pt)) (* Math/PI 0.4 (Math/random))))] {:shape (c/circle pt start-size) :direction dir}))) (filter #(nice-place? quadtree %)) (first))) (defn next-node [quadtree path] (let [cur (peek path)] (->> (repeatedly 10 (fn [] (-> (update cur :shape (fn [c] (-> (update c :r #(Math/max (* % shrink) min-size)) (update :p #(g/translate % (m/* (:direction cur) (* 2 (:r c)))))))) (update :direction wobble)))) (filter (fn [node] (and (nice-place? quadtree node) ;; avoid self-intersections (every? (fn [other] ;; (prn other) ;; (prn (g/dist (-> node :shape :p) (-> other :shape :p))) ;; (prn ) (>= (g/dist (-> node :shape :p) (-> other :shape :p)) (* 0.99 (+ (-> node :shape :r) (-> other :shape :r))))) path)))) ;; floating point errors? (first)))) ;; let's generate different paths of hyphae, each at most `max-path-length` nodes long (defn make-path [quadtree] (when-let [seed (pick-start quadtree)] (->> (iterate (fn [path] (if-let [next (next-node quadtree path)] (conj path next) path)) (list seed)) (partition 2) (filter (fn [[a b]] (= a b))) (ffirst) (take-last max-path-length)))) (comment (make-path (st/quadtree bounds)) ) ;; single circle #_(def paths [[{:shape (c/circle (g/centroid bounds) 200)}]]) ;; generated scene (def paths (let [qt (st/quadtree bounds)] (for [_ (range num-paths) :let [path (make-path qt)]] (do (reduce #(g/add-point %1 (-> %2 :shape :p) %2) qt path) ;; add-point is mutable path)))) (comment ;; save interesting scenes (spit "exports/20221229-scene-1-single-circle.edn" (vec paths)) (spit "exports/20221229-scene-2-scattered-circles.edn" (vec paths)) (spit "exports/20221229-scene-3-scatter-more.edn" (vec paths)) (spit "exports/20221229-scene-4-feeling-and-finding-larger.edn" (vec paths)) (spit "exports/20221229-scene-5-emergence-larger.edn" (vec paths)) ;; load particular scene from disk (def paths (read-string (slurp "exports/20221229-scene-5-emergence-larger.edn"))) ;; plot currently visible scene (with-open [line-us (line-us/connect "line-us.fritz.box" 1337)] (line-us/send-command! line-us "G94 S2") (doseq [#_#_coords (into [] cat (gcode/scene->gcode-seq [bounds])) ;; for calibration coords (->> (into [] (comp cat (map :shape) (map #(update % :p v/vec2))) ;; this is required because of a bug in the reader paths) (gcode/scene->gcode-seq) (into [] cat) #_(lh/rescale (r/rect [700 -1000] [1800 1000])))] (line-us/send-movement! line-us coords))) ) (defn setup [] (q/ellipse-mode :center) (q/rect-mode :corners) (q/color-mode :hsb 255) {}) (defn draw-state [_] (q/scale 0.5) (q/with-translation (m/* (:p bounds) -1) (q/background 255) (q/no-fill) (doseq [path paths {{[x y] :p r :r} :shape} path] (q/ellipse x y (* 2 r) (* 2 r))) #_(qd/draw-scene! scene))) ;; #_:clj-kondo/ignore #_(q/defsketch thirty-four :title "Thirty-Four" :size (g/scale (:size bounds) 0.5) :settings #(q/pixel-density (q/display-density)) :features [:keep-on-top] :setup setup :update identity :draw draw-state :middleware [qm/pause-on-error #_(screenshottable) qm/fun-mode])