from looping to lists

gabriel sim-laramee's Avatar

gabriel sim-laramee

26 Dec, 2023 11:36 PM

I am trying to rewrite everything I did in Postscript in Nodebox. A lot of basic geometry construction in Postscript is really hard for me in Nodebox. This piece was called grassrand in Postscript. It was an idea of a simple bed of grass, all the same at base, randomly shifted more and more at the top. The idea was simple: start at a location x,y move to a new location some units north of the first location. As the y value increases, the randomness of the x value is increased. Then I added a little extra to the y value to make the top more natural.

The problem with my Nodebox version was that I couldn't figure out how to create DIFFERENT random values for each blade. What I ended up doing was feeding a seed value into a subnetwork which I also used to translate each blade. I'm not sure if this is the best solution. It seems like the subnetwork is acting a little bit like a function but I don't understand how to control it.

Any wisdom and best practices would be appreciated.

  1. Support Staff 1 Posted by john on 27 Dec, 2023 04:25 AM

    john's Avatar

    Gabriel,

    This is a very interesting note. First of all, I find your simple design intriguing. I once did something similar with some river reeds that sway in a current.

    You are certainly correct that randomness in NodeBox is tricky. Just the other day I came across a situation in one of my own art projects where something was not as random as I thought it was. It's really easy to fool yourself about this.

    What's particularly interesting about this note is that you are not asking me to fix a thing which is not working. You have got this thing working pretty well as far as I can tell. You are just asking for best practices so that you can feel more in control. That's something I continue to work on for myself.

    CONSTANT VIGILANCE

    One best practice is constant vigilance. Part of this is, during development, dialing down to simple cases and frequently switching between Viewer and Data mode to be sure the data behind each view is what you expect.

    For example, one of the first things I did with your grass network was to dial down what I thought were the number of blades at the top number1 node to 1 and then to 2. Two blades of grass as expected, slightly different from each other. But when I switched to Data mode I saw 60 geometries. This was because your sample was now generating 60 samples rounded to either 1 or 2, so feeding only about 30 1s and 30 2s as seeds. The result was 60 blades of grass of two varieties: 30 perfect copies drawn on top of each other on the left, another 30 on the right.

    There is nothing necessarily wrong with this as long as you understand what's going on. The amount port in your sample2 node was set to 60, meaning there will be 60 blades of grass. The value of 2 in the number1 node was controlling the "trans_val" port, which seems to have something to do with where horizontally the blades are planted - though that relationship becomes more complex when trans_val exceeds the amount of blades. So, depending on those two values, you may have duplicate blades of grass drawn on top of each other. You may not be aware of this if you are not constantly checking the data view.

    NAME YOUR NODES

    Another best practice I have adopted is to name my nodes as I go, choosing those names very carefully. Naming variables is an art form. Nodebox node names are short, so you have to express each concept concisely. The process of giving each variable a specific name forces you to think more clearly about what that variable actually represents. Sometimes this clarity causes me to stop and rethink my entire approach. Providing clear names for key nodes is also very helpful when I come back to a project after a few days or months and need to figure out what the hell I was thinking.

    I also often create more nodes than I really need to further clarify what's going on. For example, when making a subnetwork you can point to any port on any node inside and just publish directly from that port. Quick and easy and works great. But I almost never do this anymore. Instead a create a separate node for each variable I need, carefully name it, publish THAT node, and then, inside my subnetwork, hook that node into the port I need it to hook to. I also usually place all variable nodes coming into a network at the top or off to one side so that when I open up a subnetwork I can immediately orient myself to everything that is coming in and where it is going.

    The other reason I do this routinely now is that, sooner or later, I discover the variable I thought I only needed once and so hooked directly to some port inside, I actually need a second time somewhere else. Separating all the variables out right from the get-go saves me the work of reorganizing and republishing later.

    THINK FUNCTIONALLY

    Related to node clarity is subnetwork clarity. Each subnetwork is a function. It will fire once for each time a value (as opposed to a list) hits one of its ports. As I've often discussed here, you can also define some ports as "list", which takes an entire list all at once. Usually a subnetwork will have at least one value port, and this is what triggers the subnetwork to fire - once for each value coming in to that port.

    This choreography between list ports and value ports is the trickiest and most subtle aspect of Nodebox, and takes years to fully master. But it is at the heart of everything.

    You have to learn to think functionally, that is break your project down into a chain of discrete functions. And you have to be clear - crystal clear - on exactly what each of your functions does each time it fires. The function is isolated from the rest of the universe. It knows nothing of what came before or what might come later. All it knows is what is in its ports each time it fires.

    This is particularly critical when it comes to random numbers. If you make a subnetwork with a single random seed fed into a seed port, and have another stream of values coming in one at a time to fire that subnetwork again and again, it will use the same seed value each time it fires. So it will do something random, but then do exactly the same random thing again the next time it fires.

    This may or may not be what you intend. If you want it to do something 10 times (by, say, feeding in the numbers 1 through 10 to another port), and you want it to use a different random seed each time, you will need to match that first list of 10 values with another list of 10 different random seeds. (I often use the seed port AS MY INDEX so that each new seed is what fires the function.)

    NOTE that using a single seed to generate more seeds inside a function may not give you the additional randomness you expect. The original seed coming into a subnetwork will trigger the same chain of randomness inside. So no matter how many times you spawn sub-randomness inside, the same seed coming in will end up doing the exact same thing the next time the function fires.

    COUNT THE VALUES COMING IN

    When you have multiple ports in a subnetwork each feeding values one a time, it's usually a good idea to be sure that all those ports have the same number of values coming in. Problems often arise when you think you have 10 values coming into two ports, but actually one port is getting 10 and the other is only getting 9. Again, constant vigilance, by checking the data view and scrolling down to see how many values are coming into each port, often pays off.

    Of course you don't always need all the streams coming into a subnetwork to have the same number of values. You may want to fire a subnetwork 100 times to make 100 shapes, but only feed in, say, 5 colors. NodeBox will automatically keep repeating those 5 colors till all 100 shapes are made, resulting in 20 shapes of each color. This is very powerful and elegant and is quite delightful - as long as it's what you intended.

    Again, strive for clarity in how you think about each function. If you have a blade of grass function, it should be clear that it makes one blade for each "index" value coming in, will repeatedly draw from the same set of 5 colors coming in the color port, and will be planted in a position determined either automatically based on the index port, or by a separate "X value" fed in a way that guarantees there will always be 100 different x positions for each of those 100 blades triggered by the index. Or use the X position AS the index which causes the firing of the blade function.

    Many variations are possible and equally good. All that really matters is that your blade function is behaving exactly as you intended.

    Which brings us back to constant vigilance. When your subnetwork outputs 100 blades of grass (which you verify by looking at both the Viewer and Data views), it still may not be clear whether it is firing 100 times, or is only firing 10 times and squirting out 10 blades each time it fires.

    START SMALL

    So part of constant vigilance is, during development, to first work on making just one blade of grass. Once you have that going, crank it up to 2 blades and see that there really are two (not 30 on top of each other at two locations), and the blade A is random but different than blade B. Then crank up production to more blades. And if you have multiple streams coming into your blade subnetwork which determine how often it fires, keep checking upstream to make sure the number of values in each of those streams are what you expect.

    A corollary of this is to scale back down when debugging. If your subnetwork is misbehaving and you can't figure out why, try inserting a slice node upstream to temporarily reduce the number of firings to just one. This often makes it MUCH easier to understand what's really going on. Once you've got the bug swatted, just remove the slice node to return to full production.

    ARRANGE YOUR FLOWERS

    One final best practice which I often harp on is to keep your nodes tidy as you go. Line them up for easier scanning. Separate functional clusters. Arrange them so that link lines cross as seldom as possible and its always easy to see at a glance what connects to what. Don't think of this as an unnecessary bother. Think of it as a restful part of the art form - like flower arranging - that gives you a few extra moments to review all your nodes and take in the overall gestalt and the flow.

    OK, that's probably more than enough wisdom to digest in one note. Please feel free to push back with more detailed questions, or just to share your own thoughts and tips on best practices. And if any lurkers are out there just following along, feel tree to just raise your hand and say "Interesting conversation! Thanks."

    Thanks again, Gabriel. Keep it coming!

    John

Reply to this discussion

Internal reply

Formatting help / Preview (switch to plain text) No formatting (switch to Markdown)

Attaching KB article:

»

Already uploaded files

  • grassrand_Nodebox.zip 514 KB

Attached Files

You can attach files up to 10MB

If you don't have an account yet, we need to confirm you're human and not a machine trying to post spam.

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

Recent Discussions

06 Feb, 2025 01:37 AM
04 Feb, 2025 06:32 AM
04 Feb, 2025 05:04 AM
30 Jan, 2025 09:31 AM
30 Jan, 2025 09:08 AM

 

30 Jan, 2025 08:40 AM
24 Jan, 2025 07:50 AM
22 Jan, 2025 11:42 PM
21 Jan, 2025 09:43 AM
21 Jan, 2025 09:41 AM
21 Jan, 2025 09:36 AM