|
| 1 | +# **Tutorial 9:** Morphing `Javis` Objects |
| 2 | +There are multiple ways to morph an object in Javis. |
| 3 | + |
| 4 | +- Using `morph_to(::Object)` method. Any Object can be morphed to any other object using this method. |
| 5 | +- Using `morph_to(::Function)` method. Similar to `morph_to(::Object)` but morphs to a function instead. Can morph an object to a function that contains Luxor calls to draw what it should morphed into. |
| 6 | +- Specifying an Action with an `Animation` along with `morph()` to make keyframed morphings. This helps making and timing a sequence of morph animations easier. |
| 7 | + |
| 8 | +## Morphing one object to another. |
| 9 | + |
| 10 | +Like other animations `morph_to(::Object)` is to be used with action. To learn more about Actions refer to [Tutorial 5](tutorial_5.md). |
| 11 | +Here is a simple code snippet on how to use `morph_to`... |
| 12 | +```julia |
| 13 | +using Javis |
| 14 | + |
| 15 | +video = Video(500,500) |
| 16 | +nframes = 160 |
| 17 | + |
| 18 | +function circdraw(color) |
| 19 | + sethue(color) |
| 20 | + setopacity(0.5) |
| 21 | + circle(O,100,:fillpreserve) |
| 22 | + setopacity(1.0) |
| 23 | + sethue("white") |
| 24 | + strokepath() |
| 25 | +end |
| 26 | + |
| 27 | +function boxdraw(color) |
| 28 | + sethue(color) |
| 29 | + box(O,100,100,:fillpreserve) |
| 30 | + setopacity(1.0) |
| 31 | + sethue("white") |
| 32 | + strokepath() |
| 33 | +end |
| 34 | +Background(1:nframes,(args...)->background("black")) |
| 35 | +boxobj = Object((v,o,f) -> boxdraw("green")) |
| 36 | +circobj = Object((v,o,f) -> circdraw("red")) |
| 37 | + |
| 38 | +transform_to_box = Action(20:nframes-20, morph_to(boxobj)) |
| 39 | +act!(circobj, transform_to_box) |
| 40 | +render(video,pathname="circ_to_box.gif") |
| 41 | +``` |
| 42 | + |
| 43 | + |
| 44 | + |
| 45 | +If you aren't familiar with this syntax `(v,o,f)-> circdraw("red")` its an "anonymous" function or sometimes called a lambda function. |
| 46 | +Basically a nameless function that is written on the spot in that line of code . One might as well use any other function `func` in place of it |
| 47 | +(which takes at least 3 arguments `video,object,frame`). Elsewhere in the docs/tutorials you will come across |
| 48 | +something of the form `Object( (args...) -> ("some code here") )`. This is [slurping](https://docs.julialang.org/en/v1/manual/faq/#The-two-uses-of-the-...-operator:-slurping-and-splatting) and is similar to packing `*args` in python. |
| 49 | + |
| 50 | +We created two objects `circobj` and `boxobj` . `circobj` ofcourse is a circle because its drawing function `(v,o,f) -> circdraw("red")` |
| 51 | +draws a circle, with a `color=red`, filling at `0.5` opacity, and then makes a white outline (stroke). |
| 52 | +`boxobj`'s function draws an opaque green box, with white outline. |
| 53 | + |
| 54 | +This Object function is called repeatedly at render-time at every frame that the object exists to draw this object. The appropriate `video`,`object`, and `frame` are passed to |
| 55 | +this function at render time. |
| 56 | +Javis then has other tricks up its sleeve to scale/move/morph whats going to be drawn depending on the |
| 57 | +frame and object to effect out animations through Actions. This is roughly the idea behind Javis's Object-Action mechanism |
| 58 | + |
| 59 | +We defined a `transform_to_box` Action which runs from frame 20 to lastframe-20 . The `Action` morphs whatever object its acted upon, into what looks |
| 60 | +like `boxobj`. Note that `boxobj` and `circobj` are separate objects all the time, even after the `Action` (it just happens that they overlap each other). As the Action keeps getting applied at render time frame by frame, the "drawing" of `circobj` starts to look like `boxobj`'s drawing. |
| 61 | + |
| 62 | +The Action is applied to the `circobj` with the `act!` function. |
| 63 | + |
| 64 | +Note that the `boxobj` is present throughout as the `circobj` is morphing. |
| 65 | +If you want to hide it you can set its opacity to 0 with another action (to make it disappear) and set its frames to be drawn for 1 frame only (for efficiency). |
| 66 | +```julia |
| 67 | +Background(1:nframes,(args...)->background("black")) |
| 68 | +boxobj = Object(1:1 , (args...) -> boxdraw("green") ) |
| 69 | +circobj = Object(1:nframes,(args...) -> circdraw("red")) |
| 70 | + |
| 71 | +transform_to_box = Action(20:nframes-20, morph_to(boxobj)) |
| 72 | +hide_action = Action(1:1, (args...)->setopacity(0.0) ) |
| 73 | + |
| 74 | +act!(circobj, transform_to_box) |
| 75 | +act!(boxobj, hide_action) |
| 76 | + |
| 77 | +render(video,pathname="circ_to_box_hidden.gif") |
| 78 | +``` |
| 79 | + |
| 80 | +However you can directly specify a shape an object has to morph to without making an Object using `morph_to(f::Function)` i.e passing a function as an argument. |
| 81 | + |
| 82 | +## Morphing an `Object` using a `Function` |
| 83 | + |
| 84 | +```julia |
| 85 | +Background(1:nframes,(args...)->background("black")) |
| 86 | +#boxobj = Object(1:1 , (args...) -> boxdraw("green") ) |
| 87 | +circobj = Object(1:nframes,(args...) -> circdraw("red")) |
| 88 | + |
| 89 | +transform_to_box = Action(20:nframes-20, morph_to(boxdraw,["blue"])) |
| 90 | +#hide_action = Action(1:1, (args...)->setopacity(0.0) ) |
| 91 | + |
| 92 | +act!(circobj, transform_to_box) |
| 93 | +#act!(boxobj, hide_action) |
| 94 | + |
| 95 | +render(video,pathname="circ_to_box_func.gif") |
| 96 | +``` |
| 97 | + |
| 98 | + |
| 99 | + |
| 100 | +Here we have morphed the circle without defining an object to morph to. Rather the shape it has to morph into |
| 101 | +is given by a `Function`. |
| 102 | +The general syntax is `morph_to(fn::Function,args::Array=[])` |
| 103 | +. `args` is an array of arguments that is to be passed to the function. |
| 104 | +Here we morph `circobj` to a shape |
| 105 | +that would is drawn by `boxdraw("blue")`. Morphed Objects can be furthur morphed into |
| 106 | +other shapes by carrying out another `Action` further in the timeline. |
| 107 | + |
| 108 | +## Keyframed morphs using Animations.jl |
| 109 | + |
| 110 | +Another mechanism for morphing is by passing `morph()` to `Action` along with an `Animation` |
| 111 | +For a tutorial on how to use Animations.jl look at [Tutorial 7](tutorial_7.md), |
| 112 | + |
| 113 | +```julia |
| 114 | +using Javis |
| 115 | +using Animations |
| 116 | +video = Video(500,500) |
| 117 | +nframes = 160 |
| 118 | + |
| 119 | +function circdraw(color) |
| 120 | + sethue(color) |
| 121 | + setopacity(0.5) |
| 122 | + circle(O,50,:fillpreserve) |
| 123 | + setopacity(1.0) |
| 124 | + sethue("white") |
| 125 | + strokepath() |
| 126 | +end |
| 127 | + |
| 128 | +function boxdraw(color) |
| 129 | + sethue(color) |
| 130 | + box(O,100,100,:fillpreserve) |
| 131 | + setopacity(1.0) |
| 132 | + sethue("white") |
| 133 | + strokepath() |
| 134 | +end |
| 135 | + |
| 136 | +function stardraw() |
| 137 | + sethue("white") |
| 138 | + star(O,100,5,0.5,0.0,:stroke) |
| 139 | +end |
| 140 | + |
| 141 | +Background(1:nframes+10,(args...)->background("black")) |
| 142 | +boxobj = Object(1:nframes+10 , (args...) -> boxdraw("green") ) |
| 143 | + |
| 144 | +anim = Animation([0,1],MorphFunction[(boxdraw,["green"]),(circdraw,["red"])] ) |
| 145 | + |
| 146 | +action = Action(1:nframes,anim,morph()) |
| 147 | +act!(boxobj,action) |
| 148 | +render(video,pathname="box_to_circ_hidden.gif") |
| 149 | +``` |
| 150 | + |
| 151 | +Take a look at `anim`. It is of type `Animation`. |
| 152 | +First lets look at a simpler instance of `Animation`. |
| 153 | +``` |
| 154 | +Ex:1 |
| 155 | +Animation([0,1],[2,4]) |
| 156 | +``` |
| 157 | +Think of Animations like a "map" or a "function" (in the math sense) thats maps values from its first argument (`[0,1]` above) to another set of values (`[2,4]`) . This means that |
| 158 | +0 gets mapped to 2 and 1 gets mapped to 4 and all values inbetween are linearly interpolated. |
| 159 | +Another Example |
| 160 | +``` |
| 161 | +Ex:2 |
| 162 | +Animation([0,0.3,1],[3,4.5,7]) |
| 163 | +``` |
| 164 | + |
| 165 | +This animation maps 0 to 3 , 0.3 -> 4.5 and 1->7. And all values inbetween are linear interpolations. |
| 166 | + |
| 167 | +Take a look at the `Animations.jl` package for an indepth explanation on how to have different interpolations to make your animations look way cooler. ( for example the `sineio` interpolaation is slow at first speeds up in between and gradually slows to a halt ) |
| 168 | + |
| 169 | +One can in principle provide values beyond 0 and 1 for the first argument however Javis requires `Animation` objects to have the first argument to be from 0 to 1. |
| 170 | +This `Animation` object is passed to an `Action`, and Javis interprets 0 to be the first frame |
| 171 | +of the Action and 1 to be the final frame of the Action. |
| 172 | + |
| 173 | +In the big code snippet above we can see that the second array passed to Animation is |
| 174 | +an array of `MorphFunction`s. |
| 175 | +`MorphFunction` is a struct . This struct has 2 fields. The fields are `func` and `args`. These arguments are used to specify drawing functions and the arguments to be passed to them , The `Array` of `MorphFunction` passed to the `Animation` defines a sequence of shapes/drawings that the `Object` should be morphed into one by one in that order. Each shape/drawing is what would have been got by calling `func(args...)` of the respective `MorphFunction`. In the example above there are only two in shapes in the sequence a green box and a red circle (`boxdraw("green")` and `circdraw("red")`). |
| 176 | +Typically the first `MorphFunction` should draw the same thing that `Object` is. |
| 177 | + |
| 178 | +The general idea of whats going on is we are making an `Animation` that maps `0` (i.e the first frame of the action.) to `MorphFunction(boxdraw,["green"])` and `1` (last frame of the action) to ` MorphFunction(circdraw,["red"])` and Javis handles the interpolation between them. |
| 179 | + |
| 180 | +Thus we have made an `Animation` called `anim`. Then we made an `action` with this `anim`. We called it `action` . Then we applied the action on our object `boxobj` to get ... |
| 181 | + |
| 182 | + |
| 183 | + |
| 184 | +The way of morphing shines when you have to do multiple morphs in a sequence and with different timings. Lets look at another example taking object to morph box(initial shape) -> star -> circle in a sequence. |
| 185 | + |
| 186 | +Change the lines describing the animation to |
| 187 | +```julia |
| 188 | +anim = Animation([0, 0.7, 1],MorphFunction[(boxdraw, ["green"]), (stardraw, []), (circdraw, ["red"])]) |
| 189 | +``` |
| 190 | +`stardraw` draws a white star without fill. The function does not take an argument and therefore the `Tuple` with `stardraw` should have an empty `Array` at its |
| 191 | +second index. If your drawing functions do not take any arguments you can pass it as function itself, and need not wrap it in a `Tuple`. |
| 192 | + |
| 193 | +Ex. suppose `mydraw1` , `mydraw2` and `mydraw4` take a color as an argument but `mydraw3` does not take any arguments. |
| 194 | + |
| 195 | +```julia |
| 196 | +anim = Animation([0, t1, t2, 1],MorphFunction[ (mydraw1,["red"]), (mydraw2,["blue"]), mydraw3, (mydraw4,["black"]) ]) |
| 197 | +``` |
| 198 | + |
| 199 | +A third way to pass functions to morph into is to simply pass a function an its arguments in a `Tuple` |
| 200 | + |
| 201 | +```julia |
| 202 | +anim = Animation([0, t1, t2, 1],MorphFunction[ (mydraw1,"red"), (mydraw2,"blue"), mydraw3, (mydraw4,"black") ]) |
| 203 | +``` |
| 204 | + |
| 205 | +When passed this way the first element of the Tuple is taken to be the function and the subsequent elements are the arguments to be |
| 206 | +passed to the function. |
| 207 | + |
| 208 | + |
| 209 | + |
| 210 | +What we see now is from the beginning to 0.7 fraction of the `Action`'s frames it carries |
| 211 | +out the morphing from a `boxdraw("green")` to `stardraw()`. |
| 212 | +And the remainder of the `Action`'s frames it morphs from `stardraw()` to `circdraw("red")`. Once again , do look up Animations.jl and Tutorial 7 to see how you pass easing functions to manipulate the timing of animations (for example ... initially slow - fast in the middle - slow in the end) . |
| 213 | +Now you know a bit about Morphing . Remember just like any other Action you can stack morphing actions with other Actions (translations, scaling etc) to bring about effects you desire. |
| 214 | + |
| 215 | +> **Author(s):** John George Francis (@arbitrandomuser) |
| 216 | +> **Date:** May 28th, 2022 \ |
| 217 | +> **Tag(s):** action, morphing, object, animation |
0 commit comments