244 lines
12 KiB
Clojure
244 lines
12 KiB
Clojure
(ns arthur.domain.tracing-test
|
|
"Tracing symbols: footage or a still to draw over, placed like any symbol and
|
|
never part of the picture — and a face's footage as one of them, under its head."
|
|
(:require [cljs.test :refer [deftest is testing]]
|
|
[arthur.demo.take :as take]
|
|
[arthur.domain.clip :as clip]
|
|
[arthur.domain.creation :as creation]
|
|
[arthur.domain.leaf :as leaf]
|
|
[arthur.domain.nest :as nest]
|
|
[arthur.domain.node :as node]
|
|
[arthur.domain.palette :as pal]
|
|
[arthur.domain.pick :as pick]
|
|
[arthur.domain.symbol :as symbol]
|
|
[arthur.events.ui :as ui]
|
|
[arthur.footage.store :as store]
|
|
[arthur.flow.freeze :as freeze]
|
|
[re-frame.core :as rf]
|
|
[re-frame.db :as rf-db]))
|
|
|
|
(def ^:private image-h 1000)
|
|
|
|
(def ^:private frozen
|
|
"The synthetic take frozen as though it had come from footage 1000px tall."
|
|
(delay (freeze/clip (assoc take/params :footage {:id "f00" :range [0 take/frames]
|
|
:width 1500 :height image-h})
|
|
{:face-1 @take/measured})))
|
|
|
|
(defn- traced
|
|
"The take with face-1's trace keys and origin set."
|
|
[t]
|
|
(freeze/head-mode {:trace t} @frozen))
|
|
|
|
(defn- near? [a b]
|
|
(every? #(< (js/Math.abs %) 1e-3) (map - a b)))
|
|
|
|
(defn- resolver [c sid opts]
|
|
(clip/resolver c sid (:store @frozen) pal/index-of opts))
|
|
|
|
(defn- traces-at [r f]
|
|
(filterv #(= :trace (:kind %)) (r f)))
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; the document
|
|
|
|
(deftest a-freeze-from-footage-makes-a-tracing-symbol-and-places-it-under-the-head
|
|
(let [c (traced nil)
|
|
plate (get-in c [:symbols :face-1 :nodes :plate])]
|
|
(is (clip/trace? (clip/symbol c :footage)))
|
|
(is (= {:footage "f00" :range [0 take/frames]} (:media (clip/symbol c :footage))))
|
|
(is (= :head (:parent plate)) "the footage rides the head")
|
|
(is (= :footage (node/source plate)))
|
|
(is (node/measured? plate) "and is registered by the measurement, not by hand")
|
|
(is (empty? (clip/problems c)) (pr-str (clip/problems c)))
|
|
(is (= c (leaf/clip "t" (leaf/leaves "t" c))) "it saves and comes back")))
|
|
|
|
(deftest the-origin-writes-the-heads-reads-and-the-keys-the-plates-holds
|
|
(is (nil? (get-in (traced {:origin :continuous :frames [12]}) [:symbols :face-1 :nodes :head :reads])))
|
|
(is (= {:holds-of :plate} (get-in (traced {:origin :keys :frames [12]}) [:symbols :face-1 :nodes :head :reads])))
|
|
(is (= {:holds [0]} (get-in (traced {:origin :start}) [:symbols :face-1 :nodes :head :reads])))
|
|
(is (= [12 40] (get-in (traced {:origin :continuous :frames [12 40]})
|
|
[:symbols :face-1 :nodes :plate :time :holds]))
|
|
"the keys are the footage's whatever the head does"))
|
|
|
|
(deftest a-tracing-symbol-that-does-not-say-what-it-shows-will-not-load
|
|
(doseq [[why sym] [["nodes" {:nodes {:x {:id :x :kind :group :z "a"}}}]
|
|
["no media" {:media {}}]
|
|
["both" {:media {:footage "f" :image "i" :range [0 10]}}]
|
|
["short range" {:media {:footage "f" :range [0 9]}}]
|
|
["no size" {:width nil}]]]
|
|
(let [c (update-in (traced nil) [:symbols :footage] merge sym)]
|
|
(is (seq (clip/problems c)) why))))
|
|
|
|
(deftest reads-must-name-a-node-that-can-be-followed
|
|
(doseq [reads [{:holds-of :nowhere} {:holds-of :head} {:holds [3 1]}
|
|
{:holds [0] :holds-of :plate}]]
|
|
(is (seq (clip/problems (assoc-in (traced nil) [:symbols :face-1 :nodes :head :reads] reads)))
|
|
(pr-str reads))))
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; registration
|
|
|
|
(defn- plate-at
|
|
"Face-1's footage op at frame `f`, its world matrix as a vector."
|
|
[c f]
|
|
(let [[op] (traces-at (resolver c :face-1 {:tracing? true}) f)]
|
|
(vec (array-seq (:m op)))))
|
|
|
|
(defn- filmed
|
|
"Where footage sitting exactly where it was filmed lands: the face's own
|
|
placement, over image height, and nothing else."
|
|
[c f]
|
|
(let [r (resolver c :face-1 nil)]
|
|
(r f)
|
|
(vec (array-seq (node/mul! (node/mat) (symbol/world-of r [:place])
|
|
(js/Float64Array. #js [(/ 1 image-h) 0 0 (/ 1 image-h) 0 0]))))))
|
|
|
|
(deftest the-footage-registers-to-the-face-it-was-filmed-with
|
|
(let [free (traced {:origin :continuous})
|
|
keyed (traced {:origin :keys :frames [12]})
|
|
ride (traced {:origin :continuous :frames [12]})
|
|
start (traced {:origin :start})]
|
|
(is (near? (filmed free 30) (plate-at free 30))
|
|
"a head reading the footage's own frame cancels out")
|
|
(is (near? (filmed keyed 30) (plate-at keyed 30))
|
|
"held at the key, face and footage both stand still where it was filmed")
|
|
(is (not (near? (filmed ride 30) (plate-at ride 30)))
|
|
"a continuous head carries the held footage along with it")
|
|
(is (= 12 (:frame (first (traces-at (resolver ride :face-1 {:tracing? true}) 30))))
|
|
"showing the held frame")
|
|
(is (not (near? (filmed start 60) (plate-at start 60)))
|
|
"footage under a head held at its start is stabilised, not where it was filmed")))
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; never in the picture
|
|
|
|
(deftest only-the-stage-asks-for-tracing
|
|
(let [c (traced nil)]
|
|
(doseq [f [0 30 100]]
|
|
(is (empty? (traces-at (resolver c :main nil) f)) "an export, a centre, a thumbnail")
|
|
(is (= 1 (count (traces-at (resolver c :main {:tracing? true}) f))) "the stage"))))
|
|
|
|
(deftest a-trace-op-comes-up-through-every-instance-above-it
|
|
(let [c (assoc-in (traced nil) [:symbols :wrap]
|
|
{:id :wrap :frames 200
|
|
:nodes {:m {:id :m :kind :instance :z "a0" :source {:symbol :main}
|
|
:channels {[:xform :pos] {:animated? false :value [30 -10]}}}}})
|
|
inner (first (traces-at (resolver c :main {:tracing? true}) 20))
|
|
outer (first (traces-at (resolver c :wrap {:tracing? true}) 20))
|
|
moved (fn [m] (let [v (vec (array-seq m))] (-> v (update 4 + 30) (update 5 - 10))))]
|
|
(is (= [:m :face-1 :plate] (:node outer)) "named by its row path, as anything drawn is")
|
|
(is (= [:face-1 :plate] (:layer outer)) "and switched by where it is, not where it is placed")
|
|
(is (near? (moved (:m inner)) (vec (array-seq (:m outer)))))))
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; a tracing layer placed by hand
|
|
|
|
(def ^:private still
|
|
{:id :still :name "ref" :type :trace :media {:image "abc"}
|
|
:frames 1 :fps 30 :width 400 :height 200 :nodes {}})
|
|
|
|
(defn- with-still []
|
|
(-> (clip/blank)
|
|
(assoc-in [:symbols :still] still)
|
|
(clip/place-symbol {} :main :still 0 :layer [160 100])
|
|
(update-in [:symbols :main :nodes :layer] assoc
|
|
:playback {:in 0 :speed 0 :end :hold} :span [0 120])))
|
|
|
|
(deftest a-still-is-placed-about-its-middle-and-shows-on-every-frame
|
|
(let [c (with-still)
|
|
r (clip/resolver c :main {} pal/index-of {:tracing? true})]
|
|
(is (empty? (clip/problems c)) (pr-str (clip/problems c)))
|
|
(is (= [200 100] (clip/center c {} :still)) "a tracing symbol's middle is its pixels'")
|
|
(doseq [f [0 50 119]]
|
|
(let [[op] (traces-at r f)]
|
|
(is (= 0 (:frame op)))
|
|
(is (= [-40 0] (vec (take 2 (drop 4 (array-seq (:m op))))))
|
|
(str "its middle on the drop point, frame " f))))))
|
|
|
|
(deftest nothing-is-created-inside-a-tracing-layer
|
|
(let [c (with-still)]
|
|
(is (= :main (:sid (creation/target c {} :main [:node :main :layer [:layer]] 10)))
|
|
"selecting a tracing layer creates beside it, as selecting a shape does")
|
|
(is (= "a tracing layer is a picture to draw over — nothing goes inside it"
|
|
(:refused (ui/drop-destination-at c {} :main 10 [:node :main :layer [:layer]]))))))
|
|
|
|
(deftest a-click-picks-what-is-drawn-before-a-reference
|
|
(let [trace {:kind :trace :node :layer :size [100 100]
|
|
:m (js/Float64Array. #js [0 1 -1 0 50 0])}
|
|
shape {:kind :rect :node :dot :cx 10 :cy 10 :size 4}]
|
|
(is (= [:dot] (pick/hit [shape trace] [10 10])) "drawn wins, though the photo is on top")
|
|
(is (= [:layer] (pick/hit [shape trace] [20 50])) "a reference where nothing is drawn")
|
|
(is (nil? (pick/hit [shape trace] [60 50])) "outside the turned image is outside it")))
|
|
|
|
(deftest a-held-layer-is-keyed-on-its-own-unfloored-frames
|
|
(let [c (assoc-in (traced {:origin :keys :frames [12]}) [:symbols :face-1 :nodes :plate :time :holds] [12])
|
|
t (nest/own-time c (:store @frozen) :face-1 [:plate] 30)]
|
|
(is (= 30 (* (:rate t) (- 30 (:at t)))) "frame 30 is 30, though it shows 12")))
|
|
|
|
(deftest a-dropped-still-lasts-the-rest-of-the-symbol-fits-it-and-is-reused
|
|
(let [id (store/install! {:clip (clip/blank) :store {}} "drop-tracing")
|
|
sym {:name "sheet" :type :trace :media {:image "abc"}
|
|
:width 400 :height 800 :nodes {}}
|
|
drop! (fn []
|
|
(rf/dispatch-sync [::ui/drop-tracing sym 30 nil nil])
|
|
(let [doc (:clip (store/entry id))
|
|
[_ _ uuid] (get-in @rf-db/app-db [:ui :selection])]
|
|
[doc (get-in doc [:symbols :main :nodes uuid])]))]
|
|
(reset! rf-db/app-db {:clip/current id :paint/revision 0
|
|
:ui {:open :main} :playback {:frame 30}})
|
|
(let [[doc n] (drop!)
|
|
sid (node/source n)
|
|
[w h] (clip/stage doc :main)
|
|
k (/ h 800)]
|
|
(is (empty? (clip/problems doc)) (pr-str (clip/problems doc)))
|
|
(is (clip/trace? (clip/symbol doc sid)))
|
|
(is (= (- (clip/frames doc :main) 30) (clip/frames doc sid))
|
|
"as long as what is left of the symbol it landed in")
|
|
(is (= [30 (clip/frames doc :main)] (node/placed-span n)))
|
|
(is (= [k k] (get-in n [:channels [:xform :scale] :value])) "as tall as the stage")
|
|
(is (= [(/ w 2) (/ h 2)] (mapv + (get-in n [:channels [:xform :pos] :value])
|
|
(get-in n [:channels [:xform :anchor] :value])))
|
|
"dropped on the timeline, it is middled on the stage")
|
|
(let [[doc2 n2] (drop!)]
|
|
(is (= sid (node/source n2)) "the same picture is the same symbol")
|
|
(is (= 1 (count (filter clip/trace? (vals (:symbols doc2))))))))))
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
(deftest dropping-a-tracing-symbol-fits-only-the-new-placement
|
|
(doseq [point [nil [80 60]]]
|
|
(let [c (traced nil)
|
|
plate (get-in c [:symbols :face-1 :nodes :plate])
|
|
sid (node/source plate)
|
|
c (assoc-in c [:symbols sid :width] 3000)
|
|
id (store/install! {:clip c :store (:store @frozen)} "fit-pool-tracing")]
|
|
(reset! rf-db/app-db {:clip/current id :paint/revision 0
|
|
:ui {:open :main} :playback {:frame 0}})
|
|
(rf/dispatch-sync [::ui/drop-symbol sid 0 point nil])
|
|
(let [doc (:clip (store/entry id))
|
|
[_ host uuid] (get-in @rf-db/app-db [:ui :selection])
|
|
n (get-in doc [:symbols host :nodes uuid])
|
|
[w h] (clip/stage doc host)
|
|
k (min (/ w 3000) (/ h image-h))]
|
|
(is (= [k k] (get-in n [:channels [:xform :scale] :value])))
|
|
(is (= (or point [(/ w 2) (/ h 2)])
|
|
(mapv + (get-in n [:channels [:xform :pos] :value])
|
|
(get-in n [:channels [:xform :anchor] :value]))))
|
|
(is (= plate (get-in doc [:symbols :face-1 :nodes :plate]))
|
|
"the face's registered tracing placement is unchanged")
|
|
(is (= (clip/symbol c sid) (clip/symbol doc sid))
|
|
"the shared tracing symbol is unchanged")))))
|
|
|
|
;; on and off
|
|
|
|
(deftest switching-one-layer-on-switches-tracing-on
|
|
(reset! rf-db/app-db {:ui {:tracing {:on? false :opacity 0.5 :hidden #{[:face-1 :plate]}}}})
|
|
(rf/dispatch-sync [::ui/show-trace [:face-1 :plate] true])
|
|
(is (= {:on? true :opacity 0.5 :hidden #{}} (get-in @rf-db/app-db [:ui :tracing])))
|
|
(testing "and switching one off leaves the rest alone"
|
|
(rf/dispatch-sync [::ui/show-trace [:face-1 :plate] false])
|
|
(is (= {:on? true :opacity 0.5 :hidden #{[:face-1 :plate]}} (get-in @rf-db/app-db [:ui :tracing]))))
|
|
(testing "from nothing"
|
|
(reset! rf-db/app-db {:ui {:tracing {:on? true}}})
|
|
(rf/dispatch-sync [::ui/show-trace [:s :n] false])
|
|
(is (= #{[:s :n]} (get-in @rf-db/app-db [:ui :tracing :hidden])))))
|