summaryrefslogtreecommitdiff
path: root/src/assets/viz/1/quil/middlewares/navigation_3d.cljc
blob: 3471cfd3dc6a8537c3c81dbf6f9e227011e8019c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
(ns quil.middlewares.navigation-3d
  (:require [quil.core :as q]))

(def ^:private missing-navigation-key-error
  (str "state map is missing :navigation-3d key. "
       "Did you accidentally removed it from the state in "
       ":update or any other handler?"))

(defn- assert-state-has-navigation
  "Asserts that state map contains :navigation-2d object."
  [state]
  (when-not (:navigation-3d state)
    (throw #?(:clj (RuntimeException. missing-navigation-key-error)
              :cljs (js/Error. missing-navigation-key-error)))))

(defn- default-position
  "Default position configuration. Check default configuration in
  'camera' function."
  []
  {:position [(/ (q/width) 2.0)
              (/ (q/height) 2.0)
              (/ (q/height) 2.0 (q/tan (/ (* q/PI 60.0) 360.0)))]
   :straight [0 0 -1]
   :up [0 1 0]})

(defn- rotate-by-axis-and-angle
  "Rotates vector v by angle with axis.
  Formula is taken from wiki:
  http://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle"
  [v axis angle]
  (let [[a-x a-y a-z] axis
        [x y z] v
        cs (q/cos angle)
        -cs (- 1 cs)
        sn (q/sin angle)
        ; Matrix is
        ; [a b c]
        ; [d e f]
        ; [g h i]
        a (+ cs (* a-x a-x -cs))
        b (- (* a-x a-y -cs)
             (* a-z sn))
        c (+ (* a-x a-z -cs)
             (* a-y sn))
        d (+ (* a-x a-y -cs)
             (* a-z sn))
        e (+ cs (* a-y a-y -cs))
        f (- (* a-y a-z -cs)
             (* a-x sn))
        g (- (* a-x a-z -cs)
             (* a-y sn))
        h (+ (* a-y a-z -cs)
             (* a-x sn))
        i (+ cs (* a-z a-z -cs))]
    [(+ (* a x) (* b y) (* c z))
     (+ (* d x) (* e y) (* f z))
     (+ (* g x) (* h y) (* i z))]))

(defn- rotate-lr
  "Rotates nav-3d configuration left-right. angle positive - rotate right,
  negative - left."
  [nav-3d angle]
  (update-in nav-3d [:straight] rotate-by-axis-and-angle (:up nav-3d) angle))

(defn- cross-product
  "Vector cross-product: http://en.wikipedia.org/wiki/Cross_product"
  [[u1 u2 u3] [v1 v2 v3]]
  [(- (* u2 v3) (* u3 v2))
   (- (* u3 v1) (* u1 v3))
   (- (* u1 v2) (* u2 v1))])

(defn- v-mult
  "Multiply vector v by scalar mult."
  [v mult]
  (mapv #(* % mult) v))

(defn- v-plus
  "Sum of 2 vectors."
  [v1 v2]
  (mapv + v1 v2))

(defn- v-opposite
  "Returns vector opposite to vector v."
  [v]
  (v-mult v -1))

(defn- v-normalize
  "Normalize vector, returning vector
  which has same direction but with norm equals to 1."
  [v]
  (let [norm (->> (map q/sq v)
                  (apply +)
                  (q/sqrt))]
    (v-mult v (/ norm))))

(defn- rotate-ud
  "Rotates nav-3d configuration up-down."
  [nav-3d angle]
  (let [axis (cross-product (:straight nav-3d) (:up nav-3d))
        rotate #(rotate-by-axis-and-angle % axis angle)]
    (-> nav-3d
        (update-in [:straight] rotate)
        (update-in [:up] rotate))))

(defn- rotate
  "Mouse handler function which rotates nav-3d configuration.
  It uses mouse from event object and pixels-in-360 to calculate
  angles to rotate."
  [state event pixels-in-360]
  (assert-state-has-navigation state)
  (if (= 0 (:p-x event) (:p-y event))
    state
    (let [dx (- (:p-x event) (:x event))
          dy (- (:y event) (:p-y event))
          angle-lr (q/map-range dx 0 pixels-in-360 0 q/TWO-PI)
          angle-ud (q/map-range dy 0 pixels-in-360 0 q/TWO-PI)]
      (update-in state [:navigation-3d]
                 #(-> %
                      (rotate-lr angle-lr)
                      (rotate-ud angle-ud))))))

(def ^:private space (keyword " "))

(defn- move
  "Keyboard handler function which moves nav-3d configuration.
  It uses keyboard key from event object to determing in which
  direction to move."
  [state event step-size]
  (assert-state-has-navigation state)
  (let [{:keys [up straight]} (:navigation-3d state)]
    (if-let [dir (condp = (:key event)
                   :w straight
                   :s (v-opposite straight)
                   space (v-opposite up)
                   :z up
                   :d (cross-product straight up)
                   :a (cross-product up straight)
                   nil)]
      (update-in state [:navigation-3d :position]
                 #(v-plus % (v-mult dir step-size)))
      state)))

(defn- setup-3d-nav
  "Custom 'setup' function which creates initial position
  configuration and puts it to the state map."
  [user-setup user-settings]
  (let [initial-state (-> user-settings
                          (select-keys [:straight :up :position])
                          (->> (merge (default-position)))
                          (update-in [:straight] v-normalize)
                          (update-in [:up] v-normalize))]
    (update-in (user-setup) [:navigation-3d]
               #(merge initial-state %))))

(defn navigation-3d
  "Enables navigation in 3D space. Similar to how it is done in
  shooters: WASD navigation, space is go up, z is go down,
  drag mouse to look around."
  [options]
  (let [; 3d-navigation related user settings
        user-settings (:navigation-3d options)
        pixels-in-360 (:pixels-in-360 user-settings 1000)
        step-size (:step-size user-settings 20)
        rotate-on (:rotate-on user-settings :mouse-dragged)

        ; user-provided handlers which will be overridden
        ; by 3d-navigation
        draw (:draw options (fn [state]))
        key-pressed (:key-pressed options (fn [state _] state))
        rotate-on-fn (rotate-on options (fn [state _] state))
        setup (:setup options (fn [] {}))]
    (assoc options

      :setup (partial setup-3d-nav setup user-settings)

      :draw (fn [state]
              (assert-state-has-navigation state)
              (let [{[c-x c-y c-z] :straight
                     [u-x u-y u-z] :up
                     [p-x p-y p-z] :position} (:navigation-3d state)]
                (q/camera p-x p-y p-z (+ p-x c-x) (+ p-y c-y) (+ p-z c-z) u-x u-y u-z))
              (draw state))

      :key-pressed (fn [state event]
                     (key-pressed (move state event step-size) event))

      rotate-on (fn [state event]
                  (rotate-on-fn (rotate state event pixels-in-360) event)) )))