From 5b71620e96c267af083adcdcccaebf1a297e66e6 Mon Sep 17 00:00:00 2001 From: arne Date: Sun, 16 Mar 2025 08:11:45 +0100 Subject: [PATCH] Thirty nine: Iterate, observe, get carried away --- flake.lock | 11 ++-- flake.nix | 2 +- src/aphorisms/thirty_nine.clj | 100 +++++++++++++++++++++++++++++ src/aphorisms/utils/middleware.clj | 33 +++++++--- 4 files changed, 130 insertions(+), 16 deletions(-) create mode 100644 src/aphorisms/thirty_nine.clj diff --git a/flake.lock b/flake.lock index 9e7b90e..6de6815 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,12 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1709703039, - "narHash": "sha256-6hqgQ8OK6gsMu1VtcGKBxKQInRLHtzulDo9Z5jxHEFY=", - "path": "/nix/store/gig8j85kj7ybjy3ksn6k3aich8j2k59y-source", - "rev": "9df3e30ce24fd28c7b3e2de0d986769db5d6225d", - "type": "path" + "lastModified": 1741513245, + "narHash": "sha256-7rTAMNTY1xoBwz0h7ZMtEcd8LELk9R5TzBPoHuhNSCk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e3e32b642a31e6714ec1b712de8c91a3352ce7e1", + "type": "github" }, "original": { "owner": "NixOS", diff --git a/flake.nix b/flake.nix index 5b55984..3bde407 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - inputs.nixpkgs.url = github:NixOS/nixpkgs; + inputs.nixpkgs.url = "github:NixOS/nixpkgs"; outputs = { self, nixpkgs }: let diff --git a/src/aphorisms/thirty_nine.clj b/src/aphorisms/thirty_nine.clj new file mode 100644 index 0000000..91b70cb --- /dev/null +++ b/src/aphorisms/thirty_nine.clj @@ -0,0 +1,100 @@ +(ns aphorisms.thirty-nine + (:require [thi.ng.geom.core :as g] + [thi.ng.geom.vector :as v] + [quil.core :as q] + [quil.middleware :as qm] + [thi.ng.geom.rect :as r] + [thi.ng.geom.line :as l] + [thi.ng.math.core :as m] + [thi.ng.math.noise :as n] + [aphorisms.utils.middleware :refer [screenshottable]])) + +(def height 800) +(def width (int (/ height (Math/sqrt 2)))) +(def bounds (r/rect width height)) + +;; To perturb a line… +;; +;; - Divide it into equal segments +;; - Take the first segment, and with some probability, rotate it and all following segments randomly, creating a kink +;; - Move to the next segment and repeat the process until you've reached the end of the line + +(defn perturb [line chaos disturbance] + (loop [[cur & tail] (g/vertices line) + pts []] + (let [[next] tail] + (if next + ;; create a vector to distort all subsequent points with; + ;; this creates a vector that only moves point b + (let [rot (if (< (Math/random) chaos) + (* (n/noise1 (* (- (Math/random) 0.5) disturbance)) disturbance) + 0)] + (recur (mapv #(-> (g/translate % (m/* cur -1)) + (g/rotate rot) + (g/translate cur)) tail) + (conj pts cur))) + (l/linestrip2 (conj pts cur)))))) + +(def padding 90) +(def step 2) +(def segments 100) + +;; generate the starting line and segment it + +(def line + (let [l (l/line2 [padding 0] [(- width padding) 0])] + (->> (range (inc segments)) + (mapv #(g/point-at l (* % (/ 1 segments)))) + (l/linestrip2)))) + +;; copy the line over and over again, adding subsequent perturbations to each copy + +(def chaos 0.4) ; what is the chance for disturbing a segment? +(def disturbance (* (/ m/TWO_PI 360) 9)) ; what is the maximum bend? + +(time + (def lines + (->> (iterate #(perturb % chaos disturbance) line) + (drop (inc (int (* 500 (Math/random))))) + (interleave (range padding (inc (- height (* 2 padding))) step)) + (partition 2) + (mapv (fn [[y line]] + (g/translate line (v/vec2 0 y))))))) + +;; below is the rendering logic + +(defn setup [] + (q/frame-rate 30) + (q/color-mode :hsb 360 100 100) + (q/rect-mode :center) + (q/ellipse-mode :center) + (q/background 350) + {}) + +(defn update-state [state] + state) + +(defn draw-state [state] + (q/background 180 0 98) + (q/stroke-weight 0.4) + (doseq [l lines] + (let [y (-> (g/vertices l) + (first) + :y) + h (/ (- y padding) (- height (* 2 padding))) + hue (mod (+ 250 (* 40 h)) 360)] + (q/stroke hue 80 80 100) + (doseq [[a b] (partition 2 1 (g/vertices l))] + (q/line a b))))) + +#_:clj-kondo/ignore +(q/defsketch thirty-nine + :title "Thirty nine" + :size [(g/width bounds) (g/height bounds)] + :settings #(q/pixel-density (q/display-density)) + :setup setup + :update update-state + :draw draw-state + #_#_ :renderer :p2d + :features [:keep-on-top :no-bind-output] + :middleware [qm/pause-on-error qm/fun-mode (screenshottable)]) diff --git a/src/aphorisms/utils/middleware.clj b/src/aphorisms/utils/middleware.clj index 4faafaf..bf620f8 100644 --- a/src/aphorisms/utils/middleware.clj +++ b/src/aphorisms/utils/middleware.clj @@ -1,21 +1,34 @@ (ns aphorisms.utils.middleware - (:require [quil.core :as q])) + (:require [quil.core :as q] + [clojure.string :as str]) + (:import [java.time LocalDateTime] + [java.time.format DateTimeFormatter])) -(defn- default-name-fn [timestamp] - (println "*ns*" *ns*) - (format "%s.png" timestamp)) +(defn- default-name-fn [prefix timestamp] + (format "%s-%s.png" prefix timestamp)) + +(defn- current-time [] + (-> + (.format DateTimeFormatter/ISO_LOCAL_DATE_TIME (LocalDateTime/now)) + (str/replace #"\.\d+$" "") + (str/replace #":" "-"))) (defn screenshottable ([] (screenshottable {})) ([{:keys [out-dir name-fn] :or {out-dir "exports" name-fn default-name-fn}}] - (fn screenshottable [opts] + (fn screenshottable' [opts] (let [key-pressed (:key-pressed opts)] - (when (fn? key-pressed) - (println "There is already a function bound to key-pressed.")) (assoc opts :key-pressed - (fn [e] - (println e) + (fn [state ev] + (when (= (:raw-key ev) \s) + (let [path (str out-dir "/" (name-fn (-> (:title opts) + (str/lower-case) + (str/replace #"[^A-Za-z0-9]" "-")) + (current-time)))] + (print "saving screenshot to" path) + (q/save path))) (when (fn? key-pressed) - (key-pressed e)))))))) + (key-pressed state ev)) + {}))))))