Recursive Trees

canvasfractals

A recursively generated tree is simply _a branch with a tree on the end of it_.

29 Aug 2014 by rm-hull
rm-hull/4a74ef32e6956801b289
recursive-trees.cljs
(ns big-bang.demo.recursive-trees
  (:require-macros
    [cljs.core.async.macros :refer [go]]
    [dommy.macros :refer [sel1 node]])
  (:require
    [cljs.core.async :refer [chan <! >!]]
    [dommy.core :refer [insert-after!]]
    [jayq.core :refer [$ hide show]]
    [big-bang.core :refer [big-bang]]
    [big-bang.components :refer [dropdown slider]]
    [enchilada :refer [ctx canvas canvas-size value-of]]
    [monet.canvas :refer [alpha circle stroke-style stroke-width fill-style fill-rect
                          begin-path line-to move-to close-path stroke composition-operation]]))
 
(def canvas-width (first (canvas-size)))
(def canvas-height (second (canvas-size)))
(def colors ["red" "green" "blue" "yellow" "purple" "orange"])
 
(def initial-state {
  :color (rand-nth colors)
  :rand (value-of :rand "y")
  :init-height (value-of :init-height 100)
  :branch-fan (value-of :branchfan 110)
  :branch-density (value-of :branchdensity 3)
  :input-angle-factor (value-of :inpafact 0.8)
  :input-height-factor (value-of :inphf 0.8)
  :depth (value-of :depth 5)})
 
(defn handle-incoming-msg [event world-state]
  (->
    world-state
    (merge event)))

(defn degrees->radians [d]
  (/ (* (double d) Math/PI) 180.0))
 
(defn draw-tree! [ctx [x y] angle branch-fan height depth world-state]
  (if (zero? depth)
    ctx
 
    (let [theta (/ branch-fan (:branch-density world-state))
          ;start (- angle (/ branch-fan 2))
          start (- angle theta)
          height-factor (if (<= depth 2)
                          (/ (:input-height-factor world-state) 2)
                          (:input-height-factor world-state))
          height' (* height height-factor)
          branch-fan' (* branch-fan (:input-angle-factor world-state))
          x' (+ x (* height (Math/cos (degrees->radians angle))))
          y' (+ y (* height (Math/sin (degrees->radians angle))))
          next-pos [x' y']]
 
      (loop [angle start
             i (:branch-density world-state)]
        (when (pos? i)
          (line-to ctx x' (- canvas-height y'))
          (if (= (:rand world-state) "n")
            
            (draw-tree! 
              ctx
              next-pos
              angle
              branch-fan'
              height'
              (dec depth)
              world-state)
            
            (draw-tree! 
              ctx
              next-pos
              (+ angle (/ theta -2) (* (rand) theta))
              branch-fan'
              (* height' (/ (+ 0.5 (rand)) 1.5))
              (dec depth)
              world-state))
 
          (move-to ctx x (- canvas-height y))

          (recur 
            (+ angle theta)
            (dec i))))

        ctx)))


(defn render [world-state]
  (->
    ctx
    (fill-style :white)
    (fill-rect {:x 0 :y 0 :w canvas-width :h canvas-height})
    (composition-operation :darker)
    (stroke-style (:color world-state))
    (stroke-width 2)
    (begin-path)
    (move-to (/ canvas-width 2) (- canvas-height 20))
    (draw-tree!
      [(/ canvas-width 2) 20]
      90
      (:branch-fan world-state)
      (:init-height world-state)
      (:depth world-state)
      world-state)
    (stroke)))

(defn box [content]
  [:span {:style "width: 300px;
display: inline-block;
border: 1px solid lightgrey;
margin-right: 5px;
margin-bottom: 5px;
padding-left: 5px;
border-radius: 3px;
background: whitesmoke;"} content])
  
(defn start []
  (let [updates-chan (chan 1)]
    (go
      (->>
        (sel1 :#canvas-area)
        (insert-after! (node
                         [:div
                           [:div
                             (box (slider
                               :id :depth
                               :label-text " Depth: "
                               :initial-value (:depth initial-state)
                               :min-value 2
                               :max-value 8
                               :step 1
                               :send-channel updates-chan))
                              (box (slider
                               :id :init-height
                               :label-text " Init height: "
                               :initial-value (:init-height initial-state)
                               :min-value 10
                               :max-value 400
                               :step 5
                               :send-channel updates-chan))
                             (dropdown
                               :id :color
                               :label-text " Color: "
                               :initial-value (:color initial-state)
                               :options (zipmap colors colors)
                               :send-channel updates-chan)]
                           [:div
                             (box (slider
                               :id :branch-fan
                               :label-text " Branch Fan: "
                               :initial-value (:branch-fan initial-state)
                               :min-value 0
                               :max-value 360
                               :step 1
                               :send-channel updates-chan))
                             (box (slider
                               :id :branch-density
                               :label-text " Branch Density: "
                               :initial-value (:branch-density initial-state)
                               :min-value 1
                               :max-value 8
                               :step 1
                               :send-channel updates-chan))
                             (dropdown
                               :id :rand
                               :label-text " Random: "
                               :initial-value (:rand initial-state)
                               :options {"y" "On" "n" "Off"}
                               :send-channel updates-chan)]
                            [:div
                             (box (slider
                               :id :input-angle-factor
                               :label-text " Input Angle Factor: "
                               :initial-value (:input-angle-factor initial-state)
                               :min-value 0.0
                               :max-value 5.0
                               :step 0.05
                               :send-channel updates-chan))
                             (box (slider
                               :id :input-height-factor
                               :label-text " Input Height Factor: "
                               :initial-value (:input-height-factor initial-state)
                               :min-value 0.0
                               :max-value 5.0
                               :step 0.05
                               :send-channel updates-chan))]]))))
    
    (big-bang
      :initial-state initial-state
      :on-receive handle-incoming-msg
      :receive-channel updates-chan
      :to-draw render)))
 
(show canvas)
(start)