Permalink
Fetching contributors…
Cannot retrieve contributors at this time
311 lines (270 sloc) 10.5 KB

Contents

thi.ng.geom.types.utils.ptf

Parallel Transport Frames

(defn compute-tangents
  [points]
  (let [t (mapv (fn [[p q]] (g/normalize (g/- q p))) (d/successive-nth 2 points))]
    (conj t (peek t))))

(defn compute-frame
  [tangents norms bnorms i]
  (let [ii (dec i)
        p  (tangents ii)
        q  (tangents i)
        a  (g/cross p q)
        n  (if-not (m/delta= 0.0 (g/mag-squared a))
             (let [theta (Math/acos (m/clamp-normalized (g/dot p q)))]
               (g/transform-vector (g/rotate-around-axis M44 (g/normalize a) theta) (norms ii)))
             (norms ii))]
    [n (g/cross q n)]))

(defn compute-first-frame
  [t]
  (let [t' (g/abs t)
        i  (if (< (t' 0) (t' 1)) 0 1)
        i  (if (< (t' 2) (t' i)) 2 i)
        n  (g/cross t (g/normalize (g/cross t (assoc V3 i 1.0))))]
    [n (g/cross t n)]))

(defn compute-frames
  [points]
  (let [tangents (compute-tangents points)
        [n b]    (compute-first-frame (first tangents))
        num      (count tangents)]
    (loop [norms [n], bnorms [b], i 1]
      (if (< i num)
        (let [[n b] (compute-frame tangents norms bnorms i)]
          (recur (conj norms n) (conj bnorms b) (inc i)))
        [points tangents norms bnorms]))))

(defn align-frames
  [[points tangents norms bnorms]]
  (let [num   (count tangents)
        a     (first norms)
        b     (peek norms)
        theta (-> (g/dot a b) (m/clamp-normalized) (Math/acos) (/ (dec num)))
        theta (if (> (g/dot (first tangents) (g/cross a b)) 0.0) (- theta) theta)]
    (loop [norms norms, bnorms bnorms, i 1]
      (if (< i num)
        (let [t (tangents i)
              n (-> M44
                    (g/rotate-around-axis t (* theta i))
                    (g/transform-vector (norms i)))
              b (g/cross t n)]
          (recur (assoc norms i n) (assoc bnorms i b) (inc i)))
        [points tangents norms bnorms]))))

(defn sweep-point
  "Takes a path point, a PTF normal & binormal and a profile point.
  Returns profile point projected on path (point)."
  [p n b [qx qy]]
  (vec3
   (mm/madd qx (n 0) qy (b 0) (p 0))
   (mm/madd qx (n 1) qy (b 1) (p 1))
   (mm/madd qx (n 2) qy (b 2) (p 2))))

