├── bin ├── repl ├── ci ├── setup ├── kaocha ├── lint ├── install-bb └── googleart_palette.clj ├── resources ├── sparkles-externs.js ├── public │ ├── shaders │ │ ├── integer-circles.vert.c │ │ ├── physarum.vert.c │ │ ├── video-shader.vert.c │ │ ├── reaction-diffusion.vert.c │ │ ├── integer-circles.frag.c │ │ ├── physarum.frag.c │ │ ├── reaction-diffusion.display.frag.c │ │ ├── kaleidoscope.frag.c │ │ ├── video-shader.frag.c │ │ └── reaction-diffusion.main.frag.c │ └── index.html ├── data_readers.cljc ├── delaunator-externs.js └── d3-delaunay-externs.js ├── src ├── shimmers │ ├── dev.cljs │ ├── scratch │ │ ├── sparkles.cljs │ │ ├── timing.cljs │ │ ├── instaparse.cljs │ │ ├── modulus.cljs │ │ ├── ndarray.cljs │ │ ├── golden.cljc │ │ ├── rotations.cljs │ │ ├── mixing.cljs │ │ ├── ellipse.cljs │ │ └── geometry.cljs │ ├── common │ │ ├── ui │ │ │ ├── canvas_attributes.cljc │ │ │ ├── svg.cljs │ │ │ ├── debug.cljc │ │ │ └── quil.cljs │ │ ├── transition_interval.cljc │ │ ├── shader.cljs │ │ ├── screen.cljc │ │ ├── video.cljs │ │ ├── particle_system.cljs │ │ ├── ui.cljs │ │ ├── framerate.cljs │ │ ├── string.cljc │ │ └── svg_export.cljs │ ├── registry.cljc │ ├── ring_server.clj │ ├── math │ │ ├── stair.cljc │ │ ├── points.cljc │ │ ├── geometry │ │ │ ├── points.cljc │ │ │ └── line.cljc │ │ ├── kinematics.cljc │ │ ├── interval.cljc │ │ ├── wobble.cljc │ │ ├── wave.cljc │ │ └── vector.cljc │ ├── sketches │ │ ├── template │ │ │ ├── quil.cljs │ │ │ ├── svg.cljs │ │ │ └── canvas.cljs │ │ ├── shattered.cljs │ │ ├── color_mapping.cljs │ │ ├── beat_table.cljs │ │ ├── precipitation.cljs │ │ ├── ring.cljs │ │ ├── imperfect_curves.cljs │ │ ├── deformed_spirals.cljs │ │ ├── scintillation.cljs │ │ ├── zigzag.cljs │ │ ├── cutouts.cljs │ │ ├── clothoid_flowers.cljs │ │ ├── punchcard.cljs │ │ ├── concentric_moire.cljs │ │ ├── squiggle_line.cljs │ │ ├── sunflower_path.cljs │ │ ├── breathing_hexes.cljs │ │ ├── slashes.cljs │ │ ├── flower_petals.cljs │ │ ├── offsetting_arcs.cljs │ │ ├── angle_of_ascent.cljs │ │ ├── spiral_approach.cljs │ │ ├── sunflower.cljs │ │ ├── stair_demo.cljs │ │ ├── yin_yang.cljs │ │ ├── hexaflexagon.cljs │ │ ├── rolling_shapes.cljs │ │ ├── object_permanence.cljs │ │ ├── slither.cljs │ │ ├── all_the_shapes_in_between.cljs │ │ ├── opposing_planes.cljs │ │ ├── marching_squares.cljs │ │ ├── skyline.cljs │ │ ├── hexaclock.cljs │ │ ├── pixel_rings.cljs │ │ ├── hexcursive.cljs │ │ ├── stretchy_lines.cljs │ │ ├── concentric_orbits.cljs │ │ ├── undulating_figures.cljs │ │ ├── designed_imperfections.cljs │ │ ├── grid_exclusion.cljs │ │ ├── misplaced_connections.cljs │ │ ├── dreamcatcher.cljs │ │ ├── vanishing_points.cljs │ │ ├── spiderwebs.cljs │ │ ├── kinematic_chain.cljs │ │ ├── gossamer_coils.cljs │ │ ├── reagent_quil_component.cljs │ │ ├── circle_connections.cljs │ │ ├── tree_rings.cljs │ │ ├── falling_gradients.cljs │ │ ├── rose.cljs │ │ ├── random_walk.cljs │ │ ├── flickering_dots.cljs │ │ ├── lattice_in_steps.cljs │ │ ├── noise_grid.cljs │ │ ├── zoetropic.cljs │ │ └── string_lights.cljs │ ├── algorithm │ │ ├── chaikin.cljc │ │ ├── random_graph.cljc │ │ ├── flow_fields.cljc │ │ ├── markov.cljc │ │ ├── marching_squares.cljc │ │ └── delaunay.cljs │ ├── model │ │ ├── harmonics.cljc │ │ └── polygraph.cljc │ ├── core.cljs │ ├── macros │ │ └── loader.cljc │ └── view │ │ └── favicon.cljs └── cljs │ └── user.cljc ├── package.json ├── vendor └── sparkles │ ├── src │ └── sparkles.js │ └── package.json ├── .gitignore ├── test └── shimmers │ ├── math │ ├── equations_test.cljc │ ├── control_test.cljc │ ├── geometry │ │ ├── points_test.cljc │ │ ├── arc_test.cljc │ │ ├── triangle_test.cljc │ │ └── intersection_test.cljc │ ├── stair_test.cljc │ ├── interval_test.cljc │ └── geometry_test.cljc │ ├── algorithm │ ├── linear_assignment_test.cljc │ ├── minimum_spanning_tree_test.cljc │ ├── rtree_test.cljc │ ├── space_colonization_test.cljc │ ├── quadtree_test.cljc │ └── mosaic_test.cljc │ ├── common │ └── transition_interval_test.cljc │ ├── figwheel_runner.cljs │ ├── test_namespaces.cljs │ └── automata │ └── memory_test.cljc ├── .dir-locals.el ├── .clj-kondo └── config.edn ├── bb.edn ├── release.cljs.edn ├── shimmers.el ├── tests.edn ├── dev.cljs.edn └── .github └── workflows └── continuous-deployment.yaml /bin/repl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | clojure -M:dev:repl 4 | -------------------------------------------------------------------------------- /bin/ci: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | bin/lint 4 | bin/kaocha unit-cljc 5 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | npm install 4 | bin/install-bb 5 | -------------------------------------------------------------------------------- /bin/kaocha: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | clojure -M:test -m "kaocha.runner" "$@" 4 | -------------------------------------------------------------------------------- /bin/lint: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | clojure -M:clj-kondo --parallel --lint src test 4 | -------------------------------------------------------------------------------- /resources/sparkles-externs.js: -------------------------------------------------------------------------------- 1 | var sparkles = { 2 | "trace": function() {} 3 | }; 4 | -------------------------------------------------------------------------------- /src/shimmers/dev.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.dev 2 | (:require [devtools.core :as devtools])) 3 | 4 | (enable-console-print!) 5 | (devtools/install!) 6 | -------------------------------------------------------------------------------- /src/shimmers/scratch/sparkles.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.scratch.sparkles 2 | (:require [sparkles])) 3 | 4 | (comment 5 | (sparkles/trace {:a 1 :b [2 3]})) 6 | 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "d3-delaunay": "^6.0.2", 4 | "delaunator": "^5.0.0", 5 | "sparkles": "file:vendor/sparkles", 6 | "ws": "^7.4.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/cljs/user.cljc: -------------------------------------------------------------------------------- 1 | #_:clj-kondo/ignore 2 | (ns cljs.user 3 | ;; Silences warnings about undeclared Var cljs.user/vec2 4 | (:require [thi.ng.geom.vector :refer [vec2 vec3]])) 5 | 6 | -------------------------------------------------------------------------------- /resources/public/shaders/integer-circles.vert.c: -------------------------------------------------------------------------------- 1 | attribute vec3 aPosition; 2 | 3 | void main() { 4 | vec4 p = vec4(aPosition,1.0); 5 | p.xy = p.xy * 2.0 - 1.0; 6 | gl_Position = p; 7 | } 8 | -------------------------------------------------------------------------------- /vendor/sparkles/src/sparkles.js: -------------------------------------------------------------------------------- 1 | var sparkles = { 2 | trace: function trace(v) { 3 | console.log("Hello From Sparkles: "); 4 | console.trace(v); 5 | return ["js data"]; 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /bin/install-bb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | wget https://github.com/babashka/babashka/releases/download/v0.8.2/babashka-0.8.2-linux-amd64.tar.gz -O babashka.tgz && 4 | tar -zxf babashka.tgz && mv -vf bb bin && rm -vf babashka.tgz 5 | -------------------------------------------------------------------------------- /src/shimmers/common/ui/canvas_attributes.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.common.ui.canvas-attributes) 2 | 3 | (defmacro defattr 4 | [sym field] 5 | `(defn ~sym 6 | ([ctx#] (~field ctx#)) 7 | ([ctx# value#] (set! (~field ctx#) value#) ctx#))) 8 | -------------------------------------------------------------------------------- /src/shimmers/registry.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.registry 2 | "Registry of loaded sketches that can participate in routing/indexing and 3 | display.") 4 | 5 | (def sketches (atom {})) 6 | 7 | (defn add! [sketch] 8 | (swap! sketches assoc (:sketch-id sketch) sketch)) 9 | -------------------------------------------------------------------------------- /resources/public/shaders/physarum.vert.c: -------------------------------------------------------------------------------- 1 | attribute vec3 aPosition; 2 | attribute vec2 aTexCoord; 3 | 4 | varying vec2 vTexCoord; 5 | 6 | void main() { 7 | vTexCoord = aTexCoord; 8 | vec4 p = vec4(aPosition,1.0); 9 | p.xy = p.xy * 2.0 - 1.0; 10 | gl_Position = p; 11 | } 12 | -------------------------------------------------------------------------------- /resources/public/shaders/video-shader.vert.c: -------------------------------------------------------------------------------- 1 | attribute vec3 aPosition; 2 | attribute vec2 aTexCoord; 3 | 4 | varying vec2 vTexCoord; 5 | 6 | void main() { 7 | vTexCoord = aTexCoord; 8 | vec4 p = vec4(aPosition,1.0); 9 | p.xy = p.xy * 2.0 - 1.0; 10 | gl_Position = p; 11 | } 12 | -------------------------------------------------------------------------------- /resources/public/shaders/reaction-diffusion.vert.c: -------------------------------------------------------------------------------- 1 | attribute vec3 aPosition; 2 | attribute vec2 aTexCoord; 3 | 4 | varying vec2 vTexCoord; 5 | 6 | void main() { 7 | vTexCoord = aTexCoord; 8 | vec4 p = vec4(aPosition,1.0); 9 | p.xy = p.xy * 2.0 - 1.0; 10 | gl_Position = p; 11 | } 12 | -------------------------------------------------------------------------------- /src/shimmers/scratch/timing.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.scratch.timing) 2 | 3 | (comment 4 | (map (fn [x] (mod (+ (int (/ x 24)) (int (/ x 23))) 7)) (filter (fn [x] (= (mod x 8) 0)) (range 512))) 5 | (map (fn [x] [x (mod (int (/ x 29)) 7)]) 6 | (filter (fn [x] (= (mod x 16) (mod (int (/ x 64)) 3))) (range 512)))) 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | figwheel_server.log 11 | /.cpcache/ 12 | node_modules/ 13 | resources/public/js/ 14 | resources/public/shimmers.html 15 | .cljs_node_repl/ 16 | /out/ 17 | /bin/bb 18 | .clj-kondo/.cache/ 19 | static-site 20 | .shadow-cljs/ 21 | -------------------------------------------------------------------------------- /vendor/sparkles/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sparkles", 3 | "version": "0.0.1", 4 | "description": "fast js routines for shimmers", 5 | "_x_main": "./src/sparkles.js", 6 | "files": ["src/sparkles.js"], 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "Charles L.G. Comstock", 11 | "license": "AGPL-3.0" 12 | } 13 | -------------------------------------------------------------------------------- /src/shimmers/scratch/instaparse.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.scratch.instaparse 2 | (:require [instaparse.core :as insta])) 3 | 4 | ;; basic example from https://github.com/Engelberg/instaparse 5 | (def as-and-bs (insta/parser 6 | "S = AB* 7 | AB = A B 8 | A = 'a'+ 9 | B = 'b'+")) 10 | 11 | (comment 12 | (as-and-bs "aaabbaaabbab")) 13 | -------------------------------------------------------------------------------- /src/shimmers/common/transition_interval.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.common.transition-interval) 2 | 3 | (defprotocol ITransitionInterval 4 | (complete? [_ t]) 5 | (percent [_ t])) 6 | 7 | (defrecord TransitionInterval [base interval] 8 | ITransitionInterval 9 | (complete? [_ t] 10 | (>= t (+ base interval))) 11 | (percent [_ t] 12 | (/ (float (- t base)) interval))) 13 | 14 | (defn after [t interval] 15 | (->TransitionInterval t interval)) 16 | -------------------------------------------------------------------------------- /resources/data_readers.cljc: -------------------------------------------------------------------------------- 1 | ;; This is a convenience so that #v2, #v3 parse. However it does throw 2 | ;; warnings on `Use of undeclared Var cljs.user/v2` if they are not *also* 3 | ;; declared in `cljs.user`. 4 | 5 | ;; TODO: investigate warnings about: 6 | ;; [Figwheel:SEVERE] java.lang.RuntimeException: Can't embed object in code, maybe print-dup not defined: [D@5b94c677 7 | ;; when used at runtime. 8 | 9 | #_:clj-kondo/ignore 10 | {v2 thi.ng.geom.vector/vec2 11 | v3 thi.ng.geom.vector/vec3} 12 | -------------------------------------------------------------------------------- /src/shimmers/common/shader.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.common.shader 2 | (:require [quil.core :as q :include-macros true])) 3 | 4 | (defn pass [shader [w h] uniforms] 5 | (q/shader shader) 6 | (doseq [[key value] uniforms] 7 | (q/set-uniform shader key value)) 8 | (q/rect 0 0 w h)) 9 | 10 | (defn transform 11 | [shader buffer image [w h] uniforms] 12 | (when (q/loaded? shader) 13 | (q/with-graphics buffer 14 | (pass shader [w h] uniforms)) 15 | (q/with-graphics image 16 | (q/image buffer 0 0 w h)))) 17 | -------------------------------------------------------------------------------- /test/shimmers/math/equations_test.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.math.equations-test 2 | (:require 3 | [clojure.test :as t :refer [deftest is] :include-macros true] 4 | [shimmers.math.equations :as sut] 5 | [thi.ng.geom.vector :as gv] 6 | [thi.ng.math.core :as tm])) 7 | 8 | (deftest cos-similarity 9 | (is (tm/delta= 1.0 (sut/cos-similarity (gv/vec2 1 0) (gv/vec2 2 0)))) 10 | (is (tm/delta= -1.0 (sut/cos-similarity (gv/vec2 1 0) (gv/vec2 -1 0)))) 11 | (is (tm/delta= 0.0 (sut/cos-similarity (gv/vec2 1 0) (gv/vec2 0 1))))) 12 | -------------------------------------------------------------------------------- /src/shimmers/scratch/modulus.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.scratch.modulus) 2 | 3 | (comment 4 | (for [x (range 10)] 5 | [x (mod x 5) (mod (- 5 x) 5)]) 6 | 7 | (for [s (range 4) 8 | p (range 2 6)] 9 | [[s p] 10 | (mapv (fn [x] (mod (+ s (mod x p)) 8)) (range 8)) 11 | (mapv (fn [x] (mod (+ s (mod (- p 1 x) p)) 8)) (range 8))]) 12 | 13 | ;; updown cyclic 14 | (for [n [3 4 5]] 15 | (for [x (range 16)] 16 | [(mod x n) 17 | (let [m (mod x (- (* 2 n) 2))] 18 | (if (< m n) m (- (* 2 n) 2 m)))]))) 19 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((nil 2 | (cider-preferred-build-tool . clojure-cli) 3 | (cider-clojure-cli-aliases . "dev") 4 | (cider-default-cljs-repl . figwheel-main) 5 | (cider-figwheel-main-default-options . ":dev") 6 | (cider-repl-display-help-banner . nil) 7 | (eval . ((lambda () (when (not (featurep 'shimmers)) 8 | (let ((shimmers-file (expand-file-name "shimmers.el" default-directory))) 9 | (when (file-exists-p shimmers-file) 10 | (load shimmers-file) 11 | (require 'shimmers))))))))) 12 | -------------------------------------------------------------------------------- /.clj-kondo/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as 2 | {shimmers.sketch/defquil clj-kondo.lint-as/def-catch-all 3 | shimmers.sketch/definition clj-kondo.lint-as/def-catch-all 4 | 5 | shimmers.common.ui.canvas-attributes/defattr 6 | clj-kondo.lint-as/def-catch-all} 7 | 8 | :linters 9 | {:unresolved-symbol 10 | ;; c-for defines loop variables for a specific range 11 | {:exclude [(shimmers.macros.loop/c-for) 12 | (shimmers.common.ui.canvas-attributes/defattr)]} 13 | 14 | :unused-namespace 15 | {:exclude 16 | [shimmers.math.geometry.line ;; extend-type to Line2 17 | ]}}} 18 | -------------------------------------------------------------------------------- /test/shimmers/algorithm/linear_assignment_test.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.algorithm.linear-assignment-test 2 | (:require [shimmers.algorithm.linear-assignment :as sut] 3 | [clojure.test :as t :refer [deftest is] :include-macros true] 4 | [thi.ng.geom.vector :as gv])) 5 | 6 | (deftest greedy-assignment-match 7 | (is (= [[(gv/vec2 0 0) (gv/vec2 1 0)] 8 | [(gv/vec2 0 1) (gv/vec2 1 1)]] 9 | (sut/greedy-assignment-match < 10 | [(gv/vec2 0 0) (gv/vec2 0 1)] 11 | [(gv/vec2 1 0) (gv/vec2 1 1)])))) 12 | -------------------------------------------------------------------------------- /src/shimmers/ring_server.clj: -------------------------------------------------------------------------------- 1 | (ns shimmers.ring-server 2 | (:require 3 | [ring.util.response :refer [resource-response content-type not-found]])) 4 | 5 | ;; the routes that we want to be resolved to index.html 6 | (def index-routes [#"^/$" #"^/sketches"]) 7 | 8 | (defn handler [req] 9 | ;; (println "ring-handler: " (:uri req) (re-find #"^/sketches" (:uri req))) 10 | (or 11 | (when (some (fn [route] (re-find route (:uri req))) index-routes) 12 | (some-> (resource-response "index.html" {:root "public"}) 13 | (content-type "text/html; charset=utf-8"))) 14 | (not-found "Not found"))) 15 | -------------------------------------------------------------------------------- /src/shimmers/scratch/ndarray.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.scratch.ndarray 2 | "From https://github.com/thi-ng/ndarray/blob/master/src/index.org and 3 | https://github.com/thi-ng/ndarray/blob/master/src/contours.org." 4 | (:require [thi.ng.ndarray.core :as nd])) 5 | 6 | (comment 7 | (def a (nd/ndarray :float64 (range 9) [3 3])) 8 | (nd/set-at a 1 1 0) 9 | (seq a) 10 | ;; => (0 1 2 3 4 5 6 7 8) 11 | (seq (nd/step a -1 nil)) 12 | ;; => (6 7 8 3 4 5 0 1 2) 13 | (seq (nd/step a 1 nil)) 14 | ;; => (0 1 2 3 4 5 6 7 8) 15 | (seq (nd/step a -1 -1)) 16 | (-> a (nd/truncate-h 2 2) (nd/truncate-l 1 1) nd/index-seq)) 17 | -------------------------------------------------------------------------------- /src/shimmers/common/screen.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.common.screen 2 | (:require [clojure.edn :as edn])) 3 | 4 | (defn sizes [] 5 | (into {} 6 | (for [size ["800x600" 7 | "900x600" 8 | "1024x768" 9 | "1600x1200" 10 | "1920x1200" ;; costly if using a pixel copy 11 | "2560x1600"]] 12 | [size size]))) 13 | 14 | (defn parse-size [screen] 15 | (->> screen 16 | (re-seq #"(\d+)x(\d+)") 17 | first 18 | rest 19 | (map edn/read-string) 20 | vec)) 21 | 22 | (comment (parse-size "800x600")) 23 | -------------------------------------------------------------------------------- /src/shimmers/scratch/golden.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.scratch.golden 2 | (:require 3 | [clojure.math :as math] 4 | [thi.ng.math.core :as tm])) 5 | 6 | ;; Playing with golden ratio splits 7 | (defn golden [n] 8 | (drop 1 (map (fn [i] (/ 1.0 (math/pow tm/PHI i))) (range (inc n))))) 9 | 10 | (comment 11 | (golden 2) 12 | (golden 3) 13 | (golden 4)) 14 | 15 | (comment 16 | (map #(tm/mix-exp 1.0 32 % 12) (range 0 1 0.05)) 17 | (map #(tm/mix-circular-flipped 0.98 16 %) (range 0 1 0.05)) 18 | (map #(tm/mix-cosine 0.5 2 %) (range 0 1 0.05)) 19 | (map #(tm/mix-lens 0.5 0.1 % 1 100) (range 0 1 0.05)) 20 | (map #(tm/mix-bezier 0.9 3 % 1.5 0.1) (range 0 1 0.05))) 21 | -------------------------------------------------------------------------------- /test/shimmers/common/transition_interval_test.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.common.transition-interval-test 2 | (:require [clojure.test :as t :refer [deftest is] :include-macros true] 3 | [shimmers.common.transition-interval :as sut] 4 | [thi.ng.math.core :as tm])) 5 | 6 | (deftest transitions 7 | (is (not (sut/complete? (sut/after 0 1) 0))) 8 | (is (not (sut/complete? (sut/after 0 1) 0.5))) 9 | (is (sut/complete? (sut/after 0 1) 1)) 10 | 11 | (is (tm/delta= 0.0 (sut/percent (sut/after 0 10) 0))) 12 | (is (tm/delta= 0.1 (sut/percent (sut/after 0 10) 1))) 13 | (is (tm/delta= 1.0 (sut/percent (sut/after 0 10) 10))) 14 | (is (tm/delta= 1.1 (sut/percent (sut/after 0 10) 11)))) 15 | -------------------------------------------------------------------------------- /src/shimmers/scratch/rotations.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.scratch.rotations 2 | (:require 3 | [shimmers.math.vector :as v] 4 | [thi.ng.geom.core :as g] 5 | [thi.ng.geom.quaternion :as quat] 6 | [thi.ng.geom.vector :as gv] 7 | [thi.ng.math.core :as tm] 8 | [shimmers.math.equations :as eq])) 9 | 10 | (comment 11 | (for [t (tm/norm-range 10) 12 | :let [theta (* eq/TAU t) 13 | a (gv/vec2) 14 | b (v/polar 1.0 theta) 15 | c (g/rotate (tm/- b a) (/ eq/TAU 6)) 16 | axis (gv/vec3 (:xy (tm/- b a)))]] 17 | {:axis axis 18 | :quat (quat/quat-from-axis-angle axis tm/HALF_PI) 19 | :rot (g/rotate-around-axis (gv/vec3 (:xy c)) axis tm/HALF_PI)})) 20 | -------------------------------------------------------------------------------- /test/shimmers/math/control_test.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.math.control-test 2 | (:require 3 | [clojure.math :as math] 4 | [clojure.test :as t :refer [deftest is] :include-macros true] 5 | [shimmers.math.control :as sut] 6 | [shimmers.math.equations :as eq] 7 | [thi.ng.math.core :as tm])) 8 | 9 | (deftest angular-delta 10 | (is (tm/delta= 0.0 (sut/angular-delta 1.0 1.0))) 11 | (is (tm/delta= 1.0 (sut/angular-delta 0.0 1.0))) 12 | (is (tm/delta= math/PI (sut/angular-delta 0.0 math/PI))) 13 | (is (tm/delta= (- (dec math/PI)) (sut/angular-delta 0.0 (inc math/PI)))) 14 | (is (tm/delta= 1.0 (sut/angular-delta 0.0 (inc eq/TAU)))) 15 | (is (tm/delta= -1.0 (sut/angular-delta 0.0 (- (inc eq/TAU))))) 16 | (is (tm/delta= 1.0 (sut/angular-delta 0.0 (- (dec eq/TAU)))))) 17 | -------------------------------------------------------------------------------- /src/shimmers/math/stair.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.math.stair 2 | (:require 3 | [clojure.math :as math] 4 | [shimmers.math.equations :as eq] 5 | [thi.ng.math.core :as tm])) 6 | 7 | (defn stairs ^double [^double k ^double x] 8 | (- x (/ (math/sin (* x eq/TAU k)) (* 8 k)))) 9 | 10 | ;; If k is negative should it be forced positive with abs or fail as a precondition? 11 | (defn staircase 12 | "Density function to subdivide `x` in [0,1] from a linear distribution to `k` 13 | higher density regions." 14 | ^double [^double k ^double x] 15 | (let [base (tm/floor k)] 16 | (cond 17 | (< k 1.0) 18 | (tm/mix* x (stairs 1.0 x) k) 19 | (tm/delta= k base) 20 | (stairs k x) 21 | :else 22 | (tm/mix* (stairs base x) (stairs (+ 1 base) x) (tm/fract k))))) 23 | -------------------------------------------------------------------------------- /test/shimmers/math/geometry/points_test.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.math.geometry.points-test 2 | (:require [shimmers.math.geometry.points :as sut] 3 | [clojure.test :as t :refer [deftest is] :include-macros true])) 4 | 5 | (deftest points-delta= 6 | (is (sut/points-delta= [[(/ 1 3) 0.3] [0.1 0.1]] 7 | [[0.333333 0.3] [0.1 0.1]]) 8 | "equals vectors") 9 | (is (not (sut/points-delta= [] [])) 10 | "vectors contain points") 11 | (is (not (sut/points-delta= [[(/ 1 3) 0.3] [0.1 0.1] [0.5 0.5]] 12 | [[0.333333 0.3] [0.1 0.1]])) 13 | "extra args for first set") 14 | (is (not (sut/points-delta= [[(/ 1 3) 0.3] [0.1 0.1]] 15 | [[0.333333 0.3] [0.1 0.1] [0.5 0.5]])) 16 | "extra args for second set")) 17 | -------------------------------------------------------------------------------- /src/shimmers/common/video.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.common.video 2 | (:require [quil.core :as q :include-macros true] 3 | [quil.sketch])) 4 | 5 | ;; TODO: use faceMode constraints to add controls to flip between "user" 6 | ;; and "environment" for mobile use. See documentation @ 7 | ;; https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia 8 | ;; https://p5js.org/reference/#/p5/createCapture 9 | (defn capture [width height] 10 | (let [capture (.createCapture (quil.sketch/current-applet) "video")] 11 | (.size capture width height) 12 | (.hide capture) 13 | capture)) 14 | 15 | (defn copy-frame [dest capture width height] 16 | (if capture 17 | (do (q/copy capture dest 18 | [0 0 width height] 19 | [0 0 width height]) 20 | dest) 21 | dest)) 22 | -------------------------------------------------------------------------------- /src/shimmers/sketches/template/quil.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.template.quil 2 | (:require 3 | [quil.core :as q :include-macros true] 4 | [quil.middleware :as m] 5 | [shimmers.common.framerate :as framerate] 6 | [shimmers.common.ui.controls :as ctrl] 7 | [shimmers.sketch :as sketch :include-macros true])) 8 | 9 | (defn setup [] 10 | (q/color-mode :hsl 1.0) 11 | {}) 12 | 13 | (defn update-state [state] 14 | state) 15 | 16 | (defn draw [state] 17 | state) 18 | 19 | (defn page [] 20 | [:div 21 | (sketch/component 22 | :size [800 600] 23 | :setup setup 24 | :update update-state 25 | :draw draw 26 | :middleware [m/fun-mode framerate/mode])]) 27 | 28 | (sketch/definition template-quil 29 | {:created-at "2025-" 30 | :tags #{} 31 | :type :quil} 32 | (ctrl/mount page)) 33 | -------------------------------------------------------------------------------- /src/shimmers/scratch/mixing.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.scratch.mixing 2 | (:require 3 | [clojure.math :as math] 4 | [thi.ng.geom.vector :as gv] 5 | [thi.ng.math.core :as tm])) 6 | 7 | (defn inv-mix [a b v] 8 | (/ (- v a) (- b a))) 9 | 10 | (defn remap [ia ib oa ob v] 11 | (tm/mix* oa ob (inv-mix ia ib v))) 12 | 13 | (comment 14 | (for [t (tm/norm-range 5)] 15 | (tm/mix-with (gv/vec2 0 0) (gv/vec2 -1 1) t tm/mix*)) 16 | 17 | (for [x (range 9 16 0.5)] 18 | (inv-mix 10 15 x)) 19 | 20 | (for [x (range 2 10 0.5)] 21 | [x (remap 4 8 10 20 x)])) 22 | 23 | ;; some easing examples 24 | (comment 25 | (map (fn [x] [x (math/pow (+ 1 x) 3)]) (range 0 1 0.1)) 26 | (map (fn [x] [x (math/pow x 4)]) (range 0 1 0.1)) 27 | (map (fn [x] [x (math/pow 4 x)]) (range 0 1 0.1)) 28 | (map (fn [x] [x (math/pow 4 (+ 1 x))]) (range 0 1 0.1))) 29 | -------------------------------------------------------------------------------- /src/shimmers/math/points.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.math.points 2 | (:require 3 | [shimmers.common.sequence :as cs] 4 | [thi.ng.geom.core :as g] 5 | [thi.ng.geom.vector :as gv])) 6 | 7 | (defn generate 8 | "Generate point 2d points in space" 9 | ([n dist] 10 | (generate n dist dist)) 11 | ([n dist-x dist-y] (repeatedly n #(gv/vec2 (dist-x) (dist-y))))) 12 | 13 | (defn minimum-separation 14 | "Only keep `points` if closest pair distance is greather than `threshold`." 15 | [threshold points] 16 | (reduce (fn [accepted p] 17 | (if (every? #(> (g/dist p %) threshold) accepted) 18 | (conj accepted p) 19 | accepted)) 20 | [] points)) 21 | 22 | (defn ranked-pairs [points] 23 | (->> (for [[u v] (cs/all-pairs points)] 24 | [(g/dist u v) [u v]]) 25 | (sort-by first) 26 | (map second))) 27 | -------------------------------------------------------------------------------- /src/shimmers/common/particle_system.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.common.particle-system 2 | (:require 3 | [quil.core :as q :include-macros true] 4 | [shimmers.math.vector :as v])) 5 | 6 | (defn step [{:keys [position velocity acceleration] :as particle}] 7 | (let [new-velocity (v/add velocity acceleration) 8 | new-position (v/add position new-velocity)] 9 | (assoc particle 10 | :last-pos position 11 | :position new-position 12 | :velocity new-velocity 13 | :acceleration acceleration))) 14 | 15 | (defn draw [particles & {:keys [weight]}] 16 | (doseq [{:keys [position last-pos color] :as particle} particles] 17 | (let [[lx ly] last-pos 18 | [x y] position] 19 | (when color 20 | (apply q/stroke color)) 21 | (when weight 22 | (q/stroke-weight (weight particle))) 23 | (q/line lx ly x y)))) 24 | 25 | -------------------------------------------------------------------------------- /src/shimmers/scratch/ellipse.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.scratch.ellipse 2 | "Experiments in clipping part of an ellipse into a bounded-box." 3 | (:require 4 | [clojure.math :as math] 5 | [shimmers.math.equations :as eq] 6 | [thi.ng.geom.core :as g] 7 | [thi.ng.geom.rect :as rect] 8 | [thi.ng.geom.vector :as gv] 9 | [thi.ng.math.core :as tm])) 10 | 11 | (defn clockwise-intercept [center bounds] 12 | (let [midpoints (map (fn [[p q]] (tm/mix p q 0.5)) (g/edges bounds))] 13 | (apply min (map #(g/heading (tm/- % center)) midpoints)))) 14 | 15 | (comment (clockwise-intercept (gv/vec2 -5 11) (rect/rect 10))) 16 | 17 | (defn ellipse-arc [center a b intercept dt] 18 | (for [t (range intercept (+ intercept eq/TAU) dt)] 19 | (tm/+ center (gv/vec2 (* a (math/cos t)) 20 | (* b (math/sin t)))))) 21 | 22 | (comment (ellipse-arc (gv/vec2 1 0) 10 10 0 0.1)) 23 | 24 | -------------------------------------------------------------------------------- /src/shimmers/sketches/template/svg.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.template.svg 2 | (:require 3 | [shimmers.common.svg :as csvg :include-macros true] 4 | [shimmers.common.ui.controls :as ctrl] 5 | [shimmers.common.ui.svg :as usvg] 6 | [shimmers.sketch :as sketch :include-macros true] 7 | [thi.ng.geom.vector :as gv])) 8 | 9 | (def width 800) 10 | (def height 600) 11 | (defn rv [x y] 12 | (gv/vec2 (* width x) (* height y))) 13 | 14 | (defn shapes [] 15 | []) 16 | 17 | (defn scene [{:keys [scene-id]}] 18 | (csvg/svg-timed {:id scene-id 19 | :width width 20 | :height height 21 | :stroke "black" 22 | :fill "white" 23 | :stroke-width 0.5} 24 | (shapes))) 25 | 26 | (sketch/definition template-svg 27 | {:created-at "2025-" 28 | :tags #{} 29 | :type :svg} 30 | (ctrl/mount (usvg/page sketch-args scene))) 31 | -------------------------------------------------------------------------------- /src/shimmers/math/geometry/points.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.math.geometry.points 2 | (:require 3 | [thi.ng.geom.vector :as gv] 4 | [thi.ng.math.core :as tm] 5 | [thi.ng.strf.core :as f])) 6 | 7 | (defn points-delta= 8 | ([as bs] (points-delta= as bs tm/*eps*)) 9 | ([as bs eps] 10 | (let [n (count as)] 11 | (and (pos? n) (= n (count bs)) 12 | (every? true? (map (fn [a b] (tm/delta= a b eps)) 13 | as bs)))))) 14 | 15 | ;; FIXME: use a defmulti or protocol 16 | (defn as-str 17 | ([point] 18 | (as-str 2 point)) 19 | ([decimals point] 20 | (apply f/format 21 | (interpose "," (repeat (count point) (f/float decimals))) 22 | point))) 23 | 24 | (comment (as-str (gv/vec2 0.3 0.4)) 25 | (as-str (gv/vec3 (/ 1 3) 0.3 0.4))) 26 | 27 | (defn midpoint 28 | ([[p q]] 29 | (midpoint p q)) 30 | ([p q] 31 | (tm/mix p q 0.5))) 32 | 33 | (comment (midpoint (gv/vec2) (gv/vec2 1 1))) 34 | -------------------------------------------------------------------------------- /bb.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | :tasks 3 | {:requires ([babashka.fs :as fs] 4 | [bb.tasks :as t]) 5 | clean 6 | {:task (do (println "Remove target directory") 7 | (fs/delete-tree "target"))} 8 | compile 9 | {:depends [clean] 10 | :task (do (shell "npm install") 11 | (clojure "-Mdev -m figwheel.main -bo release"))} 12 | create-static-site 13 | {:task (t/build-static-site 14 | :from "resources/public" 15 | :to "static-site")} 16 | rewrite-index 17 | {:task (t/rewrite-index 18 | :from "resources/public" 19 | :to "static-site" 20 | :index-file "index.html" 21 | :base-href "https://dgtized.github.io/shimmers/")} 22 | ;; For routing to page handler after not using fragments 23 | ;; ln -sf index.html 404.html 24 | build 25 | {:depends [compile create-static-site rewrite-index]} 26 | 27 | view 28 | {:task (shell "xdg-open static-site/index.html")}}} 29 | -------------------------------------------------------------------------------- /src/shimmers/sketches/template/canvas.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.template.canvas 2 | (:require 3 | [shimmers.common.ui.canvas :as canvas] 4 | [shimmers.common.ui.controls :as ctrl] 5 | [shimmers.sketch :as sketch :include-macros true])) 6 | 7 | (defn setup [_cv] 8 | {}) 9 | 10 | (defn update-state [state _dims _ms] 11 | state) 12 | 13 | (defn draw [_cv ctx [_width _height] ms] 14 | (let [_t (* 0.001 ms)] 15 | ctx)) 16 | 17 | (defn page [] 18 | (let [{:keys [canvas-state attributes]} 19 | (canvas/make-state 20 | {:width 800 21 | :height 600 22 | :setup #'setup 23 | :update #'update-state 24 | :draw #'draw} 25 | {:width-pct 0.7})] 26 | (fn [] 27 | [:div 28 | [canvas/canvas-frame attributes canvas-state canvas/animate-frame]]))) 29 | 30 | (sketch/definition template-canvas 31 | {:created-at "2025-" 32 | :tags #{} 33 | :type :canvas} 34 | (ctrl/mount (page))) 35 | -------------------------------------------------------------------------------- /resources/public/shaders/integer-circles.frag.c: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision mediump float; 3 | #endif 4 | 5 | uniform vec2 u_resolution; 6 | uniform float u_time; 7 | uniform float u_d; 8 | uniform float u_e; 9 | 10 | int max_iterations = 2048; 11 | 12 | // derived from https://www.shadertoy.com/view/4lSGRG 13 | int circle_iterations(float x0, float y0, float d, float e) { 14 | float x = x0; 15 | float y = y0; 16 | 17 | for(int iter = 0; iter < 2048; iter++) { 18 | x = x - floor(d*y); 19 | y = y + floor(e*x); 20 | 21 | if((floor(x) == floor(x0)) && floor(y) == floor(y0)) { 22 | return iter; 23 | } 24 | } 25 | return max_iterations; 26 | } 27 | 28 | void main() { 29 | vec2 st = floor(gl_FragCoord.xy - u_resolution); 30 | float d=u_d; 31 | float e=u_e; 32 | float iterations = float(circle_iterations(st.x, st.y, d, e)); 33 | float grey = log(iterations)/log(float(max_iterations)); 34 | gl_FragColor = vec4(grey,grey,grey,1.0); 35 | } 36 | -------------------------------------------------------------------------------- /test/shimmers/algorithm/minimum_spanning_tree_test.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.algorithm.minimum-spanning-tree-test 2 | (:require 3 | [clojure.test :as t :refer [deftest is] :include-macros true] 4 | [shimmers.algorithm.minimum-spanning-tree :as sut] 5 | [thi.ng.geom.vector :as gv])) 6 | 7 | (deftest kruskals 8 | (is (= [[:a :b] [:b :c] [:c :d]] 9 | (sut/kruskal [:a :b :c :d] [[:a :b] [:b :c] [:c :d] [:b :d]]))) 10 | (let [[a b c d] (map gv/vec2 [[0 0] [0 1] [0 3] [1 0]])] 11 | (is (= [[a b] [a d] [b c]] 12 | (sut/kruskal-points [a b c d]))))) 13 | 14 | (deftest prims 15 | (let [dists {[:a :b] 1 16 | [:a :c] 3 17 | [:c :b] 2 18 | [:c :d] 3}] 19 | (is (= [[:a :b] [:b :c] [:c :d]] 20 | (sut/prim (fn [a b] (or (get dists [a b]) (get dists [b a]) 1000)) 21 | [:a :b :c :d])))) 22 | (let [[a b c d] (map gv/vec2 [[0 0] [0 1] [0 3] [2 0]])] 23 | (is (= [[a b] [a d] [b c]] 24 | (sut/prim-points [a b c d]))))) 25 | -------------------------------------------------------------------------------- /src/shimmers/math/kinematics.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.math.kinematics 2 | (:require 3 | [clojure.math :as math] 4 | [shimmers.math.equations :as eq])) 5 | 6 | ;; https://www.youtube.com/watch?v=v1V3T5BPd7E 7 | ;; suvat equations 8 | ;; s : displacement 9 | ;; u : initial velocity aka v_0 10 | ;; v : final velocity aka v_f 11 | ;; a : acceleration 12 | ;; t : time 13 | 14 | ;; Limitations: only works for constant acceleration in one axis 15 | 16 | ;; s = (u + v)/2 * t 17 | (defn displacement-from-velocity-change [u v t] 18 | (* (/ (+ u v) 2) t)) 19 | 20 | ;; v = u + at 21 | (defn velocity-from-acceleration [u a t] 22 | (+ u (* a t))) 23 | 24 | ;; s = ut + (at^2)/2 25 | (defn displacement-from-initial-velocity [u a t] 26 | (+ (* u t) (/ (* a (eq/sqr t)) 2))) 27 | 28 | ;; s = vt - at^2 29 | (defn displacement-from-final-velocity [v a t] 30 | (- (* v t) (/ (* a (eq/sqr t)) 2))) 31 | 32 | ;; v^2 = u^2 + 2as 33 | ;; or v = sqrt(u^2+2as) 34 | (defn velocity-from-displacement [u a s] 35 | (math/sqrt (+ (eq/sqr u) (* 2 a s)))) 36 | -------------------------------------------------------------------------------- /src/shimmers/algorithm/chaikin.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.algorithm.chaikin 2 | "Translation of https://sighack.com/post/chaikin-curves. 3 | See also https://www.bit-101.com/blog/2021/08/chaikins-algorithm-drawing-curves/." 4 | (:require 5 | [shimmers.common.sequence :as cs] 6 | [thi.ng.math.core :as tm])) 7 | 8 | (defn cut [a b ratio] 9 | (let [ratio (if (> ratio 0.5) (- 1.0 ratio) ratio)] 10 | [(tm/mix a b ratio) 11 | (tm/mix b a ratio)])) 12 | 13 | (defn chaikin-open [points ratio] 14 | (let [r (mapcat (fn [[a b]] (cut a b ratio)) 15 | (partition 2 1 points))] 16 | (concat (take 1 points) 17 | (drop-last (rest r)) 18 | (take-last 1 points)))) 19 | 20 | (defn chaikin-closed [points ratio] 21 | (mapcat (fn [[a b]] (cut a b ratio)) 22 | (partition 2 1 (concat points (take 1 points))))) 23 | 24 | (defn chaikin [ratio closed iterations points] 25 | (let [approach (if closed chaikin-closed chaikin-open)] 26 | (cs/iterate-cycles iterations (fn [pts] (approach pts ratio)) points))) 27 | -------------------------------------------------------------------------------- /test/shimmers/algorithm/rtree_test.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.algorithm.rtree-test 2 | (:require 3 | [clojure.set :as set] 4 | [clojure.test :as t :refer [deftest is] :include-macros true] 5 | [shimmers.algorithm.rtree :as sut] 6 | [thi.ng.geom.circle :as gc] 7 | [thi.ng.geom.core :as g] 8 | [thi.ng.geom.rect :as rect] 9 | [thi.ng.geom.utils :as gu])) 10 | 11 | (deftest creation 12 | (is (nil? (sut/create []))) 13 | (let [circles (repeatedly 10 #(gc/circle (rand-int 100) (rand-int 100) (rand-int 10))) 14 | bounds (gu/coll-bounds circles) 15 | tree (sut/create circles) 16 | search (sut/search-intersection tree bounds) 17 | example (first circles)] 18 | (is (= (set circles) (set search))) 19 | (is (= (set circles) (set (sut/search-intersection tree (rect/rect 0 0 100 100))))) 20 | (is (set/subset? (set [example]) (set (sut/search-intersection tree (g/bounds example))))))) 21 | 22 | (comment (t/run-tests)) 23 | 24 | (comment (sut/create (repeatedly 30 #(gc/circle (rand-int 100) (rand-int 100) 1)))) 25 | -------------------------------------------------------------------------------- /test/shimmers/math/stair_test.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.math.stair-test 2 | (:require 3 | [clojure.test :as t :refer [deftest is] :include-macros true] 4 | [shimmers.math.stair :as ms] 5 | [thi.ng.math.core :as tm])) 6 | 7 | (deftest staircase 8 | (is (tm/delta= 9 | (map (fn [x] (ms/staircase 1.0 x)) (tm/norm-range 10.0)) 10 | [0.0 0.026526843463440863 0.08111793546310582 0.18111793546310578 0.32652684346344085 11 | 0.5 0.6734731565365591 0.8188820645368942 0.9188820645368942 0.9734731565365592 1.0])) 12 | (is (tm/delta= 13 | (map (fn [x] (ms/staircase 2.0 x)) (tm/norm-range 16.0)) 14 | [0.0 0.018305826175840784 0.0625 0.14330582617584078 0.25 0.35669417382415924 0.4375 0.48169417382415924 15 | 0.5 0.5183058261758408 0.5625 0.6433058261758408 0.75 0.8566941738241592 0.9375 0.9816941738241592 1.0])) 16 | (is (tm/delta= 17 | (map (fn [x] (ms/staircase 4.0 x)) (tm/norm-range 16.0)) 18 | [0.0 0.03125 0.125 0.21875 0.25 0.28125 0.375 0.46875 0.5 19 | 0.53125 0.625 0.71875 0.75 0.78125 0.875 0.96875 1.0]))) 20 | -------------------------------------------------------------------------------- /resources/delaunator-externs.js: -------------------------------------------------------------------------------- 1 | /* Attempting to use 2 | * https://developers.google.com/closure/compiler/docs/externs-and-exports to 3 | * force Closure compiler to recognize triangles/halfedges as properties on 4 | * Delaunator after advanced compilation. 5 | */ 6 | var Delaunator = class { 7 | constructor() { 8 | /** @type {Float64Array} */ 9 | this.coords; 10 | /** @type {Number} */ 11 | this.trianglesLen; 12 | /** @type {Uint32Array} */ 13 | this.triangles; 14 | /** @type {Uint32Array} */ 15 | this.halfedges; 16 | /** @type {Uint32Array} */ 17 | this.hull; 18 | } 19 | }; 20 | 21 | /** @return {Delaunator} */ 22 | Delaunator.from = function () {}; 23 | 24 | Delaunator.prototype = { 25 | "coords": function() {}, 26 | "triangles": function() {}, 27 | "trianglesLen": function() {}, 28 | "halfedges": function() {}, 29 | "hull": function() {}, 30 | 31 | "update": function() {}, 32 | "_addTriangle": function () {}, 33 | "_hashKey": function () {}, 34 | "_legalize": function () {}, 35 | "_link": function () {} 36 | }; 37 | -------------------------------------------------------------------------------- /test/shimmers/figwheel_runner.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:figwheel-hooks shimmers.figwheel-runner 2 | (:require 3 | [cljs-test-display.core :as td] 4 | [figwheel.main.testing :refer-macros [run-tests]] 5 | [fipp.edn :as fedn] 6 | ;; load namespaces to test 7 | [shimmers.test-namespaces])) 8 | 9 | (enable-console-print!) 10 | 11 | (defn prettier [content] 12 | (td/n :pre {} 13 | (td/n :code {} (with-out-str (fedn/pprint content {:width 80}))))) 14 | 15 | ;; modified from https://github.com/bhauman/cljs-test-display/issues/5#issuecomment-619090019 16 | ;; pretty print expected vs actual with fedn/pprint 17 | (set! td/comparison 18 | (fn comparison [{:keys [expected actual]}] 19 | (td/div 20 | (prettier expected) 21 | (td/div :actual (td/div :arrow "▶") 22 | (prettier actual))))) 23 | 24 | ;; to view, visit http://localhost:9500/figwheel-extra-main/tests 25 | (defn test-run [] 26 | (run-tests (cljs-test-display.core/init! "app-tests"))) 27 | 28 | (defn ^:after-load render-on-reload [] 29 | (test-run)) 30 | 31 | (test-run) 32 | -------------------------------------------------------------------------------- /src/shimmers/model/harmonics.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.model.harmonics 2 | (:require 3 | [clojure.math :as math] 4 | [clojure.math.combinatorics :as mc] 5 | [shimmers.math.core :as sm] 6 | [shimmers.math.deterministic-random :as dr] 7 | [thi.ng.geom.vector :as gv] 8 | [thi.ng.math.core :as tm])) 9 | 10 | (defn abc [] 11 | (let [a (dr/weighted-by #(math/pow 0.8 %) (range 1 11)) 12 | b (dr/weighted-by #(sm/lcm % a) (range 1 11)) 13 | lcm (sm/lcm a b) 14 | gcd (sm/gcd a b) 15 | c (dr/weighted 16 | [[(* (min a b) (dr/random-int 1 6)) 2.0] 17 | [lcm (if (> lcm 1.0) 1.0 0.0)] 18 | [gcd (if (> gcd 1.0) 1.0 0.0)] 19 | [(dr/random-int 1 10) 1.0]])] 20 | (tm/* (gv/vec3 a b c) 21 | (gv/vec3 (dr/rand-nth (mc/selections [-1 1] 3)))))) 22 | 23 | (comment 24 | (map (fn [x] (math/pow 0.90 x)) (range 15)) 25 | (map (fn [x] (sm/gcd 3 x)) (range 20)) 26 | (map (fn [x] (sm/lcm 3 x)) (range 20)) 27 | 28 | (dr/summary-stats (repeatedly 100 #(dr/weighted-by (fn [x] (math/pow 0.8 x)) (range 1 11))))) 29 | -------------------------------------------------------------------------------- /src/shimmers/math/interval.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.math.interval) 2 | 3 | ;; experimenting with some interval math helpers 4 | 5 | (defn min-range 6 | [[a0 _] [b0 _]] 7 | (min a0 b0)) 8 | 9 | (defn max-range 10 | [[_ a1] [_ b1]] 11 | (max a1 b1)) 12 | 13 | (defn min-max-cover [a b] 14 | [(min-range a b) (max-range a b)]) 15 | 16 | (defn overlap? 17 | "Check if range a0:a1 overlaps range b0:b1 on the number line." 18 | ([[a0 a1] [b0 b1]] 19 | (overlap? a0 a1 b0 b1)) 20 | ([a0 a1 b0 b1] 21 | (and (<= a0 b1) (<= b0 a1)))) 22 | 23 | (defn intersection 24 | ([[a0 a1] [b0 b1]] 25 | (intersection a0 a1 b0 b1)) 26 | ([a0 a1 b0 b1] 27 | (cond (<= a0 b0 b1 a1) 28 | [b0 b1] 29 | (<= b0 a0 a1 b1) 30 | [a0 a1] 31 | (<= a0 b0 a1 b1) 32 | [b0 a1] 33 | (<= b0 a0 b1 a1) 34 | [a0 b1]))) 35 | 36 | (defn overlap-range 37 | ([[a0 a1] [b0 b1]] 38 | (overlap-range a0 a1 b0 b1)) 39 | ([a0 a1 b0 b1] 40 | (when-let [hit (intersection a0 a1 b0 b1)] 41 | (let [[a b] hit] 42 | (when (< a b) 43 | [a b]))))) 44 | -------------------------------------------------------------------------------- /src/shimmers/common/ui.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.common.ui 2 | (:require [clojure.string :as str])) 3 | 4 | ;; https://coderwall.com/p/s3j4va/google-analytics-tracking-in-clojurescript 5 | ;; Bypasses externs by grabbing gtag function from window directly and then 6 | ;; converting arguments to js array 7 | (defn gtag [& args] 8 | (when js/gtag 9 | (.. (aget js/window "gtag") 10 | (apply nil (clj->js args))))) 11 | 12 | (defn screen-view [sketch-name] 13 | (gtag "event" "screen_view" 14 | {"app_name" "shimmers" 15 | "screen_name" sketch-name})) 16 | 17 | (defn code-link [sketch] 18 | (if-let [{:keys [file line]} sketch] 19 | {:filename (last (str/split file #"/")) 20 | :href 21 | (-> file 22 | ;; file can either be relative from src or an absolute path 23 | (str/replace-first #"^.*src/" 24 | "https://github.com/dgtized/shimmers/blob/master/src/") 25 | (str "#L" line))} 26 | {:filename "" :href ""})) 27 | 28 | (comment 29 | (require '[shimmers.sketches :as sketches]) 30 | (map code-link (take 2 (sketches/all)))) 31 | -------------------------------------------------------------------------------- /src/shimmers/common/ui/svg.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.common.ui.svg 2 | (:require 3 | [reagent-keybindings.keyboard :as kb] 4 | [shimmers.common.svg-export :as svg-export] 5 | [shimmers.sketch :as sketch] 6 | [shimmers.view.sketch :as view-sketch])) 7 | 8 | (defn download-shortcut [id filename] 9 | [kb/kb-action "alt-s" 10 | (fn [] (svg-export/download id filename))]) 11 | 12 | (defn page 13 | ([sketch-args explanation scene] 14 | (page (assoc sketch-args :explanation explanation) scene)) 15 | ([{:keys [sketch-id explanation explanation-div] 16 | :as sketch-args} 17 | scene] 18 | (fn [] 19 | (let [default-args {:scene-id "scene"} 20 | {:keys [scene-id] :as args} (merge default-args sketch-args)] 21 | [sketch/with-explanation 22 | [:div.canvas-frame 23 | [scene args] 24 | [download-shortcut scene-id (name sketch-id)]] 25 | (into (or explanation-div [:div]) 26 | [[:p.center [view-sketch/generate sketch-id]] 27 | (when explanation 28 | [:div.readable-width 29 | [explanation sketch-args]])])])))) 30 | -------------------------------------------------------------------------------- /test/shimmers/algorithm/space_colonization_test.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.algorithm.space-colonization-test 2 | (:require 3 | [clojure.test :as t :refer [deftest is] :include-macros true] 4 | [shimmers.algorithm.space-colonization :as sut] 5 | [shimmers.math.equations :as eq] 6 | [thi.ng.geom.vector :as gv])) 7 | 8 | (deftest closest 9 | (is (= {:position (gv/vec2 2 1)} 10 | (sut/closest-branch (gv/vec2 1 1) 11 | [{:position (gv/vec2 3 3)} 12 | {:position (gv/vec2 2 2)} 13 | {:position (gv/vec2 2 1)}])))) 14 | 15 | (deftest average-attraction 16 | (is (= (gv/vec2 eq/SQRT2_2 eq/SQRT2_2) 17 | (sut/average-attraction (sut/->Branch nil (gv/vec2)) 18 | [(gv/vec2 2 2) (gv/vec2 2 2)] 19 | 0 (gv/vec2)))) 20 | (let [branch (sut/->Branch nil (gv/vec2)) 21 | attractors [(gv/vec2 1 0) (gv/vec2 0 -1) (gv/vec2 [1 -1])]] 22 | (is (= (gv/vec2 eq/SQRT2_2 (- eq/SQRT2_2)) 23 | (sut/average-attraction branch attractors 0 (gv/vec2)))))) 24 | 25 | (comment (t/run-tests)) 26 | -------------------------------------------------------------------------------- /test/shimmers/math/interval_test.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.math.interval-test 2 | (:require 3 | [clojure.test :as t :refer [deftest is] :include-macros true] 4 | [shimmers.math.interval :as sut])) 5 | 6 | (deftest overlap? 7 | (is (not (sut/overlap? 1 2 3 4)) "no overlap") 8 | (is (sut/overlap? 1 3 3 4) "lower touch") 9 | (is (sut/overlap? 1 3 2 5) "lower overlap") 10 | (is (sut/overlap? 2 5 5 6) "upper touch") 11 | (is (sut/overlap? 2 5 4 6) "upper overlap") 12 | (is (sut/overlap? 3 4 1 6) "cover first") 13 | (is (sut/overlap? 2 5 3 4) "cover second")) 14 | 15 | (deftest intersection 16 | (is (nil? (sut/intersection 0 1 2 3)) "no overlap") 17 | (is (= [1 1] (sut/intersection 0 1 1 3)) "lower touch") 18 | (is (= [1 2] (sut/intersection 0 2 1 3)) "lower overlap") 19 | (is (= [1 2] (sut/intersection 0 2 1 2)) "lower cover/touch") 20 | (is (= [1 2] (sut/intersection 0 3 1 2)) "lower cover") 21 | (is (= [3 3] (sut/intersection -1 3 3 4)) "upper touch") 22 | (is (= [3 3] (sut/intersection 3 4 2 3)) "upper touch") 23 | (is (= [-1 1] (sut/intersection -1 3 -2 1)) "upper overlap") 24 | (is (= [1 2] (sut/intersection 1 2 0 3)) "upper cover")) 25 | -------------------------------------------------------------------------------- /src/shimmers/algorithm/random_graph.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.algorithm.random-graph 2 | (:require 3 | [clojure.set :as set] 4 | [loom.alg :as la] 5 | [loom.graph :as lg] 6 | #?(:cljs 7 | [shimmers.algorithm.delaunator :as deltor]) 8 | [shimmers.common.sequence :as cs] 9 | [shimmers.math.deterministic-random :as dr] 10 | [shimmers.math.graph :as graph] 11 | [thi.ng.geom.core :as g])) 12 | 13 | (defn planar [points] 14 | (let [all-edges (cs/all-pairs points) 15 | mst (la/prim-mst (graph/edges->graph all-edges)) 16 | mst-edges (set (lg/edges mst)) 17 | possible-edges (set/difference (set all-edges) mst-edges)] 18 | (reduce (fn [g [a b]] 19 | (if (and (or (< (min (lg/out-degree g a) (lg/out-degree g b)) 2) 20 | (dr/chance 0.2)) 21 | (graph/planar-edge? g a b)) 22 | (lg/add-edges g [a b (g/dist a b)]) 23 | g)) 24 | mst 25 | (dr/shuffle possible-edges)))) 26 | 27 | #?(:cljs 28 | (defn voronoi [points] 29 | (let [edges (deltor/triangle-edges points)] 30 | (graph/edges->labeled-graph edges)))) 31 | -------------------------------------------------------------------------------- /src/shimmers/sketches/shattered.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.shattered 2 | (:require 3 | [shimmers.common.svg :as csvg] 4 | [shimmers.common.ui.controls :as ctrl] 5 | [shimmers.math.geometry :as geometry] 6 | [shimmers.sketch :as sketch :include-macros true] 7 | [shimmers.view.sketch :as view-sketch] 8 | [thi.ng.geom.rect :as rect] 9 | [thi.ng.geom.vector :as gv])) 10 | 11 | (def width 800) 12 | (def height 600) 13 | (defn rv [x y] 14 | (gv/vec2 (* width x) (* height y))) 15 | 16 | (defn shapes [] 17 | (mapcat geometry/shatter 18 | [(rect/rect (rv 0.05 0.05) (rv 0.45 0.85)) 19 | (rect/rect (rv 0.55 0.15) (rv 0.95 0.95))] 20 | [{:n 100 :mode :midpoint} 21 | {:n 100 :mode :trisect}])) 22 | 23 | (defn scene [] 24 | (csvg/svg-timed {:width width 25 | :height height 26 | :stroke "black" 27 | :fill "white" 28 | :stroke-width 0.5} 29 | (shapes))) 30 | 31 | (sketch/definition shattered 32 | {:created-at "2022-01-01" 33 | :type :svg 34 | :tags #{:deterministic}} 35 | (ctrl/mount (view-sketch/static-page scene :shattered))) 36 | -------------------------------------------------------------------------------- /resources/d3-delaunay-externs.js: -------------------------------------------------------------------------------- 1 | var Delaunay = class { 2 | constructor() { 3 | this.inedges; 4 | this.points; 5 | this.triangles; 6 | this.halfedges; 7 | this.hull; 8 | } 9 | 10 | update() {} 11 | /** @return {Voronoi} */ 12 | voronoi() {} 13 | neighbors() {} 14 | find() {} 15 | render() {} 16 | renderPoints() {} 17 | renderHull() {} 18 | hullPolygon() {} 19 | renderTriangles() {} 20 | trianglePolygons() {} 21 | trianglePolygon() {} 22 | }; 23 | /** @return {Delaunay} */ 24 | Delaunay.from = function() {}; 25 | 26 | var Path = class { 27 | constructor() {} 28 | 29 | moveTo() {} 30 | closePath() {} 31 | lineTo() {} 32 | arc() {} 33 | rect() {} 34 | value() {} 35 | }; 36 | 37 | var Polygon = class { 38 | constructor() {} 39 | 40 | moveTo() {} 41 | closePath() {} 42 | lineTo() {} 43 | value() {} 44 | }; 45 | 46 | var Voronoi = class { 47 | constructor() { 48 | this.delaunay; 49 | this.vectors; 50 | this.circumcenters; 51 | this.xmax; 52 | this.ymax; 53 | } 54 | 55 | update() {} 56 | render() {} 57 | renderBounds() {} 58 | renderCell() {} 59 | cellPolygons() {} 60 | cellPolygon() {} 61 | contains() {} 62 | neighbors() {} 63 | }; 64 | -------------------------------------------------------------------------------- /src/shimmers/common/framerate.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.common.framerate 2 | (:require 3 | [goog.dom :as dom] 4 | [quil.core :as q] 5 | [shimmers.common.string :as scs])) 6 | 7 | (defn display [host value] 8 | (let [rate (cond (= value "") "" 9 | (= value 0) "" 10 | :else (scs/cl-format "~1,2$ fps" value))] 11 | (when-let [node (dom/getElement host)] 12 | (dom/setTextContent node rate)))) 13 | 14 | (defn mode 15 | "Quil Middleware to update framerate" 16 | [options] 17 | (let [draw (:draw options (fn [_])) 18 | host (get options :performance-id "framerate") 19 | timed-draw (fn timed-draw [& args] 20 | (apply draw args) 21 | (display host (q/current-frame-rate)))] 22 | (assoc options :draw timed-draw))) 23 | 24 | (defn sampler 25 | ([] (sampler {})) 26 | ([{:keys [host] :or {host "framerate"}}] 27 | (let [frame-rate (atom {:frames '(1) :t 0})] 28 | (fn [t] 29 | (let [{lt :t :keys [frames]} @frame-rate 30 | rate (* 1000 (/ (count frames) (apply + frames)))] 31 | (swap! frame-rate assoc :t t :frames (conj (take 4 frames) (- t lt))) 32 | (display host rate) 33 | rate))))) 34 | -------------------------------------------------------------------------------- /test/shimmers/algorithm/quadtree_test.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.algorithm.quadtree-test 2 | (:require 3 | [clojure.test :as t :refer [deftest is] :include-macros true] 4 | [shimmers.algorithm.quadtree :as sut] 5 | [thi.ng.geom.circle :as gc] 6 | [thi.ng.geom.core :as g] 7 | [thi.ng.geom.spatialtree :as spatialtree])) 8 | 9 | (deftest deletion 10 | (let [c1 (gc/circle 0 0 1) 11 | c1' (gc/circle 0 0 2) 12 | c2 (gc/circle 0 1 1) 13 | c3 (gc/circle 1 1 1) 14 | c4 (gc/circle -1 2 1) 15 | q (-> (sut/circletree -10 -10 20 20) 16 | (g/add-point (:p c1) c1))] 17 | (is (= [c1] (spatialtree/select-with-shape q (g/bounds q)))) 18 | (is (= [] (spatialtree/select-with-shape (g/delete-point q (:p c1)) (g/bounds q)))) 19 | (is (= [c1'] (spatialtree/select-with-shape (sut/replace-point q (:p c1) c1') (g/bounds q)))) 20 | 21 | (let [q2 (reduce (fn [g c] (g/add-point g (:p c) c)) 22 | (sut/circletree -10 -10 20 20) 23 | [c1 c2 c3 c4])] 24 | (is (= (set [c1 c2 c3 c4]) (set (sut/all-data q2)))) 25 | (is (= (set [c1' c2 c3 c4]) 26 | (set (sut/all-data (sut/replace-point q2 (:p c1) c1')))))))) 27 | 28 | (comment (t/run-tests)) 29 | -------------------------------------------------------------------------------- /resources/public/shaders/physarum.frag.c: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision mediump float; 3 | #endif 4 | 5 | varying vec2 vTexCoord; 6 | 7 | uniform vec2 resolution; 8 | uniform sampler2D trail; 9 | uniform float decay; 10 | 11 | vec4 blur(sampler2D tex, vec2 pos, vec2 texel) { 12 | vec4 color = vec4(0.0); 13 | color += texture2D(tex, pos + vec2(-1.0,-1.0) * texel) * 1.0/16.0; 14 | color += texture2D(tex, pos + vec2(0.0, -1.0) * texel) * 2.0/16.0; 15 | color += texture2D(tex, pos + vec2(1.0, -1.0) * texel) * 1.0/16.0; 16 | color += texture2D(tex, pos + vec2(-1.0, 0.0) * texel) * 2.0/16.0; 17 | color += texture2D(tex, pos + vec2(0.0,0.0) * texel) * 4.0/16.0; 18 | color += texture2D(tex, pos + vec2(1.0, 0.0) * texel) * 2.0/16.0; 19 | color += texture2D(tex, pos + vec2(-1.0, 1.0) * texel) * 1.0/16.0; 20 | color += texture2D(tex, pos + vec2(0.0, 1.0) * texel) * 2.0/16.0; 21 | color += texture2D(tex, pos + vec2(1.0, 1.0) * texel) * 1.0/16.0; 22 | 23 | return color; 24 | } 25 | 26 | void main() { 27 | vec2 pos = vTexCoord.xy; 28 | pos.y = 1.0 - pos.y; 29 | 30 | float scale = 1.0; 31 | vec2 texelSize = vec2(scale/resolution.x, scale/resolution.y); 32 | 33 | float v = blur(trail, pos, texelSize).x * decay; 34 | 35 | gl_FragColor = vec4(v,v,v,1.0); 36 | } 37 | -------------------------------------------------------------------------------- /src/shimmers/sketches/color_mapping.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.color-mapping 2 | (:require 3 | [shimmers.common.svg :as csvg] 4 | [shimmers.common.ui.controls :as ctrl] 5 | [shimmers.math.deterministic-random :as dr] 6 | [shimmers.sketch :as sketch :include-macros true] 7 | [shimmers.view.sketch :as view-sketch] 8 | [thi.ng.geom.rect :as rect] 9 | [thi.ng.geom.vector :as gv] 10 | [thi.ng.math.core :as tm])) 11 | 12 | (def width 800) 13 | (def height 600) 14 | 15 | (defn color [seed i j] 16 | (let [n (dr/noise-at-point seed 0.02 (gv/vec2 i j))] 17 | (csvg/hsl (mod (* n tm/PHI) 1.0) 0.5 0.45 1.0))) 18 | 19 | (defn shapes [] 20 | (let [[cols rows] [80 60] 21 | w (/ width cols) 22 | h (/ height rows) 23 | seed (dr/noise-seed)] 24 | (for [j (range rows) 25 | i (range cols)] 26 | (vary-meta (rect/rect (* i w) (* j h) w h) 27 | assoc :fill (color seed i j))))) 28 | 29 | (defn scene [] 30 | (csvg/svg-timed {:width width 31 | :height height 32 | :stroke "none"} 33 | (shapes))) 34 | 35 | (sketch/definition color-mapping 36 | {:created-at "2022-05-02" 37 | :type :svg 38 | :tags #{}} 39 | (ctrl/mount (view-sketch/static-page scene :color-mapping))) 40 | -------------------------------------------------------------------------------- /src/shimmers/math/wobble.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.math.wobble 2 | (:require 3 | [clojure.math :as math] 4 | [shimmers.math.deterministic-random :as dr] 5 | [shimmers.math.equations :as eq] 6 | [shimmers.math.vector :as v])) 7 | 8 | (defn create 9 | ([r] (create r (dr/random-tau))) 10 | ([r c] {:r r :c c})) 11 | 12 | (defn sin [{:keys [r c]} p t] 13 | (math/sin (+ (* r t) p c))) 14 | 15 | (defn cos [{:keys [r c]} p t] 16 | (math/sin (+ (* r t) p c))) 17 | 18 | (defn tsin [{:keys [r c]} p t] 19 | (math/sin (* eq/TAU (+ (* r t) p c)))) 20 | 21 | (defn tcos [{:keys [r c]} p t] 22 | (math/cos (* eq/TAU (+ (* r t) p c)))) 23 | 24 | (defn cube-sin [osc p t] 25 | (eq/cube (sin osc p t))) 26 | 27 | (defn cube-tsin [osc p t] 28 | (eq/cube (tsin osc p t))) 29 | 30 | (defn cube-cos [osc p t] 31 | (eq/cube (cos osc p t))) 32 | 33 | (defn cube-tcos [osc p t] 34 | (eq/cube (tcos osc p t))) 35 | 36 | (defn R [^double f ^double p ^double a ^double s] 37 | (v/polar a (* eq/TAU (+ (* s f) p)))) 38 | 39 | (defn O ^double [^double f ^double p ^double s] 40 | (math/sin (* eq/TAU (+ (* s f) p)))) 41 | 42 | (defn osc [f p v d s] 43 | (+ v (* d (math/sin (* eq/TAU (+ (* s f) p)))))) 44 | 45 | (defn create-osc [f v d] 46 | {:f f :v v :d d 47 | :fe (fn [p s] (osc f p v d s))}) 48 | -------------------------------------------------------------------------------- /src/shimmers/sketches/beat_table.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.beat-table 2 | (:require 3 | [shimmers.common.ui.controls :as ctrl] 4 | [shimmers.common.ui.svg :as usvg] 5 | [shimmers.sketch :as sketch :include-macros true])) 6 | 7 | ;; search for time change locations on base 64 or 48 8 | (comment (filter (fn [x] (or (= 0 (mod x 64)) 9 | (= 0 (mod x 48)))) (range 2049))) 10 | 11 | (defn scene [_] 12 | (let [divisions [1 4 6 8 12 16 24 32]] 13 | [:table 14 | (into [:tbody] 15 | (for [division divisions] 16 | (into [:tr] 17 | (cons [:th (if (= division 1) 18 | "beat" 19 | (str "1/" division))] 20 | (for [beat (range 65) 21 | :when (some (fn [div] (= 0 (mod beat div))) 22 | [4 6 8 12 16 24 32])] 23 | [:td {:style {:width "1.8em" :text-align "right"}} 24 | (if (= division 1) beat 25 | (str (mod beat division)))])))))])) 26 | 27 | (sketch/definition beat-table 28 | {:created-at "2024-08-30" 29 | :tags #{} 30 | :type :svg} 31 | (ctrl/mount (usvg/page sketch-args scene))) 32 | -------------------------------------------------------------------------------- /release.cljs.edn: -------------------------------------------------------------------------------- 1 | ^{:open-url "http://[[server-hostname]]:[[server-port]]/index.html"} 2 | {:main shimmers.core 3 | 4 | :externs [#_"p5-externs.js" 5 | "sparkles-externs.js" 6 | "delaunator-externs.js" 7 | "d3-delaunay-externs.js"] 8 | :foreign-libs [#_{:file "node_modules/p5/lib/p5.js" 9 | :file-min "node_modules/p5/lib/p5.min.js" 10 | :provides ["cljsjs.p5"]} 11 | {:file "node_modules/sparkles/src/sparkles.js" 12 | :provides ["sparkles"]} 13 | {:file "node_modules/delaunator/delaunator.js" 14 | :file-min "node_modules/delaunator/delaunator.min.js" 15 | :provides ["delaunator"]} 16 | {:file "node_modules/d3-delaunay/dist/d3-delaunay.js" 17 | :file-min "node_modules/d3-delaunay/dist/d3-delaunay.min.js" 18 | :provides ["d3-delaunay"]}] 19 | :infer-externs true 20 | ;; :pseudo-names true 21 | ;; :pretty-print true 22 | 23 | :compiler-stats true 24 | :verbose false 25 | :parallel-build true 26 | 27 | :clean-outputs true 28 | :optimizations :advanced 29 | :source-map "target/public/cljs-out/release-main.js.map" 30 | 31 | ;; TODO: see https://clojurescript.org/reference/compiler-options 32 | :fingerprint true 33 | } 34 | -------------------------------------------------------------------------------- /src/shimmers/sketches/precipitation.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.precipitation 2 | (:require 3 | [quil.core :as q :include-macros true] 4 | [quil.middleware :as m] 5 | [shimmers.common.framerate :as framerate] 6 | [shimmers.common.ui.controls :as ctrl] 7 | [shimmers.sketch :as sketch :include-macros true])) 8 | 9 | (defn rain [noise] 10 | (let [x0 (q/random (- 140) (+ 140 (q/width))) 11 | angle (q/radians (+ (* 3 (q/random-gaussian)) 12 | (* 110 (- 1 noise)))) 13 | x1 (+ x0 (* 200 (q/cos angle))) 14 | s0 (- (rand) 0.2) 15 | s1 (+ s0 (q/random noise))] 16 | (q/stroke-weight (q/random 0.1 noise)) 17 | (q/line (q/lerp x0 x1 s0) (q/lerp 0 (q/height) s0) 18 | (q/lerp x0 x1 s1) (q/lerp 0 (q/height) s1)))) 19 | 20 | (defn draw [_] 21 | (q/background 0 10) 22 | (q/stroke 255 128) 23 | (let [noise (q/noise 0 (/ (q/frame-count) 1000))] 24 | ;; (q/print-every-n-millisec 500 noise) 25 | (dotimes [_ (rand-int (* 8 noise))] 26 | (rain noise)))) 27 | 28 | (defn page [] 29 | (sketch/component 30 | :size [800 600] 31 | :draw draw 32 | :middleware [m/fun-mode framerate/mode])) 33 | 34 | (sketch/definition precipitation 35 | {:created-at "2021-01-19" 36 | :tags #{} 37 | :type :quil} 38 | (ctrl/mount page)) 39 | -------------------------------------------------------------------------------- /shimmers.el: -------------------------------------------------------------------------------- 1 | ;; inspired by https://github.com/nextjournal/clerk/blob/main/clerk.el 2 | 3 | (require 'cider-client) 4 | (require 'cider-eval) 5 | (require 'cider-mode) 6 | 7 | ;; Is there a way to switch-to or open tab? 8 | (defun shimmers-visit-tests () 9 | (interactive) 10 | (shell-command "xdg-open http://localhost:9500/figwheel-extra-main/tests")) 11 | 12 | ;; TODO: add function to force reload or reset atoms? 13 | ;; also consider adding support for reloading with same seed 14 | ;; and consider keeping track of last active ns so that binding works from library code? 15 | (defun shimmers-visit-sketch () 16 | "Direct browser to (re)load the associated sketch for the current namespace." 17 | (interactive) 18 | (let ((ns (cider-current-ns))) 19 | (if (and ns (string-match-p "shimmers\\.sketches\\..*" ns)) 20 | (progn 21 | (message (format "Visiting sketch %s" ns)) 22 | (cider-interactive-eval 23 | (format "(if-let [page (:parameters (shimmers.core/visit! '%s))] 24 | [(keyword (get-in page [:path :name])) 25 | (get-in page [:query :seed])] 26 | \"Unknown sketch %s!\")" ns ns))) 27 | (error (format "Namespace %s is not a sketch." ns))))) 28 | 29 | (define-key cider-mode-map (kbd "") #'shimmers-visit-sketch) 30 | 31 | (provide 'shimmers) 32 | -------------------------------------------------------------------------------- /src/shimmers/sketches/ring.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.ring 2 | (:require 3 | [quil.core :as q :include-macros true] 4 | [quil.middleware :as m] 5 | [shimmers.common.framerate :as framerate] 6 | [shimmers.common.quil :as cq] 7 | [shimmers.common.ui.controls :as ctrl] 8 | [shimmers.math.vector :as v] 9 | [shimmers.sketch :as sketch :include-macros true])) 10 | 11 | (defn setup [] 12 | {:theta 0.0}) 13 | 14 | (defn update-state [state] 15 | (update state :theta + 0.08)) 16 | 17 | (defn draw [{:keys [theta]}] 18 | (q/background 255 4) 19 | (q/stroke 10 64) 20 | (q/translate (/ (q/width) 2) (/ (q/height) 2)) 21 | (let [radial-noise (q/noise (q/cos (/ theta 2)) (q/sin (/ theta 2))) 22 | length (cq/rel-h 0.15) 23 | radius (+ (cq/rel-h 0.35) (* (- radial-noise 0.5) length)) 24 | pos (v/polar radius theta)] 25 | (q/stroke-weight (+ 0.8 radial-noise)) 26 | (q/line pos 27 | (v/+polar pos 28 | (* radial-noise length) 29 | (+ theta radial-noise))))) 30 | 31 | (defn page [] 32 | (sketch/component 33 | :size [800 600] 34 | :setup setup 35 | :update update-state 36 | :draw draw 37 | :middleware [m/fun-mode framerate/mode])) 38 | 39 | (sketch/definition ring 40 | {:created-at "2020-12-27" 41 | :tags #{} 42 | :type :quil} 43 | (ctrl/mount page)) 44 | -------------------------------------------------------------------------------- /src/shimmers/algorithm/flow_fields.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.algorithm.flow-fields 2 | (:require 3 | [shimmers.math.deterministic-random :as dr] 4 | [shimmers.math.equations :as eq] 5 | [shimmers.math.vector :as v] 6 | [thi.ng.math.core :as tm])) 7 | 8 | (defn noise-force [seed scale force] 9 | (fn [p] 10 | (v/polar force (* (dr/noise-at-point-01 seed scale p) eq/TAU)))) 11 | 12 | (defn flow [start-pt inside? next-pt lifespan] 13 | (let [path 14 | (->> start-pt 15 | (iterate next-pt) 16 | (take-while inside?) 17 | (take lifespan))] 18 | (when (> (count path) 1) 19 | path))) 20 | 21 | (defn forward [start inside? force-fn lifespan] 22 | (flow start inside? (fn [p] (tm/+ p (force-fn p))) lifespan)) 23 | 24 | (defn backward [start inside? force-fn lifespan] 25 | (flow start inside? (fn [p] (tm/- p (force-fn p))) lifespan)) 26 | 27 | (defn bidirectional [start inside? force-fn lifespan] 28 | (let [behind (backward start inside? force-fn lifespan) 29 | ahead (forward start inside? force-fn lifespan) 30 | path (seq (concat (if (seq behind) 31 | (reverse (rest behind)) 32 | []) 33 | (if (seq ahead) 34 | ahead 35 | [])))] 36 | (when (> (count path) 1) 37 | path))) 38 | -------------------------------------------------------------------------------- /test/shimmers/math/geometry/arc_test.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.math.geometry.arc-test 2 | (:require 3 | [clojure.test :as t :refer [deftest is] :include-macros true] 4 | [shimmers.math.geometry.arc :as sut] 5 | [thi.ng.math.core :as tm] 6 | [shimmers.math.equations :as eq] 7 | [thi.ng.geom.core :as g] 8 | [thi.ng.geom.vector :as gv])) 9 | 10 | (deftest half-angle 11 | (is (tm/delta= tm/HALF_PI (sut/half-angle (sut/arc 0 tm/PI)))) 12 | (is (tm/delta= tm/HALF_PI (sut/half-angle (sut/arc (- tm/HALF_PI) tm/HALF_PI)))) 13 | (is (tm/delta= (* 0.4 eq/TAU) (sut/half-angle (sut/arc (* eq/TAU 0.1) (* eq/TAU 0.9))))) 14 | (is (tm/delta= (* 0.1 eq/TAU) (sut/half-angle (sut/arc (* eq/TAU 0.9) (* eq/TAU 0.1))))) 15 | (is (tm/delta= (* 0.1 eq/TAU) (sut/half-angle (sut/arc (- (* eq/TAU 0.1)) (* eq/TAU 0.1)))))) 16 | 17 | (deftest contains-point 18 | (is (g/contains-point? (sut/arc (- tm/QUARTER_PI) tm/QUARTER_PI) (gv/vec2 0.0 0))) 19 | (is (g/contains-point? (sut/arc (- tm/QUARTER_PI) tm/QUARTER_PI) (gv/vec2 0.5 0))) 20 | (is (g/contains-point? (sut/arc (- tm/QUARTER_PI) tm/QUARTER_PI) (gv/vec2 1.0 0))) 21 | (is (not (g/contains-point? (sut/arc (- tm/QUARTER_PI) tm/QUARTER_PI) (gv/vec2 -0.5 0)))) 22 | (is (not (g/contains-point? (sut/arc (- tm/QUARTER_PI) tm/QUARTER_PI) (gv/vec2 1.5 0)))) 23 | (is (not (g/contains-point? (sut/arc (- tm/QUARTER_PI) tm/QUARTER_PI) (gv/vec2 0 0.5))))) 24 | -------------------------------------------------------------------------------- /resources/public/shaders/reaction-diffusion.display.frag.c: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision mediump float; 3 | #endif 4 | 5 | #ifndef PI 6 | #define PI 3.141592653589793 7 | #endif 8 | 9 | varying vec2 vTexCoord; 10 | 11 | uniform sampler2D image; 12 | uniform int mode; 13 | uniform bool invert; 14 | 15 | // From https://stackoverflow.com/a/17897228/34450 16 | // All components are in the range [0…1], including hue. 17 | vec3 hsv2rgb(vec3 c) 18 | { 19 | vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); 20 | vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); 21 | return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); 22 | } 23 | 24 | void main() { 25 | vec2 pos = vTexCoord.xy; 26 | vec4 col = texture2D(image, pos); 27 | float grey = 0.0; 28 | if(mode == 0) { 29 | grey = abs(col.y-col.x); 30 | } else if (mode == 1) { 31 | grey = col.x; 32 | } else if (mode == 2) { 33 | grey = col.y; 34 | } else if (mode == 3) { 35 | if(col.y >= col.x) { 36 | grey = 1.0; 37 | } else { 38 | grey = 0.0; 39 | } 40 | } 41 | 42 | if(invert) { 43 | grey = 1.0 - grey; 44 | } 45 | 46 | if(mode == 4) { 47 | float hue = 1.0 - ((atan(col.y, col.x) + PI) / 2.0 * PI); 48 | float dist = 1.0 - distance(col.xy, vec2(0.0,0.0)); 49 | gl_FragColor = vec4(hsv2rgb(vec3(hue, dist, 1.0)), 1.0); 50 | } else { 51 | gl_FragColor = vec4(grey,grey,grey,1.0); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/shimmers/sketches/imperfect_curves.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.imperfect-curves 2 | (:require 3 | [clojure.math :as math] 4 | [shimmers.common.svg :as csvg] 5 | [shimmers.common.ui.controls :as ctrl] 6 | [shimmers.math.deterministic-random :as dr] 7 | [shimmers.sketch :as sketch :include-macros true] 8 | [shimmers.view.sketch :as view-sketch] 9 | [thi.ng.geom.line :as gl] 10 | [thi.ng.geom.vector :as gv] 11 | [thi.ng.math.core :as tm])) 12 | 13 | ;; original plan was a perspective drawing of two building faces joined by a curve 14 | 15 | (def width 800) 16 | (def height 600) 17 | (defn rv [x y] 18 | (gv/vec2 (* width x) (* height y))) 19 | 20 | (defn upper-ellipse [p rx ry] 21 | (for [t (range tm/PI tm/TWO_PI 0.1)] 22 | (tm/+ (gv/vec2 (* rx (math/cos t)) 23 | (* ry (math/sin t))) 24 | p))) 25 | 26 | (defn shapes [] 27 | (for [t (dr/var-range 10)] 28 | (gl/linestrip2 (upper-ellipse (rv 0.5 0.5) (* t 0.3 width) (* t 0.4 height))))) 29 | 30 | (defn scene [] 31 | (csvg/svg-timed {:width width 32 | :height height 33 | :stroke "black" 34 | :fill "white" 35 | :stroke-width 0.5} 36 | (shapes))) 37 | 38 | (sketch/definition imperfect-curves 39 | {:created-at "2022-02-08" 40 | :type :svg 41 | :tags #{}} 42 | (ctrl/mount (view-sketch/static-page scene :imperfect-curves))) 43 | -------------------------------------------------------------------------------- /src/shimmers/sketches/deformed_spirals.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.deformed-spirals 2 | (:require 3 | [quil.core :as q :include-macros true] 4 | [quil.middleware :as m] 5 | [shimmers.common.framerate :as framerate] 6 | [shimmers.common.quil :as cq] 7 | [shimmers.common.ui.controls :as ctrl] 8 | [shimmers.math.vector :as v] 9 | [shimmers.sketch :as sketch :include-macros true] 10 | [thi.ng.math.core :as tm])) 11 | 12 | ;; Concept was to make nearby spirals deform eachother like a gravity well, but 13 | ;; just doing a noise deformation was interesting on it's own. 14 | 15 | (defn spiral [center dr dtheta steps t] 16 | (for [theta (range 0 (* steps dtheta) dtheta)] 17 | (let [pos (v/polar (* dr (/ theta tm/TWO_PI)) (+ theta (* 1.5 t))) 18 | [nx ny] (tm/div pos 192) 19 | n (q/noise nx ny t)] 20 | (tm/+ center (tm/+ pos (v/polar 40 (* n tm/TWO_PI))))))) 21 | 22 | (defn draw [_] 23 | (q/color-mode :hsl 1.0) 24 | (q/background 1.0) 25 | (q/stroke-weight 0.8) 26 | (q/no-fill) 27 | (-> (cq/rel-vec 0.5 0.5) 28 | (spiral 9.0 0.9 425 (/ (q/frame-count) 800)) 29 | cq/draw-curve-path)) 30 | 31 | (defn page [] 32 | (sketch/component 33 | :size [800 600] 34 | :draw draw 35 | :middleware [m/fun-mode framerate/mode])) 36 | 37 | (sketch/definition deformed-spirals 38 | {:created-at "2021-10-10" 39 | :tags #{} 40 | :type :quil} 41 | (ctrl/mount page)) 42 | -------------------------------------------------------------------------------- /src/shimmers/model/polygraph.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.model.polygraph 2 | "a graph of points where each node is uniquely identified but also maps to an updatable position." 3 | (:require 4 | [loom.attr :as lga] 5 | [loom.graph :as lg] 6 | [thi.ng.geom.core :as g] 7 | [thi.ng.geom.vector :as gv])) 8 | 9 | (defn nodes->points [graph] 10 | (->> graph 11 | lg/nodes 12 | (map (fn [node] [node (lga/attr graph node :pos)])) 13 | (into {}))) 14 | 15 | ;; FIXME: awful performance, makes creating a graph 2*N^2, need spatial index or hashing 16 | (defn point->node 17 | [graph point] 18 | (let [points (nodes->points graph) 19 | tolerance 0.01] 20 | (or (some (fn [[node pos]] 21 | (when (< (g/dist-squared point pos) tolerance) 22 | node)) 23 | points) 24 | (count points)))) 25 | 26 | (defn add-edge [graph [p q]] 27 | (let [a (point->node graph p) 28 | b (point->node graph q)] 29 | (-> graph 30 | (lg/add-edges [a b (g/dist p q)]) 31 | (lga/add-attr a :pos p) 32 | (lga/add-attr b :pos q)))) 33 | 34 | (defn edgegraph [edges] 35 | (reduce add-edge (lg/weighted-graph) edges)) 36 | 37 | (comment 38 | (edgegraph [[(gv/vec2 0 0) (gv/vec2 10 0)] 39 | [(gv/vec2 10 0) (gv/vec2 0 10)] 40 | [(gv/vec2 0 10) (gv/vec2 0 0)]]) 41 | 42 | (defn polygraph [_edges]) 43 | (defn pointgraph [_points])) 44 | -------------------------------------------------------------------------------- /src/shimmers/sketches/scintillation.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.scintillation 2 | (:require 3 | [quil.core :as q :include-macros true] 4 | [quil.middleware :as m] 5 | [shimmers.common.framerate :as framerate] 6 | [shimmers.common.ui.controls :as ctrl] 7 | [shimmers.sketch :as sketch :include-macros true])) 8 | 9 | (defn setup [] 10 | {:theta 0}) 11 | 12 | (defn update-state [state] 13 | (update state :theta + 0.005)) 14 | 15 | (defn draw [{:keys [theta]}] 16 | (q/background 255 32) 17 | (q/stroke-weight 0.5) 18 | (let [hspacing (+ 20 (* (q/cos theta) 10)) 19 | hcount (/ (q/width) hspacing) 20 | vspacing (+ 20 (* (q/sin theta) 10)) 21 | vcount (/ (q/height) vspacing)] 22 | (dotimes [i hcount] 23 | (let [x0 (* i hspacing) 24 | x1 (+ 20 (* i 2 hspacing (q/cos theta)))] 25 | (q/line x0 0 x1 (q/height)) 26 | (q/line x1 0 x0 (q/height)))) 27 | (dotimes [j vcount] 28 | (let [y0 (* j vspacing) 29 | y1 (+ 20 (* j 2 vspacing (q/sin theta)))] 30 | (q/line 0 y0 (q/width) y1) 31 | (q/line 0 y1 (q/width) y0))))) 32 | 33 | (defn page [] 34 | (sketch/component 35 | :size [800 600] 36 | :setup setup 37 | :update update-state 38 | :draw draw 39 | :middleware [m/fun-mode framerate/mode])) 40 | 41 | (sketch/definition scintillation 42 | {:created-at "2021-01-16" 43 | :tags #{} 44 | :type :quil} 45 | (ctrl/mount page)) 46 | -------------------------------------------------------------------------------- /src/shimmers/core.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.core 2 | (:require 3 | [goog.dom :as dom] 4 | [reagent.core :as r] 5 | [reagent.dom.client :as rdomc] 6 | [reitit.frontend.easy :as rfe] 7 | [shimmers.routing :as routing] 8 | [shimmers.sketches :as sketches] 9 | [shimmers.view.sketch :as view-sketch])) 10 | 11 | ;; Uncomment to see javascript source of functions at repl 12 | ;; (set! cljs.core/*print-fn-bodies* true) 13 | ;; Or just (str the-function) 14 | 15 | (defonce shimmer-root (rdomc/create-root (dom/getElement "shimmer-mount"))) 16 | (defonce !active-route (r/atom nil)) 17 | (comment @!active-route) 18 | 19 | (defn init [] 20 | (routing/start! !active-route) 21 | (rdomc/render shimmer-root [routing/page-root !active-route])) 22 | 23 | ;; initialize sketch on first-load 24 | (defonce start-up (init)) 25 | 26 | ;; TODO: support string for filename lookup but registry contains both relative 27 | ;; and absolute filenames. 28 | (defn visit! 29 | "Force router to view a specific sketch in browser by keyword or namespace" 30 | [id] 31 | (when-let [sketch (cond (keyword? id) 32 | (sketches/by-name id) 33 | (symbol? id) 34 | (sketches/by-ns id))] 35 | (view-sketch/sketch-link rfe/push-state (:sketch-id sketch)))) 36 | 37 | (comment (visit! 'shimmers.sketches.cube) 38 | (visit! :superposition) 39 | (visit! 'shimmers.sketches.unit-circle)) 40 | -------------------------------------------------------------------------------- /src/shimmers/sketches/zigzag.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.zigzag 2 | (:require 3 | [quil.core :as q :include-macros true] 4 | [shimmers.common.framerate :as framerate] 5 | [shimmers.common.ui.controls :as ctrl] 6 | [shimmers.sketch :as sketch])) 7 | 8 | (defn seconds-since-epoch [] 9 | (/ (.getTime (js/Date.)) 1000.0)) 10 | 11 | (defn polar-noise [angle] 12 | (q/noise (+ (q/cos angle) 1) 13 | (+ (q/sin angle) 1))) 14 | 15 | (defn zig [gap [x y] t] 16 | (when (and (> x 0) (< y (q/height))) 17 | (let [step-size (* gap (/ 9 16)) 18 | h (- gap step-size) 19 | a (+ (/ (+ x y) 32) t) 20 | jitter (* h (polar-noise a)) 21 | nx (- x step-size jitter) 22 | ny (+ y step-size jitter)] 23 | (q/line x y nx y) 24 | (q/line nx y nx ny) 25 | (recur gap [nx ny] t)))) 26 | 27 | (defn draw [] 28 | (q/background 255 32) 29 | (q/stroke 32 16) 30 | (q/stroke-weight 0.6) 31 | (let [t (/ (seconds-since-epoch) 10) 32 | m (q/map-range (q/cos (/ t 4)) -1 1 16 64) 33 | gap (/ (q/width) (/ m 2))] 34 | ;; (q/print-every-n-millisec 200 (str t " " m)) 35 | (dotimes [i m] 36 | (zig gap [(* i gap) 0] (/ t 256))))) 37 | 38 | (defn page [] 39 | (sketch/component 40 | :size [800 800] 41 | :draw draw 42 | :middleware [framerate/mode])) 43 | 44 | (sketch/definition zigzag 45 | {:created-at "2020-12-04" 46 | :tags #{} 47 | :type :quil} 48 | (ctrl/mount page)) 49 | 50 | -------------------------------------------------------------------------------- /src/shimmers/sketches/cutouts.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.cutouts 2 | (:require 3 | [shimmers.common.svg :as csvg :include-macros true] 4 | [shimmers.common.ui.controls :as ctrl] 5 | [shimmers.math.deterministic-random :as dr] 6 | [shimmers.sketch :as sketch :include-macros true] 7 | [shimmers.view.sketch :as view-sketch] 8 | [thi.ng.geom.rect :as rect] 9 | [thi.ng.geom.vector :as gv])) 10 | 11 | (def width 800) 12 | (def height 600) 13 | (defn rv [x y] 14 | (gv/vec2 (* width x) (* height y))) 15 | 16 | (defn shapes [] 17 | (mapcat (fn [[x0 x1]] 18 | (map (fn [[y0 y1]] 19 | (let [sx (dr/random 0.005 (* 0.33 (- x1 x0))) 20 | sy (dr/random 0.005 (* 0.2 (- y1 y0))) 21 | r (dr/random 8.0)] 22 | (vary-meta (rect/rect (rv (+ x0 sx) (+ y0 sy)) 23 | (rv (- x1 sx) (- y1 sy))) 24 | assoc :rx r :ry r))) 25 | (partition 2 1 (dr/gaussian-range 0.08 0.02)))) 26 | (partition 2 1 (dr/gaussian-range 0.05 0.01)))) 27 | 28 | (defn scene [] 29 | (csvg/svg-timed {:width width 30 | :height height 31 | :stroke "black" 32 | :fill "none" 33 | :stroke-width 1.0} 34 | (shapes))) 35 | 36 | (sketch/definition cutouts 37 | {:created-at "2023-02-04" 38 | :type :svg 39 | :tags #{}} 40 | (ctrl/mount (view-sketch/static-page scene :cutouts))) 41 | -------------------------------------------------------------------------------- /test/shimmers/test_namespaces.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.test-namespaces 2 | (:require 3 | ;; namespaces to test 4 | [shimmers.algorithm.linear-assignment-test] 5 | [shimmers.algorithm.line-clipping-test] 6 | [shimmers.algorithm.lines-test] 7 | [shimmers.algorithm.minimum-spanning-tree-test] 8 | [shimmers.algorithm.mosaic-test] 9 | [shimmers.algorithm.quadtree-test] 10 | [shimmers.algorithm.rtree-test] 11 | [shimmers.algorithm.space-colonization-test] 12 | [shimmers.algorithm.square-packing-test] 13 | [shimmers.algorithm.polygon-detection-test] 14 | [shimmers.algorithm.wave-function-collapse-test] 15 | [shimmers.automata.memory-test] 16 | [shimmers.automata.simplify-test] 17 | [shimmers.common.sequence-test] 18 | [shimmers.common.svg-test] 19 | [shimmers.common.transition-interval-test] 20 | [shimmers.math.core-test] 21 | [shimmers.math.control-test] 22 | [shimmers.math.equations-test] 23 | [shimmers.math.interval-test] 24 | [shimmers.math.geometry-test] 25 | [shimmers.math.geometry.arc-test] 26 | [shimmers.math.geometry.collisions-test] 27 | [shimmers.math.geometry.ellipse-test] 28 | [shimmers.math.geometry.group-test] 29 | [shimmers.math.geometry.intersection-test] 30 | [shimmers.math.geometry.line-test] 31 | [shimmers.math.geometry.points-test] 32 | [shimmers.math.geometry.rectangle-test] 33 | [shimmers.math.geometry.triangle-test] 34 | [shimmers.math.graph-test] 35 | [shimmers.math.hexagon-test] 36 | [shimmers.math.stair-test])) 37 | -------------------------------------------------------------------------------- /src/shimmers/sketches/clothoid_flowers.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.clothoid-flowers 2 | (:require 3 | [clojure.math :as math] 4 | [quil.core :as q :include-macros true] 5 | [quil.middleware :as m] 6 | [shimmers.common.framerate :as framerate] 7 | [shimmers.common.quil :as cq] 8 | [shimmers.common.ui.controls :as ctrl] 9 | [shimmers.math.deterministic-random :as dr] 10 | [shimmers.math.equations :as eq] 11 | [shimmers.sketch :as sketch :include-macros true] 12 | [thi.ng.geom.vector :as gv] 13 | [thi.ng.math.core :as tm])) 14 | 15 | (defn setup [] 16 | (q/noise-seed (dr/seed)) 17 | (q/color-mode :hsl 1.0) 18 | {:t 0.0}) 19 | 20 | (defn update-state [state] 21 | (update state :t + 0.01)) 22 | 23 | (defn draw [{:keys [t]}] 24 | (q/ellipse-mode :radius) 25 | (q/no-stroke) 26 | (q/fill 0.0 0.3) 27 | 28 | (q/translate (cq/rel-vec 0.5 0.5)) 29 | (let [rotation (* eq/TAU (q/noise (* 0.01 t))) 30 | length (+ 40 (* 20 (math/sin t)))] 31 | (->> (concat (eq/clothoid 18 length 30 -1 (+ rotation 0.0) (gv/vec2)) 32 | (eq/clothoid 12 length 50 -1 (+ rotation math/PI) (gv/vec2))) 33 | (mapv #(tm/* % 12)) 34 | (cq/plot (fn [p] (cq/circle p 0.3)))))) 35 | 36 | (defn page [] 37 | (sketch/component 38 | :size [800 600] 39 | :setup setup 40 | :update update-state 41 | :draw draw 42 | :middleware [m/fun-mode framerate/mode])) 43 | 44 | (sketch/definition clothoid-flowers 45 | {:created-at "2021-11-23" 46 | :tags #{:deterministic} 47 | :type :quil} 48 | (ctrl/mount page)) 49 | -------------------------------------------------------------------------------- /src/shimmers/common/string.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.common.string 2 | #?(:cljs (:require 3 | [cljs.pprint :as pp] 4 | [goog.string :as gstring] 5 | [goog.string.format]) 6 | :clj (:require [clojure.pprint :as pp])) 7 | #?(:clj (:refer-clojure :exclude [format]))) 8 | 9 | ;; see https://martinklepsch.org/posts/requiring-closure-namespaces.html for 10 | ;; weirdness around requiring goog.string.format to force it to create format 11 | ;; in goog.string. Requiring goog.string will not add format to the namespace, 12 | ;; but works until compiling with :optimization :advanced. 13 | 14 | #?(:cljs 15 | (defn format [fmt & args] 16 | (apply gstring/format fmt args)) 17 | :clj 18 | (def format clojure.core/format)) 19 | 20 | ;; https://gigamonkeys.com/book/a-few-format-recipes.html 21 | (defn cl-format [fmt & args] 22 | (apply pp/cl-format nil fmt args)) 23 | 24 | ;; See https://github.com/brandonbloom/fipp/issues/83 for symbol hack, basically 25 | ;; it's forcing EdnPrinter to believe it's a symbol and not a float so it 26 | ;; doesn't wrap it in a string. 27 | (defn fixed-width 28 | "Format float `v` to `width` decimal places as long as it's not infinite." 29 | ([v] (fixed-width v 2)) 30 | ([v width] 31 | #?(:cljs 32 | (if (or (integer? v) (infinite? v)) 33 | v 34 | (symbol (.toFixed v width))) 35 | :clj 36 | (if (or (integer? v) 37 | (= v Double/POSITIVE_INFINITY) 38 | (= v Double/NEGATIVE_INFINITY)) 39 | v 40 | (symbol (format (str "%." width "f") v)))))) 41 | -------------------------------------------------------------------------------- /src/shimmers/sketches/punchcard.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.punchcard 2 | (:require 3 | [shimmers.common.svg :as csvg] 4 | [shimmers.common.ui.controls :as ctrl] 5 | [shimmers.math.deterministic-random :as dr] 6 | [shimmers.sketch :as sketch :include-macros true] 7 | [shimmers.view.sketch :as view-sketch] 8 | [thi.ng.geom.core :as g] 9 | [thi.ng.geom.rect :as rect] 10 | [thi.ng.geom.vector :as gv] 11 | [thi.ng.math.core :as tm])) 12 | 13 | ;; Inspired by: https://twitter.com/akacastor/status/1480657994241036289/photo/1 14 | (def width 800) 15 | (def height 600) 16 | (defn rv [x y] 17 | (gv/vec2 (* width x) (* height y))) 18 | 19 | (defn shapes [bounds] 20 | (let [columns (g/subdivide bounds {:cols 16 :rows (dr/weighted {1 3 2 1})})] 21 | (apply concat 22 | (for [column columns 23 | :let [grid (g/subdivide (g/scale-size column 0.9) {:cols 8 :rows 64})]] 24 | (->> grid 25 | (dr/random-sample (dr/random 0.2 0.8)) 26 | (map (fn [b] (g/translate (rect/rect 4) (tm/+ (:p b) (gv/vec2 1 0))))) 27 | (into [(g/scale-size column 0.95)])))))) 28 | 29 | (defn scene [] 30 | (csvg/svg-timed {:width width 31 | :height height 32 | :stroke "black" 33 | :fill "white" 34 | :stroke-width 0.8} 35 | (shapes (g/scale-size (csvg/screen width height) 0.95)))) 36 | 37 | (sketch/definition punchcard 38 | {:created-at "2022-01-22" 39 | :type :svg 40 | :tags #{}} 41 | (ctrl/mount (view-sketch/static-page scene :punchcard))) 42 | -------------------------------------------------------------------------------- /src/shimmers/sketches/concentric_moire.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.concentric-moire 2 | (:require 3 | [clojure.math :as math] 4 | [quil.core :as q :include-macros true] 5 | [quil.middleware :as m] 6 | [shimmers.common.framerate :as framerate] 7 | [shimmers.common.quil :as cq] 8 | [shimmers.common.ui.controls :as ctrl] 9 | [shimmers.sketch :as sketch :include-macros true])) 10 | 11 | (defn setup [] 12 | (q/frame-rate 20) 13 | (q/color-mode :hsl 1.0) 14 | {:circles [{:pos (cq/rel-pos 0.2 0.5) 15 | :spacing 20 :upper 48 :weight 1.0} 16 | {:pos (cq/rel-pos 0.8 0.5) 17 | :spacing 20 :upper 48 :weight 1.0} 18 | {:pos (cq/rel-pos 0.5 0.25) 19 | :spacing 10 :upper 64 :weight 0.5} 20 | {:pos (cq/rel-pos 0.5 0.75) 21 | :spacing 10 :upper 64 :weight 0.5}]}) 22 | 23 | (defn update-state [state] 24 | state) 25 | 26 | (defn draw [{:keys [circles]}] 27 | (q/background 1.0) 28 | (q/no-fill) 29 | (q/ellipse-mode :radius) 30 | (let [change (* 0.5 (+ 1 (math/sin (/ (q/frame-count) 40))))] 31 | (doseq [{:keys [pos spacing upper weight]} circles] 32 | (q/stroke-weight weight) 33 | (doseq [r (range 0 upper)] 34 | (cq/circle pos (* spacing (+ r change))))))) 35 | 36 | (defn page [] 37 | (sketch/component 38 | :size [800 600] 39 | :setup setup 40 | :update update-state 41 | :draw draw 42 | :middleware [m/fun-mode framerate/mode])) 43 | 44 | (sketch/definition concentric-moire 45 | {:created-at "2021-08-14" 46 | :tags #{} 47 | :type :quil} 48 | (ctrl/mount page)) 49 | -------------------------------------------------------------------------------- /src/shimmers/sketches/squiggle_line.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.squiggle-line 2 | (:require 3 | [quil.core :as q :include-macros true] 4 | [quil.middleware :as m] 5 | [shimmers.algorithm.hand-drawn :as hand-drawn] 6 | [shimmers.common.framerate :as framerate] 7 | [shimmers.common.quil :as cq] 8 | [shimmers.common.ui.controls :as ctrl] 9 | [shimmers.sketch :as sketch :include-macros true] 10 | [thi.ng.geom.line :as gl])) 11 | 12 | (defn rel-line [{[p q] :points}] 13 | (gl/line2 (cq/rel-vec p) (cq/rel-vec q))) 14 | 15 | (defn setup [] 16 | (q/color-mode :hsl 1.0) 17 | (q/no-loop) 18 | {:lines (mapv rel-line [(gl/line2 0.2 0.2 0.8 0.2) 19 | (gl/line2 0.3 0.3 0.6 0.3) 20 | (gl/line2 0.7 0.25 0.7 0.6) 21 | (gl/line2 0.8 0.35 0.8 0.8) 22 | (gl/line2 0.2 0.7 0.5 0.4) 23 | (gl/line2 0.5 0.5 0.6 0.7)])}) 24 | 25 | (defn update-state [state] 26 | state) 27 | 28 | (defn draw [{:keys [lines]}] 29 | (q/background 1.0) 30 | (doseq [{[p q] :points} lines] 31 | (hand-drawn/line p q))) 32 | 33 | (defn page [] 34 | [sketch/with-explanation 35 | (sketch/component 36 | :size [800 600] 37 | :setup setup 38 | :update update-state 39 | :draw draw 40 | :middleware [m/fun-mode framerate/mode]) 41 | [:p "Demonstration of simulated hand drawn lines in various orientations."]]) 42 | 43 | ;; TODO: convert to SVG? 44 | (sketch/definition squiggle-line 45 | {:created-at "2021-11-05" 46 | :tags #{:demo} 47 | :type :quil} 48 | (ctrl/mount page)) 49 | -------------------------------------------------------------------------------- /src/shimmers/sketches/sunflower_path.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.sunflower-path 2 | (:require 3 | [clojure.math :as math] 4 | [shimmers.common.svg :as csvg :include-macros true] 5 | [shimmers.common.ui.controls :as ctrl] 6 | [shimmers.common.ui.svg :as usvg] 7 | [shimmers.math.equations :as eq] 8 | [shimmers.math.vector :as v] 9 | [shimmers.sketch :as sketch :include-macros true] 10 | [thi.ng.geom.vector :as gv] 11 | [thi.ng.math.core :as tm])) 12 | 13 | (def width 800) 14 | (def height 600) 15 | (defn rv [x y] 16 | (gv/vec2 (* width x) (* height y))) 17 | 18 | (defn shapes [{:keys [points alpha]}] 19 | (let [center (rv 0.5 0.5) 20 | radius (* 0.45 (min width height)) 21 | exterior (min (int (* alpha (math/sqrt points))) points) 22 | interior (- points exterior)] 23 | (csvg/path (into [[:M center]] 24 | (for [i (range points) 25 | :let [r (if (< i interior) (/ (float i) (inc interior)) 1.0) 26 | theta (* eq/TAU (/ i (eq/sqr tm/PHI)))]] 27 | [:L (v/+polar center (* r radius) theta)]))))) 28 | 29 | (defn scene [{:keys [scene-id]}] 30 | (csvg/svg-timed {:id scene-id 31 | :width width 32 | :height height 33 | :stroke "black" 34 | :fill "white" 35 | :stroke-width 0.5} 36 | (shapes {:points 256 :alpha 1.0}))) 37 | 38 | (sketch/definition sunflower-path 39 | {:created-at "2024-09-30" 40 | :tags #{} 41 | :type :svg} 42 | (ctrl/mount (usvg/page sketch-args scene))) 43 | -------------------------------------------------------------------------------- /src/shimmers/sketches/breathing_hexes.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.breathing-hexes 2 | (:require 3 | [quil.core :as q :include-macros true] 4 | [quil.middleware :as m] 5 | [shimmers.common.framerate :as framerate] 6 | [shimmers.common.quil :as cq] 7 | [shimmers.common.ui.controls :as ctrl] 8 | [shimmers.math.hexagon :as hex] 9 | [shimmers.math.wave :as wave] 10 | [shimmers.sketch :as sketch :include-macros true] 11 | [thi.ng.geom.core :as g] 12 | [thi.ng.geom.vector :as gv] 13 | [thi.ng.math.core :as tm])) 14 | 15 | (defn setup [] 16 | (q/color-mode :hsl 1.0) 17 | {}) 18 | 19 | ;; Is there a way to make this smoother and stutter less? Also, the transition 20 | ;; from a single cell to multiple is a little jarring, maybe someway to smooth 21 | ;; that? 22 | (defn draw [_] 23 | (q/background 1.0 0.025) 24 | (q/stroke-weight 0.5) 25 | (q/stroke 0.0 1.0) 26 | (q/fill 1.0 0.1) 27 | (let [t (q/millis) 28 | divisions (int (tm/map-interval (wave/triangle 4000 t) [-1 1] [0 20])) 29 | r (/ (* 0.4 (q/height)) 30 | (+ 10 divisions))] 31 | (q/with-translation (cq/rel-pos 0.5 0.5) 32 | (q/with-rotation [(/ t 12000)] 33 | (doseq [pos (hex/cube-spiral (gv/vec3) divisions) 34 | :let [hex (hex/cube-hexagon pos r)]] 35 | (cq/draw-shape (g/vertices hex 6))))))) 36 | 37 | (defn page [] 38 | (sketch/component 39 | :size [800 600] 40 | :setup setup 41 | :draw draw 42 | :middleware [m/fun-mode framerate/mode])) 43 | 44 | (sketch/definition breathing-hexes 45 | {:created-at "2021-05-23" 46 | :tags #{} 47 | :type :quil} 48 | (ctrl/mount page)) 49 | -------------------------------------------------------------------------------- /bin/googleart_palette.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bb 2 | 3 | ;; Install babashka to local path with something like: 4 | ;; wget https://github.com/babashka/babashka/releases/download/v0.3.8/babashka-0.3.8-linux-amd64-static.tar.gz -O babashka.tgz && 5 | ;; tar -zxf babashka.tgz && mv -vf bb ~/usr/bin && rm -vf babashka.tgz 6 | 7 | (require '[babashka.curl :as curl] 8 | '[clojure.string :refer [replace-first]] 9 | '[cheshire.core :as json]) 10 | 11 | (defn hexify [color] 12 | (apply str (cons "#" (map #(format "%02x" %) color)))) 13 | 14 | (defn extract-rgb [x] 15 | (let [elements (get x "palette")] 16 | (for [elem elements] 17 | (hexify (vec (map #(get elem %) ["r" "g" "b"])))))) 18 | 19 | (defn fetch [id] 20 | (let [address (str "https://artsexperiments.withgoogle.com/artpalette/search/" id) 21 | response (curl/get address)] 22 | (if (= 200 (:status response)) 23 | (map extract-rgb (json/parse-string (replace-first (:body response) ")]}',\n" ""))) 24 | response))) 25 | 26 | ;; The colors were in the URL slug all along, no scraping necessary. This was 27 | ;; completely unnecessary other than learning how to use babashka, the palette 28 | ;; colors are the the key for lookup, but this gives whatever the PER picture 29 | ;; palette is? Maybe dominate colors clustered for that picture and interelated 30 | ;; with "nearby" pictures in the cluster? 31 | (prn (fetch "9f9fa3-aeae07-bcbbb7-cf8311")) 32 | 33 | ;; So https://artsexperiments.withgoogle.com/artpalette/colors/9f9fa3-aeae07-bcbbb7-cf8311 34 | ;; gives the whole page and the color editor just changes the ids in the url to 35 | ;; pass off to search to look for images. 36 | 37 | -------------------------------------------------------------------------------- /src/shimmers/macros/loader.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.macros.loader 2 | (:require 3 | [clojure.string :as str] 4 | ;; [cljs.analyzer :as ana] 5 | [cljs.analyzer.api :as ana-api])) 6 | 7 | (defn namespace-to-id [namespace] 8 | (keyword (last (str/split (str namespace) #"\.")))) 9 | 10 | (defmacro all-sketches 11 | "Create sketch definitions from every namespace under shimmers.sketches.*" 12 | [] 13 | `[~@(keep (fn [ns] 14 | ;; This *attempts* to handle if a sketch is required or not from 15 | ;; shimmers.core. However it appears that at compile time *all* 16 | ;; namespaces are known, but at runtime, the symbol creation 17 | ;; fails as the namespace isn't technically loaded. 18 | ;; 19 | ;; Need a mechanism for detecting if namespace loaded that works 20 | ;; at runtime & compile time. 21 | ;; 22 | ;; Context: 23 | ;; https://github.com/clojure/clojurescript/blob/master/src/main/cljs/cljs/test.cljc#L336 24 | ;; https://www.deepbluelambda.org/programming/clojure/how-clojure-works-namespace-metadata 25 | (when-let [raw-ns# (ana-api/find-ns ns)] 26 | (when-let [raw-fn# (ana-api/ns-resolve ns 'run-sketch)] 27 | `(merge (select-keys (quote ~raw-fn#) [:file :line]) 28 | {:id (namespace-to-id (quote ~ns)) 29 | :doc (:doc (quote ~raw-ns#)) 30 | :fn ~(symbol (name ns) "run-sketch")})))) 31 | (filter #(re-matches #"^shimmers.sketches.s.*" (name %)) 32 | (ana-api/all-ns)))]) 33 | -------------------------------------------------------------------------------- /resources/public/shaders/kaleidoscope.frag.c: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision mediump float; 3 | #endif 4 | 5 | #define pi 3.14159 6 | 7 | varying vec2 vTexCoord; 8 | 9 | uniform vec2 u_resolution; 10 | uniform float u_time; 11 | uniform int u_mode; 12 | uniform float blades; 13 | uniform float zoom; 14 | uniform float power1; 15 | uniform float power2; 16 | 17 | uniform sampler2D frame; 18 | uniform sampler2D frame10; 19 | uniform sampler2D frame25; 20 | 21 | // cribbed from https://www.shadertoy.com/view/MtKXDR 22 | vec2 kaleidoscope(vec2 uv) 23 | { 24 | float th = atan(uv.y, uv.x); 25 | float r = pow(length(uv), power1); 26 | 27 | float q = 2. * pi / blades; 28 | th = abs(mod(th + cos(0.1*u_time), q) - 0.5 * q); 29 | return pow(r, power2)*vec2(cos(th), sin(th)) * zoom; 30 | } 31 | 32 | vec2 transform(vec2 at) 33 | { 34 | vec2 v; 35 | float fov = 0.5*sin(u_time*0.2); 36 | v.x = at.x * cos(fov) - at.y * sin(fov) - 0.2 * sin(fov); 37 | v.y = at.x * sin(fov) + at.y * cos(fov) + 0.2 * cos(fov); 38 | return v; 39 | } 40 | 41 | vec4 scene(sampler2D tex, vec2 at) 42 | { 43 | return texture2D(tex, transform(mod(at+0.0, 1.0)) * 2.0); 44 | } 45 | 46 | void main() { 47 | vec2 pos = vTexCoord.xy; 48 | pos.x = mix(-1.0,1.0,pos.x); 49 | pos.y = mix(-1.0,1.0,pos.y); 50 | pos.y *= u_resolution.y / u_resolution.x; 51 | 52 | vec4 color; 53 | if(u_mode == 0) { 54 | color = scene(frame, kaleidoscope(pos)); 55 | } else if(u_mode == 1) { 56 | color.r = scene(frame, kaleidoscope(pos)).r; 57 | color.g = scene(frame10, kaleidoscope(pos)).g; 58 | color.b = scene(frame25, kaleidoscope(pos)).b; 59 | } 60 | 61 | gl_FragColor = vec4(color.rgb, 1.0); 62 | } 63 | -------------------------------------------------------------------------------- /src/shimmers/sketches/slashes.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.slashes 2 | (:require 3 | [shimmers.algorithm.line-clipping :as clip] 4 | [shimmers.common.svg :as csvg :include-macros true] 5 | [shimmers.common.ui.controls :as ctrl] 6 | [shimmers.math.deterministic-random :as dr] 7 | [shimmers.sketch :as sketch :include-macros true] 8 | [shimmers.view.sketch :as view-sketch] 9 | [thi.ng.geom.vector :as gv])) 10 | 11 | (def width 800) 12 | (def height 600) 13 | (defn rv [x y] 14 | (gv/vec2 (* width x) (* height y))) 15 | 16 | (defn shapes [] 17 | (let [bounds (csvg/screen width height) 18 | hatch (partial clip/variable-hatching bounds)] 19 | (concat (hatch (dr/random 5.0 6.0) 0 20 | (int (dr/random 6 16)) (constantly 10) (constantly 1)) 21 | (hatch (dr/random 5.0 6.0) (* width 0.8) 22 | 10 #(dr/random 5 15) #(dr/random 0.8 6)) 23 | (hatch (dr/random 3.5 4.5) (* width 0.4) 24 | (int (dr/random 4 16)) #(dr/random 5 15) #(dr/random 0.5 4)) 25 | (hatch (dr/random 3.5 4.5) (* width 1.3) 26 | 8 #(dr/random 5 15) #(dr/random 0.5 2))))) 27 | 28 | (defn scene [] 29 | (csvg/svg-timed {:width width 30 | :height height 31 | :stroke "black" 32 | :fill "white" 33 | :stroke-width 1.0} 34 | (mapv (fn [{:keys [width] :as line}] 35 | (vary-meta line assoc :stroke-width width)) 36 | (shapes)))) 37 | 38 | (sketch/definition slashes 39 | {:created-at "2021-08-20" 40 | :type :svg 41 | :tags #{:deterministic}} 42 | (ctrl/mount (view-sketch/static-page scene :slashes))) 43 | -------------------------------------------------------------------------------- /src/shimmers/math/wave.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.math.wave 2 | (:require 3 | [clojure.math :as math] 4 | [shimmers.math.equations :as eq])) 5 | 6 | ;; https://en.wikipedia.org/wiki/List_of_periodic_functions 7 | ;; Consider implementing cycloid 8 | 9 | (defn square 10 | "Square wave function from -1 to 1 with frequency `f`" 11 | [f t] 12 | (+ (* 2 (- (* 2 (math/floor (* f t))) 13 | (math/floor (* 2 f t)))) 1)) 14 | 15 | (defn pulse 16 | [frequency duty-cycle t] 17 | (let [v (mod (* frequency t) 1.0)] 18 | (if (< v duty-cycle) 1 -1))) 19 | 20 | (defn sawtooth 21 | "Sawtooth wave function from -1 to 1 over period `p`." 22 | [p t] 23 | (let [f (/ t p)] 24 | (* 2 (- f (math/floor (+ 0.5 f)))))) 25 | 26 | (defn triangle 27 | "Linear wave function from -1 to 1 over period `p`." 28 | [p t] 29 | (let [f (math/floor (+ (/ (* 2 t) p) 0.5))] 30 | (* (/ 4 p) (- t (* (/ p 2) f)) (math/pow -1 f)))) 31 | 32 | (defn triangle01 33 | "Linear wave function from 0 to 1 over period `p`." 34 | [p t] 35 | (* 2 (abs (- (/ t p) (math/floor (+ (/ t p) (/ 1 2))))))) 36 | 37 | (defn sinc [x] 38 | (let [t (* x eq/TAU)] 39 | (if (zero? t) 40 | 0 41 | (/ (math/sin t) t)))) 42 | 43 | (comment 44 | (map (fn [t] [t (square 1.0 t)]) (range -2 2 0.1)) 45 | (map (fn [t] [t (pulse 1.0 0.25 t)]) (range -2 2 0.125)) 46 | (map (fn [t] [t (pulse 1.0 0.75 t)]) (range -2 2 0.125)) 47 | (map (fn [t] [t (pulse 2.0 0.25 t)]) (range -2 2 0.125)) 48 | (map (fn [t] [t (sawtooth 1.0 t)]) (range -2 2 0.1)) 49 | (map (fn [t] [t (triangle (/ 1.0 9) t)]) (range -2 2 0.1)) 50 | (map (fn [t] [t (triangle01 1 t)]) (range -1 1 0.1)) 51 | (map (fn [t] [t (sinc t)]) (range -3 3 0.1))) 52 | -------------------------------------------------------------------------------- /tests.edn: -------------------------------------------------------------------------------- 1 | #kaocha/v1 2 | {:tests [{:id :unit-cljs 3 | :type :kaocha.type/cljs 4 | :source-paths ["src"] 5 | :test-paths ["test"] 6 | ;; :cljs/repl-env cljs.repl.node/repl-env ; this is the default 7 | :cljs/repl-env cljs.repl.browser/repl-env 8 | 9 | :cljs/timeout 20000 ;; github ci needs more timeout? 10 | :cljs/compiler-options 11 | {:externs [#_"p5-externs.js" 12 | "sparkles-externs.js" 13 | "delaunator-externs.js" 14 | "d3-delaunay-externs.js"] 15 | :foreign-libs [#_{:file "node_modules/p5/lib/p5.js" 16 | :file-min "node_modules/p5/lib/p5.min.js" 17 | :provides ["cljsjs.p5"] 18 | :global-exports {p5 P5}} 19 | {:file "node_modules/sparkles/src/sparkles.js" 20 | :provides ["sparkles"]} 21 | {:file "node_modules/delaunator/delaunator.js" 22 | :file-min "node_modules/delaunator/delaunator.min.js" 23 | :provides ["delaunator"]} 24 | {:file "node_modules/d3-delaunay/dist/d3-delaunay.js" 25 | :file-min "node_modules/d3-delaunay/dist/d3-delaunay.min.js" 26 | :provides ["d3-delaunay"]}]} 27 | :infer-externs true} 28 | {:id :unit-cljc 29 | :type :kaocha.type/clojure.test 30 | :source-paths ["src"] 31 | :test-paths ["test"]}] 32 | ;; :capture-output? false 33 | ;; :bindings {kaocha.type.cljs/*debug* true} 34 | :reporter [kaocha.report/documentation] 35 | } 36 | -------------------------------------------------------------------------------- /src/shimmers/sketches/flower_petals.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.flower-petals 2 | (:require 3 | [clojure.math :as math] 4 | [quil.core :as q :include-macros true] 5 | [quil.middleware :as m] 6 | [shimmers.common.framerate :as framerate] 7 | [shimmers.common.quil :as cq] 8 | [shimmers.common.ui.controls :as ctrl] 9 | [shimmers.math.equations :as eq] 10 | [shimmers.math.vector :as v] 11 | [shimmers.sketch :as sketch :include-macros true])) 12 | 13 | (defn setup [] 14 | (q/color-mode :hsl 1.0) 15 | {:t 0.0}) 16 | 17 | (defn update-state [state] 18 | (update state :t + 0.005)) 19 | 20 | (defn r-pos [r0 r blades theta] 21 | (+ r0 (* r (math/cos (* blades theta))))) 22 | 23 | (defn draw [{:keys [t]}] 24 | (q/background 1.0) 25 | (q/ellipse-mode :radius) 26 | (q/stroke 0.0) 27 | (let [center (cq/rel-vec 0.5 0.5) 28 | r0 (+ (cq/rel-h 0.1) (* (cq/rel-h 0.05) (math/sin t))) 29 | r (cq/rel-h 0.35) 30 | blades (+ 5 (* 4 (math/cos (* 0.1 t)))) 31 | dt 0.05] 32 | (q/fill 0.0 0.3 0.3) 33 | ;; (cq/circle (v/+polar center (r-pos r0 r blades t) t) 3) 34 | (q/no-fill) 35 | (q/begin-shape) 36 | (doseq [theta (range 0 (* (inc blades) eq/TAU) dt)] 37 | (let [p (v/+polar center 38 | (r-pos r0 r blades theta) 39 | (- t theta)) 40 | [x y] p] 41 | (q/curve-vertex x y)))) 42 | (q/end-shape)) 43 | 44 | (defn page [] 45 | (sketch/component 46 | :size [800 600] 47 | :setup setup 48 | :update update-state 49 | :draw draw 50 | :middleware [m/fun-mode framerate/mode])) 51 | 52 | (sketch/definition flower-petals 53 | {:created-at "2023-01-09" 54 | :tags #{:genuary2023} 55 | :type :quil} 56 | (ctrl/mount page)) 57 | -------------------------------------------------------------------------------- /resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | shimmers 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | © 2020-2025 Charles L.G. Comstock 29 | (portfolio) 30 | (github) 31 | rev:abcdef12 32 |
33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/shimmers/sketches/offsetting_arcs.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.offsetting-arcs 2 | (:require 3 | [clojure.math :as math] 4 | [quil.core :as q :include-macros true] 5 | [quil.middleware :as m] 6 | [shimmers.common.framerate :as framerate] 7 | [shimmers.common.ui.controls :as ctrl] 8 | [shimmers.math.equations :as eq] 9 | [shimmers.sketch :as sketch :include-macros true] 10 | [thi.ng.math.core :as tm])) 11 | 12 | ;; A variation on https://twitter.com/incre_ment/status/1482584406933979136 13 | (defn setup [] 14 | (set! (.-disableFriendlyErrors js/p5) true) 15 | (q/color-mode :hsl 1.0) 16 | {:t 0}) 17 | 18 | (defn update-state [state] 19 | (update state :t + 0.005)) 20 | 21 | ;; TODO: consider using video as input instead of noise? 22 | ;; also any means to up the resolution 23 | (defn draw [{:keys [t]}] 24 | (q/background 1.0) 25 | (q/ellipse-mode :radius) 26 | (q/no-stroke) 27 | (q/fill 0.0 0.4) 28 | ;; (q/no-stroke) 29 | (let [scale 10] 30 | (doseq [a (range 0 (q/width) scale)] 31 | (doseq [b (range 0 (q/height) scale)] 32 | (let [n1 (q/noise (* a 0.01) (* b 0.01) t) 33 | n2 (q/noise (* tm/PHI t) (* b 0.003) (* a 0.003)) 34 | theta (* eq/TAU n2) 35 | x (+ a (* n1 (math/tan (* eq/TAU (+ t n1)))))] 36 | (q/arc x (+ b (- x a)) scale scale 37 | (+ theta (* eq/TAU (math/sin t))) 38 | (+ theta (* eq/TAU n1)))))))) 39 | 40 | (defn page [] 41 | (sketch/component 42 | :size [800 600] 43 | :setup setup 44 | :update update-state 45 | :draw draw 46 | :middleware [m/fun-mode framerate/mode])) 47 | 48 | (sketch/definition offsetting-arcs 49 | {:created-at "2022-01-16" 50 | :tags #{} 51 | :type :quil} 52 | (ctrl/mount page)) 53 | -------------------------------------------------------------------------------- /src/shimmers/sketches/angle_of_ascent.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.angle-of-ascent 2 | (:require 3 | [clojure.math :as math] 4 | [shimmers.common.svg :as csvg] 5 | [shimmers.common.ui.controls :as ctrl] 6 | [shimmers.common.ui.svg :as usvg] 7 | [shimmers.sketch :as sketch :include-macros true] 8 | [thi.ng.geom.core :as g] 9 | [thi.ng.geom.line :as gl] 10 | [thi.ng.geom.rect :as rect] 11 | [thi.ng.geom.vector :as gv])) 12 | 13 | (def width 800) 14 | (def height 600) 15 | (defn rv [x y] 16 | (gv/vec2 (* width x) (* height y))) 17 | 18 | (defn lines [{p :p [w h] :size} f n] 19 | (for [i (range 0 n) 20 | :let [x (f (/ i n))]] 21 | (-> (gl/line2 (gv/vec2 (* w x) 0) 22 | (gv/vec2 (* w x) h)) 23 | (g/translate p)))) 24 | 25 | (defn shapes [] 26 | (let [a (rect/rect (rv 0.1 0.75) (rv 0.45 0.9)) 27 | b (rect/rect (rv 0.325 0.425) (rv 0.675 0.575)) 28 | c (rect/rect (rv 0.55 0.1) (rv 0.9 0.25))] 29 | (concat [a b c] 30 | (lines a (fn [x] (- 1 (math/pow x 2.5))) 30) 31 | (lines b (fn [x] 32 | (let [k 1.4] 33 | (if (< x 0.5) 34 | (- 0.5 (math/pow x k)) 35 | (+ 0.5 (math/pow (- x 0.5) k))))) 50) 36 | (lines c (fn [x] (math/pow x 2.5)) 30)))) 37 | 38 | (defn scene [{:keys [scene-id]}] 39 | (csvg/svg-timed {:id scene-id 40 | :width width 41 | :height height 42 | :stroke "black" 43 | :fill "white" 44 | :stroke-width 1.0} 45 | (shapes))) 46 | 47 | (sketch/definition angle-of-ascent 48 | {:created-at "2022-01-31" 49 | :type :svg 50 | :tags #{}} 51 | (ctrl/mount (usvg/page sketch-args scene))) 52 | -------------------------------------------------------------------------------- /src/shimmers/sketches/spiral_approach.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.spiral-approach 2 | (:require 3 | [clojure.math :as math] 4 | [shimmers.common.svg :as csvg :include-macros true] 5 | [shimmers.common.ui.controls :as ctrl] 6 | [shimmers.math.deterministic-random :as dr] 7 | [shimmers.math.equations :as eq] 8 | [shimmers.math.vector :as v] 9 | [shimmers.sketch :as sketch :include-macros true] 10 | [shimmers.view.sketch :as view-sketch] 11 | [thi.ng.geom.line :as gl] 12 | [thi.ng.geom.vector :as gv] 13 | [thi.ng.math.core :as tm])) 14 | 15 | (def width 800) 16 | (def height 600) 17 | (defn rv [x y] 18 | (gv/vec2 (* width x) (* height y))) 19 | 20 | (defn spiral [p n v s] 21 | (for [f (tm/norm-range n) 22 | :let [t (+ f s)]] 23 | (v/+polar p 24 | (* tm/PHI (math/pow v (+ s (* 9 t)))) 25 | (* 7 t eq/TAU)))) 26 | 27 | (defn shapes [] 28 | (for [_s (tm/norm-range 10)] 29 | (gl/linestrip2 (spiral (rv 0.5 0.5) 30 | (dr/random-int 1500 2500) 31 | (+ tm/PHI (dr/gaussian 0.0 0.2)) 32 | (dr/gaussian 0.2 0.1))))) 33 | 34 | (defn scene [] 35 | (csvg/svg-timed {:width width 36 | :height height 37 | :stroke "black" 38 | :fill "white" 39 | :stroke-width 0.66} 40 | (shapes))) 41 | 42 | (defn page [] 43 | (fn [] 44 | [sketch/with-explanation 45 | [:div.canvas-frame [scene]] 46 | [view-sketch/generate :spiral-approach] 47 | [:div.readable-width 48 | "Experimenting with varying the exponential factor of a spiral."]])) 49 | 50 | (sketch/definition spiral-approach 51 | {:created-at "2024-02-10" 52 | :tags #{} 53 | :type :svg} 54 | (ctrl/mount page)) 55 | -------------------------------------------------------------------------------- /src/shimmers/sketches/sunflower.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.sunflower 2 | (:require 3 | [clojure.math :as math] 4 | [quil.core :as q :include-macros true] 5 | [quil.middleware :as m] 6 | [shimmers.common.framerate :as framerate] 7 | [shimmers.common.quil :as cq] 8 | [shimmers.common.ui.controls :as ctrl] 9 | [shimmers.math.equations :as eq] 10 | [shimmers.math.vector :as v] 11 | [shimmers.sketch :as sketch :include-macros true])) 12 | 13 | ;; https://stackoverflow.com/questions/28567166/uniformly-distribute-x-points-inside-a-circle 14 | (defn setup [] 15 | (q/color-mode :hsl 1.0) 16 | {:alpha 0.0 17 | :points 768}) 18 | 19 | (defn update-state [state] 20 | (let [t (/ (q/millis) 1000.0)] 21 | (assoc state :alpha (+ 10.0 (* 10.0 (math/sin (* 0.15 t)))) 22 | :t t))) 23 | 24 | (defn draw [{:keys [points alpha t]}] 25 | (q/background 1.0) 26 | (let [center (cq/rel-vec 0.5 0.5) 27 | radius (* 0.45 (min (q/height) (q/width))) 28 | exterior (min (int (* alpha (math/sqrt points))) points) 29 | interior (- points exterior) 30 | k-theta (* eq/TAU (+ (- 3 (math/sqrt 5)) (* 0.025 (math/sin (* 0.05 (/ t eq/TAU))))))] 31 | (dotimes [i points] 32 | (let [r (if (< i interior) (/ (float i) (inc interior)) 1.0) 33 | theta (+ (* 0.05 t) (* i k-theta))] 34 | (cq/circle (v/+polar center (* r radius) theta) 35 | (+ 2.5 (* 1.5 (math/sin (+ t theta))))))))) 36 | 37 | (defn page [] 38 | [:div 39 | (sketch/component 40 | :size [800 600] 41 | :setup setup 42 | :update update-state 43 | :draw draw 44 | :middleware [m/fun-mode framerate/mode])]) 45 | 46 | (sketch/definition sunflower 47 | {:created-at "2024-04-08" 48 | :tags #{} 49 | :type :quil} 50 | (ctrl/mount page)) 51 | -------------------------------------------------------------------------------- /src/shimmers/common/ui/debug.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.common.ui.debug 2 | #?(:cljs 3 | (:require 4 | [shimmers.common.edn :as sc-edn] 5 | [shimmers.common.ui.controls :as ctrl]))) 6 | 7 | #?(:cljs (def untyped sc-edn/untyped)) 8 | 9 | ;; cljs only 10 | (defmacro time-it 11 | "Evaluates expr and stores it in debug `atom` at `key`. Returns the value of expr." 12 | [atom key expr] 13 | `(let [start# (cljs.core/system-time) 14 | ret# ~expr] 15 | (cljs.core/swap! ~atom cljs.core/assoc-in ~key 16 | (cljs.core/str (.toFixed (- (cljs.core/system-time) start#) 3) 17 | " msecs")) 18 | ret#)) 19 | 20 | ;; cljs only 21 | (defmacro span-prof 22 | [desc expr] 23 | `(let [start# (cljs.core/system-time) 24 | ret# ~expr 25 | stop# (cljs.core/system-time)] 26 | (cljs.core/tap> [:profile {:desc ~desc :start start# :stop stop#}]) 27 | ret#)) 28 | 29 | (defn profile-to [sink] 30 | (fn [tap-value] 31 | (when (= :profile (first tap-value)) 32 | (swap! sink conj (second tap-value))))) 33 | 34 | #?(:cljs 35 | (defn pre-edn 36 | ([edn] (pre-edn edn {})) 37 | ([edn options] 38 | [:pre.debug [:code (with-out-str (sc-edn/pprint edn options))]]))) 39 | 40 | #?(:cljs 41 | (defn display 42 | ([atom] 43 | (display atom {})) 44 | ([atom params] 45 | (pre-edn (deref atom) params)))) 46 | 47 | #?(:cljs 48 | (do 49 | (defn state 50 | ([] (ctrl/state {})) 51 | ([init] (ctrl/state init))))) 52 | 53 | (defn with-tap-log [f] 54 | (let [tap-log (atom []) 55 | result (with-redefs [tap> (fn [v] (swap! tap-log conj v) true)] 56 | (f))] 57 | {:result result :log @tap-log})) 58 | 59 | (comment (with-tap-log (fn [] (tap> 1) (tap> 2) 3))) 60 | -------------------------------------------------------------------------------- /test/shimmers/math/geometry/triangle_test.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.math.geometry.triangle-test 2 | (:require 3 | [clojure.test :as t :refer [deftest is] :include-macros true] 4 | [shimmers.math.geometry.triangle :as sut] 5 | [thi.ng.geom.core :as g] 6 | [thi.ng.geom.triangle :as gt] 7 | [thi.ng.geom.vector :as gv] 8 | [thi.ng.math.core :as tm])) 9 | 10 | (deftest decomposition 11 | (let [t (gt/triangle2 [0 0] [0 10] [10 0])] 12 | (is (= [(gt/triangle2 [0 10] [5 5] [0 0]) 13 | (gt/triangle2 [10 0] [5 5] [0 0])] 14 | (sut/decompose t {:sample (fn [] 0.5)}))) 15 | (is (= [(gt/triangle2 [0 10] [10 0] [5 5]) 16 | (gt/triangle2 [10 0] [0 0] [5 5]) 17 | (gt/triangle2 [0 0] [0 10] [5 5])] 18 | (sut/decompose t {:mode :centroid 19 | :inner-point (fn [_] (gv/vec2 5 5))}))) 20 | (is (= [(gt/triangle2 [[0 10] [5 5] [0 5]]) 21 | (gt/triangle2 [[10 0] [5 5] [5 0]]) 22 | (gt/triangle2 [0 0] [5 0] [0 5]) 23 | (gt/triangle2 [5 5] [5 0] [0 5])] 24 | (sut/decompose t {:mode :inset 25 | :sample (fn [] 0.5)}))) 26 | (is (= [(gt/triangle2 [[0 10] [2 8] [0 0]]) 27 | (gt/triangle2 [[10 0] [8 2] [0 0]]) 28 | (gt/triangle2 [2 8] [8 2] [0 0])] 29 | (sut/decompose t {:mode :trisect 30 | :sample-low (fn [] 0.2) 31 | :sample-high (fn [] 0.8)}))))) 32 | 33 | (deftest area 34 | (let [t (gt/triangle2 [0 0] [2 0] [0 2])] 35 | (is (tm/delta= 2 (g/area t))) 36 | (is (tm/delta= 2 (sut/signed-area t)))) 37 | (let [t (gt/triangle2 [2 0] [0 0] [0 2])] 38 | (is (tm/delta= 2 (g/area t))) 39 | (is (tm/delta= -2 (sut/signed-area t))))) 40 | 41 | (comment (t/run-tests)) 42 | -------------------------------------------------------------------------------- /src/shimmers/algorithm/markov.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.algorithm.markov 2 | (:require [clojure.math.combinatorics :as mc])) 3 | 4 | ;; inspired by https://jackrusher.com/journal/markov-creativity.html 5 | (defn learn 6 | ([dataset prefix-len suffix-len] 7 | (learn dataset prefix-len suffix-len identity {})) 8 | ([dataset prefix-len suffix-len reconstitute model] 9 | (->> dataset 10 | (partition (+ prefix-len suffix-len) 1) 11 | (reduce (fn [m example] 12 | (let [[prefix suffix] (split-at prefix-len example)] 13 | (update m (reconstitute prefix) 14 | (fnil #(conj % (reconstitute suffix)) [])))) 15 | model)))) 16 | 17 | (defn predict 18 | ([model] (predict model (-> model keys rand-nth))) 19 | ([model start] 20 | (if-let [transitions (get model start)] 21 | (let [choice (rand-nth transitions)] 22 | (lazy-cat (take 1 start) (predict model (concat (drop 1 start) choice)))) 23 | start))) 24 | 25 | (defn combine [& args] 26 | (apply merge-with (comp vec concat) args)) 27 | 28 | (defn all-transitions 29 | "Calculate all possible transitions from a set of states." 30 | [states prefix-len] 31 | (apply combine 32 | (for [prefix (mc/selections states prefix-len) 33 | suffix states] 34 | {prefix [(list suffix)]}))) 35 | 36 | (comment 37 | (def example "the quick brown fox jumped over the lazy dog") 38 | (learn example 2 1) 39 | (learn example 2 2 (partial apply str) {}) 40 | 41 | (apply str (take 100 (predict (learn example 3 1) (seq "the")))) 42 | (apply str (take 100 (predict (learn example 2 1)))) 43 | 44 | (combine (learn "the quick" 2 1) (learn "the brown" 2 1)) 45 | (take 10 (predict (all-transitions [:a :b :c] 1))) 46 | (take 10 (predict (all-transitions [:a :b :c] 2)))) 47 | -------------------------------------------------------------------------------- /src/shimmers/common/ui/quil.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.common.ui.quil 2 | (:require 3 | [quil.core :as q :include-macros true] 4 | [reagent.core :as r])) 5 | 6 | (defn fps-overlay [performance-id] 7 | [:div.performance 8 | {:id performance-id 9 | :style {:position "absolute" 10 | :color "#000" 11 | :background "#ddd" 12 | :opacity 0.66 13 | :right 0 14 | :top 0 15 | :padding "0.1em 0.33em" 16 | :z-index 100}}]) 17 | 18 | (defn configure-fps-overlay [sketch-args] 19 | (let [overlay (:performance-id sketch-args) 20 | performance-id (if (= overlay :fps-overlay) 21 | (name (gensym "performance_")) 22 | "framerate")] 23 | (assoc sketch-args :performance-id performance-id))) 24 | 25 | ;; Amalgamation of: 26 | ;; https://github.com/quil/quil/issues/320#issuecomment-534859573 27 | ;; https://github.com/simon-katz/nomisdraw/blob/for-quil-api-request/src/cljs/nomisdraw/utils/nomis_quil_on_reagent.cljs 28 | 29 | (defn sketch-component [sketch-args] 30 | (let [!dom-node (get sketch-args :dom-node (atom nil)) 31 | {:keys [performance-id] :as options} (configure-fps-overlay sketch-args)] 32 | (r/create-class 33 | {:display-name "quil-sketch" 34 | :component-did-mount 35 | (fn [] 36 | (apply q/sketch (apply concat (assoc options :host @!dom-node)))) 37 | :component-will-unmount 38 | (fn [] 39 | (when-let [div-host @!dom-node] 40 | (q/with-sketch (.-processing-obj div-host) (q/exit)))) 41 | :render 42 | (fn [_sketch-args] 43 | [:div.canvas-frame {:style {:position "relative"} 44 | :ref (fn [el] (reset! !dom-node el))} 45 | (when-not (= performance-id "framerate") 46 | [fps-overlay performance-id])])}))) 47 | -------------------------------------------------------------------------------- /src/shimmers/math/geometry/line.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.math.geometry.line 2 | (:require 3 | [thi.ng.geom.core :as g] 4 | [thi.ng.geom.vector :as gv] 5 | [thi.ng.math.core :as tm] 6 | #?(:clj [thi.ng.geom.types] 7 | :cljs [thi.ng.geom.types :refer [Line2 Line3]])) 8 | #?(:clj 9 | (:import [thi.ng.geom.types Line2 Line3]))) 10 | 11 | (extend-type Line2 12 | tm/IDeltaEquals 13 | (delta= 14 | ([_ line] (tm/delta= _ line tm/*eps*)) 15 | ([{[a b] :points} line eps] 16 | (and (instance? Line2 line) 17 | (let [{[c d] :points} line] 18 | (and (tm/delta= a c eps) (tm/delta= b d eps)))))) 19 | g/IHeading 20 | (heading [{[p q] :points}] 21 | (g/heading (tm/- q p))) 22 | (heading-xy [{[p q] :points}] 23 | (g/heading-xy (tm/- q p))) 24 | (angle-between [{[p q] :points} v] 25 | (g/angle-between (tm/- q p) (gv/vec2 v))) 26 | (slope-xy [{[p q] :points}] 27 | (g/slope-xy (tm/- q p)))) 28 | 29 | (extend-type Line3 30 | tm/IDeltaEquals 31 | (delta= 32 | ([_ line] (tm/delta= _ line tm/*eps*)) 33 | ([{[a b] :points} line eps] 34 | (and (instance? Line3 line) 35 | (let [{[c d] :points} line] 36 | (and (tm/delta= a c eps) (tm/delta= b d eps)))))) 37 | g/IHeading 38 | (heading [{[p q] :points}] 39 | (g/heading (tm/- q p))) 40 | (heading-xy [{[p q] :points}] 41 | (g/heading-xy (tm/- q p))) 42 | (heading-xz [{[p q] :points}] 43 | (g/heading-xz (tm/- q p))) 44 | (heading-yz [{[p q] :points}] 45 | (g/heading-yz (tm/- q p))) 46 | (angle-between [{[p q] :points} v] 47 | (g/angle-between (tm/- q p) (gv/vec3 v))) 48 | (slope-xy [{[p q] :points}] 49 | (g/slope-xy (tm/- q p))) 50 | (slope-xz [{[p q] :points}] 51 | (g/slope-xz (tm/- q p))) 52 | (slope-yz [{[p q] :points}] 53 | (g/slope-yz (tm/- q p)))) 54 | 55 | ;; TODO generic midpoint? 56 | -------------------------------------------------------------------------------- /src/shimmers/sketches/stair_demo.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.stair-demo 2 | (:require 3 | [shimmers.common.sequence :as cs] 4 | [shimmers.common.svg :as csvg :include-macros true] 5 | [shimmers.common.ui.controls :as ctrl] 6 | [shimmers.common.ui.svg :as usvg] 7 | [shimmers.math.stair :as ms] 8 | [shimmers.sketch :as sketch :include-macros true] 9 | [thi.ng.geom.line :as gl] 10 | [thi.ng.geom.vector :as gv])) 11 | 12 | (def width 800) 13 | (def height 600) 14 | (defn rv [x y] 15 | (gv/vec2 (* width x) (* height y))) 16 | 17 | (defn plot 18 | ([f] 19 | (plot f {:resolution 0.0025})) 20 | ([f {:keys [resolution]}] 21 | (mapv (fn [x] (rv x (- 1.0 (f x)))) 22 | (range 0 1 resolution)))) 23 | 24 | (defn shapes [k] 25 | (let [w 0.0075] 26 | [(csvg/path (csvg/segmented-path (plot (fn [x] (ms/staircase k x)))) 27 | {:stroke-width 1.5}) 28 | (csvg/group {:stroke-width 0.66} 29 | (cs/midsection 30 | (for [x (range 0 1 0.1)] 31 | (gl/line2 (rv x (- 1.0 w)) (rv x 1.0))))) 32 | (csvg/group {:stroke-width 0.66} 33 | (cs/midsection 34 | (for [y (range 0 1 0.1)] 35 | (gl/line2 (rv 0.0 y) (rv w y)))))])) 36 | 37 | (defonce ui-state (ctrl/state {:k 3.0})) 38 | 39 | (defn scene [{:keys [scene-id]}] 40 | (let [{:keys [k]} @ui-state] 41 | (csvg/svg-timed {:id scene-id 42 | :width width 43 | :height height 44 | :stroke "black" 45 | :fill "none" 46 | :stroke-width 0.5} 47 | (shapes k)))) 48 | 49 | (defn ui-controls [] 50 | [:div.contained 51 | [ctrl/numeric ui-state "k" [:k] [0.01 32.0 0.01]]]) 52 | 53 | (sketch/definition stair-demo 54 | {:created-at "2025-11-17" 55 | :tags #{:demo} 56 | :type :svg} 57 | (ctrl/mount (usvg/page sketch-args ui-controls scene))) 58 | -------------------------------------------------------------------------------- /src/shimmers/sketches/yin_yang.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.yin-yang 2 | (:require 3 | [clojure.math :as math] 4 | [quil.core :as q :include-macros true] 5 | [quil.middleware :as m] 6 | [shimmers.common.framerate :as framerate] 7 | [shimmers.common.quil :as cq] 8 | [shimmers.common.ui.controls :as ctrl] 9 | [shimmers.sketch :as sketch :include-macros true])) 10 | 11 | (defn sum-square [r1 r2] 12 | (+ (* r1 r1) (* r2 r2))) 13 | 14 | (defn setup [] 15 | (q/color-mode :hsl 255 255 255 255) 16 | (let [r-size (cq/rel-h 0.125)] 17 | {:direction 0.001 18 | :r-size r-size 19 | :r1 r-size 20 | :r2 r-size})) 21 | 22 | (defn migrate-volume [{:keys [direction r1 r2 r-size] :as state}] 23 | (let [rN (+ r1 (* direction r2))] 24 | (assoc state 25 | :r1 rN 26 | :r2 (math/sqrt (- (sum-square r-size r-size) (* rN rN)))))) 27 | 28 | (defn update-state [{:keys [r1 r2] :as state}] 29 | (if (or (< r1 10) (< r2 10)) 30 | (migrate-volume (update state :direction * -1)) 31 | (migrate-volume state))) 32 | 33 | (defn draw [{:keys [r1 r2]}] 34 | (q/ellipse-mode :radius) 35 | (q/translate (/ (q/width) 2) (/ (q/height) 2)) 36 | (let [fc (q/frame-count) 37 | sc (/ fc 20)] 38 | (when (#{0 1 3 6 10 15} (mod fc 19)) 39 | (q/rotate (/ fc 180)) 40 | (if (even? fc) 41 | (q/fill 255 255 255 10) 42 | (q/no-fill)) 43 | (q/stroke (mod (+ sc r1) 255) 100 140 48) 44 | (q/ellipse (- r1) 0 r1 r1) 45 | (q/stroke (mod (- sc r2) 255) 100 140 48) 46 | (q/ellipse r2 0 r2 r2)))) 47 | 48 | (defn page [] 49 | (sketch/component 50 | :size [800 600] 51 | :setup setup 52 | :update update-state 53 | :draw draw 54 | :middleware [m/fun-mode framerate/mode])) 55 | 56 | (sketch/definition yin-yang 57 | {:created-at "2021-02-03" 58 | :tags #{} 59 | :type :quil} 60 | (ctrl/mount page)) 61 | -------------------------------------------------------------------------------- /src/shimmers/sketches/hexaflexagon.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.hexaflexagon 2 | (:require 3 | [shimmers.common.svg :as csvg :include-macros true] 4 | [shimmers.common.ui.controls :as ctrl] 5 | [shimmers.math.equations :as eq] 6 | [shimmers.math.vector :as v] 7 | [shimmers.sketch :as sketch :include-macros true] 8 | ;; [shimmers.view.sketch :as view-sketch] 9 | [thi.ng.geom.core :as g] 10 | [thi.ng.geom.triangle :as gt] 11 | [thi.ng.geom.vector :as gv] 12 | [thi.ng.math.core :as tm])) 13 | 14 | (def width 800) 15 | (def height 600) 16 | (defn rv [x y] 17 | (gv/vec2 (* width x) (* height y))) 18 | 19 | (defn triangle-strip [pos dir length n] 20 | (for [i (range n) 21 | :let [p (v/+polar pos (* length (tm/floor (+ 0.5 (/ i 2.0)))) 0)]] 22 | (if (even? i) 23 | (gt/triangle2 p (v/+polar p length 0) (v/+polar p length dir)) 24 | (gt/triangle2 (v/+polar p length (* 2 dir)) p (v/+polar p length dir))))) 25 | 26 | (defn shapes [] 27 | (for [[s l] (mapv vector 28 | (concat (triangle-strip (rv 0.05 0.5) (/ eq/TAU 6) (/ (* width 0.8) 4.5) 9) 29 | (triangle-strip (rv 0.05 0.5) (- (/ eq/TAU 6)) (/ (* width 0.8) 4.5) 9)) 30 | (cycle [1 1 2 2 3 3]))] 31 | (csvg/group {} s 32 | (csvg/center-label (g/centroid s) (str l) {})))) 33 | 34 | (defn scene [] 35 | (csvg/svg-timed {:width width 36 | :height height 37 | :stroke "black" 38 | :fill "none" 39 | :stroke-width 1.0} 40 | (shapes))) 41 | 42 | (defn page [] 43 | (fn [] 44 | [sketch/with-explanation 45 | [:div.canvas-frame [scene]] 46 | ;; [view-sketch/generate :hexaflexagon] 47 | [:div.readable-width]])) 48 | 49 | (sketch/definition hexaflexagon 50 | {:created-at "2024-05-21" 51 | :tags #{} 52 | :type :svg} 53 | (ctrl/mount page)) 54 | -------------------------------------------------------------------------------- /src/shimmers/sketches/rolling_shapes.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.rolling-shapes 2 | (:require 3 | [quil.core :as q :include-macros true] 4 | [quil.middleware :as m] 5 | [shimmers.common.framerate :as framerate] 6 | [shimmers.common.quil :as cq] 7 | [shimmers.common.ui.controls :as ctrl] 8 | [shimmers.sketch :as sketch :include-macros true] 9 | [thi.ng.geom.core :as g] 10 | [thi.ng.geom.line :as gl] 11 | [thi.ng.geom.vector :as gv] 12 | [thi.ng.math.core :as tm])) 13 | 14 | ;; Concept here is to "roll" a particle polygon along a line 15 | ;; Related to https://en.wikipedia.org/wiki/Cyclogon 16 | ;; Currently only playing with rolling a single line 17 | 18 | (defn setup [] 19 | (q/color-mode :hsl 1.0) 20 | (q/frame-rate 30) 21 | {:t 0 22 | :shapes [(gl/line2 (cq/rel-pos 0.1 0.2) (cq/rel-pos 0.2 0.2))]}) 23 | 24 | ;; FIXME: Works for a line on the first pass, but goes below for second 25 | (defn rotate [t shape] 26 | (let [vertices (g/vertices shape) 27 | cycle (mod (q/floor (/ t tm/PI)) 9) 28 | idx (mod (inc cycle) (count vertices)) 29 | vertex (nth vertices idx) 30 | offset (gv/vec2 (* (- cycle idx) (g/width shape)) 0)] 31 | (-> shape 32 | (g/translate (tm/- vertex)) 33 | (g/rotate t) 34 | (g/translate vertex) 35 | (g/translate offset)))) 36 | 37 | (defn update-state [state] 38 | (-> state (update :t + 0.05))) 39 | 40 | (defn draw [{:keys [shapes t]}] 41 | (q/stroke-weight 0.25) 42 | (q/no-fill) 43 | (doseq [s (map (partial rotate t) shapes)] 44 | (cq/draw-path (g/vertices s)))) 45 | 46 | (defn page [] 47 | (sketch/component 48 | :size [800 600] 49 | :setup setup 50 | :update update-state 51 | :draw draw 52 | :middleware [m/fun-mode framerate/mode])) 53 | 54 | (sketch/definition rolling-shapes 55 | {:created-at "2021-06-30" 56 | :tags #{} 57 | :type :quil} 58 | (ctrl/mount page)) 59 | -------------------------------------------------------------------------------- /src/shimmers/sketches/object_permanence.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.object-permanence 2 | (:require 3 | [quil.core :as q :include-macros true] 4 | [quil.middleware :as m] 5 | [shimmers.common.framerate :as framerate] 6 | [shimmers.common.ui.controls :as ctrl] 7 | [shimmers.sketch :as sketch :include-macros true] 8 | [thi.ng.geom.core :as g] 9 | [thi.ng.geom.vector :as gv] 10 | [thi.ng.math.core :as tm])) 11 | 12 | (defn setup [] 13 | {:looking-at (gv/vec2)}) 14 | 15 | (defn mouse-position [] 16 | (let [hx (/ (q/width) 2) 17 | hy (/ (q/height) 2)] 18 | (gv/vec2 (/ (- (q/mouse-x) hx) hx) 19 | (/ (- (q/mouse-y) hy) hy)))) 20 | 21 | (defn update-state [state] 22 | (let [mouse (mouse-position)] 23 | ;; (q/print-every-n-millisec 100 [state mouse]) 24 | (update state :looking-at #(tm/normalize (tm/+ % (g/scale mouse 0.15)))))) 25 | 26 | (defn draw-eye [x y looking-at] 27 | (q/ellipse x y 20 16) 28 | (let [[lx ly] (g/scale looking-at 2) 29 | dx (+ x lx) 30 | dy (+ y ly)] 31 | (q/with-fill 0 32 | (q/ellipse dx dy 2 2)))) 33 | 34 | (defn draw [{:keys [looking-at]}] 35 | (q/background 255) 36 | (let [W (q/width) 37 | H (q/height) 38 | eye-x (/ W 14) 39 | eye-y (- (/ H 10))] 40 | (q/translate (/ W 2) (/ H 2)) 41 | (q/rotate (q/lerp -0.02 0.02 (first looking-at))) 42 | (q/ellipse 0 0 (/ W 3) (/ H 1.5)) 43 | (draw-eye (- eye-x) eye-y looking-at) 44 | (draw-eye eye-x eye-y looking-at) 45 | (let [clip 0.2] 46 | (q/arc 0 (/ H 8) (/ W 6) (/ H 10) clip (- tm/PI clip))))) 47 | 48 | (defn page [] 49 | (sketch/component 50 | :size [800 600] 51 | :setup setup 52 | :update update-state 53 | :draw draw 54 | :middleware [m/fun-mode framerate/mode])) 55 | 56 | (sketch/definition object-permanence 57 | {:created-at "2021-01-23" 58 | :tags #{} 59 | :type :quil} 60 | (ctrl/mount page)) 61 | -------------------------------------------------------------------------------- /src/shimmers/sketches/slither.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.slither 2 | (:require 3 | [clojure.math :as math] 4 | [quil.core :as q :include-macros true] 5 | [quil.middleware :as m] 6 | [shimmers.common.framerate :as framerate] 7 | [shimmers.common.quil :as cq] 8 | [shimmers.common.ui.controls :as ctrl] 9 | [shimmers.math.equations :as eq] 10 | [shimmers.math.vector :as v] 11 | [shimmers.sketch :as sketch :include-macros true] 12 | [thi.ng.math.core :as tm])) 13 | 14 | (defn setup [] 15 | (q/color-mode :hsl 1.0) 16 | {:t 0}) 17 | 18 | (defn update-state [state] 19 | (update state :t + 0.01)) 20 | 21 | (defn draw [{:keys [t]}] 22 | (q/no-fill) 23 | (q/background 1.0 0.3) 24 | (q/translate (cq/rel-vec 0.5 0.5)) 25 | (let [h (* (cq/rel-h 0.49) (+ 0.8 (* 0.2 (+ (* 0.7 (eq/unit-cos (+ 0.1 (* 0.4 t)))) 26 | (* 0.3 (eq/unit-cos (* 1.7 t))))))) 27 | b (* (cq/rel-h 0.05) (+ 0.1 (eq/unit-sin (* 0.65 t))))] 28 | (doseq [v (tm/norm-range 23)] 29 | (q/begin-shape) 30 | (apply q/curve-vertex (v/polar b (* eq/TAU v))) 31 | (apply q/curve-vertex (v/polar (+ b (* h 0.05)) (* eq/TAU (+ v (* 0.075 (math/sin (* 0.33 t))))))) 32 | (apply q/curve-vertex (v/polar (* h (+ 0.5 (* 0.125 (math/cos (* 0.9 t))))) 33 | (* eq/TAU (+ v (* 0.09 (math/sin (* 1.33 t))))))) 34 | (apply q/curve-vertex (v/polar (* h 0.95) (* eq/TAU (+ v (* 0.075 (math/sin (* 0.25 t))))))) 35 | (apply q/curve-vertex (v/polar (* h 1.0) (* eq/TAU v))) 36 | (q/end-shape)))) 37 | 38 | (defn page [] 39 | [:div 40 | (sketch/component 41 | :size [800 600] 42 | :setup setup 43 | :update update-state 44 | :draw draw 45 | :middleware [m/fun-mode framerate/mode])]) 46 | 47 | (sketch/definition slither 48 | {:created-at "2023-07-29" 49 | :tags #{} 50 | :type :quil} 51 | (ctrl/mount page)) 52 | -------------------------------------------------------------------------------- /test/shimmers/algorithm/mosaic_test.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.algorithm.mosaic-test 2 | (:require [shimmers.algorithm.mosaic :as mosaic] 3 | [clojure.test :as t :refer [deftest is] :include-macros true] 4 | [thi.ng.geom.vector :as gv])) 5 | 6 | (def seed [{:pos (gv/vec2 0 0) :fill :a} 7 | {:pos (gv/vec2 1 0) :fill :b} 8 | {:pos (gv/vec2 0 1) :fill :c} 9 | {:pos (gv/vec2 1 1) :fill :d}]) 10 | 11 | (deftest rotate-right 12 | (is (= [{:pos (gv/vec2 0 0) :fill :c} 13 | {:pos (gv/vec2 1 0) :fill :a} 14 | {:pos (gv/vec2 0 1) :fill :d} 15 | {:pos (gv/vec2 1 1) :fill :b}] 16 | (mosaic/rotate-r seed))) 17 | (is (= [{:pos (gv/vec2 0 0) :fill :d} 18 | {:pos (gv/vec2 1 0) :fill :c} 19 | {:pos (gv/vec2 0 1) :fill :b} 20 | {:pos (gv/vec2 1 1) :fill :a}] 21 | (->> seed 22 | mosaic/rotate-r 23 | mosaic/rotate-r))) 24 | (is (= [{:pos (gv/vec2 0 0) :fill :b} 25 | {:pos (gv/vec2 1 0) :fill :d} 26 | {:pos (gv/vec2 0 1) :fill :a} 27 | {:pos (gv/vec2 1 1) :fill :c}] 28 | (->> seed 29 | mosaic/rotate-r 30 | mosaic/rotate-r 31 | mosaic/rotate-r))) 32 | (is (= seed (->> seed 33 | mosaic/rotate-r 34 | mosaic/rotate-r 35 | mosaic/rotate-r 36 | mosaic/rotate-r)))) 37 | 38 | (deftest flip-x 39 | (is (= [{:pos (gv/vec2 1 0) :fill :a} 40 | {:pos (gv/vec2 0 0) :fill :b} 41 | {:pos (gv/vec2 1 1) :fill :c} 42 | {:pos (gv/vec2 0 1) :fill :d}] 43 | (mosaic/flip-x seed)))) 44 | 45 | (deftest flip-y 46 | (is (= [{:pos (gv/vec2 0 1) :fill :a} 47 | {:pos (gv/vec2 1 1) :fill :b} 48 | {:pos (gv/vec2 0 0) :fill :c} 49 | {:pos (gv/vec2 1 0) :fill :d}] 50 | (mosaic/flip-y seed)))) 51 | -------------------------------------------------------------------------------- /src/shimmers/sketches/all_the_shapes_in_between.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.all-the-shapes-in-between 2 | (:require 3 | [shimmers.common.svg :as csvg :include-macros true] 4 | [shimmers.common.ui.controls :as ctrl] 5 | [shimmers.math.geometry.triangle :as triangle] 6 | [shimmers.sketch :as sketch :include-macros true] 7 | [shimmers.view.sketch :as view-sketch] 8 | [thi.ng.geom.circle :as gc] 9 | [thi.ng.geom.core :as g] 10 | [thi.ng.geom.polygon :as gp] 11 | [thi.ng.geom.vector :as gv] 12 | [thi.ng.math.core :as tm])) 13 | 14 | (def width 800) 15 | (def height 600) 16 | (defn rv [x y] 17 | (gv/vec2 (* width x) (* height y))) 18 | 19 | (def shape-seq 20 | (let [circle (gc/circle (gv/vec2) (* 0.05 height))] 21 | [circle 22 | (g/as-polygon circle 5) 23 | (g/bounds circle) 24 | (triangle/inscribed-equilateral circle 0.0)])) 25 | 26 | (defn path [t] 27 | (g/point-at (gc/circle (rv 0.5 0.5) (* height 0.4)) t)) 28 | 29 | (defn morph [from to t] 30 | (for [v (butlast (tm/norm-range 32))] 31 | (tm/mix (g/point-at from v) (g/point-at to v) t))) 32 | 33 | (defn shapes [] 34 | (for [t (butlast (tm/norm-range 24))] 35 | (let [base (int (* t (count shape-seq)))] 36 | (g/translate (gp/polygon2 (morph (nth shape-seq base) 37 | (nth shape-seq (mod (inc base) (count shape-seq))) 38 | (mod (* t (count shape-seq)) 1.0))) 39 | (path t))))) 40 | 41 | (defn scene [] 42 | (csvg/svg-timed {:width width 43 | :height height 44 | :stroke "black" 45 | :fill "white" 46 | :stroke-width 1.0} 47 | (shapes))) 48 | 49 | (sketch/definition all-the-shapes-in-between 50 | {:created-at "2023-01-02" 51 | :type :svg 52 | :tags #{:genuary2023}} 53 | (ctrl/mount (view-sketch/static-page scene :all-the-shapes-in-between))) 54 | -------------------------------------------------------------------------------- /src/shimmers/sketches/opposing_planes.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.opposing-planes 2 | (:require 3 | [clojure.math :as math] 4 | [shimmers.common.svg :as csvg :include-macros true] 5 | [shimmers.common.ui.controls :as ctrl] 6 | [shimmers.common.ui.svg :as usvg] 7 | [shimmers.math.deterministic-random :as dr] 8 | [shimmers.math.equations :as eq] 9 | [shimmers.sketch :as sketch :include-macros true] 10 | [thi.ng.geom.polygon :as gp] 11 | [thi.ng.geom.vector :as gv] 12 | [thi.ng.math.core :as tm])) 13 | 14 | (def width 800) 15 | (def height 600) 16 | (defn rv [x y] 17 | (gv/vec2 (* width x) (* height y))) 18 | 19 | (defn ellipse [p a b] 20 | (gp/polygon2 21 | (for [t (range 0 eq/TAU (/ eq/TAU 100))] 22 | (tm/+ p 23 | (gv/vec2 (* a (math/cos t)) 24 | (* b (math/sin t))))))) 25 | 26 | (defn e-range [r n] 27 | (range 4.0 r (/ r n))) 28 | 29 | (defn shapes [] 30 | (concat 31 | (for [r (e-range (* height 0.4) 20)] 32 | (csvg/group {:stroke-width (tm/clamp (dr/gaussian 1.9 0.8) 0.5 10.0)} 33 | (ellipse (tm/+ (rv 0.5 0.5) (dr/jitter (/ r 12.0))) 34 | (* 1.3 r) (* 0.8 r)))) 35 | (for [r (dr/gaussian-range (* height (/ 0.4 25)) (* height (/ 0.4 50)) 36 | true [4.0 (* height 0.45)])] 37 | (csvg/group {:stroke-width (tm/clamp (dr/gaussian 2.9 1.5) 0.5 10.0)} 38 | (ellipse (tm/+ (rv 0.5 0.5) (dr/jitter (/ r 12.0))) 39 | (* 1.4 r) (* 1.75 r)))))) 40 | 41 | (defn scene [{:keys [scene-id]}] 42 | (csvg/svg-timed {:id scene-id 43 | :width width 44 | :height height 45 | :stroke "black" 46 | :fill "none" 47 | :stroke-width 0.5} 48 | (shapes))) 49 | 50 | (sketch/definition opposing-planes 51 | {:created-at "2025-01-19" 52 | :tags #{} 53 | :type :svg} 54 | (ctrl/mount (usvg/page sketch-args scene))) 55 | -------------------------------------------------------------------------------- /test/shimmers/math/geometry_test.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.math.geometry-test 2 | (:require 3 | [clojure.test :as t :refer [deftest is] :include-macros true] 4 | [shimmers.math.equations :as eq] 5 | [shimmers.math.geometry :as sut] 6 | [thi.ng.geom.circle :as gc] 7 | [thi.ng.geom.core :as g] 8 | [thi.ng.geom.vector :as gv] 9 | [thi.ng.math.core :as tm])) 10 | 11 | (deftest radial-sort 12 | (let [points (map gv/vec2 [[0 0] 13 | [1 0] [1 1] [0 1] 14 | [-1 1] [-1 0] [-1 -1] 15 | [0 -1] [1 -1]])] 16 | (is (tm/delta= [[0 0] [1 0] [1 1] [0 1] [-1 1] [-1 0] [-1 -1] [0 -1] [1 -1]] 17 | (sut/radial-sort (gv/vec2 0 0) points))) 18 | (is (tm/delta= [[1 1] [0 1] [-1 1] [0 0] [1 0] [-1 0] [-1 -1] [0 -1] [1 -1]] 19 | (sut/radial-sort (gv/vec2 2 0) points))))) 20 | 21 | (deftest circle-circle-intersection 22 | (let [c1 (gc/circle 1) 23 | c2 (gc/circle 1)] 24 | (is (some? (g/intersect-shape c1 c2)) 25 | "directly overlapping is an intersection with no resolution") 26 | ;; Returns [(gv/vec2 (/ 0 0) (/ 0 0)) (gv/vec2 (/ 0 0) (/ 0 0))] 27 | (is (tm/delta= (g/intersect-shape c1 (g/translate c2 (gv/vec2 0.5 0))) 28 | [(gv/vec2 0.25 0.968246) (gv/vec2 0.25 -0.968246)]) 29 | "at direct overlap find the intersection points") 30 | (is (= (g/intersect-shape c1 (g/translate c2 (gv/vec2 1 0))) 31 | [(gv/vec2 0.5 eq/SQRT3_2) (gv/vec2 0.5 (- eq/SQRT3_2))]) 32 | "at direct overlap find the intersection points") 33 | (is (= (g/intersect-shape c1 (g/translate c2 (gv/vec2 2 0))) 34 | [(gv/vec2 1 0) (gv/vec2 1 0)]) 35 | "at furtherest overlap find the intersection points") 36 | (is (not (g/intersect-shape c1 (g/translate c2 (gv/vec2 2.0001 0)))) 37 | "outside of range, return no intersection"))) 38 | 39 | (comment (t/run-tests)) 40 | -------------------------------------------------------------------------------- /src/shimmers/scratch/geometry.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.scratch.geometry 2 | (:require 3 | [shimmers.algorithm.polygon-detection :as poly-detect] 4 | [thi.ng.geom.core :as g] 5 | [thi.ng.geom.line :as gl] 6 | [thi.ng.geom.rect :as rect] 7 | [thi.ng.geom.utils :as gu] 8 | [thi.ng.geom.vector :as gv] 9 | [thi.ng.math.core :as tm])) 10 | 11 | ;; FIXME: inlined from geom.utils, to explore problems with centroid of concave 12 | ;; polygons 13 | (defn fit-all-into-bounds 14 | "Takes an AABB or rect and seq of shapes, proportionally scales and 15 | repositions all items to fit into given bounds. Returns lazyseq of 16 | transformed entities. Does not support collections of mixed 2D/3D 17 | entities. Use rects as target bounds for 2D colls." 18 | [bounds coll] 19 | ;; check minimum bounds 20 | (let [b (update (gu/coll-bounds coll) :size tm/max (gv/vec2 1 1)) 21 | s (reduce min (tm/div (get bounds :size) (get b :size))) 22 | b' (g/center (g/scale b s) (g/centroid bounds)) 23 | concave (some poly-detect/concave? coll)] 24 | (for [shape coll 25 | ;; temporary hardcoded offset 26 | :let [center (if (and concave (> (count (:points shape)) 2)) 27 | (tm/+ (g/centroid shape) (gv/vec2 0 2)) 28 | (g/centroid shape))]] 29 | (-> shape 30 | (g/center (g/unmap-point b' (g/map-point b center))) 31 | (g/scale-size s))))) 32 | 33 | (comment 34 | (let [horizontal (gl/line2 [0 5] [10 5]) 35 | vertical (gl/line2 [5 0] [5 10]) 36 | diagonal (gl/line2 [0 0] [10 10]) 37 | bounds (rect/rect 0 0 8 8)] 38 | {:bounds-h (gu/coll-bounds [horizontal]) 39 | :bounds-v (gu/coll-bounds [vertical]) 40 | :bounds-d (gu/coll-bounds [diagonal]) 41 | :fit-h (fit-all-into-bounds bounds [horizontal]) 42 | :fit-v (fit-all-into-bounds bounds [vertical]) 43 | :fit-d (fit-all-into-bounds bounds [diagonal])})) 44 | -------------------------------------------------------------------------------- /src/shimmers/sketches/marching_squares.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.marching-squares 2 | (:require 3 | [clojure.math :as math] 4 | [quil.core :as q :include-macros true] 5 | [quil.middleware :as m] 6 | [shimmers.algorithm.marching-squares :as iso] 7 | [shimmers.common.framerate :as framerate] 8 | [shimmers.common.quil :as cq] 9 | [shimmers.common.ui.controls :as ctrl] 10 | [shimmers.sketch :as sketch :include-macros true] 11 | [thi.ng.math.core :as tm] 12 | [thi.ng.strf.core :as f])) 13 | 14 | (defonce ui-state 15 | (ctrl/state {:threshold 0.5 16 | :divisor 7.0})) 17 | 18 | (defn noise [s t x y] 19 | (q/noise (* x s) (* y s) t)) 20 | 21 | (defn setup [] 22 | (q/color-mode :hsl 1.0) 23 | {:n 40}) 24 | 25 | (defn draw [{:keys [n]}] 26 | (let [t (/ (q/millis) 50000.0) 27 | sx (/ (q/width) n) 28 | sy (/ (q/height) n) 29 | {:keys [threshold divisor]} @ui-state 30 | m (/ 1 (math/pow 2 divisor))] 31 | (doseq [px (tm/norm-range n)] 32 | (doseq [py (tm/norm-range n)] 33 | (let [[x y] (cq/rel-vec px py)] 34 | (q/no-stroke) 35 | (q/fill (noise m t x y)) 36 | (q/rect x y sx sy) 37 | (q/no-fill) 38 | (q/stroke 0.0 1.0) 39 | (doseq [[p q] (iso/lines [x y] [sx sy] (partial noise m t) threshold)] 40 | (q/line p q))))))) 41 | 42 | (defn page [] 43 | [sketch/with-explanation 44 | (sketch/component 45 | :size [800 800] 46 | :setup setup 47 | :draw draw 48 | :middleware [m/fun-mode framerate/mode]) 49 | [ctrl/container 50 | [ctrl/slider ui-state (fn [v] (f/format ["Divisor 1 / 2 ^ " (f/float 1)] v)) 51 | [:divisor] [5.0 12.0 0.1]] 52 | [ctrl/slider ui-state (fn [v] (str "Threshold " v)) 53 | [:threshold] [0.0 1.0 0.01]]]]) 54 | 55 | (sketch/definition marching-squares 56 | {:created-at "2021-09-20" 57 | :tags #{} 58 | :type :quil} 59 | (ctrl/mount page)) 60 | -------------------------------------------------------------------------------- /resources/public/shaders/video-shader.frag.c: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision mediump float; 3 | #endif 4 | 5 | varying vec2 vTexCoord; 6 | 7 | uniform vec2 u_resolution; 8 | uniform vec2 u_mouse; 9 | uniform float u_time; 10 | uniform int u_mode; 11 | 12 | uniform sampler2D videoTexture; 13 | 14 | void specular_mouse() { 15 | vec2 mp = u_mouse/u_resolution; 16 | vec2 st = gl_FragCoord.xy/u_resolution; 17 | float pct = 0.0; 18 | pct = distance(st-(mp-vec2(0.2)), vec2(1.5)); 19 | 20 | vec2 pos = vTexCoord; 21 | pos.y = 1.0 - pos.y; 22 | 23 | vec4 tex = texture2D(videoTexture, pos); 24 | vec4 color = vec4(tex.rgb, 1.0); 25 | vec4 blend = vec4(st.x, st.y, (st.x + st.y), abs(sin(u_time/1.2))); 26 | gl_FragColor = mix(color, blend, clamp(1.2-pct, -0.3, 0.5)); 27 | } 28 | 29 | void kernel(inout vec4 n[9], sampler2D tex, vec2 pos, float w, float h) { 30 | n[0] = texture2D(tex, pos + vec2(-w,-h)); 31 | n[1] = texture2D(tex, pos + vec2(0.0, -h)); 32 | n[2] = texture2D(tex, pos + vec2(w, -h)); 33 | n[3] = texture2D(tex, pos + vec2(-w, 0.0)); 34 | n[4] = texture2D(tex, pos); 35 | n[5] = texture2D(tex, pos + vec2(w, 0.0)); 36 | n[6] = texture2D(tex, pos + vec2(-w, h)); 37 | n[7] = texture2D(tex, pos + vec2(0.0, h)); 38 | n[8] = texture2D(tex, pos + vec2(w, h)); 39 | } 40 | 41 | void edge_detection() { 42 | vec2 pos = vTexCoord; 43 | pos.y = 1.0 - pos.y; 44 | 45 | vec4 n[9]; 46 | kernel(n, videoTexture, pos, 2.0/u_resolution.x, 2.0/u_resolution.y); 47 | vec4 edge = 8.0*n[4] - n[0] - n[1] - n[2] - n[3] - n[5] - n[6] - n[7] - n[8]; 48 | 49 | vec4 color = vec4(0.0); 50 | 51 | if(length(edge.rgb) > 0.33) { 52 | color = vec4(edge.rgb,1.0); 53 | } else { 54 | vec4 avg = (n[4] + n[1] + n[3] + n[5] + n[7])/5.0; 55 | color = vec4(avg.rgb, 0.66); 56 | } 57 | gl_FragColor = color; 58 | } 59 | 60 | void main() { 61 | if(u_mode == 0) { 62 | specular_mouse(); 63 | } else if(u_mode == 1) { 64 | edge_detection(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/shimmers/math/vector.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.math.vector 2 | (:require 3 | [clojure.math :as math] 4 | [thi.ng.geom.core :as g] 5 | [thi.ng.geom.rect :as rect] 6 | [thi.ng.geom.vector :as gv] 7 | [thi.ng.math.core :as tm])) 8 | 9 | (def v2 gv/vec2) 10 | (def v3 gv/vec3) 11 | 12 | (def ^:const up (v3 0 1 0)) 13 | (def ^:const down (v3 0 -1 0)) 14 | (def ^:const left (v3 -1 0 0)) 15 | (def ^:const right (v3 1 0 0)) 16 | (def ^:const forward (v3 0 0 1)) 17 | (def ^:const back (v3 0 0 -1)) 18 | 19 | (defn add [v1 v2] 20 | (tm/+ v1 v2)) 21 | 22 | (defn wrap2d [[x y] xmax ymax] 23 | (v2 (tm/wrap-range x xmax) 24 | (tm/wrap-range y ymax))) 25 | 26 | (defn clamp-bounds [bounds [x y]] 27 | (v2 (tm/clamp x (rect/left bounds) (rect/right bounds)) 28 | (tm/clamp y (rect/bottom bounds) (rect/top bounds)))) 29 | 30 | (defn polar 31 | ([theta] (polar 1.0 theta)) 32 | ([r theta] 33 | (v2 (* r (math/cos theta)) 34 | (* r (math/sin theta))))) 35 | 36 | (defn +polar [p r theta] 37 | (tm/+ p (polar r theta))) 38 | 39 | (defn -polar [p r theta] 40 | (tm/- p (polar r theta))) 41 | 42 | (defn snap-to 43 | "Snap an input angle `dir` to the closest multiple of `radians`." 44 | [dir radians] 45 | (if (> radians 0) 46 | (-> (g/heading dir) 47 | (/ radians) 48 | math/round 49 | (* radians) 50 | polar) 51 | dir)) 52 | 53 | (defn turn-right [[x y]] 54 | (v2 y (- x))) 55 | 56 | (defn turn-left [[x y]] 57 | (v2 (- y) x)) 58 | 59 | (defn orientation [[px py] [qx qy] [rx ry]] 60 | (let [val (- (* (- qy py) (- rx qx)) 61 | (* (- qx px) (- ry qy)))] 62 | (tm/sign val))) 63 | 64 | ;; TODO: use https://github.com/mourner/robust-predicates 65 | (defn orient2d [[ax ay] [bx by] [cx cy]] 66 | (- (* (- ay cy) (- bx cx)) 67 | (* (- ax cx) (- by cy)))) 68 | 69 | #?(:cljs 70 | (defn contains-NaN? [v] 71 | (some js/isNaN v))) 72 | 73 | #?(:cljs 74 | (defn valid? [v] 75 | (not-any? js/isNaN v))) 76 | -------------------------------------------------------------------------------- /test/shimmers/automata/memory_test.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.automata.memory-test 2 | (:require 3 | [clojure.test :as t :refer [deftest is] :include-macros true] 4 | [shimmers.automata.memory :as sut])) 5 | 6 | (def pages 8) 7 | (def a1 (sut/->Allocation 0 2 2)) 8 | (def a2 (sut/->Allocation 0 4 1)) 9 | 10 | (deftest next-free-chunk 11 | (is (= 0 (sut/next-free pages [a1] 0))) 12 | (is (= 4 (sut/next-free pages [a1] 2))) 13 | (is (= 4 (sut/next-free pages [a1] 4))) 14 | (is (= 5 (sut/next-free pages [a1] 5))) 15 | (is (= 0 (sut/next-free pages [a1] 8)))) 16 | 17 | (deftest next-boundary 18 | (is (= 2 (sut/next-bounds 8 [a1] 0))) 19 | (is (= 4 (sut/next-bounds 8 [a1 a2] 3))) 20 | (is (= 8 (sut/next-bounds 8 [a1] 5)))) 21 | 22 | (def A sut/->Allocation) 23 | (def single-alloc [(A 1 4 2)]) 24 | (def split-alloc [(A 2 4 1) (A 2 6 1)]) 25 | 26 | (deftest allocation 27 | (is (= [(A 2 0 2)] (sut/allocate 2 8 single-alloc 2 0)) 28 | "places before the existing allocation") 29 | (is (= [(A 2 2 2)] (sut/allocate 2 8 single-alloc 2 2)) 30 | "places before existing alloc, but relative to start") 31 | (is (= [(A 2 6 1) (A 2 2 2)] (sut/allocate 2 8 single-alloc 3 2)) 32 | "places upto existing alloc, and remaining after") 33 | (is (= [(A 2 0 2) (A 2 6 2)] (sut/allocate 2 8 single-alloc 4 4)) 34 | "places after existing alloc, and remaining that is larger than page starts from 0") 35 | (is (= [(A 2 6 2)] (sut/allocate 2 8 single-alloc 2 6)) 36 | "places after existing alloc, relative to start") 37 | (is (= [(A 2 0 2)] (sut/allocate 2 8 single-alloc 2 8)) 38 | "places before existing alloc as start is at page boundary") 39 | (is (= [(A 3 5 1) (A 3 0 4)] 40 | (sut/allocate 3 8 split-alloc 5 0)) 41 | "splits first 4 allocations prior to existing, and last between") 42 | (is (= [(A 3 7 1) (A 3 5 1) (A 3 1 3)] 43 | (sut/allocate 3 8 split-alloc 5 1)) 44 | "splits into all the remaining gaps in memory")) 45 | 46 | (comment (t/run-tests)) 47 | -------------------------------------------------------------------------------- /src/shimmers/algorithm/marching_squares.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.algorithm.marching-squares 2 | "https://en.wikipedia.org/wiki/Marching_squares" 3 | (:require 4 | [thi.ng.geom.vector :as gv] 5 | [thi.ng.math.core :as tm])) 6 | 7 | (defn corners [[x y] [xs ys]] 8 | [(gv/vec2 x y) 9 | (gv/vec2 (+ x xs) y) 10 | (gv/vec2 (+ x xs) (+ y ys)) 11 | (gv/vec2 x (+ y ys))]) 12 | 13 | (defn corner-values 14 | "Lookup values at each corner of cell starting from upper-left and going 15 | clockwise to bottom left." 16 | [lookup corners] 17 | (for [[i j] corners] 18 | (lookup i j))) 19 | 20 | ;; (.toString (bit-shift-left (bit-or 0 1) 1) 2) 21 | 22 | (defn threshold [values cutoff] 23 | (bit-shift-right 24 | (reduce (fn [binary v] 25 | (let [r (if (>= v cutoff) 1 0)] 26 | (bit-shift-left (bit-or binary r) 1))) 27 | 0 values) 28 | 1)) 29 | 30 | (comment (threshold [0.4 0.5 0.2 0.1] 0.5) 31 | (threshold [0.4 0.4 0.2 0.1] 0.5)) 32 | 33 | (defn avg [a b] 34 | (/ (+ a b) 2)) 35 | 36 | (defn iso-lookup [[nw ne se sw] [a b c d] bit-value] 37 | (let [north (tm/mix nw ne (avg a b)) 38 | east (tm/mix ne se (avg b c)) 39 | south (tm/mix sw se (avg c d)) 40 | west (tm/mix nw sw (avg d a))] 41 | (case bit-value 42 | 0 [] 43 | 1 [[west south]] 44 | 2 [[south east]] 45 | 3 [[west east]] 46 | 4 [[north east]] 47 | 5 [[west north] 48 | [south east]] 49 | 6 [[north south]] 50 | 7 [[west north]] 51 | 8 [[west north]] 52 | 9 [[north south]] 53 | 10 [[west south] 54 | [north east]] 55 | 11 [[north east]] 56 | 12 [[east west]] 57 | 13 [[south east]] 58 | 14 [[west south]] 59 | 15 []))) 60 | 61 | (defn lines [[x y] [sx sy] lookup cutoff] 62 | (let [corners (corners [x y] [sx sy]) 63 | values (corner-values lookup corners) 64 | bit-value (threshold values cutoff)] 65 | (iso-lookup corners values bit-value))) 66 | -------------------------------------------------------------------------------- /src/shimmers/sketches/skyline.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.skyline 2 | (:require 3 | [shimmers.common.svg :as csvg :include-macros true] 4 | [shimmers.common.ui.controls :as ctrl] 5 | [shimmers.common.ui.svg :as usvg] 6 | [shimmers.math.deterministic-random :as dr] 7 | [shimmers.sketch :as sketch :include-macros true] 8 | [thi.ng.geom.core :as g] 9 | [thi.ng.geom.line :as gl] 10 | [thi.ng.geom.rect :as rect] 11 | [thi.ng.geom.vector :as gv] 12 | [thi.ng.math.core :as tm])) 13 | 14 | (def width 900) 15 | (def height 600) 16 | (defn rv [x y] 17 | (gv/vec2 (* width x) (* height y))) 18 | 19 | (defn building [w h] 20 | [(rect/rect 0 0 (* width w) (* height h))]) 21 | 22 | (defn shapes [] 23 | (apply concat 24 | (for [[a b] (->> (dr/density-range 0.025 0.05 false) 25 | (filter (fn [x] (<= 0.05 x 0.95))) 26 | (partition 2 1)) 27 | :let [h (dr/gaussian (- 0.4 (abs (- b 0.5))) 0.025)]] 28 | (concat (mapv (fn [s] (g/translate s (tm/+ (rv 0.0 0.6) (gv/vec2 (* width a) (* height (- h)))))) 29 | (building (- b a) h)) 30 | [(gl/line2 (rv 0 0.6) (rv 1.0 0.6))] 31 | (mapv (fn [s] (g/translate 32 | s 33 | (tm/+ (rv 0.0 0.6) (gv/vec2 (* width a) 0.0)))) 34 | (building (- b a) (* 0.7 h))))))) 35 | 36 | (defn scene [{:keys [scene-id]}] 37 | (csvg/svg-timed {:id scene-id 38 | :width width 39 | :height height 40 | :stroke "black" 41 | :fill "none" 42 | :stroke-width 0.5} 43 | (shapes))) 44 | 45 | 46 | (defn explanation [_] 47 | [:p "Genuary 2025 Day 20 - Generative Architecture"]) 48 | 49 | 50 | (sketch/definition skyline 51 | {:created-at "2025-01-20" 52 | :tags #{:genuary2025} 53 | :type :svg} 54 | (ctrl/mount 55 | (usvg/page sketch-args explanation scene))) 56 | -------------------------------------------------------------------------------- /src/shimmers/sketches/hexaclock.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.hexaclock 2 | (:require 3 | [clojure.math :as math] 4 | [quil.core :as q :include-macros true] 5 | [shimmers.common.framerate :as framerate] 6 | [shimmers.common.quil :as cq] 7 | [shimmers.common.ui.controls :as ctrl] 8 | [shimmers.sketch :as sketch :include-macros true])) 9 | 10 | (defn spur-angles [] 11 | (for [spur (range 0 6)] 12 | (- (/ (* spur 60 math/PI) 180) 13 | (/ math/PI 2)))) 14 | 15 | (defn spur [radius angle] 16 | [(* radius (q/cos angle)) 17 | (* radius (q/sin angle))]) 18 | 19 | (defn hexagon [radius amt] 20 | (let [angles (spur-angles) 21 | full (q/floor amt)] 22 | (doseq [idx (range 0 6) 23 | :while (< idx full)] 24 | (q/line (spur radius (nth angles idx)) 25 | (spur radius (nth angles (mod (inc idx) 6))))) 26 | (cq/lerp-line (spur radius (nth angles full)) 27 | (spur radius (nth angles (mod (inc full) 6))) 28 | (- amt full)))) 29 | 30 | (defn draw [] 31 | (q/frame-rate 6) 32 | (q/background 255 24) 33 | (let [sec (q/map-range (q/seconds) 0 60 0 6) 34 | minute (q/map-range (q/minute) 0 60 0 6) 35 | hour (q/map-range (mod (q/hour) 12) 0 12 0 6) 36 | cx (cq/rel-w 0.5) 37 | cy (cq/rel-h 0.5) 38 | small-radius (min cx cy) 39 | rH (/ small-radius 1.2) 40 | rM (/ small-radius 1.6) 41 | rS (/ small-radius 2.2)] 42 | (q/translate cx cy) 43 | (q/stroke 0 0 200 64) 44 | (q/stroke-weight 4) 45 | (hexagon rS sec) 46 | (q/stroke 0 200 0 64) 47 | (q/stroke-weight 8) 48 | (hexagon rM minute) 49 | (q/stroke 200 0 0 64) 50 | (q/stroke-weight 16) 51 | (hexagon rH hour))) 52 | 53 | (defn page [] 54 | (sketch/component 55 | :size [800 600] 56 | :draw draw 57 | :middleware [framerate/mode])) 58 | 59 | (sketch/definition hexaclock 60 | {:created-at "2020-12-04" 61 | :tags #{} 62 | :type :quil} 63 | (ctrl/mount page)) 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/shimmers/sketches/pixel_rings.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.pixel-rings 2 | (:require 3 | [clojure.math :as math] 4 | [quil.core :as q :include-macros true] 5 | [quil.middleware :as m] 6 | [shimmers.common.framerate :as framerate] 7 | [shimmers.common.quil :as cq] 8 | [shimmers.common.ui.controls :as ctrl] 9 | [shimmers.math.deterministic-random :as dr] 10 | [shimmers.math.equations :as eq] 11 | [shimmers.math.vector :as v] 12 | [shimmers.sketch :as sketch :include-macros true])) 13 | 14 | (defn draw-samples [n pos-f] 15 | (dotimes [_ n] (cq/circle (pos-f) 1.0))) 16 | 17 | (defn draw [_] 18 | (q/color-mode :hsl 1.0) 19 | (q/fill 0) 20 | (q/no-stroke) 21 | (let [center (cq/rel-vec 0.15 0.4)] 22 | (doseq [radius (range 0.05 1.0 0.08)] 23 | (draw-samples 24 | (* radius 12000) 25 | (fn [] 26 | (let [r (* (cq/rel-h radius) (math/sqrt (dr/gaussian 0.9 0.08))) 27 | theta (dr/gaussian 0.0 0.5)] 28 | (v/+polar center r theta)))))) 29 | (let [center (cq/rel-vec 0.8 0.55)] 30 | (doseq [radius (range 0.05 0.9 0.05)] 31 | (draw-samples 32 | 8000 33 | (fn [] 34 | (let [r (* (cq/rel-h radius) (math/sqrt (dr/gaussian 0.9 0.15))) 35 | theta (dr/gaussian (* 0.45 eq/TAU) 0.8)] 36 | (v/+polar center r theta)))))) 37 | (q/fill 0.0 0.7 0.35) 38 | (let [center (cq/rel-vec 0.55 0.85)] 39 | (doseq [radius (range 0.05 0.8 0.1)] 40 | (draw-samples 41 | (* radius 16000) 42 | (fn [] 43 | (let [r (* (cq/rel-h radius) (math/sqrt (dr/gaussian 0.9 0.1))) 44 | theta (dr/gaussian (* 0.75 eq/TAU) (* 0.35 (- 1.2 radius)))] 45 | (v/+polar center r theta)))))) 46 | (q/no-loop)) 47 | 48 | (defn page [] 49 | (sketch/component 50 | :size [900 600] 51 | :draw draw 52 | :middleware [m/fun-mode framerate/mode])) 53 | 54 | (sketch/definition pixel-rings 55 | {:created-at "2023-02-23" 56 | :tags #{} 57 | :type :quil} 58 | (ctrl/mount page)) 59 | -------------------------------------------------------------------------------- /src/shimmers/common/svg_export.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.common.svg-export 2 | "Convert a generated SVG into a file for download using an image data url") 3 | 4 | (def svg-header 5 | " 6 | 8 | ") 9 | 10 | ;; cribbed from http://bl.ocks.org/curran/7cf9967028259ea032e8 11 | (defn as-file [svg comment] 12 | (let [svg-as-xml (.serializeToString (js/XMLSerializer.) svg) 13 | data-url (str "data:image/svg+xml," 14 | (js/encodeURIComponent svg-header) 15 | (js/encodeURIComponent (str "\n")) 16 | (js/encodeURIComponent svg-as-xml))] 17 | data-url)) 18 | 19 | (defn download-svg [id filename comment] 20 | (let [el (.getElementById js/document id) 21 | data-url (as-file el comment) 22 | link (.createElement js/document "a")] 23 | (.appendChild (.-body js/document) link) 24 | (.setAttribute link "href" data-url) 25 | (.setAttribute link "download" filename) 26 | (.click link))) 27 | 28 | (defn seed [] 29 | ;; FIXME: grab meta info from sketch info instead of parsing from URL? 30 | (let [href (.-href (.-location js/window))] 31 | (when-let [m (re-find #"\?seed=(\d+)" href)] 32 | (second m)))) 33 | 34 | (defn download [id filename] 35 | (let [href (.-href (.-location js/window)) 36 | comment (str "Generated By: " href "\n" 37 | "At: " (.toUTCString (js/Date.)) "\n") 38 | ;; TODO: include git sha in filename or comment? 39 | ;; TODO: ensure url uses public url instead of localhost + deal with sha 40 | filename (str filename "-" (seed) ".svg") 41 | action (download-svg id filename comment) 42 | ;; button (fn [] (.log js/console filename "\n" comment)) 43 | ] 44 | action)) 45 | 46 | (defn button [id filename] 47 | [:input {:type "button" :value "Download" :on-click #(download id filename)}]) 48 | -------------------------------------------------------------------------------- /src/shimmers/sketches/hexcursive.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.hexcursive 2 | (:require 3 | [quil.core :as q :include-macros true] 4 | [quil.middleware :as m] 5 | [shimmers.common.quil :as cq] 6 | [shimmers.common.ui.controls :as ctrl] 7 | [shimmers.math.hexagon :as hex] 8 | [shimmers.sketch :as sketch :include-macros true] 9 | [thi.ng.geom.core :as g] 10 | [thi.ng.geom.vector :as gv] 11 | [thi.ng.math.core :as tm])) 12 | 13 | (defn color [hex i] 14 | (assoc hex :color 15 | [(if (odd? i) 0.45 0.55) 16 | 0.65 17 | (tm/map-interval (/ i 7) 0.0 1.0 0.55 0.95) 18 | 0.8])) 19 | 20 | (defn subdivide [depth {:keys [p r] :as h}] 21 | (if (> depth 8) 22 | [h] 23 | (let [children 24 | (map-indexed (fn [i x] (-> x 25 | (hex/cube-hexagon (/ r 3)) 26 | (g/translate p) 27 | (color (mod (+ i depth) 7)))) 28 | (hex/cube-spiral (gv/vec3) 1))] 29 | (concat [h] 30 | (take depth children) 31 | (mapcat subdivide (map #(+ depth %) (range 1 8)) 32 | (drop depth children)))))) 33 | 34 | (defn setup [] 35 | (q/no-loop) 36 | (q/color-mode :hsl 1.0) 37 | (let [r (* (/ 0.99 tm/SQRT3) (q/height))] 38 | {:shapes (subdivide -1 39 | (assoc (hex/hexagon (gv/vec2) r) :color [1.0 1.0]))})) 40 | 41 | (defn draw [{:keys [shapes]}] 42 | (q/stroke-weight 0.3) 43 | (q/no-fill) 44 | (q/translate (cq/rel-pos 0.5 0.5)) 45 | (doseq [a-hex shapes] 46 | (apply q/fill (:color a-hex)) 47 | (-> a-hex 48 | (g/vertices 6) 49 | cq/draw-shape))) 50 | 51 | (defn page [] 52 | (sketch/component 53 | :size [1200 1000] 54 | :setup setup 55 | :draw draw 56 | :middleware [m/fun-mode])) 57 | 58 | ;; TODO: convert to svg 59 | (sketch/definition hexcursive 60 | {:created-at "2021-06-04" 61 | :tags #{:static} 62 | :type :quil} 63 | (ctrl/mount page)) 64 | -------------------------------------------------------------------------------- /src/shimmers/sketches/stretchy_lines.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.stretchy-lines 2 | (:require 3 | [clojure.math :as math] 4 | [quil.core :as q :include-macros true] 5 | [quil.middleware :as m] 6 | [shimmers.common.framerate :as framerate] 7 | [shimmers.common.quil :as cq] 8 | [shimmers.common.ui.controls :as ctrl] 9 | [shimmers.math.deterministic-random :as dr] 10 | [shimmers.math.equations :as eq] 11 | [shimmers.sketch :as sketch :include-macros true] 12 | [thi.ng.geom.circle :as gc] 13 | [thi.ng.geom.core :as g])) 14 | 15 | (defn setup [] 16 | (q/color-mode :hsl 1.0) 17 | (q/stroke-weight 0.5) 18 | {:outline (dr/rand-nth [(gc/circle (cq/rel-vec 0.5 0.5) (cq/rel-h 0.5)) 19 | (g/as-polygon (gc/circle (cq/rel-vec 0.5 0.5) (cq/rel-h 0.5)) 6) 20 | (cq/screen-rect 0.9)]) 21 | :weights (repeatedly 2 #(dr/random -0.1 0.1)) 22 | :phase (repeatedly 2 dr/random-tau)}) 23 | 24 | (defn update-state [state] 25 | state) 26 | 27 | (defn draw [{[w0 w1] :weights [p0 p1] :phase :keys [outline]}] 28 | (q/background 1.0) 29 | (let [N 256 30 | secs (/ (q/millis) 1000.0) 31 | t (+ (* 0.9 secs) 32 | (math/sin (+ w0 (* 0.75 secs) 33 | (* 2 (eq/cube (math/sin (+ p0 (* 0.5 secs))))))))] 34 | (dotimes [i N] 35 | (let [a (mod (/ (- i (* 0.09 t) (* 0.6 N (math/sin (+ (* w0 i) (* 0.25 t) p0)))) 36 | (float N)) 1.0) 37 | b (mod (/ (+ i (* 0.13 t) (* 0.7 N (math/sin (- (* w1 i) (* 0.35 t) p1)))) 38 | (float N)) 1.0)] 39 | (q/line (g/point-at outline a) 40 | (g/point-at outline b)))))) 41 | 42 | (defn page [] 43 | [:div 44 | (sketch/component 45 | :size [800 600] 46 | :setup setup 47 | :update update-state 48 | :draw draw 49 | :middleware [m/fun-mode framerate/mode])]) 50 | 51 | (sketch/definition stretchy-lines 52 | {:created-at "2024-05-31" 53 | :tags #{} 54 | :type :quil} 55 | (ctrl/mount page)) 56 | -------------------------------------------------------------------------------- /src/shimmers/sketches/concentric_orbits.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.concentric-orbits 2 | (:require 3 | [clojure.math :as math] 4 | [shimmers.common.svg :as csvg :include-macros true] 5 | [shimmers.common.ui.controls :as ctrl] 6 | [shimmers.math.deterministic-random :as dr] 7 | [shimmers.math.equations :as eq] 8 | [shimmers.math.vector :as v] 9 | [shimmers.sketch :as sketch :include-macros true] 10 | [shimmers.view.sketch :as view-sketch] 11 | [thi.ng.geom.circle :as gc] 12 | [thi.ng.geom.vector :as gv] 13 | [thi.ng.math.core :as tm])) 14 | 15 | (def width 800) 16 | (def height 600) 17 | (defn rv [x y] 18 | (gv/vec2 (* width x) (* height y))) 19 | 20 | (defn max-radius [r n] 21 | (/ r (+ math/E (math/sin (/ (/ n math/E) eq/TAU))))) 22 | 23 | (defn orbit [{:keys [p r]} n phase] 24 | (for [t (butlast (tm/norm-range n))] 25 | (let [radius (max-radius r n)] 26 | (gc/circle (v/+polar p 27 | (- r radius) 28 | (+ phase (* eq/TAU t))) 29 | radius)))) 30 | 31 | (defn shapes [] 32 | (let [prime (gc/circle (rv 0.5 0.5) (* 0.48 height))] 33 | (loop [orbits [] depth 0 layer [prime]] 34 | (if (> depth 3) 35 | (into orbits layer) 36 | (recur (into orbits layer) 37 | (inc depth) 38 | (mapcat (fn [parent] 39 | (orbit parent (dr/random-int 2 7) (dr/random-tau))) 40 | layer)))))) 41 | 42 | (defn scene [] 43 | (csvg/svg-timed {:width width 44 | :height height 45 | :stroke "black" 46 | :fill "none" 47 | :stroke-width 1.0} 48 | (shapes))) 49 | 50 | (defn page [] 51 | (fn [] 52 | [sketch/with-explanation 53 | [:div.canvas-frame [scene]] 54 | [view-sketch/generate :concentric-orbits] 55 | [:div.readable-width]])) 56 | 57 | (sketch/definition concentric-orbits 58 | {:created-at "2024-03-11" 59 | :tags #{} 60 | :type :svg} 61 | (ctrl/mount page)) 62 | -------------------------------------------------------------------------------- /src/shimmers/sketches/undulating_figures.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.undulating-figures 2 | (:require 3 | [quil.core :as q :include-macros true] 4 | [quil.middleware :as m] 5 | [shimmers.common.framerate :as framerate] 6 | [shimmers.common.quil :as cq] 7 | [shimmers.common.ui.controls :as ctrl] 8 | [shimmers.sketch :as sketch :include-macros true])) 9 | 10 | (defn setup [] 11 | (q/noise-detail 6 0.4) 12 | (q/frame-rate 30) 13 | (q/color-mode :hsl 1.0) 14 | {}) 15 | 16 | ;; How to make interlace overlap so one figure is drawn with over draw from the 17 | ;; left, and the other from the right, so all the figures are in the overlap? 18 | (defn draw [_] 19 | (q/background 1.0) 20 | (q/no-stroke) 21 | (q/no-fill) 22 | (let [[ma mb] [(q/noise (/ (q/frame-count) 160) 50 50) 23 | (q/noise (/ (q/frame-count) 220) 90 100)] 24 | [ma mb] (if (<= ma mb) [ma mb] [mb ma])] 25 | (loop [y 0] 26 | (when (<= y 1.0) 27 | (let [a (max ma (rand)) 28 | b (max mb (rand)) 29 | thickness (* 0.01 (rand)) 30 | padding (* 0.015 (rand))] 31 | (q/stroke 0.95 0.5 0.5 1.0) 32 | (q/rect (cq/rel-w (- 0.9 (* 0.4 33 | (q/noise (/ (q/frame-count) 200) y)))) 34 | (cq/rel-h y) 35 | (cq/rel-w (- b)) 36 | (cq/rel-h (* 0.7 thickness))) 37 | (q/stroke 0.55 0.5 0.5 1.0) 38 | (q/rect (cq/rel-w (+ 0.1 (* 0.4 39 | (q/noise (/ (q/frame-count) 180) y)))) 40 | (cq/rel-h y) 41 | (cq/rel-w a) 42 | (cq/rel-h (* 0.3 thickness))) 43 | (recur (+ y thickness padding))))))) 44 | 45 | (defn page [] 46 | (sketch/component 47 | :size [1024 768] 48 | :setup setup 49 | :draw draw 50 | :middleware [m/fun-mode framerate/mode])) 51 | 52 | (sketch/definition undulating-figures 53 | {:created-at "2021-06-05" 54 | :tags #{} 55 | :type :quil} 56 | (ctrl/mount page)) 57 | -------------------------------------------------------------------------------- /src/shimmers/sketches/designed_imperfections.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.designed-imperfections 2 | (:require 3 | [clojure.math :as math] 4 | [shimmers.common.svg :as csvg :include-macros true] 5 | [shimmers.common.ui.controls :as ctrl] 6 | [shimmers.common.ui.svg :as usvg] 7 | [shimmers.math.deterministic-random :as dr] 8 | [shimmers.math.equations :as eq] 9 | [shimmers.math.vector :as v] 10 | [shimmers.sketch :as sketch :include-macros true] 11 | [thi.ng.geom.circle :as gc] 12 | [thi.ng.geom.core :as g] 13 | [thi.ng.geom.rect :as rect] 14 | [thi.ng.geom.vector :as gv] 15 | [thi.ng.math.core :as tm])) 16 | 17 | (def width 900) 18 | (def height 600) 19 | (defn rv [x y] 20 | (gv/vec2 (* width x) (* height y))) 21 | 22 | (defn shapes [] 23 | (let [seed (dr/noise-seed) 24 | center (rv 0.5 0.5)] 25 | (->> (for [box (g/subdivide (csvg/screen width height) 26 | {:cols (/ width 6) :rows (/ height 6)})] 27 | (let [pos (g/centroid box) 28 | n (dr/noise-at-point seed 0.008 pos) 29 | damp (/ 1.0 (Math/pow 1.005 (g/dist pos center)))] 30 | (gc/circle (tm/+ pos 31 | (tm/+ (v/polar (* 10.0 damp) (* eq/TAU n)) 32 | (v/polar (* 20.0 (- 1.0 damp)) (g/heading (tm/- pos center))))) 33 | (+ 2.0 (* 0.8 damp (math/sin (* eq/TAU (- 1.0 n)))))))) 34 | (dr/map-random-sample (fn [{:keys [r]}] (/ r 200.0)) 35 | (fn [{:keys [p r]}] (g/center (rect/rect (* 1.9 r)) p)))))) 36 | 37 | (defn scene [{:keys [scene-id]}] 38 | (csvg/svg-timed {:id scene-id 39 | :width width 40 | :height height 41 | :stroke "black" 42 | :fill "none" 43 | :stroke-width 1.0} 44 | (shapes))) 45 | 46 | (sketch/definition designed-imperfections 47 | {:created-at "2024-07-28" 48 | :tags #{} 49 | :type :svg} 50 | (ctrl/mount (usvg/page sketch-args scene))) 51 | -------------------------------------------------------------------------------- /src/shimmers/sketches/grid_exclusion.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.grid-exclusion 2 | (:require 3 | [shimmers.algorithm.square-packing :as square] 4 | [shimmers.common.svg :as csvg] 5 | [shimmers.common.ui.controls :as ctrl] 6 | [shimmers.math.deterministic-random :as dr] 7 | [shimmers.math.geometry :as geometry] 8 | [shimmers.sketch :as sketch :include-macros true] 9 | [shimmers.view.sketch :as view-sketch] 10 | [thi.ng.geom.core :as g] 11 | [thi.ng.geom.vector :as gv])) 12 | 13 | (def width 800) 14 | (def height 600) 15 | (defn rv [x y] 16 | (gv/vec2 (* width x) (* height y))) 17 | 18 | (defn shapes [squares remaining] 19 | (if (or (> (count squares) 200) (empty? remaining)) 20 | squares 21 | (let [{[w h] :size :as rect} 22 | (->> remaining 23 | (filter (fn [{:keys [size]}] (every? #(> % 20) size))) 24 | (dr/weighted-by g/area)) 25 | area (* w h) 26 | min-size (geometry/min-axis rect) 27 | [sq & panes] 28 | (square/split-panes 29 | rect 30 | (int (cond (> area 1400) 31 | (* min-size (dr/rand-nth [0.2 0.25 0.33 0.5])) 32 | (> area 800) 33 | (* min-size (dr/rand-nth [0.5 0.66 0.75 0.8])) 34 | :else 35 | min-size)) 36 | [0 0] (dr/weighted {(square/row-major rect) 2 37 | :all 1}))] 38 | (recur (conj squares sq) 39 | (into (remove #{rect} remaining) 40 | (filter square/has-area? panes)))))) 41 | 42 | (defn scene [] 43 | (csvg/svg-timed {:width width 44 | :height height 45 | :stroke "black" 46 | :fill "none" 47 | :stroke-width 1.0} 48 | (shapes [] [(g/scale-size (csvg/screen width height) 0.95)]))) 49 | 50 | (sketch/definition grid-exclusion 51 | {:created-at "2022-11-01" 52 | :type :svg 53 | :tags #{}} 54 | (ctrl/mount (view-sketch/static-page scene :grid-exclusion))) 55 | -------------------------------------------------------------------------------- /src/shimmers/view/favicon.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.view.favicon 2 | (:require 3 | [clojure.math :as math] 4 | [goog.dom :as dom])) 5 | 6 | (defn circle [ctx [x y] r [hue brightness]] 7 | (.beginPath ctx) 8 | (.arc ctx x y r 0 (* 2 math/PI)) 9 | (set! (.-fillStyle ctx) (str "hsl(" hue ", 80%, " brightness "%, 0.45)")) 10 | (.fill ctx)) 11 | 12 | (defn mag [base scale f offset phase t] 13 | (+ base (* 0.066 base scale (f (+ offset (* phase t)))))) 14 | 15 | (defn pattern [ctx size] 16 | (let [m (/ size 2) 17 | t (/ (.now js/Date.) 6000) 18 | r (/ size 3) 19 | hue (int (mag 180 30 math/sin 0 0.85 t)) 20 | width (+ 80 (* 20 (math/cos (+ 100 (* 0.66 t)))))] 21 | (circle ctx [(mag m 6 math/cos 9 0.95 t) 22 | (mag m 4 math/sin 10 0.95 t)] 23 | r [hue 24 | (int (mag 50 8 math/sin 30 0.7 t))]) 25 | (circle ctx [(mag m 2 math/cos -10 1.0 t) 26 | (mag m 5 math/sin -10 1.0 t)] 27 | r [(mod (- hue width) 360) 28 | (int (mag 55 6 math/sin 10 0.8 t))]) 29 | (circle ctx [(mag m 3 math/cos 2 1.05 t) 30 | (mag m 6 math/sin 1 1.05 t)] 31 | r [(mod (+ hue width) 360) 32 | (int (mag 45 9 math/cos -10 0.9 t))]))) 33 | 34 | ;; Translated from https://medium.com/@alperen.talaslioglu/building-dynamic-favicon-with-javascript-223ad7999661 35 | (defn favicon [] 36 | (let [icon (dom/getElement "favicon") 37 | size 24 38 | canvas (dom/getElement "favicon-canvas")] 39 | (set! (.-width canvas) size) 40 | (set! (.-height canvas) size) 41 | (pattern (dom/getCanvasContext2D canvas) size) 42 | (set! (.-href icon) (.toDataURL canvas "image/png")))) 43 | 44 | (defonce favicon-active (atom true)) 45 | (defn auto-update! [interval] 46 | (when @favicon-active 47 | (favicon) 48 | (.setTimeout js/window auto-update! interval))) 49 | 50 | (defn start [interval] 51 | (reset! favicon-active true) 52 | (auto-update! interval)) 53 | 54 | (defn stop [] 55 | (reset! favicon-active false)) 56 | -------------------------------------------------------------------------------- /src/shimmers/sketches/misplaced_connections.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.misplaced-connections 2 | (:require 3 | [shimmers.common.svg :as csvg :include-macros true] 4 | [shimmers.common.ui.controls :as ctrl] 5 | [shimmers.sketch :as sketch :include-macros true] 6 | [shimmers.view.sketch :as view-sketch] 7 | [thi.ng.geom.vector :as gv] 8 | [shimmers.math.deterministic-random :as dr] 9 | [thi.ng.geom.line :as gl])) 10 | 11 | (def width 800) 12 | (def height 600) 13 | (defn rv [x y] 14 | (gv/vec2 (* width x) (* height y))) 15 | 16 | (defn find-cuts [offsets] 17 | (let [a (dr/rand-nth (butlast offsets))] 18 | [a (dr/rand-nth (drop-while #(<= % a) offsets))])) 19 | 20 | ;; TODO: for v-offsets maybe detect if line actually meets there? 21 | ;; or go through and trim lines that don't connect? 22 | ;; experiment with multiple gaps? 23 | (defn shapes [] 24 | (let [h-offsets (dr/gaussian-range 0.04 0.03) 25 | v-offsets (dr/gaussian-range 0.03 0.03)] 26 | (concat 27 | (mapcat concat 28 | (for [h h-offsets] 29 | (if (dr/chance 0.66) 30 | (let [[a b] (find-cuts v-offsets)] 31 | [(gl/line2 (rv 0.0 h) (rv a h)) 32 | (gl/line2 (rv b h) (rv 1.0 h))]) 33 | [(gl/line2 (rv 0.0 h) (rv 1.0 h))]))) 34 | (mapcat concat 35 | (for [v v-offsets] 36 | (if (dr/chance 0.66) 37 | (let [[a b] (find-cuts h-offsets)] 38 | [(gl/line2 (rv v 0.0) (rv v a)) 39 | (gl/line2 (rv v b) (rv v 1.0))]) 40 | [(gl/line2 (rv v 0.0) (rv v 1.0))])))))) 41 | 42 | (defn scene [] 43 | (csvg/svg-timed {:width width 44 | :height height 45 | :stroke "black" 46 | :fill "none" 47 | :stroke-width 1.0} 48 | (shapes))) 49 | 50 | (sketch/definition misplaced-connections 51 | {:created-at "2023-03-10" 52 | :type :svg 53 | :tags #{}} 54 | (ctrl/mount (view-sketch/static-page scene :misplaced-connections))) 55 | -------------------------------------------------------------------------------- /src/shimmers/sketches/dreamcatcher.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.dreamcatcher 2 | (:require 3 | [clojure.math :as math] 4 | [quil.core :as q :include-macros true] 5 | [quil.middleware :as m] 6 | [shimmers.common.framerate :as framerate] 7 | [shimmers.common.quil :as cq] 8 | [shimmers.common.ui.controls :as ctrl] 9 | [shimmers.math.equations :as eq] 10 | [shimmers.sketch :as sketch :include-macros true] 11 | [thi.ng.geom.circle :as gc] 12 | [thi.ng.geom.core :as g] 13 | [thi.ng.geom.vector :as gv] 14 | [thi.ng.math.core :as tm])) 15 | 16 | (defn setup [] 17 | (q/color-mode :hsl 1.0) 18 | {}) 19 | 20 | (defn next-row [row decay] 21 | (map (fn [[a b]] [(tm/mix (tm/mix a b 0.5) (gv/vec2) decay) b]) 22 | (partition 2 1 (conj row (first row))))) 23 | 24 | (defn dreamloop [{:keys [points row limit decay] :as state}] 25 | (if (< (count points) limit) 26 | (let [added-row (next-row row decay)] 27 | (recur (-> state 28 | (update :points concat 29 | (cons (last (last added-row)) (mapcat identity added-row))) 30 | (assoc :row (mapv first added-row))))) 31 | state)) 32 | 33 | (defn update-state [] 34 | (let [shape (gc/circle (cq/rel-h 0.48)) 35 | points (g/vertices shape 16) 36 | fc (q/frame-count)] 37 | (dreamloop {:points points 38 | :row points 39 | :limit 1000 40 | :rotation (* eq/TAU (math/sin (/ fc 400))) 41 | :decay (+ 0.08 (* 0.05 (math/sin (/ fc 160))))}))) 42 | 43 | (defn draw [{:keys [points rotation]}] 44 | (q/background 1.0) 45 | (q/no-fill) 46 | (q/with-translation (cq/rel-vec 0.5 0.5) 47 | (q/with-rotation [rotation] 48 | (cq/draw-path points)))) 49 | 50 | (defn page [] 51 | (sketch/component 52 | :size [800 600] 53 | :setup setup 54 | :update update-state 55 | :draw draw 56 | :middleware [m/fun-mode framerate/mode])) 57 | 58 | (sketch/definition dreamcatcher 59 | {:created-at "2021-10-11" 60 | :tags #{} 61 | :type :quil} 62 | (ctrl/mount page)) 63 | -------------------------------------------------------------------------------- /src/shimmers/sketches/vanishing_points.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.vanishing-points 2 | (:require 3 | [quil.core :as q :include-macros true] 4 | [quil.middleware :as m] 5 | [shimmers.common.framerate :as framerate] 6 | [shimmers.common.quil :as cq] 7 | [shimmers.common.ui.controls :as ctrl] 8 | [shimmers.common.ui.debug :as debug] 9 | [shimmers.math.vector :as v] 10 | [shimmers.sketch :as sketch :include-macros true] 11 | [thi.ng.geom.core :as g] 12 | [thi.ng.geom.vector :as gv] 13 | [thi.ng.math.core :as tm])) 14 | 15 | (defonce defo (debug/state)) 16 | 17 | (defn setup [] 18 | (q/color-mode :hsl 1.0) 19 | {:bounds (cq/screen-rect) 20 | :mouse (gv/vec2)}) 21 | 22 | (defn update-state [{:keys [bounds] :as state}] 23 | (assoc state :mouse (v/clamp-bounds bounds (cq/mouse-position)))) 24 | 25 | (defn draw [{:keys [bounds mouse] :as state}] 26 | (q/background 1.0 0.5) 27 | (q/fill 1.0 0.1) 28 | (reset! defo state) 29 | (let [scale (tm/map-interval (q/sin (/ (q/frame-count) 100)) -1 1 0.1 0.5) 30 | outer (g/scale-size bounds 0.85) 31 | inner (g/scale bounds scale) 32 | bounded-mouse (v/clamp-bounds (g/center inner (cq/rel-vec 0.5 0.5)) mouse) 33 | cursor (g/center inner bounded-mouse)] 34 | (cq/draw-polygon outer) 35 | (let [vertex-pairs (map vector (g/vertices outer) (g/vertices cursor)) 36 | divisions (partition 2 1 (conj vertex-pairs (last vertex-pairs)))] 37 | (doseq [[p q] vertex-pairs] 38 | (q/line p q)) 39 | (doseq [t (tm/norm-range 5)] 40 | (doseq [[[p1 q1] [p2 q2]] divisions] 41 | (q/line (tm/mix p1 q1 t) (tm/mix p2 q2 t))))) 42 | (cq/draw-polygon cursor))) 43 | 44 | (defn page [] 45 | [sketch/with-explanation 46 | (sketch/component 47 | :size [800 600] 48 | :setup setup 49 | :update update-state 50 | :draw draw 51 | :middleware [m/fun-mode framerate/mode]) 52 | [debug/display defo]]) 53 | 54 | (sketch/definition vanishing-points 55 | {:created-at "2022-03-05" 56 | :tags #{} 57 | :type :quil} 58 | (ctrl/mount page)) 59 | -------------------------------------------------------------------------------- /src/shimmers/sketches/spiderwebs.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.spiderwebs 2 | (:require 3 | [shimmers.common.svg :as csvg] 4 | [shimmers.common.ui.controls :as ctrl] 5 | [shimmers.math.deterministic-random :as dr] 6 | [shimmers.sketch :as sketch :include-macros true] 7 | [shimmers.view.sketch :as view-sketch] 8 | [thi.ng.geom.circle :as gc] 9 | [thi.ng.geom.core :as g] 10 | [thi.ng.geom.line :as gl] 11 | [thi.ng.geom.polygon :as gp] 12 | [thi.ng.geom.vector :as gv] 13 | [thi.ng.math.core :as tm])) 14 | 15 | (def width 800) 16 | (def height 600) 17 | (defn rv [x y] 18 | (gv/vec2 (* width x) (* height y))) 19 | 20 | (defn shapes [] 21 | (let [center (rv 0.5 0.5) 22 | radius (* 0.4 height) 23 | n-points (dr/random-int 15 23) 24 | points (mapv (fn [p] (tm/+ p (dr/jitter (* 0.08 radius)))) 25 | (g/vertices (gc/circle center radius) n-points)) 26 | center-r 0.02 27 | center-circle (gp/polygon2 (map #(tm/mix center % center-r) points)) 28 | spiral (->> (dr/gaussian-range 0.0015 0.0015) 29 | (drop-while #(< % center-r)) 30 | (map (fn [point r] (tm/mix center point r)) 31 | (cycle points))) 32 | 33 | radial-lines 34 | (->> spiral 35 | (map-indexed vector) 36 | (reduce (fn [lines [i p]] 37 | (update lines (mod i n-points) 38 | (fnil (fn [pts] (conj pts p)) []))) 39 | {}) 40 | vals 41 | (map gl/linestrip2))] 42 | (conj radial-lines 43 | center-circle 44 | (gl/linestrip2 spiral)))) 45 | 46 | (defn scene [] 47 | (csvg/svg-timed {:width width 48 | :height height 49 | :stroke "black" 50 | :fill "white" 51 | :stroke-width 0.5} 52 | (shapes))) 53 | 54 | (sketch/definition spiderwebs 55 | {:created-at "2022-11-03" 56 | :type :svg 57 | :tags #{}} 58 | (ctrl/mount (view-sketch/static-page scene :spiderwebs))) 59 | -------------------------------------------------------------------------------- /src/shimmers/sketches/kinematic_chain.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.kinematic-chain 2 | "Inspired by https://www.youtube.com/watch?v=hbgDqyy8bIwa" 3 | (:require 4 | [quil.core :as q :include-macros true] 5 | [quil.middleware :as m] 6 | [shimmers.algorithm.kinematic-chain :as chain] 7 | [shimmers.common.framerate :as framerate] 8 | [shimmers.common.quil :as cq] 9 | [shimmers.common.ui.controls :as ctrl] 10 | [shimmers.sketch :as sketch :include-macros true] 11 | [thi.ng.geom.core :as g] 12 | [thi.ng.math.core :as tm])) 13 | 14 | (defonce ui-state (ctrl/state {:mode :sin})) 15 | 16 | (defn noise-target [rate bw bh] 17 | (let [t (q/millis)] 18 | (cq/rel-vec (q/noise bw (/ t rate)) 19 | (q/noise bh (/ t rate))))) 20 | 21 | (defn sin-target [] 22 | (let [t (q/millis)] 23 | (cq/rel-vec (tm/map-interval (q/cos (+ tm/PI (/ t 10000))) [-1 1] [0.05 0.95]) 24 | (tm/map-interval (q/sin (/ t 2000)) [-1 1] [0.1 0.9])))) 25 | 26 | (def modes {:sin sin-target 27 | :mouse cq/mouse-position 28 | :noise (partial noise-target 4500 100 200)}) 29 | 30 | (defn follow [] 31 | ((get modes (:mode @ui-state)))) 32 | 33 | (defn setup [] 34 | (q/color-mode :hsl 1.0) 35 | {:chain (assoc (chain/make-chain (follow) 96 16) 36 | :color [0.35 0.5 0.5 0.03])}) 37 | 38 | (defn update-state [state] 39 | (-> state 40 | (update :chain chain/chain-update nil (follow)) 41 | (update-in [:chain :color 0] (fn [c] (mod (+ 0.001 c) 1.0))))) 42 | 43 | (defn draw [{:keys [chain]}] 44 | (q/no-fill) 45 | ;; (q/background 255) 46 | (apply q/stroke (:color chain)) 47 | (cq/draw-path (g/vertices chain))) 48 | 49 | (defn page [] 50 | [sketch/with-explanation 51 | (sketch/component 52 | :size [800 600] 53 | :setup setup 54 | :update update-state 55 | :draw draw 56 | :middleware [m/fun-mode framerate/mode]) 57 | [ctrl/change-mode ui-state (keys modes)]]) 58 | 59 | (sketch/definition kinematic-chain 60 | {:created-at "2021-03-19" 61 | :tags #{:demo} 62 | :type :quil} 63 | (ctrl/mount page)) 64 | -------------------------------------------------------------------------------- /dev.cljs.edn: -------------------------------------------------------------------------------- 1 | ^{:css-dirs ["resources/public/css"] 2 | :watch-dirs ["src" "test"] 3 | :extra-main-files {:tests {:main shimmers.figwheel-runner}} 4 | 5 | :open-file-command "emacsclient" 6 | ;; Define handler to enable clientside SPA routing 7 | :ring-handler shimmers.ring-server/handler} 8 | {:preloads [shimmers.dev] 9 | :main shimmers.core 10 | 11 | ;; used http://jmmk.github.io/javascript-externs-generator/ to create 12 | ;; resources/p5-externs.js with latest externs from p5.js version 1.3.1 13 | ;; Then override (:require cljsjs.p5) to in quil to use the version installed in node_modules. 14 | :externs [#_"p5-externs.js" 15 | "sparkles-externs.js" 16 | "delaunator-externs.js" 17 | "d3-delaunay-externs.js"] 18 | :foreign-libs [#_{:file "node_modules/p5/lib/p5.js" 19 | :file-min "node_modules/p5/lib/p5.min.js" 20 | :provides ["cljsjs.p5"]} 21 | {:file "node_modules/sparkles/src/sparkles.js" 22 | :provides ["sparkles"]} 23 | {:file "node_modules/delaunator/delaunator.js" 24 | :file-min "node_modules/delaunator/delaunator.min.js" 25 | :provides ["delaunator"]} 26 | {:file "node_modules/d3-delaunay/dist/d3-delaunay.js" 27 | :file-min "node_modules/d3-delaunay/dist/d3-delaunay.min.js" 28 | :provides ["d3-delaunay"]}] 29 | :optimizations :none 30 | :infer-externs true 31 | :pseudo-names true 32 | :pretty-print true 33 | 34 | :parallel-build true 35 | :compiler-stats true 36 | ;; :verbose true 37 | 38 | ;; never got bundle to work but just overriding the extern above seemed sufficient 39 | ;; :clean-outputs true 40 | ;; :target :bundle 41 | ;; :bundle-cmd {:none ["npx" "webpack" "--mode=development" :output-to "-o" :final-output-to] 42 | ;; :default ["npx" "webpack" :output-to "-o" :final-output-to]} 43 | ;; :closure-defines {cljs.core/*global* "window"} 44 | 45 | :closure-defines {cljs-test-display.core/notifications false 46 | cljs-test-display.core/printing false} 47 | } 48 | -------------------------------------------------------------------------------- /src/shimmers/sketches/gossamer_coils.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.gossamer-coils 2 | (:require 3 | [quil.core :as q :include-macros true] 4 | [quil.middleware :as m] 5 | [shimmers.algorithm.kinematic-chain :as chain] 6 | [shimmers.common.framerate :as framerate] 7 | [shimmers.common.quil :as cq] 8 | [shimmers.common.ui.controls :as ctrl] 9 | [shimmers.sketch :as sketch :include-macros true] 10 | [thi.ng.geom.core :as g] 11 | [thi.ng.geom.vector :as gv])) 12 | 13 | (defn circle-target [center r] 14 | (let [fc (/ (q/frame-count) 100) 15 | adjusted-r (+ (* 50 (- (q/noise r (* 2 fc)) 0.5)) r)] 16 | (g/translate (g/as-cartesian (gv/vec2 adjusted-r fc)) 17 | center))) 18 | 19 | (defn setup [] 20 | (q/color-mode :hsl 1.0) 21 | {:chains [(assoc (chain/make-chain (cq/rel-vec 0.5 0) 80 4) 22 | :color [0.35 0.5 0.5 0.025]) 23 | (assoc (chain/make-chain (cq/rel-vec 0.5 0.5) 80 4) 24 | :color [0.65 0.5 0.5 0.025]) 25 | (assoc (chain/make-chain (cq/rel-vec 0.5 1.0) 80 4) 26 | :color [0.95 0.5 0.5 0.025])]}) 27 | 28 | (defn update-state [{:keys [chains] :as state}] 29 | (assoc state :chains 30 | (map-indexed (fn [idx chain] 31 | (chain/chain-update 32 | chain 33 | (cq/rel-vec (+ 0.15 (* 0.6 (/ idx 2))) 0.5) 34 | (circle-target (cq/rel-vec (/ idx 2) 0.5) 35 | (* 0.8 (inc idx) (cq/rel-h 0.2))))) 36 | chains))) 37 | 38 | (defn draw [{:keys [chains]}] 39 | (q/no-fill) 40 | ;; (q/background 255) 41 | (doseq [chain chains] 42 | (apply q/stroke (:color chain)) 43 | (cq/draw-path (g/vertices chain)))) 44 | 45 | (defn page [] 46 | (sketch/component 47 | :size [800 600] 48 | :setup setup 49 | :update update-state 50 | :draw draw 51 | :middleware [m/fun-mode framerate/mode])) 52 | 53 | (sketch/definition gossamer-coils 54 | {:created-at "2021-03-15" 55 | :tags #{} 56 | :type :quil} 57 | (ctrl/mount page)) 58 | -------------------------------------------------------------------------------- /src/shimmers/sketches/reagent_quil_component.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.reagent-quil-component 2 | (:require 3 | [quil.core :as q :include-macros true] 4 | [quil.middleware :as m] 5 | [shimmers.common.framerate :as framerate] 6 | [shimmers.common.quil :as cq] 7 | [shimmers.common.ui.controls :as ctrl] 8 | [shimmers.math.vector :as v] 9 | [shimmers.sketch :as sketch :include-macros true])) 10 | 11 | (defn setup [speed] 12 | (fn [] 13 | (q/color-mode :hsl 1.0) 14 | (when (< speed 0.05) 15 | (q/frame-rate 30)) 16 | {:t 0 17 | :speed speed})) 18 | 19 | (defn update-state [state] 20 | (update state :t + (:speed state))) 21 | 22 | (defn draw [{:keys [t]}] 23 | (q/ellipse-mode :radius) 24 | (q/background 1.0) 25 | (q/no-stroke) 26 | (q/fill 0.0) 27 | (q/translate (/ (q/width) 2) (/ (q/height) 2)) 28 | (cq/circle (v/polar (cq/rel-h 0.4) t) 20)) 29 | 30 | ;; TODO: adjusting width/height dynamically? 31 | (defn page [] 32 | [:div.contained 33 | [:p.explanation.readable-width 34 | "Experimenting with wrapping Quil sketches in a Reagent Component"] 35 | (sketch/component 36 | :size [800 300] 37 | :performance-id :fps-overlay 38 | :setup (setup 0.01) 39 | :update update-state 40 | :draw draw 41 | :middleware [m/fun-mode framerate/mode]) 42 | [:p.explanation.readable-width 43 | "and now the same sketch, but with a setup function using dθ of 0.05"] 44 | (sketch/component 45 | :size [800 300] 46 | :performance-id :fps-overlay 47 | :setup (setup 0.05) 48 | :update update-state 49 | :draw draw 50 | :middleware [m/fun-mode framerate/mode]) 51 | [:p.explanation.readable-width 52 | "The sketches are nameless and mostly statically defined, as the macro " 53 | [:code "sketch/component"] " wraps the underlying " 54 | [:code "update"] " and " [:code "draw"] " routines, allowing live updates of 55 | those functions to propagate to the corresponding view."]]) 56 | 57 | (sketch/definition reagent-quil-component 58 | {:created-at "2022-03-29" 59 | :type :quil 60 | :tags #{}} 61 | (ctrl/mount page)) 62 | -------------------------------------------------------------------------------- /.github/workflows/continuous-deployment.yaml: -------------------------------------------------------------------------------- 1 | name: Continuous Deployment 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | workflow_dispatch: 9 | 10 | concurrency: 11 | group: "pages" 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | build: 16 | name: Build 17 | runs-on: ubuntu-latest 18 | 19 | permissions: 20 | contents: read 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v6 25 | 26 | - name: Cache deps.edn dependencies 27 | uses: actions/cache@v5 28 | with: 29 | path: ~/.m2/repository 30 | key: clj-${{ runner.os }}-${{ hashFiles('**/deps.edn') }} 31 | restore-keys: clj-${{ runner.os }}- 32 | 33 | - name: Setup Java 34 | uses: actions/setup-java@v5 35 | with: 36 | distribution: temurin 37 | java-version: 21 38 | 39 | - name: Setup Clojure 40 | uses: DeLaGuardo/setup-clojure@13.4 41 | with: 42 | cli: latest 43 | bb: latest 44 | 45 | - name: Setup Node.js 46 | uses: actions/setup-node@v6 47 | with: 48 | node-version: 16 49 | 50 | - name: Install dependencies 51 | run: npm ci 52 | 53 | - name: Lint 54 | run: bin/lint 55 | 56 | - name: Test CLJC 57 | run: bin/kaocha unit-cljc 58 | 59 | - name: Build 60 | run: bb build 61 | 62 | - name: Upload artifact 63 | id: deployment 64 | uses: actions/upload-pages-artifact@v4 65 | with: 66 | path: 'static-site' 67 | 68 | publish: 69 | name: Publish 70 | needs: build 71 | runs-on: ubuntu-latest 72 | 73 | permissions: 74 | pages: write 75 | id-token: write 76 | 77 | environment: 78 | name: github-pages 79 | url: ${{ steps.deployment.outputs.page_url }} 80 | 81 | steps: 82 | - name: Configure Pages 83 | uses: actions/configure-pages@v5 84 | 85 | - name: Deploy to GitHub Pages 86 | id: deployment 87 | uses: actions/deploy-pages@v4 88 | -------------------------------------------------------------------------------- /resources/public/shaders/reaction-diffusion.main.frag.c: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision mediump float; 3 | #endif 4 | 5 | varying vec2 vTexCoord; 6 | 7 | uniform vec2 resolution; 8 | uniform sampler2D concentrations; 9 | 10 | uniform float diffusionA; 11 | uniform float diffusionB; 12 | uniform float feed; 13 | uniform float kill; 14 | uniform float deltaT; 15 | 16 | // Translated from https://ciphrd.com/2019/08/24/reaction-diffusion-on-shader/ 17 | vec2 laplacian(sampler2D tex, vec2 pos, vec2 texel) { 18 | vec2 ab = vec2(0.0,0.0); 19 | 20 | ab += texture2D(tex, pos + vec2(-1.0,-1.0)*texel).xy * 0.05; 21 | ab += texture2D(tex, pos + vec2( 0.0,-1.0)*texel).xy * 0.2; 22 | ab += texture2D(tex, pos + vec2( 1.0,-1.0)*texel).xy * 0.05; 23 | ab += texture2D(tex, pos + vec2(-1.0, 0.0)*texel).xy * 0.2; 24 | ab += texture2D(tex, pos + vec2( 0.0, 0.0)*texel).xy * -1.0; 25 | ab += texture2D(tex, pos + vec2( 1.0, 0.0)*texel).xy * 0.2; 26 | ab += texture2D(tex, pos + vec2(-1.0, 1.0)*texel).xy * 0.05; 27 | ab += texture2D(tex, pos + vec2( 0.0, 1.0)*texel).xy * 0.2; 28 | ab += texture2D(tex, pos + vec2( 1.0, 1.0)*texel).xy * 0.05; 29 | 30 | return ab; 31 | } 32 | 33 | vec2 laplacianCartesian(sampler2D tex, vec2 pos, vec2 texel) { 34 | vec2 ab = vec2(0.0,0.0); 35 | 36 | ab += texture2D(tex, pos + vec2( 0.0,-1.0)*texel).xy; 37 | ab += texture2D(tex, pos + vec2(-1.0, 0.0)*texel).xy; 38 | ab += texture2D(tex, pos + vec2( 1.0, 0.0)*texel).xy; 39 | ab += texture2D(tex, pos + vec2( 0.0, 1.0)*texel).xy; 40 | ab += texture2D(tex, pos + vec2( 0.0, 0.0)*texel).xy * -4.0; 41 | 42 | return ab; 43 | } 44 | 45 | // Why is this drifting from right to left? 46 | void main() { 47 | vec2 pos = vTexCoord.xy; 48 | vec2 texel = 1.0 / resolution; 49 | 50 | vec4 current = texture2D(concentrations, pos); 51 | float a = current.x; 52 | float b = current.y; 53 | vec2 lp = laplacian(concentrations, pos, texel); 54 | 55 | float da = diffusionA*lp.x - a*b*b + feed*(1.0-a); 56 | float db = diffusionB*lp.y + a*b*b - (kill + feed)*b; 57 | 58 | vec2 ab = current.xy + vec2(da, db) * deltaT; 59 | 60 | gl_FragColor = vec4(ab.xy,0.0,1.0); 61 | } 62 | -------------------------------------------------------------------------------- /src/shimmers/sketches/circle_connections.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.circle-connections 2 | (:require 3 | [clojure.math :as math] 4 | [quil.core :as q :include-macros true] 5 | [quil.middleware :as m] 6 | [shimmers.common.framerate :as framerate] 7 | [shimmers.common.quil :as cq] 8 | [shimmers.common.quil-draws-geom :as qdg] 9 | [shimmers.common.ui.controls :as ctrl] 10 | [shimmers.math.equations :as eq] 11 | [shimmers.math.vector :as v] 12 | [shimmers.sketch :as sketch :include-macros true] 13 | [thi.ng.geom.circle :as gc] 14 | [thi.ng.math.core :as tm])) 15 | 16 | (defn vertices [{:keys [p r]} n theta] 17 | (for [i (range n)] 18 | (v/+polar p r (* eq/TAU (+ (/ i n) theta))))) 19 | 20 | (defn setup [] 21 | (q/color-mode :hsl 1.0) 22 | {:t 0.0}) 23 | 24 | (defn update-state [state] 25 | (assoc state :t (/ (q/millis) 1000.0))) 26 | 27 | (defn draw [{:keys [t]}] 28 | (q/background 1.0) 29 | (q/ellipse-mode :radius) 30 | (q/fill 1.0) 31 | 32 | (let [circles [(gc/circle (cq/rel-vec 0.08 0.5) (cq/rel-w 0.075)) 33 | (gc/circle (cq/rel-vec 0.92 0.5) (cq/rel-w 0.075))] 34 | [c0 c1] circles 35 | n 20 36 | v0 (vertices c0 n (* 0.075 math/PI (math/sin (* t 0.3)))) 37 | v1 (vertices c1 n (* 0.075 math/PI (math/cos (* t 0.4)))) 38 | d (* 0.3 (math/cos (* 0.1 t)))] 39 | (q/stroke-weight 1.25) 40 | (doseq [c circles] 41 | (qdg/draw c)) 42 | 43 | (q/stroke-weight 0.5) 44 | (q/fill 0.0) 45 | (doseq [[p0 p1 i] (map vector v0 v1 (range n)) 46 | :let [pt (eq/unit-cos (+ (* 0.2 t) (* d i)))]] 47 | (q/line p0 p1) 48 | (qdg/draw (gc/circle (tm/mix p0 p1 pt) 0.9))) 49 | 50 | (q/fill 1.0) 51 | (doseq [p (concat v0 v1)] 52 | (qdg/draw (gc/circle p 2.0))))) 53 | 54 | (defn page [] 55 | [:div 56 | (sketch/component 57 | :size [800 600] 58 | :setup setup 59 | :update update-state 60 | :draw draw 61 | :middleware [m/fun-mode framerate/mode])]) 62 | 63 | (sketch/definition circle-connections 64 | {:created-at "2023-07-31" 65 | :tags #{} 66 | :type :quil} 67 | (ctrl/mount page)) 68 | -------------------------------------------------------------------------------- /src/shimmers/sketches/tree_rings.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.tree-rings 2 | (:require 3 | [clojure.math :as math] 4 | [shimmers.algorithm.lines :as lines] 5 | [shimmers.common.svg :as csvg :include-macros true] 6 | [shimmers.common.ui.controls :as ctrl] 7 | [shimmers.common.ui.svg :as usvg] 8 | [shimmers.math.deterministic-random :as dr] 9 | [shimmers.math.equations :as eq] 10 | [shimmers.sketch :as sketch :include-macros true] 11 | [thi.ng.geom.core :as g] 12 | [thi.ng.geom.vector :as gv] 13 | [thi.ng.math.core :as tm])) 14 | 15 | ;; inspired by https://gorillasun.de/blog/radial-perlin-noise-and-generative-tree-rings 16 | 17 | (def width 900) 18 | (def height 600) 19 | (defn rv [x y] 20 | (gv/vec2 (* width x) (* height y))) 21 | 22 | (defn ring [seed r n displace] 23 | (let [split-chance (+ 0.25 (* 0.75 (dr/noise-at-point-01 seed 0.035 (gv/vec2 0.0 r)))) 24 | base-t (dr/random-tau) 25 | points (for [t (range 0 eq/TAU (/ eq/TAU n))] 26 | (let [p (g/as-cartesian (gv/vec2 r (+ t base-t))) 27 | noise (dr/noise-at-point-01 seed 0.002 p)] 28 | (tm/+ p (g/as-cartesian (gv/vec2 displace (* eq/TAU noise))))))] 29 | (lines/split-segments split-chance points))) 30 | 31 | (defn shapes [] 32 | (let [radius (int (/ height 2.1)) 33 | seed (dr/noise-seed)] 34 | (mapcat (fn [r] 35 | (ring seed 36 | (* r radius) 37 | (int (math/pow 30 (+ 1 r))) 38 | (math/ceil (* radius 0.025 (+ 1 r))))) 39 | (dr/gaussian-range 0.01 0.012)))) 40 | 41 | (defn scene [{:keys [scene-id]}] 42 | (csvg/svg-timed {:id scene-id 43 | :width width 44 | :height height 45 | :stroke "black" 46 | :fill "none" 47 | :stroke-width 0.66} 48 | (csvg/group {:transform (csvg/translate (rv 0.5 0.5))} 49 | (shapes)))) 50 | 51 | (sketch/definition tree-rings 52 | {:created-at "2022-10-22" 53 | :type :svg 54 | :tags #{:static :deterministic}} 55 | (ctrl/mount (usvg/page sketch-args scene))) 56 | -------------------------------------------------------------------------------- /src/shimmers/sketches/falling_gradients.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.falling-gradients 2 | (:require 3 | [clojure.math :as math] 4 | [quil.core :as q :include-macros true] 5 | [quil.middleware :as m] 6 | [shimmers.common.framerate :as framerate] 7 | [shimmers.common.quil :as cq] 8 | [shimmers.common.ui.controls :as ctrl] 9 | [shimmers.math.deterministic-random :as dr] 10 | [shimmers.math.equations :as eq] 11 | [shimmers.math.geometry.triangle :as triangle] 12 | [shimmers.sketch :as sketch :include-macros true] 13 | [thi.ng.math.core :as tm])) 14 | 15 | (defn setup [] 16 | (q/noise-seed (dr/seed)) 17 | (q/color-mode :hsl 1.0) 18 | (q/no-loop) 19 | {}) 20 | 21 | (defn discrete-curve [slices phase scale offset] 22 | (for [x (tm/norm-range slices)] 23 | [x (* scale (q/noise (* x phase) offset))])) 24 | 25 | (defn random-triangle-at [pos rotation scale] 26 | (triangle/inscribed-equilateral {:p pos :r scale} rotation)) 27 | 28 | (defn draw [] 29 | (q/background 1.0) 30 | (q/fill 0.2 0.008) 31 | (q/no-stroke) 32 | (let [slices (dr/random-int 64 192) 33 | curve (discrete-curve slices 2 0.4 1000) 34 | depth-curve (map second (discrete-curve slices 5 1.0 50000)) 35 | slice-width (cq/rel-w (/ 1.0 slices))] 36 | (doseq [[[x1 y1] depth] (map vector curve depth-curve) 37 | :let [theta (dr/random-tau)]] 38 | (let [f (dr/random -0.0075 -0.0125)] 39 | (doseq [s (range 400) 40 | :let [d (* depth (math/exp (* f s)))]] 41 | (-> (cq/rel-vec x1 (+ y1 d)) 42 | (random-triangle-at (+ theta (* eq/TAU d)) 43 | slice-width) 44 | cq/draw-polygon)))))) 45 | 46 | (defn page [] 47 | [sketch/with-explanation 48 | (sketch/component 49 | :size [900 600] 50 | :setup setup 51 | :draw draw 52 | :middleware [m/fun-mode framerate/mode]) 53 | [:p.readable-width 54 | "Layer triangles as they slowly rotate and fall to a specific depth."]]) 55 | 56 | (sketch/definition falling-gradients 57 | {:created-at "2021-05-04" 58 | :tags #{:static :deterministic} 59 | :type :quil} 60 | (ctrl/mount page)) 61 | -------------------------------------------------------------------------------- /src/shimmers/sketches/rose.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.rose 2 | (:require 3 | [quil.core :as q :include-macros true] 4 | [quil.middleware :as m] 5 | [shimmers.common.framerate :as framerate] 6 | [shimmers.common.quil :as cq] 7 | [shimmers.common.ui.controls :as ctrl] 8 | [shimmers.math.core :as sm] 9 | [shimmers.math.vector :as v] 10 | [shimmers.sketch :as sketch :include-macros true] 11 | [thi.ng.math.core :as tm])) 12 | 13 | (defn blob [base r0 r1] 14 | {:shape 15 | (for [theta (sm/range-subdivided tm/TWO_PI (rand-nth [8 12 16 20])) 16 | :let [xoff (+ 1 (q/cos theta)) 17 | yoff (+ 1 (q/sin theta)) 18 | r (q/map-range (q/noise xoff yoff base) 0 1 19 | r0 r1)]] 20 | [theta r]) 21 | :base base}) 22 | 23 | (defn setup [] 24 | {:rings [] 25 | :z 0}) 26 | 27 | (defn scale [z base] 28 | (q/sin (/ (- z base) 20.0))) 29 | 30 | (defn out-of-bounds? [z {:keys [base]}] 31 | (< (scale z base) 0.0)) 32 | 33 | (defn add-rings [z rings] 34 | (if (and (< (count rings) 100) 35 | (> (- z (get (first rings) :base 0.0)) 6.0)) 36 | (conj rings (blob z 0.3 0.9)) 37 | rings)) 38 | 39 | (defn update-state [{:keys [z rings] :as state}] 40 | (assoc (update state :z + 0.05) 41 | :rings (add-rings z (remove (partial out-of-bounds? z) rings)))) 42 | 43 | (defn scale-shape [{:keys [shape base]} z] 44 | (map (fn [[theta r]] 45 | (v/polar (* r (/ (q/height) 1.9) (scale z base)) 46 | theta)) 47 | shape)) 48 | 49 | (defn draw [{:keys [z rings]}] 50 | (q/background 255 (rand-nth [2.0 4.0 4.0 16.0])) 51 | (q/stroke 200 50 50 255) 52 | (q/stroke-weight 0.25) 53 | (q/no-fill) 54 | (q/translate (/ (q/width) 2) (/ (q/height) 2)) 55 | (doseq [ring rings] 56 | (cq/draw-curve-shape (scale-shape ring z)))) 57 | 58 | (defn page [] 59 | (sketch/component 60 | :size [800 600] 61 | :setup setup 62 | :update update-state 63 | :draw draw 64 | :middleware [m/fun-mode framerate/mode])) 65 | 66 | (sketch/definition rose 67 | {:created-at "2021-02-10" 68 | :tags #{} 69 | :type :quil} 70 | (ctrl/mount page)) 71 | -------------------------------------------------------------------------------- /src/shimmers/sketches/random_walk.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.random-walk 2 | (:require 3 | [quil.core :as q :include-macros true] 4 | [quil.middleware :as m] 5 | [shimmers.common.framerate :as framerate] 6 | [shimmers.common.particle-system :as particles] 7 | [shimmers.common.quil :as cq] 8 | [shimmers.common.ui.controls :as ctrl] 9 | [shimmers.math.color :as color] 10 | [shimmers.math.vector :as v] 11 | [shimmers.sketch :as sketch :include-macros true] 12 | [thi.ng.geom.core :as g] 13 | [thi.ng.geom.vector :as gv] 14 | [thi.ng.math.core :as tm])) 15 | 16 | (defn make-particle [] 17 | (let [initial-pos (cq/rel-vec (rand) (rand))] 18 | {:last-pos initial-pos 19 | :position initial-pos 20 | :velocity (gv/vec2 (q/random-2d)) 21 | :acceleration (gv/vec2 (q/random-2d)) 22 | :color (color/random)})) 23 | 24 | (defn constrain2d [[x y] lower upper] 25 | (gv/vec2 (tm/clamp x lower upper) 26 | (tm/clamp y lower upper))) 27 | 28 | (defn update-particle 29 | [{:keys [position velocity acceleration] :as particle}] 30 | (let [new-velocity (-> (v/add velocity acceleration) (constrain2d -1.5 1.5)) 31 | new-position (v/add position new-velocity) 32 | wrapped-position (v/wrap2d new-position (q/width) (q/height))] 33 | (assoc particle 34 | :last-pos (if (= wrapped-position new-position) position wrapped-position) 35 | :position wrapped-position 36 | :velocity new-velocity 37 | :acceleration (g/scale (gv/vec2 (q/random-2d)) 0.5)))) 38 | 39 | (defn setup [] 40 | (q/background "white") 41 | {:particles (repeatedly 50 make-particle)}) 42 | 43 | (defn update-state [state] 44 | (update-in state [:particles] (partial map update-particle))) 45 | 46 | (defn draw [{:keys [particles]}] 47 | ;; (q/background 256 16) 48 | (particles/draw particles)) 49 | 50 | (defn page [] 51 | (sketch/component 52 | :size [800 600] 53 | :setup setup 54 | :update update-state 55 | :draw draw 56 | :middleware [m/fun-mode framerate/mode])) 57 | 58 | (sketch/definition random-walk 59 | {:created-at "2020-10-21" 60 | :tags #{} 61 | :type :quil} 62 | (ctrl/mount page)) 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/shimmers/sketches/flickering_dots.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.flickering-dots 2 | (:require 3 | [shimmers.common.svg :as csvg :include-macros true] 4 | [shimmers.common.ui.controls :as ctrl] 5 | [shimmers.common.ui.svg :as usvg] 6 | [shimmers.math.deterministic-random :as dr] 7 | [shimmers.sketch :as sketch :include-macros true] 8 | [thi.ng.geom.circle :as gc] 9 | [thi.ng.geom.core :as g] 10 | [thi.ng.geom.vector :as gv])) 11 | 12 | (def width 800) 13 | (def height 800) 14 | (defn rv [x y] 15 | (gv/vec2 (* width x) (* height y))) 16 | 17 | (defn subdivide [p r] 18 | (if (or (> p 0.66) (dr/chance p)) 19 | [r] 20 | (mapcat (fn [rs] 21 | (subdivide (+ p (dr/random 0.075 0.225)) rs)) 22 | (g/subdivide r {:rows 2 :cols 2})))) 23 | 24 | (defn shapes [bounds] 25 | (let [squares 26 | (mapcat (fn [r] (subdivide 0.33 r)) 27 | (g/subdivide bounds {:rows 10 :cols 10}))] 28 | [(csvg/group {} 29 | (for [s squares] 30 | (g/scale-size s 0.9))) 31 | (csvg/group {:stroke "none" 32 | :fill "white"} 33 | (for [s squares 34 | v (g/vertices s) 35 | :let [r (/ (g/width s) 8)]] 36 | (gc/circle v r)))])) 37 | 38 | (defn scene [{:keys [scene-id]}] 39 | (csvg/svg-timed {:id scene-id 40 | :width width 41 | :height height 42 | :stroke "black" 43 | :fill "black" 44 | :stroke-width 0.5} 45 | (shapes (g/scale-size (csvg/screen width height) 0.95)))) 46 | 47 | 48 | (defn explanation [_] 49 | [:div {:style {:width "75ch"}} 50 | [:p "Genuary 2025 - Day 19 - Op Art"] 51 | [:p "Using the " 52 | [:a {:href "https://en.wikipedia.org/wiki/Grid_illusion"} "Grid Illusion"] 53 | " as a basis for some optical illusion art. The grid is then selectively 54 | subdivided for several iterations. The eye is tricked into finding black 55 | dots at different intersections."]]) 56 | 57 | (sketch/definition flickering-dots 58 | {:created-at "2025-01-19" 59 | :tags #{:genuary2025} 60 | :type :svg} 61 | (ctrl/mount (usvg/page sketch-args explanation scene))) 62 | -------------------------------------------------------------------------------- /src/shimmers/sketches/lattice_in_steps.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.lattice-in-steps 2 | (:require 3 | [quil.core :as q :include-macros true] 4 | [quil.middleware :as m] 5 | [shimmers.algorithm.random-points :as rp] 6 | [shimmers.common.framerate :as framerate] 7 | [shimmers.common.quil :as cq] 8 | [shimmers.common.ui.controls :as ctrl] 9 | [shimmers.sketch :as sketch :include-macros true] 10 | [thi.ng.geom.core :as g] 11 | [thi.ng.geom.polygon :as gp] 12 | [thi.ng.geom.vector :as gv] 13 | [thi.ng.math.core :as tm])) 14 | 15 | (defn setup [] 16 | (q/color-mode :hsl 1.0) 17 | (let [[a b c] (map #(tm/+ % (cq/rel-pos 0.5 0.5)) 18 | [(gv/vec2 15 0) (gv/vec2 -15 -15) (gv/vec2 -15 15)])] 19 | {:nodes [a b c] 20 | :edges [[a b] [b c] [c a]]})) 21 | 22 | ;; Not quite working, idea was to keep adding triangles on each outside face, 23 | ;; but somehow this is also generating polygons. Maybe from the discontinuity at 24 | ;; the wrap-around for the hull? 25 | ;; Never mind, discontunity is from convex hull skipping "outer" points. Need a "shrink" operation? 26 | (defn update-state [{:keys [nodes] :as state}] 27 | (let [hull (gp/convex-hull* nodes) 28 | [p q] (rand-nth (partition 2 1 hull)) 29 | m (->> #(rp/confused-midpoint p q 0.9) 30 | (repeatedly 20) 31 | (remove (fn [c] (g/contains-point? (gp/polygon2 hull) c))) 32 | (filter (fn [c] (g/contains-point? (cq/screen-rect) c))) 33 | first)] 34 | (if m 35 | (-> state 36 | (update :nodes conj m) 37 | (update :edges into [[p m] [m q]])) 38 | state))) 39 | 40 | (defn draw [{:keys [nodes edges]}] 41 | (q/background 1.0) 42 | (q/stroke-weight 0.5) 43 | (q/ellipse-mode :radius) 44 | (doseq [node nodes] 45 | (cq/circle node 0.5)) 46 | (doseq [[p q] edges] 47 | (q/line p q))) 48 | 49 | (defn page [] 50 | (sketch/component 51 | :size [800 600] 52 | :setup setup 53 | :update update-state 54 | :draw draw 55 | :middleware [m/fun-mode framerate/mode])) 56 | 57 | (sketch/definition lattice-in-steps 58 | {:created-at "2021-05-29" 59 | :tags #{} 60 | :type :quil} 61 | (ctrl/mount page)) 62 | -------------------------------------------------------------------------------- /src/shimmers/algorithm/delaunay.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.algorithm.delaunay 2 | (:require 3 | ["d3-delaunay"] 4 | [thi.ng.geom.polygon :as gp] 5 | [thi.ng.geom.rect :as rect] 6 | [thi.ng.geom.triangle :as gt] 7 | [thi.ng.geom.vector :as gv])) 8 | 9 | ;; clojure wrapper for https://github.com/d3/d3-delaunay 10 | 11 | ;; reference: https://www.cs.cmu.edu/~quake/triangle.html 12 | 13 | (set! *warn-on-infer* true) 14 | 15 | ;; d3-delaunay polygons and triangles API results include the initial point as 16 | ;; the closing point, so they need to be trimmed of the last point. 17 | 18 | (defn delaunay-from ^js/Delaunay [points] 19 | (js/d3.Delaunay.from (clj->js points))) 20 | 21 | (defn delaunay-triangles [points] 22 | (let [^js/Delaunay delaunay (delaunay-from points)] 23 | (for [triangle (.trianglePolygons delaunay) 24 | :let [[a b c] (js->clj triangle)]] 25 | (gt/triangle2 a b c)))) 26 | 27 | (comment (delaunay-triangles [[0 0] [0 10] [10 10] [0 10] [3 3] [7 7] [3 7]])) 28 | 29 | (defn convex-hull [points] 30 | (let [^js/Delaunay delaunay (delaunay-from points) 31 | hull (js->clj (.hullPolygon delaunay))] 32 | (gp/polygon2 (butlast hull)))) 33 | 34 | (comment (convex-hull [[0 0] [5 0] [5 10]])) 35 | 36 | (defn bounds->ranges [{[xmin ymin] :p [w h] :size}] 37 | [xmin ymin (+ xmin w) (+ ymin h)]) 38 | 39 | (comment (bounds->ranges (rect/rect 3 4 6 8))) 40 | 41 | (defn voronoi-cells [points bounds] 42 | (let [^js/Delaunay delaunay (delaunay-from points) 43 | ^js/Voronoi voronoi (.voronoi delaunay (clj->js (bounds->ranges bounds)))] 44 | (for [cell (.cellPolygons voronoi)] 45 | (gp/polygon2 (butlast (js->clj cell)))))) 46 | 47 | (comment (voronoi-cells [[5 5] [8 8] [3 6] [7 4]] (rect/rect 0 0 10 10))) 48 | 49 | (defn voronoi-circumcenters [points bounds] 50 | (let [^js/Delaunay delaunay (delaunay-from points) 51 | ^js/Voronoi voronoi (.voronoi delaunay (clj->js (bounds->ranges bounds)))] 52 | (for [t (range 0 (alength (.-circumcenters voronoi)) 2)] 53 | (gv/vec2 (aget (.-circumcenters voronoi) t) 54 | (aget (.-circumcenters voronoi) (inc t)))))) 55 | 56 | (comment (voronoi-circumcenters [[5 5] [8 8] [3 6] [7 4]] (rect/rect 0 0 10 10))) 57 | -------------------------------------------------------------------------------- /src/shimmers/sketches/noise_grid.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.noise-grid 2 | "Display a tiling grid of noise function to show dicontinuities." 3 | (:require 4 | [quil.core :as q :include-macros true] 5 | [quil.middleware :as m] 6 | [quil.sketch] 7 | [shimmers.common.framerate :as framerate] 8 | [shimmers.common.ui.controls :as ctrl] 9 | [shimmers.math.core :as sm] 10 | [shimmers.sketch :as sketch :include-macros true])) 11 | 12 | (defn noise-at [x y _ factor fc] 13 | (q/noise (/ x factor) (/ y factor) fc)) 14 | 15 | (defn reflect-noise-at [x y size factor fc] 16 | (let [qx (sm/reflect-into x size) 17 | qy (sm/reflect-into y size)] 18 | (q/noise (/ qx factor) (/ qy factor) fc))) 19 | 20 | (defn draw-square [size factor noise-fn] 21 | (let [fc (/ (q/frame-count) factor)] 22 | (dotimes [y (inc size)] 23 | (dotimes [x (inc size)] 24 | (q/stroke (* 255 (noise-fn x y size factor fc))) 25 | (q/point x y))))) 26 | 27 | (defonce ui 28 | (atom {:reflect false 29 | :factor 10})) 30 | 31 | (defn setup [] 32 | (q/frame-rate 10) 33 | (let [size (int (/ (q/width) 3)) 34 | applet (quil.sketch/current-applet) 35 | reflect (.createCheckbox applet "Reflect Tile" (:reflect @ui)) 36 | factor (.createSlider applet 2 64 (:factor @ui) 2) 37 | _ (.createSpan applet "Factor")] 38 | (.changed reflect (fn [] (swap! ui assoc :reflect (.checked reflect)))) 39 | (.changed factor (fn [] (swap! ui assoc :factor (.value factor)))) 40 | {:size size 41 | :tile (q/create-graphics size size)})) 42 | 43 | (defn draw [{:keys [size tile]}] 44 | (q/background "white") 45 | (let [{:keys [reflect factor]} @ui] 46 | (q/with-graphics tile 47 | (draw-square 48 | size 49 | factor 50 | (if reflect reflect-noise-at noise-at))) 51 | (dotimes [gy 3] 52 | (dotimes [gx 3] 53 | (q/image tile (* gx size) (* gy size)))))) 54 | 55 | (defn page [] 56 | (sketch/component 57 | :size [400 400] 58 | :setup setup 59 | :draw draw 60 | :middleware [m/fun-mode framerate/mode])) 61 | 62 | (sketch/definition noise-grid 63 | {:created-at "2020-11-01" 64 | :tags #{:demo} 65 | :type :quil} 66 | (ctrl/mount page)) 67 | -------------------------------------------------------------------------------- /src/shimmers/sketches/zoetropic.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.zoetropic 2 | (:require 3 | [quil.core :as q :include-macros true] 4 | [quil.middleware :as m] 5 | [shimmers.common.framerate :as framerate] 6 | [shimmers.common.sequence :as cs] 7 | [shimmers.common.ui.controls :as ctrl] 8 | [shimmers.common.video :as video] 9 | [shimmers.math.deterministic-random :as dr] 10 | [shimmers.sketch :as sketch :include-macros true])) 11 | 12 | (def modes [:modular :delayed :rewind :chance-rewind :random]) 13 | (defonce ui-state (ctrl/state {:mode :modular})) 14 | 15 | (defn setup [] 16 | (q/color-mode :hsl 1.0) 17 | (let [s 50 18 | buffer 36 19 | [w h] [(* 3 s) (* 2 s)] 20 | rate 12] 21 | (q/frame-rate rate) 22 | {:capture (video/capture w h) 23 | :width w 24 | :height h 25 | :frames (vec (repeatedly buffer #(q/create-image w h)))})) 26 | 27 | (defn active-mode [{:keys [frames]}] 28 | (case (:mode @ui-state) 29 | :modular [0 (mod (q/frame-count) (count frames))] 30 | :delayed [-1 0] 31 | :rewind [1 (dec (count frames))] 32 | :chance-rewind 33 | (dr/weighted {[-1 0] 5 34 | [(dr/random-int 8) (+ 6 (dr/random-int 12))] 1}) 35 | :random [0 (dr/random-int (count frames))])) 36 | 37 | (defn update-state [{:keys [capture width height] :as state}] 38 | (let [[r offset] (active-mode state)] 39 | (-> state 40 | (update :frames (comp vec (partial cs/rotate r))) 41 | (update-in [:frames offset] 42 | video/copy-frame capture width height)))) 43 | 44 | (defn draw [{:keys [frames width height]}] 45 | (doseq [i (range (count frames)) 46 | :let [frame (nth frames i)]] 47 | (q/image frame 48 | (* width (mod i 6)) 49 | (* height (int (/ i 6))) 50 | width height))) 51 | 52 | (defn page [] 53 | [sketch/with-explanation 54 | (sketch/component 55 | :size [900 600] 56 | :setup setup 57 | :update update-state 58 | :draw draw 59 | :middleware [m/fun-mode framerate/mode]) 60 | [ctrl/change-mode ui-state modes]]) 61 | 62 | (sketch/definition zoetropic 63 | {:created-at "2021-04-17" 64 | :tags #{:camera} 65 | :type :quil} 66 | (ctrl/mount page)) 67 | -------------------------------------------------------------------------------- /test/shimmers/math/geometry/intersection_test.cljc: -------------------------------------------------------------------------------- 1 | (ns shimmers.math.geometry.intersection-test 2 | (:require 3 | [clojure.test :as t :refer [deftest is] :include-macros true] 4 | [shimmers.math.geometry.intersection :as sut] 5 | [thi.ng.geom.circle :as gc] 6 | [thi.ng.geom.vector :as gv])) 7 | 8 | (deftest circle-segment-isec 9 | (is (sut/circle-segment-intersect? (gc/circle [1 1] 1) (gv/vec2 1 1) (gv/vec2 2 2))) 10 | (is (sut/circle-segment-intersect? (gc/circle [1 1] 1) (gv/vec2 1 1) (gv/vec2 2 0)))) 11 | 12 | (deftest circle-ray 13 | (is (= {:type :impale 14 | :isec [(gv/vec2 1 0) (gv/vec2 3 0)] 15 | :points [(gv/vec2 1 0) (gv/vec2 3 0)]} 16 | (sut/circle-ray (gc/circle [2 0] 1) (gv/vec2) (gv/vec2 5 0)))) 17 | (is (= {:type :impale 18 | :isec [(gv/vec2 3 0) (gv/vec2 1 0)] 19 | :points [(gv/vec2 3 0) (gv/vec2 1 0)]} 20 | (sut/circle-ray (gc/circle [2 0] 1) (gv/vec2 5 0) (gv/vec2)))) 21 | (is (= {:type :exit 22 | :isec [(gv/vec2 1 0)] 23 | :points [(gv/vec2 3 0) (gv/vec2 1 0)]} 24 | (sut/circle-ray (gc/circle [2 0] 1) (gv/vec2 2 0) (gv/vec2)))) 25 | (is (= {:type :poke 26 | :isec [(gv/vec2 1 0)] 27 | :points [(gv/vec2 1 0) (gv/vec2 3 0)]} 28 | (sut/circle-ray (gc/circle [2 0] 1) (gv/vec2) (gv/vec2 1 0)))) 29 | (is (= {:type :past :isec [] :points [(gv/vec2 1 0) (gv/vec2 3 0)]} 30 | (sut/circle-ray (gc/circle [2 0] 1) (gv/vec2 4 0) (gv/vec2 5 0)))) 31 | (is (= {:type :before :isec [] :points [(gv/vec2 1 0) (gv/vec2 3 0)]} 32 | (sut/circle-ray (gc/circle [2 0] 1) (gv/vec2 -1 0) (gv/vec2)))) 33 | (is (= {:type :inside :isec [] :points [(gv/vec2 0 0) (gv/vec2 4 0)]} 34 | (sut/circle-ray (gc/circle [2 0] 2) (gv/vec2 1 0) (gv/vec2 3 0)))) 35 | (is (= {:type :inside :isec [] :points [(gv/vec2 2 -2) (gv/vec2 2 2)]} 36 | (sut/circle-ray (gc/circle [2 0] 2) (gv/vec2 2 0) (gv/vec2 2 1))) 37 | "vertical inside") 38 | ;; FIXME: why discrepency between clj/cljs 39 | (is (= #?(:cljs {:type :tangent :isec [(gv/vec2 1 0)] :points [(gv/vec2 1 0)]} 40 | :clj nil) 41 | (sut/circle-ray (gc/circle [2 0] 1) (gv/vec2 1 0) (gv/vec2 1 1))))) 42 | 43 | (comment (t/run-tests)) 44 | -------------------------------------------------------------------------------- /src/shimmers/sketches/string_lights.cljs: -------------------------------------------------------------------------------- 1 | (ns shimmers.sketches.string-lights 2 | "Concept is bokah circles from out of focus string lights in a backdrop." 3 | (:require 4 | [quil.core :as q :include-macros true] 5 | [quil.middleware :as m] 6 | [shimmers.algorithm.random-points :as rp] 7 | [shimmers.common.framerate :as framerate] 8 | [shimmers.common.quil :as cq] 9 | [shimmers.common.ui.controls :as ctrl] 10 | [shimmers.math.deterministic-random :as dr] 11 | [shimmers.math.equations :as eq] 12 | [shimmers.sketch :as sketch :include-macros true] 13 | [thi.ng.geom.vector :as gv] 14 | [thi.ng.math.core :as tm])) 15 | 16 | ;; modify fill/size opacity by y-pos or time to animate? 17 | (defn string-line [p q n r] 18 | (for [point (repeatedly n #(tm/mix p q (dr/random)))] 19 | {:point (rp/confusion-disk point r) 20 | :fill [(dr/gaussian 0.125 0.03) 0.6 0.75 1.0] 21 | :size (dr/rand-nth [8 9 10 11 12]) 22 | :rate (dr/random 0.2 0.9)})) 23 | 24 | (defn setup [] 25 | (q/color-mode :hsl 1.0) 26 | {:strings 27 | (for [x (range 0.15 0.95 0.1)] 28 | (string-line (gv/vec2 x 0.05) (gv/vec2 (- x 0.05) 0.95) 29 | (* 150 x) (* 0.025 x)))}) 30 | 31 | (defn update-state [{:keys [strings] :as state}] 32 | (let [t (/ (q/frame-count) 60) 33 | strings 34 | (for [string strings] 35 | (for [{:keys [point rate fill] :as light} string 36 | :let [[_ y] point 37 | center (eq/unit-cos (* t rate)) 38 | alpha (eq/gaussian 0.8 center 0.25 y)]] 39 | (assoc light :fill (assoc fill 3 alpha))))] 40 | (assoc state :strings strings))) 41 | 42 | (defn draw [{:keys [strings]}] 43 | (q/background 0.15) 44 | (q/no-stroke) 45 | (doseq [string strings] 46 | (doseq [{:keys [point fill size]} string] 47 | (apply q/fill fill) 48 | (cq/circle (cq/rel-vec point) size)))) 49 | 50 | (defn page [] 51 | (sketch/component 52 | :size [800 600] 53 | :setup setup 54 | :update update-state 55 | :draw draw 56 | :middleware [m/fun-mode framerate/mode])) 57 | 58 | (sketch/definition string-lights 59 | {:created-at "2021-08-31" 60 | :tags #{:deterministic} 61 | :type :quil} 62 | (ctrl/mount page)) 63 | --------------------------------------------------------------------------------