All examples defined below will be tangled into the /babel/examples/
subfolder of this project and can be directly loaded into a running
REPL, e.g. via (load-file "examples/ex03.clj")
. Loading the first
example will take a few seconds, since the thi.ng/geom library is
quite large, however subsequent calls will take < 1 sec…
;; run all examples
(doseq [i (range 1 6)] (load-file (str "examples/ex0" i ".clj")))
(See index.org for further information how to tangle & build this project)
Each example generates one or more 3D models in binary STL format, which are saved in the /out directory and can then be imported into other 3d applications. If you need a simple mesh viewer, we recommend Meshlab.
(:require
[thi.ng.morphogen.core :as mg]
[thi.ng.geom.core :as g]
[thi.ng.geom.vector :as v :refer [vec3]]
[thi.ng.geom.aabb :as a])
(:import
[thi.ng.morphogen.core BoxNode])
At some point these will be refactored and moved into core ns…
(defn make-stripes
"Returns a tree which subdivides form into `n` columns and only
keeps those for whose index the given predicate returns a truthy
value. If no predicate is given, `even?` is used by default."
([n] (make-stripes even? n))
([pred n]
(mg/subdiv :cols n :out (mapv #(if (pred %) {}) (range n)))))
(ns ex01
<<require-mg>>)
(def tree
(let [branch (fn [[dir lpos]]
(mg/subdiv-inset
:dir :y :inset 0.05
:out {lpos (mg/subdiv dir 3 :out {1 nil}) 4 nil}))
module (mg/subdiv-inset
:dir :y :inset 0.4
:out (mapv branch [[:cols 0] [:cols 1] [:slices 2] [:slices 3]]))]
(mg/subdiv
:rows 3
:out [module
(mg/subdiv
:rows 3 :out {1 (mg/subdiv :cols 3 :out [nil {} nil])})
module])))
(mg/save-stl-mesh (mg/seed-box (a/aabb 1 0.5 1)) tree "out/ex01.stl")
(ns ex02
<<require-mg>>)
<<stripes>>
(defn stripes*
"Similar to `make-stripes`, but replaces the killed off columns with
connector elements by splitting each in 3x3 rows/slices and only
keeping the center one. Returns tree of columns as created by
`make-stripes` and connectors between them."
[pred n]
(loop [acc (make-stripes pred n) i (if (= pred even?) 1 0)]
(if (< i n)
(recur
(assoc-in
acc [:out i]
(mg/subdiv
:rows 3 :slices 3 :out {4 {}} :empty? true))
(+ i 2))
acc)))
(def tree
"Main tree. Splits seed form into 3x3 cols/slices, removes center
and replaces others with striped versions."
(let [se (stripes* even? 9)
so (mg/subdiv :rows 2 :out [(stripes* odd? 9)])]
(mg/subdiv :cols 3 :slices 3 :out [se so se se nil se se so se])))
(mg/save-stl-mesh (mg/seed-box (a/aabb 1 0.2 1)) tree "out/ex02.stl")
The following short code is all what’s needed to generate the mesh
shown in the image above: A sphere segment (generated with
sphere-lattice-seg
) is used as seed shape to create a number of hexagons,
which due to the side angles of the seed shape automatically arrange
themselves in a spherical constellation. The entire structure is only
using the reflection
operator. Since the operator tree is context
free & distinct from any actual geometry node hierarchy, we can encode
various repetitive sub-transformations using simple functions.
(ns ex03
<<require-mg>>)
(def t
"Arrangement of 10 hexagons as sequence of nested reflections."
(let [hex (mg/apply-recursively (mg/reflect :e) 5 [1] 1)
reflected-hex (mg/reflect :n :out [{} hex])
inject #(-> hex
(assoc-in (mg/child-path [1 1 0]) %)
(assoc-in (mg/child-path [1 1 1 1 0]) %))
seed-clone (mg/reflect :s :out [{} (inject reflected-hex)])]
(mg/reflect :s :out [(inject seed-clone) (inject reflected-hex)])))
;; apply to sphere lattice segment to form hemisphere
(mg/save-stl-mesh (mg/seed-box (mg/sphere-lattice-seg 6 0.25 0.0955 0.2)) t "out/ex03.stl")
;; apply to circle lattice segment to form flat structure
(mg/save-stl-mesh (mg/seed-box (mg/circle-lattice-seg 6 0.25 0.2)) t "out/ex03-alt.stl")
The example also includes a variation of the same tree applied to a circle lattice segment forming a flat, planar structure instead of a hemisphere. Just uncomment the last line to see its result.
This structure very nicely demonstrates the potential of the whole morphogenic approach and uses the same hexagonal base elements as the previous example, but with the tree having much deeper nesting to create the additional antenna structures & hollowed skeleton cells, all of which are unfolded & extruded from the 60 individual hexagon line segments. The resulting structure has 51840 faces, 8460 times that the initial seed form/box!
(ns ex04
(:require
[thi.ng.morphogen.core :as mg]
[thi.ng.geom.core :as g]
[thi.ng.geom.aabb :as a])
(:import
[thi.ng.morphogen.core BoxNode]))
(defn hollow-out
"Returns a tree which applies the `subdiv-inset` operator to a form
in the given direction and removes the center child or optionally
injects given :out vector/map of children. If `empty?` is true, all
children are removed by default (only makes sense if other children
are given, else it should be false)."
([dir inset]
(hollow-out dir inset false {4 nil}))
([dir inset empty? out]
(mg/subdiv-inset :dir dir :inset inset :out out :empty? empty?)))
(defn antenna-ring
"Returns tree which extrudes form in given direction (east/west),
splits it into 3 columns, scales side edges of last to half length
and then forms ring using last column by recursively reflecting it
`n` times."
[dir edge1 edge2 ring-child-id n]
(let [ring (mg/apply-recursively (mg/reflect :n) n [1] 1)]
(mg/extrude
:dir dir :len 0.1
:out
[(mg/scale-edge
edge1 :y
:out
[(mg/subdiv
:cols 3
:out
{ring-child-id (mg/scale-edge edge2 :z :out [ring])})])])))
(def antenna-rings
"The two east/west facing ring modules attached to the tip of the antenna."
[(antenna-ring :w :ab :bf 0 13)
(antenna-ring :e :cd :cg 2 13)])
(def antenna-tip
"The last (thinnest) two antenna segments with rings attached to the lower one."
(mg/subdiv
:cols 3
:out [nil
(mg/extrude
:dir :b :len 0.5
:out [(mg/subdiv
:slices 8
:out {0 (hollow-out :z 0.01 false {4 (mg/extrude :dir :b :len 1)})
1 (mg/subdiv :cols 2 :out antenna-rings)})])
nil]))
(def antenna-main
"Main antenna parts without base ."
(hollow-out
:z 0.025 true
{4 (mg/extrude
:dir :b :len 0.25
:out [(hollow-out :z 0.025 false {4 antenna-tip})])}))
(defn antenna-module
"Complete antenna module with configurable base."
[inset]
(mg/extrude
:dir :b :len 0.2
:out [(hollow-out
:z inset false
{4 (mg/subdiv :slices 2 :out {0 antenna-main})})]))
(def tree
"Main tree. First forms arrangement of 10 hexagons using seed form &
sequence of nested reflections. Then applies skeletonization and
attaches antennas to all 60 segments. These are injected using the
`map-leaves` function which replaces all leaf nodes in the tree with
a new subtree."
(let [hex (mg/apply-recursively (mg/reflect :e) 5 [1] 1)
refl-hex (mg/reflect :n :out [{} hex])
inject #(-> hex
(assoc-in (mg/child-path [1 1 0]) %)
(assoc-in (mg/child-path [1 1 1 1 0]) %))
seed-clone (mg/reflect :s :out {1 (inject refl-hex)})
inset 0.03
skeleton (hollow-out
:y inset false
[(hollow-out :z inset)
(mg/subdiv
:cols 3
:out [(hollow-out :z inset)
(antenna-module inset)
(hollow-out :z inset)])
(hollow-out :x inset)
(hollow-out :x inset)])]
(->> (mg/reflect :s :out [(inject seed-clone) (inject refl-hex)])
(mg/map-leaves (constantly skeleton)))))
;; apply to sphere lattice segment to form hemisphere
(mg/save-stl-mesh (mg/seed-box (mg/sphere-lattice-seg 6 0.25 0.0955 0.2)) tree "out/ex04.stl")
(ns ex05
<<require-mg>>)
<<stripes>>
(defn stripes*
"Similar to `make-stripes`, but replaces the killed off columns with
connector elements by splitting each with the
keeping the center one. Returns tree of columns as created by
`make-stripes` and connectors between them."
[pred gap-opts n]
(loop [acc (make-stripes pred n) i (if (= pred even?) 1 0)]
(if (< i n)
(recur
(assoc-in acc [:out i] (apply mg/subdiv gap-opts))
(+ i 2))
acc)))
(def tree
(mg/subdiv
:cols 3
:out [(mg/subdiv
:cols 2 :out [(mg/subdiv-inset :dir :x :inset 0.005 :out {4 nil}) {}])
(stripes* odd? [:rows 3 :out [nil {} nil]] 19)
(mg/subdiv
:cols 2 :out [{} (mg/subdiv-inset :dir :x :inset 0.0055 :out {4 {}} :empty? true)])]))
(mg/save-stl-mesh (mg/seed-box (a/aabb 1 0.2 0.2)) tree "out/ex05.stl")