Sierpinski Carpet using Seesaw

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
2
down vote

favorite












I decided to write a Sierpinski Carpet generator. This has been something I wanted to try since watching the Coding Train this summer. A screenshot of it in action with some experimental coloring:



Example screenshot



This is my second version, since I decided to try rewriting it from scratch to see if I could come up with a better design. Instead of subdividing the tile into 9 then popping out the middle, I decided to just not generate the centre tile from the start. I also decided to hard-code splitting tiles in 3x3 divisions (although it's still adjustable). It didn't seem worth it to thread that information everywhere when anything other than a 3x3 division is basically the same thing, it just becomes more expensive sooner.



A few things that I'd appreciate comments on:



  • When working with Swing's style of painting, I'm having a hard time feeding the paint method the state to draw. I'm used to Quil's draw method that manages the animation state for you, and automatically passes the state everywhere so it can be used. The best way I've found is to store the state in an atom, and give the state atom to paint so it can deref it as needed. Given the paint method isn't fed any information by Swing/Seesaw, I don't understand how else I'm supposed to tell paint what to draw.


  • To take strain off the UI thread, I decided to do the calculations inside a Future. This turned my atom of tiles to draw into an atom of future tiles to draw. Inside the paint method, I'm checking both if the atom has been set yet, and if it has, if it's realized?. If it is, I deref it, and draw the tiles it gives me. The seems a little messy though. Any suggestions here would be appreciated.


I appreciate any and all input though! Don't feel restricted to doing massive reviews or focusing on the above points!



Note, I'm using the term "menger sponge" throughout the program because that's actually originally what I was going to do, then realized that Seesaw doesn't seem to support 3D drawing. A Sierpinski Carpet is basically a 2D Menger Sponge though, so I didn't think a full refactor of everything was necessary.




(ns sponge.menger.helpers
(:import [java.awt Component]
[javax.swing RootPaneContainer JButton]))

(defn component-size
"Returns a pair of [width height] of the component"
[^Component c]
[(.getWidth c) (.getHeight c)])

(defn wrap
"Wraps n so it's between min-n (inclusive) and max-n (exclusive)."
[n min-n max-n]
(let [limit (- max-n min-n)]
(+ (mod n limit) min-n)))

(defn map-range
"Maps a value in the inclusive range [start1 stop1] to a value in the range [start2 stop2].
Stolen and translated from Processing."
[value start1 stop1 start2 stop2]
(+ start2
(* (- stop2 start2)
(/ (- value start1)
(- stop1 start1)))))

(defn set-enter-default
"Causes the given button to be fired when the Enter key is pressed."
[^RootPaneContainer root, ^JButton target]
(.setDefaultButton (.getRootPane root) target))



(ns sponge.menger.second-sponge)

(def subdivions 3)

(defrecord Tile [tl-x tl-y width])

(defn subdivide-tile-without-center
"Divides a tile into subdivions many divisions per side, skipping the middle tile.
A subdivions of 3 will split the tile into a grid of 8 tiles."
[tile]
(let [:keys [tl-x tl-y width] tile
sub-width (double (/ width subdivions))

; Calculates the limits for a dimension based on the given starting coordinate
dim-range #(range % (+ % (- width sub-width)) (dec sub-width))

; The middle square index. Assumes subdivions is odd.
remove-i (int (/ subdivions 2))]

(for [[yi y] (map-indexed vector (dim-range tl-y))
[xi x] (map-indexed vector (dim-range tl-x))
:when (not= xi yi remove-i)]

(->Tile x y sub-width))))

(defn fully-subdivide
"Divides the collection of tiles into sub-tiles"
[tiles]
(mapcat subdivide-tile-without-center tiles))

(defn fully-subdivide-n-iterations
"Iterates fully-subdivide n-iterations many times."
[tiles n-iterations]
(nth (iterate fully-subdivide tiles)
n-iterations))



(ns sponge.menger.first-try
(:require [seesaw.core :as sc]
[seesaw.graphics :as sg]
[seesaw.color :as s-col]
[seesaw.font :as sf]

[sponge.menger.second-sponge :as p]
[sponge.menger.helpers :as ph]))

(def screen-width 1500)

(def default-sponge-width 1e9)

(def control-font (sf/font :size 30))

(defn sponge->canvas
"Returns a triplet of [mapped-x, mapped-y, mapped-width]"
[canvas sponge-x sponge-y sponge-width]
(let [[width height] (ph/component-size canvas)
small-dim (min width height)
map-r #(ph/map-range %, 0 default-sponge-width, 0 small-dim)]

[(map-r sponge-x) (map-r sponge-y) (map-r sponge-width)]))

(defn test-color-f [x y width]
(let [w #(ph/wrap % 1 256)
i-width (/ 1 width)]
(s-col/color (w (* x 5 i-width))
(w (* y 0.01 i-width))
(w (* x y i-width 0.001)))))

(defn paint
"Partially accepts
- An atom containing a future list of tiles to be drawn
- A function that accepts the top-left x/y coordinates of a tile, and the tile's width,
and returns a Color to draw the tile as.

To be used as a :paint function."
[future-tiles-atom color-f cvs g]
(let [fut-tiles? @future-tiles-atom]
(when (and fut-tiles? (realized? fut-tiles?))
(doseq [:keys [tl-x tl-y width] @fut-tiles?
:let [[sx sy sw] (sponge->canvas cvs tl-x tl-y width)
color (color-f tl-x tl-y width)]]
(sg/draw g
(sg/rect sx sy sw)
(sg/style :background color))))))

(defn future-tiles
"Returns a future that will evaluate to a sequence of tiles to be drawn.
Repaints the canvas once done."
[canvas n-iterations]
(future
(let [tiles (p/fully-subdivide-n-iterations
[(p/->Tile 0 0 default-sponge-width)]
n-iterations)]

(doall tiles)

(sc/invoke-later
(sc/repaint! canvas))

tiles)))

(defn new-canvas [tiles-atom]
(sc/canvas :paint (partial paint tiles-atom test-color-f)))

(defn iterations-selector-controls
"Returns controls that reset the tiles-atom with a new future when activated."
[canvas tiles-atom]
(let [selector (sc/spinner :font control-font,
:model (sc/spinner-model 1 :from 0, :to 20, :by 1))
sub-button (sc/button :text "Generate", :font control-font, :id :sub-btn)]

(sc/listen sub-button
:action (fn [_]
(swap! tiles-atom
(fn [old-fut]
(when old-fut
(future-cancel old-fut))

(future-tiles canvas (sc/value selector))))))

(sc/horizontal-panel :items [selector sub-button])))

(defn new-main-panel
(let [future-tiles-atom (atom nil)
canvas (new-canvas future-tiles-atom)
controls (iterations-selector-controls canvas future-tiles-atom)]

(sc/border-panel :center canvas, :south controls)))

(defn frame
(let [content (new-main-panel)
f (sc/frame :size [screen-width :by screen-width], :content content)
sub-btn (sc/select f [:#sub-btn])]

(ph/set-enter-default f sub-btn)

f))

(defn -main
(-> (frame)
(sc/show!)))


I tried to keep the centres to see what the coloring alone looked like. It actually looks pretty cool:



Cool color design







share|improve this question



























    up vote
    2
    down vote

    favorite












    I decided to write a Sierpinski Carpet generator. This has been something I wanted to try since watching the Coding Train this summer. A screenshot of it in action with some experimental coloring:



    Example screenshot



    This is my second version, since I decided to try rewriting it from scratch to see if I could come up with a better design. Instead of subdividing the tile into 9 then popping out the middle, I decided to just not generate the centre tile from the start. I also decided to hard-code splitting tiles in 3x3 divisions (although it's still adjustable). It didn't seem worth it to thread that information everywhere when anything other than a 3x3 division is basically the same thing, it just becomes more expensive sooner.



    A few things that I'd appreciate comments on:



    • When working with Swing's style of painting, I'm having a hard time feeding the paint method the state to draw. I'm used to Quil's draw method that manages the animation state for you, and automatically passes the state everywhere so it can be used. The best way I've found is to store the state in an atom, and give the state atom to paint so it can deref it as needed. Given the paint method isn't fed any information by Swing/Seesaw, I don't understand how else I'm supposed to tell paint what to draw.


    • To take strain off the UI thread, I decided to do the calculations inside a Future. This turned my atom of tiles to draw into an atom of future tiles to draw. Inside the paint method, I'm checking both if the atom has been set yet, and if it has, if it's realized?. If it is, I deref it, and draw the tiles it gives me. The seems a little messy though. Any suggestions here would be appreciated.


    I appreciate any and all input though! Don't feel restricted to doing massive reviews or focusing on the above points!



    Note, I'm using the term "menger sponge" throughout the program because that's actually originally what I was going to do, then realized that Seesaw doesn't seem to support 3D drawing. A Sierpinski Carpet is basically a 2D Menger Sponge though, so I didn't think a full refactor of everything was necessary.




    (ns sponge.menger.helpers
    (:import [java.awt Component]
    [javax.swing RootPaneContainer JButton]))

    (defn component-size
    "Returns a pair of [width height] of the component"
    [^Component c]
    [(.getWidth c) (.getHeight c)])

    (defn wrap
    "Wraps n so it's between min-n (inclusive) and max-n (exclusive)."
    [n min-n max-n]
    (let [limit (- max-n min-n)]
    (+ (mod n limit) min-n)))

    (defn map-range
    "Maps a value in the inclusive range [start1 stop1] to a value in the range [start2 stop2].
    Stolen and translated from Processing."
    [value start1 stop1 start2 stop2]
    (+ start2
    (* (- stop2 start2)
    (/ (- value start1)
    (- stop1 start1)))))

    (defn set-enter-default
    "Causes the given button to be fired when the Enter key is pressed."
    [^RootPaneContainer root, ^JButton target]
    (.setDefaultButton (.getRootPane root) target))



    (ns sponge.menger.second-sponge)

    (def subdivions 3)

    (defrecord Tile [tl-x tl-y width])

    (defn subdivide-tile-without-center
    "Divides a tile into subdivions many divisions per side, skipping the middle tile.
    A subdivions of 3 will split the tile into a grid of 8 tiles."
    [tile]
    (let [:keys [tl-x tl-y width] tile
    sub-width (double (/ width subdivions))

    ; Calculates the limits for a dimension based on the given starting coordinate
    dim-range #(range % (+ % (- width sub-width)) (dec sub-width))

    ; The middle square index. Assumes subdivions is odd.
    remove-i (int (/ subdivions 2))]

    (for [[yi y] (map-indexed vector (dim-range tl-y))
    [xi x] (map-indexed vector (dim-range tl-x))
    :when (not= xi yi remove-i)]

    (->Tile x y sub-width))))

    (defn fully-subdivide
    "Divides the collection of tiles into sub-tiles"
    [tiles]
    (mapcat subdivide-tile-without-center tiles))

    (defn fully-subdivide-n-iterations
    "Iterates fully-subdivide n-iterations many times."
    [tiles n-iterations]
    (nth (iterate fully-subdivide tiles)
    n-iterations))



    (ns sponge.menger.first-try
    (:require [seesaw.core :as sc]
    [seesaw.graphics :as sg]
    [seesaw.color :as s-col]
    [seesaw.font :as sf]

    [sponge.menger.second-sponge :as p]
    [sponge.menger.helpers :as ph]))

    (def screen-width 1500)

    (def default-sponge-width 1e9)

    (def control-font (sf/font :size 30))

    (defn sponge->canvas
    "Returns a triplet of [mapped-x, mapped-y, mapped-width]"
    [canvas sponge-x sponge-y sponge-width]
    (let [[width height] (ph/component-size canvas)
    small-dim (min width height)
    map-r #(ph/map-range %, 0 default-sponge-width, 0 small-dim)]

    [(map-r sponge-x) (map-r sponge-y) (map-r sponge-width)]))

    (defn test-color-f [x y width]
    (let [w #(ph/wrap % 1 256)
    i-width (/ 1 width)]
    (s-col/color (w (* x 5 i-width))
    (w (* y 0.01 i-width))
    (w (* x y i-width 0.001)))))

    (defn paint
    "Partially accepts
    - An atom containing a future list of tiles to be drawn
    - A function that accepts the top-left x/y coordinates of a tile, and the tile's width,
    and returns a Color to draw the tile as.

    To be used as a :paint function."
    [future-tiles-atom color-f cvs g]
    (let [fut-tiles? @future-tiles-atom]
    (when (and fut-tiles? (realized? fut-tiles?))
    (doseq [:keys [tl-x tl-y width] @fut-tiles?
    :let [[sx sy sw] (sponge->canvas cvs tl-x tl-y width)
    color (color-f tl-x tl-y width)]]
    (sg/draw g
    (sg/rect sx sy sw)
    (sg/style :background color))))))

    (defn future-tiles
    "Returns a future that will evaluate to a sequence of tiles to be drawn.
    Repaints the canvas once done."
    [canvas n-iterations]
    (future
    (let [tiles (p/fully-subdivide-n-iterations
    [(p/->Tile 0 0 default-sponge-width)]
    n-iterations)]

    (doall tiles)

    (sc/invoke-later
    (sc/repaint! canvas))

    tiles)))

    (defn new-canvas [tiles-atom]
    (sc/canvas :paint (partial paint tiles-atom test-color-f)))

    (defn iterations-selector-controls
    "Returns controls that reset the tiles-atom with a new future when activated."
    [canvas tiles-atom]
    (let [selector (sc/spinner :font control-font,
    :model (sc/spinner-model 1 :from 0, :to 20, :by 1))
    sub-button (sc/button :text "Generate", :font control-font, :id :sub-btn)]

    (sc/listen sub-button
    :action (fn [_]
    (swap! tiles-atom
    (fn [old-fut]
    (when old-fut
    (future-cancel old-fut))

    (future-tiles canvas (sc/value selector))))))

    (sc/horizontal-panel :items [selector sub-button])))

    (defn new-main-panel
    (let [future-tiles-atom (atom nil)
    canvas (new-canvas future-tiles-atom)
    controls (iterations-selector-controls canvas future-tiles-atom)]

    (sc/border-panel :center canvas, :south controls)))

    (defn frame
    (let [content (new-main-panel)
    f (sc/frame :size [screen-width :by screen-width], :content content)
    sub-btn (sc/select f [:#sub-btn])]

    (ph/set-enter-default f sub-btn)

    f))

    (defn -main
    (-> (frame)
    (sc/show!)))


    I tried to keep the centres to see what the coloring alone looked like. It actually looks pretty cool:



    Cool color design







    share|improve this question























      up vote
      2
      down vote

      favorite









      up vote
      2
      down vote

      favorite











      I decided to write a Sierpinski Carpet generator. This has been something I wanted to try since watching the Coding Train this summer. A screenshot of it in action with some experimental coloring:



      Example screenshot



      This is my second version, since I decided to try rewriting it from scratch to see if I could come up with a better design. Instead of subdividing the tile into 9 then popping out the middle, I decided to just not generate the centre tile from the start. I also decided to hard-code splitting tiles in 3x3 divisions (although it's still adjustable). It didn't seem worth it to thread that information everywhere when anything other than a 3x3 division is basically the same thing, it just becomes more expensive sooner.



      A few things that I'd appreciate comments on:



      • When working with Swing's style of painting, I'm having a hard time feeding the paint method the state to draw. I'm used to Quil's draw method that manages the animation state for you, and automatically passes the state everywhere so it can be used. The best way I've found is to store the state in an atom, and give the state atom to paint so it can deref it as needed. Given the paint method isn't fed any information by Swing/Seesaw, I don't understand how else I'm supposed to tell paint what to draw.


      • To take strain off the UI thread, I decided to do the calculations inside a Future. This turned my atom of tiles to draw into an atom of future tiles to draw. Inside the paint method, I'm checking both if the atom has been set yet, and if it has, if it's realized?. If it is, I deref it, and draw the tiles it gives me. The seems a little messy though. Any suggestions here would be appreciated.


      I appreciate any and all input though! Don't feel restricted to doing massive reviews or focusing on the above points!



      Note, I'm using the term "menger sponge" throughout the program because that's actually originally what I was going to do, then realized that Seesaw doesn't seem to support 3D drawing. A Sierpinski Carpet is basically a 2D Menger Sponge though, so I didn't think a full refactor of everything was necessary.




      (ns sponge.menger.helpers
      (:import [java.awt Component]
      [javax.swing RootPaneContainer JButton]))

      (defn component-size
      "Returns a pair of [width height] of the component"
      [^Component c]
      [(.getWidth c) (.getHeight c)])

      (defn wrap
      "Wraps n so it's between min-n (inclusive) and max-n (exclusive)."
      [n min-n max-n]
      (let [limit (- max-n min-n)]
      (+ (mod n limit) min-n)))

      (defn map-range
      "Maps a value in the inclusive range [start1 stop1] to a value in the range [start2 stop2].
      Stolen and translated from Processing."
      [value start1 stop1 start2 stop2]
      (+ start2
      (* (- stop2 start2)
      (/ (- value start1)
      (- stop1 start1)))))

      (defn set-enter-default
      "Causes the given button to be fired when the Enter key is pressed."
      [^RootPaneContainer root, ^JButton target]
      (.setDefaultButton (.getRootPane root) target))



      (ns sponge.menger.second-sponge)

      (def subdivions 3)

      (defrecord Tile [tl-x tl-y width])

      (defn subdivide-tile-without-center
      "Divides a tile into subdivions many divisions per side, skipping the middle tile.
      A subdivions of 3 will split the tile into a grid of 8 tiles."
      [tile]
      (let [:keys [tl-x tl-y width] tile
      sub-width (double (/ width subdivions))

      ; Calculates the limits for a dimension based on the given starting coordinate
      dim-range #(range % (+ % (- width sub-width)) (dec sub-width))

      ; The middle square index. Assumes subdivions is odd.
      remove-i (int (/ subdivions 2))]

      (for [[yi y] (map-indexed vector (dim-range tl-y))
      [xi x] (map-indexed vector (dim-range tl-x))
      :when (not= xi yi remove-i)]

      (->Tile x y sub-width))))

      (defn fully-subdivide
      "Divides the collection of tiles into sub-tiles"
      [tiles]
      (mapcat subdivide-tile-without-center tiles))

      (defn fully-subdivide-n-iterations
      "Iterates fully-subdivide n-iterations many times."
      [tiles n-iterations]
      (nth (iterate fully-subdivide tiles)
      n-iterations))



      (ns sponge.menger.first-try
      (:require [seesaw.core :as sc]
      [seesaw.graphics :as sg]
      [seesaw.color :as s-col]
      [seesaw.font :as sf]

      [sponge.menger.second-sponge :as p]
      [sponge.menger.helpers :as ph]))

      (def screen-width 1500)

      (def default-sponge-width 1e9)

      (def control-font (sf/font :size 30))

      (defn sponge->canvas
      "Returns a triplet of [mapped-x, mapped-y, mapped-width]"
      [canvas sponge-x sponge-y sponge-width]
      (let [[width height] (ph/component-size canvas)
      small-dim (min width height)
      map-r #(ph/map-range %, 0 default-sponge-width, 0 small-dim)]

      [(map-r sponge-x) (map-r sponge-y) (map-r sponge-width)]))

      (defn test-color-f [x y width]
      (let [w #(ph/wrap % 1 256)
      i-width (/ 1 width)]
      (s-col/color (w (* x 5 i-width))
      (w (* y 0.01 i-width))
      (w (* x y i-width 0.001)))))

      (defn paint
      "Partially accepts
      - An atom containing a future list of tiles to be drawn
      - A function that accepts the top-left x/y coordinates of a tile, and the tile's width,
      and returns a Color to draw the tile as.

      To be used as a :paint function."
      [future-tiles-atom color-f cvs g]
      (let [fut-tiles? @future-tiles-atom]
      (when (and fut-tiles? (realized? fut-tiles?))
      (doseq [:keys [tl-x tl-y width] @fut-tiles?
      :let [[sx sy sw] (sponge->canvas cvs tl-x tl-y width)
      color (color-f tl-x tl-y width)]]
      (sg/draw g
      (sg/rect sx sy sw)
      (sg/style :background color))))))

      (defn future-tiles
      "Returns a future that will evaluate to a sequence of tiles to be drawn.
      Repaints the canvas once done."
      [canvas n-iterations]
      (future
      (let [tiles (p/fully-subdivide-n-iterations
      [(p/->Tile 0 0 default-sponge-width)]
      n-iterations)]

      (doall tiles)

      (sc/invoke-later
      (sc/repaint! canvas))

      tiles)))

      (defn new-canvas [tiles-atom]
      (sc/canvas :paint (partial paint tiles-atom test-color-f)))

      (defn iterations-selector-controls
      "Returns controls that reset the tiles-atom with a new future when activated."
      [canvas tiles-atom]
      (let [selector (sc/spinner :font control-font,
      :model (sc/spinner-model 1 :from 0, :to 20, :by 1))
      sub-button (sc/button :text "Generate", :font control-font, :id :sub-btn)]

      (sc/listen sub-button
      :action (fn [_]
      (swap! tiles-atom
      (fn [old-fut]
      (when old-fut
      (future-cancel old-fut))

      (future-tiles canvas (sc/value selector))))))

      (sc/horizontal-panel :items [selector sub-button])))

      (defn new-main-panel
      (let [future-tiles-atom (atom nil)
      canvas (new-canvas future-tiles-atom)
      controls (iterations-selector-controls canvas future-tiles-atom)]

      (sc/border-panel :center canvas, :south controls)))

      (defn frame
      (let [content (new-main-panel)
      f (sc/frame :size [screen-width :by screen-width], :content content)
      sub-btn (sc/select f [:#sub-btn])]

      (ph/set-enter-default f sub-btn)

      f))

      (defn -main
      (-> (frame)
      (sc/show!)))


      I tried to keep the centres to see what the coloring alone looked like. It actually looks pretty cool:



      Cool color design







      share|improve this question













      I decided to write a Sierpinski Carpet generator. This has been something I wanted to try since watching the Coding Train this summer. A screenshot of it in action with some experimental coloring:



      Example screenshot



      This is my second version, since I decided to try rewriting it from scratch to see if I could come up with a better design. Instead of subdividing the tile into 9 then popping out the middle, I decided to just not generate the centre tile from the start. I also decided to hard-code splitting tiles in 3x3 divisions (although it's still adjustable). It didn't seem worth it to thread that information everywhere when anything other than a 3x3 division is basically the same thing, it just becomes more expensive sooner.



      A few things that I'd appreciate comments on:



      • When working with Swing's style of painting, I'm having a hard time feeding the paint method the state to draw. I'm used to Quil's draw method that manages the animation state for you, and automatically passes the state everywhere so it can be used. The best way I've found is to store the state in an atom, and give the state atom to paint so it can deref it as needed. Given the paint method isn't fed any information by Swing/Seesaw, I don't understand how else I'm supposed to tell paint what to draw.


      • To take strain off the UI thread, I decided to do the calculations inside a Future. This turned my atom of tiles to draw into an atom of future tiles to draw. Inside the paint method, I'm checking both if the atom has been set yet, and if it has, if it's realized?. If it is, I deref it, and draw the tiles it gives me. The seems a little messy though. Any suggestions here would be appreciated.


      I appreciate any and all input though! Don't feel restricted to doing massive reviews or focusing on the above points!



      Note, I'm using the term "menger sponge" throughout the program because that's actually originally what I was going to do, then realized that Seesaw doesn't seem to support 3D drawing. A Sierpinski Carpet is basically a 2D Menger Sponge though, so I didn't think a full refactor of everything was necessary.




      (ns sponge.menger.helpers
      (:import [java.awt Component]
      [javax.swing RootPaneContainer JButton]))

      (defn component-size
      "Returns a pair of [width height] of the component"
      [^Component c]
      [(.getWidth c) (.getHeight c)])

      (defn wrap
      "Wraps n so it's between min-n (inclusive) and max-n (exclusive)."
      [n min-n max-n]
      (let [limit (- max-n min-n)]
      (+ (mod n limit) min-n)))

      (defn map-range
      "Maps a value in the inclusive range [start1 stop1] to a value in the range [start2 stop2].
      Stolen and translated from Processing."
      [value start1 stop1 start2 stop2]
      (+ start2
      (* (- stop2 start2)
      (/ (- value start1)
      (- stop1 start1)))))

      (defn set-enter-default
      "Causes the given button to be fired when the Enter key is pressed."
      [^RootPaneContainer root, ^JButton target]
      (.setDefaultButton (.getRootPane root) target))



      (ns sponge.menger.second-sponge)

      (def subdivions 3)

      (defrecord Tile [tl-x tl-y width])

      (defn subdivide-tile-without-center
      "Divides a tile into subdivions many divisions per side, skipping the middle tile.
      A subdivions of 3 will split the tile into a grid of 8 tiles."
      [tile]
      (let [:keys [tl-x tl-y width] tile
      sub-width (double (/ width subdivions))

      ; Calculates the limits for a dimension based on the given starting coordinate
      dim-range #(range % (+ % (- width sub-width)) (dec sub-width))

      ; The middle square index. Assumes subdivions is odd.
      remove-i (int (/ subdivions 2))]

      (for [[yi y] (map-indexed vector (dim-range tl-y))
      [xi x] (map-indexed vector (dim-range tl-x))
      :when (not= xi yi remove-i)]

      (->Tile x y sub-width))))

      (defn fully-subdivide
      "Divides the collection of tiles into sub-tiles"
      [tiles]
      (mapcat subdivide-tile-without-center tiles))

      (defn fully-subdivide-n-iterations
      "Iterates fully-subdivide n-iterations many times."
      [tiles n-iterations]
      (nth (iterate fully-subdivide tiles)
      n-iterations))



      (ns sponge.menger.first-try
      (:require [seesaw.core :as sc]
      [seesaw.graphics :as sg]
      [seesaw.color :as s-col]
      [seesaw.font :as sf]

      [sponge.menger.second-sponge :as p]
      [sponge.menger.helpers :as ph]))

      (def screen-width 1500)

      (def default-sponge-width 1e9)

      (def control-font (sf/font :size 30))

      (defn sponge->canvas
      "Returns a triplet of [mapped-x, mapped-y, mapped-width]"
      [canvas sponge-x sponge-y sponge-width]
      (let [[width height] (ph/component-size canvas)
      small-dim (min width height)
      map-r #(ph/map-range %, 0 default-sponge-width, 0 small-dim)]

      [(map-r sponge-x) (map-r sponge-y) (map-r sponge-width)]))

      (defn test-color-f [x y width]
      (let [w #(ph/wrap % 1 256)
      i-width (/ 1 width)]
      (s-col/color (w (* x 5 i-width))
      (w (* y 0.01 i-width))
      (w (* x y i-width 0.001)))))

      (defn paint
      "Partially accepts
      - An atom containing a future list of tiles to be drawn
      - A function that accepts the top-left x/y coordinates of a tile, and the tile's width,
      and returns a Color to draw the tile as.

      To be used as a :paint function."
      [future-tiles-atom color-f cvs g]
      (let [fut-tiles? @future-tiles-atom]
      (when (and fut-tiles? (realized? fut-tiles?))
      (doseq [:keys [tl-x tl-y width] @fut-tiles?
      :let [[sx sy sw] (sponge->canvas cvs tl-x tl-y width)
      color (color-f tl-x tl-y width)]]
      (sg/draw g
      (sg/rect sx sy sw)
      (sg/style :background color))))))

      (defn future-tiles
      "Returns a future that will evaluate to a sequence of tiles to be drawn.
      Repaints the canvas once done."
      [canvas n-iterations]
      (future
      (let [tiles (p/fully-subdivide-n-iterations
      [(p/->Tile 0 0 default-sponge-width)]
      n-iterations)]

      (doall tiles)

      (sc/invoke-later
      (sc/repaint! canvas))

      tiles)))

      (defn new-canvas [tiles-atom]
      (sc/canvas :paint (partial paint tiles-atom test-color-f)))

      (defn iterations-selector-controls
      "Returns controls that reset the tiles-atom with a new future when activated."
      [canvas tiles-atom]
      (let [selector (sc/spinner :font control-font,
      :model (sc/spinner-model 1 :from 0, :to 20, :by 1))
      sub-button (sc/button :text "Generate", :font control-font, :id :sub-btn)]

      (sc/listen sub-button
      :action (fn [_]
      (swap! tiles-atom
      (fn [old-fut]
      (when old-fut
      (future-cancel old-fut))

      (future-tiles canvas (sc/value selector))))))

      (sc/horizontal-panel :items [selector sub-button])))

      (defn new-main-panel
      (let [future-tiles-atom (atom nil)
      canvas (new-canvas future-tiles-atom)
      controls (iterations-selector-controls canvas future-tiles-atom)]

      (sc/border-panel :center canvas, :south controls)))

      (defn frame
      (let [content (new-main-panel)
      f (sc/frame :size [screen-width :by screen-width], :content content)
      sub-btn (sc/select f [:#sub-btn])]

      (ph/set-enter-default f sub-btn)

      f))

      (defn -main
      (-> (frame)
      (sc/show!)))


      I tried to keep the centres to see what the coloring alone looked like. It actually looks pretty cool:



      Cool color design









      share|improve this question












      share|improve this question




      share|improve this question








      edited Jan 20 at 22:40
























      asked Jan 15 at 0:05









      Carcigenicate

      2,32411128




      2,32411128

























          active

          oldest

          votes











          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f185116%2fsierpinski-carpet-using-seesaw%23new-answer', 'question_page');

          );

          Post as a guest



































          active

          oldest

          votes













          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes










           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f185116%2fsierpinski-carpet-using-seesaw%23new-answer', 'question_page');

          );

          Post as a guest













































































          Popular posts from this blog

          Chat program with C++ and SFML

          Function to Return a JSON Like Objects Using VBA Collections and Arrays

          Will my employers contract hold up in court?