(defn sweep-profile
  [profile [points _ norms bnorms]]
  (let [frames (map vector points norms bnorms)
        tx     (fn [[p n b]] (mapv #(sweep-point p n b %) profile))
        frame0 (tx (first frames))]
    (->> (next frames) ;; TODO transducer
         (reduce
          (fn [[faces prev] frame]
            (let [curr  (tx frame)
                  curr  (conj curr (first curr))
                  faces (->> (mapcat
                              (fn [a b] [(vector (a 0) (a 1) (b 1) (b 0))])
                              (d/successive-nth 2 prev)
                              (d/successive-nth 2 curr))
                             (concat faces))]
              [faces curr]))
          [nil (conj frame0 (first frame0))])
         (first))))

(defn sweep-mesh
  [points profile & [{:keys [mesh align?]}]]
  (let [frames (compute-frames points)
        frames (if align? (align-frames frames) frames)]
    (->> frames
         (sweep-profile profile)
         (g/into (or mesh (bm/basic-mesh))))))

(defn sweep-strand
  [[p _ n b] r theta delta profile]
  (-> (mapv
       #(->> (vec2 r (mm/madd % delta theta))
             (g/as-cartesian)
             (sweep-point (p %) (n %) (b %)))
       (range (count p)))
      (sweep-mesh profile {:align? true})))

(defn sweep-strands
  [base r strands twists profile]
  (let [delta (/ (* twists TWO_PI) (dec (count (first base))))]
    (->> (m/norm-range strands)
         (butlast)
         (#?(:clj pmap :cljs map) #(sweep-strand base r (* % TWO_PI) delta profile)))))

(defn sweep-strand-mesh
  [base r strands twists profile & [{:as opts}]]
  (->> (sweep-strands base r strands twists profile)
       (reduce g/into (or (:mesh opts) (bm/basic-mesh)))))

Luxor torus knot examples

The following brief examples demonstrate usage of the sweep-mesh and sweep-strand-mesh functions in combination with thi.ng/luxor’s test scene setup. The first example computes a Cinquefoil knot (a (5,2)-torus knot) and sweeps a circle along the path using PTF. The second demo also uses the same knot as basis, but instead of sweeping it directly, first computes N strands, each rotating around the path at given radius and each strand swept as mesh individually (then combined into a single mesh using g/into). Both examples are tangled into the /babel/examples directory, but will only work in a REPL which has both the geom and luxor libs on its classpath.

../../assets/ptf-knot.jpg

(ns ptf-knot
  (:require
   [thi.ng.geom.core :as g]
   [thi.ng.geom.core.vector :as v]
   [thi.ng.geom.aabb :as a]
   [thi.ng.geom.circle :as c]
   [thi.ng.geom.types.utils.ptf :as ptf]
   [thi.ng.math.core :as m :refer [THIRD_PI TWO_PI]]
   [thi.ng.luxor.scenes :as scene]
   [thi.ng.luxor.io :as lio]))

(defn cinquefoil
  [t]
  (let [t  (* t m/TWO_PI)
        pt (* 2.0 t)
        qt (* 5.0 t)
        qc (+ 3.0 (Math/cos qt))]
    (v/vec3 (* qc (Math/cos pt)) (* qc (Math/sin pt)) (Math/sin qt))))

(-> (scene/base-scene {:width 640 :height 360})
    (scene/add-main-mesh
     (ptf/sweep-mesh
      (mapv cinquefoil (m/norm-range 400))
      (g/vertices (c/circle 0.5) 20)
      {:align? true})
     {:id :knot :bounds (a/aabb 1.5) :target [0 0.5 -2] :rx (- m/THIRD_PI)})
    (lio/serialize-scene "ptf-knot" false)
    (lio/export-scene)
    (dorun))

Some example renders to illustrate the weave-mesh approach/function with different parameters:

../../assets/ptf-knot-weave.jpg

../../assets/ptf-knot-weave2.jpg

(ns ptf-knot2
  (:require
   [thi.ng.geom.core :as g]
   [thi.ng.geom.core.vector :as v]
   [thi.ng.geom.aabb :as a]
   [thi.ng.geom.circle :as c]
   [thi.ng.geom.types.utils.ptf :as ptf]
   [thi.ng.math.core :as m]
   [thi.ng.luxor.scenes :as scene]
   [thi.ng.luxor.io :as lio]))

(defn cinquefoil
  [t]
  (let [t  (* t m/TWO_PI)
        pt (* 2.0 t)
        qt (* 5.0 t)
        qc (+ 3.0 (Math/cos qt))]
    (v/vec3 (* qc (Math/cos pt)) (* qc (Math/sin pt)) (Math/sin qt))))

(def knot
  (-> (mapv cinquefoil (m/norm-range 400))
      (ptf/compute-frames)
      (ptf/align-frames)))

(-> (scene/base-scene {:width 640 :height 360})
    (scene/add-main-mesh
     (ptf/sweep-strand-mesh knot 0.5 10 7 (g/vertices (c/circle 0.1) 20))
      ;;(ptf/sweep-strand-mesh knot 0.8 6 12 (g/vertices (c/circle 0.1) 20))
     {:id :knot-weave :bounds (a/aabb 1.5) :target [0 0.5 -2] :rx (- m/THIRD_PI)})
    (lio/serialize-scene "ptf-knot-weave" false)
    (lio/export-scene)
    (dorun))

../../assets/ptf-knot-weave-spectrum.jpg

(ns ptf-knot3
  (:require
   [thi.ng.geom.core :as g]
   [thi.ng.geom.core.vector :as v]
   [thi.ng.geom.circle :as c]
   [thi.ng.geom.types.utils.ptf :as ptf]
   [thi.ng.math.core :as m]
   [thi.ng.luxor.core :as lux]
   [thi.ng.luxor.scenes :as scene]
   [thi.ng.luxor.io :as lio]
   [thi.ng.color.core :as col]))

(defn cinquefoil
  [t]
  (let [t  (* t m/TWO_PI)
        pt (* 2.0 t)
        qt (* 5.0 t)
        qc (+ 3.0 (Math/cos qt))]
    (v/vec3 (* qc (Math/cos pt)) (* qc (Math/sin pt)) (Math/sin qt))))

(defn add-meshes
  [scene meshes opts]
  (let [hue (/ 1.0 (count meshes))]
    (reduce
     (fn [scene [i mesh]]
       (let [mat (str "matte-hue-" i)]
         (-> scene
             (lux/material-matte mat {:diffuse (col/hsva (* i hue) 1.0 0.9)})
             (scene/add-mesh mesh (assoc opts :material mat :id (str "strand-" i))))))
     scene (zipmap (range) meshes))))

(def knot
  (-> (mapv cinquefoil (m/norm-range 400))
      (ptf/compute-frames)
      (ptf/align-frames)))

(-> (scene/base-scene {:width 640 :height 360})
    (add-meshes
     (ptf/sweep-strands knot 0.5 10 7 (g/vertices (c/circle 0.1) 20))
     {:tx {:translate [0 0.5 -2] :scale 0.175 :rx -65}})
    (lio/serialize-scene "ptf-knot-weave-spectrum" false)
    (lio/export-scene)
    (dorun))

../../assets/ptf-spline.jpg

(ns ptf-spline
  (:require
   [thi.ng.geom.core :as g]
   [thi.ng.geom.core.vector :as v]
   [thi.ng.geom.aabb :as a]
   [thi.ng.geom.bezier :as b]
   [thi.ng.geom.circle :as c]
   [thi.ng.geom.types.utils.ptf :as ptf]
   [thi.ng.math.core :as m]
   [thi.ng.luxor.core :as lux]
   [thi.ng.luxor.scenes :as scene]
   [thi.ng.luxor.io :as lio]))

(-> (scene/base-scene {:width 640 :height 360})
    (assoc-in [:camera "perspective" :focaldistance 1] 1.4)
    (scene/add-main-mesh
     (ptf/sweep-mesh
      (-> (for [i (range 100)] (v/randvec3))
          (b/auto-spline3)
          (g/vertices))
      (g/vertices (c/circle 0.025) 10))
     {:id :spline :bounds (a/aabb 1.25) :target [0 0.625 -1.75] :rx (- m/THIRD_PI)})
    (lio/serialize-scene "ptf-spline" false)
    (lio/export-scene)
    (dorun))

Namespace declaration

(ns thi.ng.geom.types.utils.ptf
  #?(:cljs (:require-macros [thi.ng.math.macros :as mm]))
  (:require
   [thi.ng.geom.core :as g]
   [thi.ng.geom.core.vector :refer [vec2 vec3 V3]]
   [thi.ng.geom.core.matrix :refer [matrix44 M44]]
   [thi.ng.geom.basicmesh :as bm]
   [thi.ng.dstruct.core :as d]
   [thi.ng.math.core :as m :refer [*eps* TWO_PI]]
   #?(:clj [thi.ng.math.macros :as mm])))

<<ptf>>