heyarne.aphorisms/src/aphorisms/thirty_four.clj
arne 2b8ca91ae4 Finish thirty-four
"Emergence", pau's christmas present
2022-12-31 12:20:18 +01:00

161 lines
5.7 KiB
Clojure

(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])