Signed Distance Node
Attached is a node, signed_dist, that computes the minimum signed distance from points to a shape boundary. Distances outside the shape are postiive; distances inside are negative. Thanks to Jussi Jokinen for spurring my interest in signed distances.
Signed_dist takes five parameters:
- Points. A grid of points (typically used to form a canvas of pixels)
- Shape. A path or geometry, normally closed
- Normalize. If checked, distances will be normalized (-1 to 1) based on the maximum exterior and interior distances
- Exterior Focus. Changes the distribution of distance values for points outside shapes. A positive focus will make the values more quickly approach the maximum, negative will do the reverse. The effect can be better understood when distance values are used to drive pixel colors. Values can range from -99 to +99.
- interior Focus. Does the same thing but for points inside shapes.
NOTE: for the purposed of this function, points that land exactly on the boundary of the shape are considered to be outside. I made a much more complex version of this node that counted boundary points as being inside (as is the normal practice), but it ran twice as slowly, so I went with the simpler, faster version.
Signed distance functions (SDFs) are primarily used to handle challenging real time animation like flickering flames. Flames represented as multiple vector paths with complex geometries which change second by second and merge and separate in near-infinite ways can be hard to compute at scale. SDFs provide a way of representing shapes not as distinct splines, but as emergent properties of a field of pixels, where each position in the field (each pixel) is given a number which can then code for color. More about SDFs here:
https://en.wikipedia.org/wiki/Signed_distance_function
Nodebox is nowhere near fast enough to animate flames in real time. But, as Jussi has demonstrated, signed distances can be used in all sorts of generative art projects. Jussi used signed distances as a displacement or terrain map, which he then used to draw lines which he fed to a plotter. I can see many other possibilities.
If you use signed_dist to color "pixels" (a grid of squares), you will have to be content with a relatively low resolution. Even a 100 x 100 pixel grid will require firing the node 10,000 times. I worked to make this node as efficient as I could. The time required to calculate 10,000 distance will depend on how complex the shape is. On my MacBook Pro:
- a simple 400 diameter circle at 100 x 100 took under 1 second
- a 1500 point TimesNewRoman lower case at 100 x 100 took 2 seconds
- the inkblot monster shown in the screenshot at 100 x 100 took 12 seconds
Those numbers are just for the distances; more time will be required to draw and color the "pixels". I find I can produce 200 x 200 PNGs (40,000 pixels) without too much waiting; going higher than about 320 x 320 (100,000+ pixels) may cause "out of memory" errors.
I have attached the node with a demo (see screenshot).
The demo allows you to try three different shapes:
- an overlapping ball and star
- a lower case a
- an inkblot monster (roughly similar to what Jussi described making)
The display_grid subnetwork lets you optionally display the distance numbers for each pixel and/or overlay an outline of the shape.
You can also easily change the canvas size and resolution using the make_grid subnetwork. Set the resolution to a low number, like 25, with the show numbers option on, and a simple shape to more easily see the distance numbers and the effect of changing focus. Choose normalize to see the distances displayed on a scale from -100 to 100 (multiplied by 100 for ease of use). Drag the exterior and interior focus up and down to see how this affects the distances, and thus the colors.
NOTE: when using display_grid, negative focuses will not show correctly unless you have normalized the distances. In order to display negatively focused distances, you need to know the maximum distance when unfocused, which display_grid has no way of knowing based only on unnormalized distances. If you are using your own code to display images, this will not be an issue (and it's now easy to normalize in any event).
I've had a lot of fun making this node, and am eager to see what people do with it. Enjoy!
-
signed_distance_screenshot.png 1.93 MB
- signed_dist_demo.ndbx.zip 25.2 KB
Keyboard shortcuts
Generic
? | Show this help |
---|---|
ESC | Blurs the current field |
Comment Form
r | Focus the comment reply box |
---|---|
^ + ↩ | Submit the comment |
You can use Command ⌘
instead of Control ^
on Mac
1 Posted by florisdejonge on 28 May, 2024 06:33 PM
Hi John,
This looks like a another really cool node to use. I really liked Jussi's blobs monsters posted on Instagram as well. @Jussi: did you use this function for these designs?
Just wondering whether to what extent this would differ from using the attractor node? And in addition, would it be possible to use the attractor node (or the signed distance node for that matter) not just for the input contour path, but for the whole shape? Currently, both the inside and outside of the geometry respond to it. See attached example. Would this node be a solution to that? Or is there another way?
Floris
Support Staff 2 Posted by john on 29 May, 2024 05:47 AM
Floris,
The Attraction and Signed_Distance nodes are very similar. Signed_Distance runs a tad slower but gives you the ability to distinguish inside from outside and focus each independently.
But both these nodes (and my earlier attractor node) would all recognize the contours as part of the input path and respond accordingly. I'm not sure what you mean by "the whole shape". Do you mean the outer contour only, ignoring any holes in the original shape?
Like this? (See attached demo and screenshot)
To make the "no holes" version of the lower case 'a' I separated out the contours using my contours node, grouped them, then compounded the group with itself. There may be other ways of doing this depending on your situation. This method removes any "holes" but preserves separate contours like the dot over a lower case 'I'.
Is that what you meant?
3 Posted by florisdejonge on 01 Jun, 2024 10:15 AM
Hi John,
Thanks for the reply. The attractor node responds to an input shape. But it does so by responding to just the contour line, not the shape itself as a whole.
In the attached example this is demonstrated by the smaller squares both inside and outside the shape. I think it would be cool if I could determine which way it would respond: only inside the shape, or only outside. Which would lead in the example to only large squares within the shape and not a gradient on both sides.
I hope I am explaining this clearly. Is this possible with any of these nodes?
Floris
Support Staff 4 Posted by john on 01 Jun, 2024 10:56 AM
Floris,
The whole point of signed distances is that they do distinguish inside from outside.
So in your example, if you use attractor to get the distance from the contour line and use that to determine square size, all the numbers are positive and you end up with the same gradient on both sides.
But if you use signed_distance instead, the distance magnitudes would be the same, but the signs would vary: positive distances outside the shape, negative distances inside the shape. Using that information you could use a compare and cull node to only draw squares inside the shape, or use a switch node to draw squares on the inside and circles on the outside, or whatever else you want.
Does that make sense? Or am I still misunderstanding you?
John
5 Posted by florisdejonge on 08 Jun, 2024 09:00 AM
Thanks John. Finally had the opportunity to look into your explanation. It took me a while to figure out how I would want to use (and check to understand) the node. But this is indeed what I wanted: to distinguish the inside from outside when applying the effect. Attached is a screenshot of the setup of the network, with the same shape as my previous post. This is a great node and I hope you will add it to your library.
Floris