tag:support.nodebox.net,2012-11-01:/discussions/show-your-work/86-morphNodeBox: Discussion 2016-10-30T04:43:21Ztag:support.nodebox.net,2012-11-01:Comment/362248892015-03-09T09:44:28Z2015-03-09T09:44:28ZMorph<div><p>Cool! I've attached a screenshot so people can see what it does
before downloading.</p>
<p>It seems like it just animates the bounds. Could it work with
arbitrary shapes as well (for example, ellipses or text paths)?</p></div>Frederik De Blesertag:support.nodebox.net,2012-11-01:Comment/362248892015-03-10T09:24:58Z2015-03-10T09:24:58ZMorph<div><p>Frederik, yes, just bounds (height, width, position) plus fill
color. And it only works with rectangles. But it's surprising how
much you can do just with that.</p>
<p>I would have liked to handle roundness as well, but there
doesn't seem to be a way within NodeBox to access that property. If
I could, morph would automatically morph from normal rectangles to
rounded rectangles. If there was a clean way to determine if one of
the incoming shapes was an elipse, morph could substitute a
fully-rounded rectangle for the elipse and gradually turn it into a
rectangle (or whatever else was attached).</p>
<p>Another key addition would be rotation. It would be handy to
feed a before rectangle and an after rotated rectangle; morph would
then automatically spin one into the other. But in NodeBox rotation
is not a property of a shape; a rotated shape is just a different
path.</p>
<p>As you suggest, it would be nicer still if morph could be made
even more general purpose. If two arbitrary shapes (paths) have the
same number of inflection points you could simply map them directly
and transform a huge variety of shapes by simply morphing each line
segment the same way I current morph rectangles That would solve
the rotation problem. Morphing to or from shapes with a different
number of points or with curves would be more complex. You could
probably just resample the point count of the more complex "after"
shape onto the simpler "before" shape and gradually move those
points into the after position keeping them connected. I don't know
how you are representing curves, so that would a bit trickier.</p>
<p>You might want to consider adding some kind of morph as an
official node. Even my limited version is extremely versatile. All
sorts of different complex looking transformations (movement, color
cycling, growing, distorting, etc.) can be created effortlessly and
intuitively with this single node. By tying the position of other
shapes, like text labels, to the output of the morph node you can
make compound objects move together. And you have full control over
the animation speed by simply varying the range conversion.</p></div>johntag:support.nodebox.net,2012-11-01:Comment/362248892015-03-10T12:51:30Z2015-03-10T12:51:30ZMorph<div><p>I couldn't help myself and made a first cut at a generic morph
node. I've attached an ndbx file with seven examples and a
before/during/after screenshot. To see the transformations hit play
or scrub the frames between 1 and 100.</p>
<p>The first example shows the geometrical transformation (you can
see the underlying line segments) using the nodes before I grouped
them into a subnetwork. The other six examples use the genMorph
subnetwork which hides the line segments and also morphs the color;
each of those examples goes from blue to red.</p>
<p>The basic approach is to combine the points of shape A with a
resample of B's points along shape A. Then do the reverse: combine
a resample of A's points along shape B with the points of shape B.
Then gradually interpolate between corresponding pairs of points
from both lists using Connect to draw intermediate shapes.</p>
<p>The intermediate results are a little spikier than I'd like. The
result would be smoother if you could resort the points in both
lists to follow along the outline of their shapes in order. Text
comes out a bit funky because you can see the connecting lines
between individual letters. It doesn't handle any shapes with
curves: if you try to morph a rectangle into an ellipse you wind up
with an octagon instead.</p>
<p>So this needs work but is a big step closer to the general
purpose morph you asked for.</p>
<p>John</p></div>johntag:support.nodebox.net,2012-11-01:Comment/362248892015-03-11T08:47:42Z2015-03-11T08:47:42ZMorph<div><p>Another update.</p>
<p>Turns out I was overthinking it. Simply pulling the point lists
and interpolating (allowing NodeBox to do its wrapping trick if the
count is unequal) is simpler and produces cleaner results.</p>
<p>In this version I also added the ability to morph strokeWidths,
but ran into a bug. Doing a lookup on strokeWidth works fine UNLESS
strokeWidth is zero; in that case the lookup returns a 1. Also,
strokeColor is undefined when strokeWidth is zero, so looking it up
caused the entire subNetork to fail even if I used a switch. I will
file these problems as a bug report so you can track them
separately.</p>
<p>Still wish I could handle curves. Any suggestions?</p>
<p>The revised ndbx file shows a few new examples. I was able to
make the text less funky by passing it as a list of characters
instead of a single string. I have also included an updated set of
screenshots. Please disregard my earlier version.</p>
<p>John</p></div>johntag:support.nodebox.net,2012-11-01:Comment/362248892015-03-12T04:51:02Z2015-03-12T04:51:02ZMorph<div><p>It occurred to me that it is strange to post still images of a
morph animation when it's so easy to export animations. mp4
attached.</p></div>johntag:support.nodebox.net,2012-11-01:Comment/362248892015-03-19T11:49:35Z2015-03-19T11:49:35ZMorph<div><p>OK, so I've been peeking inside of node box SVG files and I
think I see a devilishly simple way of making a truly generic
morph. Here is what I found:</p>
<ol>
<li>All NodeBox shapes are paths.<br></li>
<li>All NodeBox paths are composed of nothing other than lines and
curves.</li>
</ol>
<p>To clarify, a NodeBox ellipse is not coded as an SVG ellipse
object; it is coded as a path consisting of four adjoining curves,
where "curve" is a cubic bezier with two control points. NodeBox
paths consist of an initial Move (starting location) followed by a
series of L point pairs (lines) and C point triplets (curves).</p>
<p>[You can see this by turning on the undocumented "points"
feature in the viewer. Apparently inflection points show as blue
dots and control points show as red dots.]</p>
<p>First, can the NodeBox staff confirm this? Any exceptions?</p>
<p>If this is true, we could "normalize" all NodeBox shapes by
converting every line into a flat curve (a cubic bezier in which
the start point, end point, and both control points are collinear).
A flat curve would look just like a line segment. Once everything
is a series of curves all we have to do is interpolate from each
before curve to each after curve.</p>
<p>The morph node would have 3 ports: a before shape, and after
shape, and a timing input (a float T from 0 to 100).</p>
<ul>
<li>Morph(0) returns the before shape untouched</li>
<li>Morph(1) returns the normalized before shape (looks the same
but has more points in the path)</li>
<li>Morph(99) returns the normalized after shape</li>
<li>Morph(100) returns the untouched after shape</li>
</ul>
<p>Morph (1.01 to 98.99) simply interpolates between each before
curve and each after curve. The beginning and ending points
gradually move from their before positions to their after
positions. Likewise, the two control points gradually move from
before to after. If morphing from a line segment to another line
segment, the control points should remain collinear at all times so
no curves will be seen. If one or both of the before or after
segments is a curve, that curve will gradually morph. A line might
slowly bend, a concave arc might turn into a convex one, etc.</p>
<p>The interpolation function is trivial. One easy way to do it is
just make a line between each pair of before and after points and
feed that line (along with the T value) into a point on path
node.</p>
<p>If the before shape has more or fewer points than the after
shape, NodeBox's wrapping behavior will handle this automatically.
So if a triangle morphs into a rectangle, Nodebox will
automatically create a fourth edge of the triangle drawn over one
of the original edges. All you have to do is feed both normalized
lists into an interpolate function and the morphing should just
happen naturally.</p>
<p>To create this generic morph node all you need is access to the
actual details of the paths (the L's and C's). This is where I hit
a wall. I don't know how to do that. By doing a lookup I can derive
the points, but can't tell which are inflection points and which
are control points. Nor can I create new "normalized" paths
consisting entirely of cubic curves.</p>
<p>[Aside: I find it curious that NodeBox shapes are built entirely
out of cubic beziers but we are not given a cubic bezier node to
make such shapes ourselves, only a quadratic bezier node. I think
you can build cubic beziers using pairs of quadratic beziers, but
it's a bit of a pain. Why not give us mere mortals the real
thing?]</p>
<p>So here is my conundrum. I think Frederik could make an ideal
generic morph node in an afternoon and it might instantly become
one of the most powerful and versatile nodes in the whole NodeBox
arsenal. But even if I tried to roll my own custom node, I don't
understand how to access and change the actual coding of NodeBox
paths.</p>
<p>So...</p>
<p>Could one of you guys whip up a Morph node and just hand it to
us on a silver platter? This could be a fine addition to NodeBox
Live as well as NodeBox 3.</p>
<p>If that's not possible, and if there is some way to do what I
propose in a custom node, could you give me some clue how to access
and modify the hidden structure of NodeBox paths so I can normalize
and interpolate them myself?</p>
<p>Thanks!</p>
<p>John</p></div>johntag:support.nodebox.net,2012-11-01:Comment/362248892015-03-20T18:56:56Z2015-03-20T18:56:56ZMorph<div><p>Hey John,</p>
<p>Your deduction is correct: all shapes are paths, and all paths
consist of béziers and lines.</p>
<p>I've tried in the past to make a generic morph node.
Specifically, I wanted it to work on letters. I've gotten quite far
but hit a wall, well 2 actually:</p>
<ol>
<li>
<p>The starting point of a path is not always the same. So if path
A starts at the top and path B starts at the bottom, the morph is
going to look weird. I've thought about implementing heuristics
that could find the starting point, but I haven't done it yet. In
addition,the orientation could be different (clockwise vs
counterclockwise); I think that could be detected
automatically.</p>
</li>
<li>
<p>Paths can have multiple contours. If we're making a generic
morph we can't assume the amount of contours is going to be equal.
Even if they are equal, they could be flipped, e.g. The inner
contour of the "O" could have index 0 in path A and index 1 in path
B.</p>
</li>
</ol>
<p>Anyway, I can send you the code I have so you can take a look at
it. If you have any solutions for the two problems above, I'd also
love to hear them :-)</p>
<p>Best,</p>
<p>F</p></div>Frederik De Blesertag:support.nodebox.net,2012-11-01:Comment/362248892015-03-20T20:32:14Z2015-03-20T20:32:14ZMorph<div><p>Frederik,</p>
<p>Yes, I noticed both of those issues but didn't mention them as
my post was already pretty long.</p>
<p>I think even a single-contour, starting-point-insensitive morph
would be <em>extremely</em> useful. It would cover the vast
majority of cases outside of letters and even for letters would
cover movement, scaling, and rotation, (and color if you add that
in as I did).</p>
<p>Even weird morphs are often acceptable, and in many cases they
aren't that weird especially when morphing a shape into another
version of itself (at different scale, rotation, position, and/or
color).</p>
<p>I would dearly love to have such a thing right now. I need to
smoothly morph a stacked bar chart into a series of donut charts in
specific locations. Your current morph would probably do that in a
single bound.</p>
<p>So YES, please post or send me your code! Do you have it in the
form of a custom node that I could just snap into place?</p>
<p>In fact, if you don't have time to work on it, I'd encourage you
to release it as a built-in node. For v1 just include a note in
documentation that it doesn't currently handle multiple contours as
gracefully as it might. Later you could issue an update with the
improved version; I don't think doing so would break the code of
anyone using the more limited morph.</p>
<p>That said, I think both problems are solvable. It sounds like
you already have a pretty good idea how to tackle starting
position. My first cut at that would have been to define 8 possible
compass points for a starting position (North, NorthEast, East,
etc.) based on a comparison of the initial path position to the
centroid, and then shift the target path until its start matched
the same compass point as the source path. You could guess at
rotation based on the first two positions in a path with different
x values (second point east of start = clockwise).</p>
<p>That approach is not precise, but my hunch is that it most cases
it would produce acceptable morphs. I think some mild rotation
during a morph is acceptable even when it's not strictly necessary;
this technique would probably avoid violent flips and twists. (You
may already have an even better approach.)</p>
<p>One important point, though. I think it might be extremely
useful to give users an option to control the matching of starting
points. Many times they won't care and the default behavior will be
good enough. But sometimes they will.</p>
<p>For example, when I morph from a stacked bar to a donut I want
all the segments to move together and slowly bend into place.
Ideally, if the target donut is west of the bar I want the bar to
curve clockwise and if east counterclockwise. Any algorithm you
come up with might cause some of the segments to behave differently
than their peers or flop about unnecessarily.</p>
<p>One easy way to afford this control would be to simply expose an
optional shift index. At 0 (the default) the target path is
unshifted relative the normal morphing algorithm. If non-zero, you
simply shift the points again after whatever shifting your
algorithm already did. The user could experiment with this index
until they got the precise affect they wanted. For bonus points you
could also provide a second checkbox to reverse the path order of
the target path; this would let users switch from clockwise to
counterclockwise if need be.</p>
<p>Providing this user control might also relieve any anxiety you
have about perfecting your algorithm. Your algorithm doesn't have
to be perfect if users have the ability to adjust it when needed.
In fact you could release your morph as is (with no attempt to
automatically adjust starting position) and let users adjust it
themselves (if they care). I would be happy with that.</p>
<p>To handle contours my first instinct is to break the morph into
a series of separate simultaneous morphs. To morph from a triangle
to the letter O, I would feed the triangle path into the outer
contour and simultaneously feed the same triangle path into the
inner contour. (This implies an extra step to classify contours as
outer vs. inner.)</p>
<p>Morphing multiple-to-multiple contour shapes becomes complex if
you to need to determine and respect hierarchies of contours (which
in theory could be arbitrarily deep, though in practice more than
two levels are probably rare). My instinct here is to not to let
the perfect become the enemy of the good. It might be sufficient to
map source outer contour to target outer contour and then morph any
remaining inner contours to each other at random. For text strings
there might be a bit of momentary weirdness inside a subset of
letters but I suspect the overall movement would be relatively
orderly.</p>
<p>Those are my initial thoughts. If I have any inspirations I will
pass them along. In the meantime I look forward to trying your
not-yet-perfect-but-alreadly-extremely-useful morphing code.</p>
<p>Thanks!</p>
<p>John</p></div>johntag:support.nodebox.net,2012-11-01:Comment/362248892015-03-20T21:04:40Z2015-03-20T21:04:40ZMorph<div><p>Hey John,</p>
<p>The version I had was for NodeBox 2, so I converted it to
NodeBox 3. The node is quite simple since it doesn't do any of the
heuristics we talked about.</p></div>Frederik De Blesertag:support.nodebox.net,2012-11-01:Comment/362248892015-03-20T21:05:33Z2015-03-20T21:05:33ZMorph<div><p>Here's a screenshot, with the morphing going out of the original
bounds to create a "super-fat" letter:</p></div>Frederik De Blesertag:support.nodebox.net,2012-11-01:Comment/362248892015-03-20T22:44:01Z2015-03-20T22:44:01ZMorph<div><p>Frederik,</p>
<p>This is wonderful! Thank you.</p>
<p>Attached is a sample NodeBox demo I made which morphs a bar (a
tall rectangle) into a donut segment (formed by removing an eclipse
from an arc). I include both the NodeBox doc and an MP4 showing the
transformation.</p>
<p>To begin with I followed your technique of resampling both
shapes, but the resulting morph was, as you would say, "weird".</p>
<p>I was able to fix this by shifting and reversing the target path
list. I did this by first converting the target path to a list of
points (using an earlier subnetwork I made - is there a more
elegant way of doing this?). I then fed that list into a shift
node, set to 6, and then a reverse node, then a connect and a
colorize node to turn it back into a solid shape.</p>
<p>In order to find the magical setting of 6 for the shift I simply
advanced the morph to about 20 and then scrubbed along the shift
index until the result looked symmetrical. This was not too hard to
do, so I think it would be practical to expose a shift parameter
and reverse checkbox in your morph node. In fact you could
automatically resample as well.</p>
<p>The reverse node was crucial by the way. Without it I was not
able to get a non-weird morph.</p>
<p>This morph is not perfect. Ideally, the top and bottom of the
bar and the two flat edges of the donut segment should be rendered
at all stages as simple line segments. As it is those edges develop
a temporary bump during morphing which I'd rather do without. But
this is not possible with a general resample along the entire
path.</p>
<p>One simple improvement, then, would be to only resample the
curves. As I said earlier, I don't know how to see inside the path
structure to tell the curves from the lines. Can you do that? If
so, it might be fairly simple to only resample the curves.</p>
<p>If you could:</p>
<ul>
<li>incorporate curve resampling within the morph function</li>
<li>wrap unequal path lengths instead of rejecting unequal path
lengths</li>
<li>add a shift integer parameter</li>
<li>add a reverse boolean parameter</li>
</ul>
<p>you might have a pretty decent generic morph!</p>
<p>Thanks again!</p>
<p>John</p></div>johntag:support.nodebox.net,2012-11-01:Comment/362248892015-03-21T03:47:12Z2015-03-21T03:47:12ZMorph<div><p>One more video example: the word "before" morphing to
"after!"</p>
<p>The video shows two versions. The top version just feeds the two
words directly into a single instance of your morph node with only
one change: I broke each word into a list of individual characters
and spaced them equally using a grid.</p>
<p>The bottom version morphs each letter separately (and kerns them
together better than a uniform grid allows). For each letter I used
the same technique from my bar-to-donut animation to shift the
order of points in the target path with each letter getting a
different shifting value. None of the letters required
reversals.</p>
<p>If you download the video and turn on the looping feature of
your video player you can see the difference in each letter. The
morphs in the bottom example are a bit smoother.</p>
<p>Adjusting some letters was fairly easy, but others were quite
difficult. Some lessons:</p>
<ol>
<li>
<p>The contours problem may be overrated. I'm not sure handling
them separately would make that much of an improvement (other than
getting rid of the barely noticeable cleft between contours).</p>
</li>
<li>
<p>Some morphs are a bit weird no matter what you do. I defy you
turn an e into an exclamation mark without some weirdness. I tried
countless shift variations but none were noticeably better than the
unadjusted version.</p>
</li>
<li>
<p>Shifting made a noticeable improvement in some cases. But
overall, I'm not sure the adjusted version is dramatically better
than the unadjusted version.</p>
</li>
</ol>
<p>From my previous example I conclude that giving users the option
to shift and/or reverse is important.</p>
<p>From this example I conclude that we may be reaching the point
of diminishing returns. Fancy start-point optimization and
multi-contour handling algorithms would be a ton of work for a
fairly modest improvement in most cases. And some morphing
weirdness is inescapable.</p>
<p>John</p></div>johntag:support.nodebox.net,2012-11-01:Comment/362248892016-10-29T02:46:13Z2016-10-30T04:43:21ZMorph<div><p>A follow up...</p>
<p>The "bar to donut segment" morph posted above suffered from an
unsightly bulge during the transition.</p>
<p>This happens because the outside donut curve is longer than the
inside curve. So when the bar morphs it has to make up the
difference by borrowing from one side to pay the other.</p>
<p>In order to fix this I broke the rectangle into its four edges
and adjusted its sampling so that more points appeared on the right
(outer) edge and fewer points on the left.</p>
<p>To figure out the precise number of points needed on each edge
of the rectangle, I turned on the "Point Numbers" checkbox,
adjusted the points on the donut using shift and reverse nodes so
that both the rectangle and donut started in the upper left corner
and travelled clockwise, and then noted how many points were on the
outer and inner curves of the donut segment.</p>
<p>I have attached the revised networks (in a folder including
Frederik's morphing node) and an MP4 showing the improved
animation. The pale green bar on the left is the earlier approach
with uniform distribution; you can see the bulge appear on the
bottom during the transition. The gray bar on the right shows the
improved variable distribution. As you can see, the transformation
is now smooth and free of bulges.</p>
<p>(There is still a slight imperfection in two corners. That could
be corrected by slightly modifying two of the points in the donut
distribution, or just using more points altogether.)</p>
<p>The moral of this story is that, even with the current
limitations of Frederik's morph node, it is possible to get decent
results even for tricky transformations - but only if you are crazy
enough to fine tune the distributions.</p></div>john