setzer22 / blackjack

A procedural, node-based modelling tool, made in rust 🦀
Mozilla Public License 2.0
1.42k stars 64 forks source link

Flow control nodes (mostly for game engine integration) #70

Open Zireael07 opened 2 years ago

Zireael07 commented 2 years ago

Things like

especially the second one would allow using the same blackjack file for e.g. buildings with optional parts like overhangs, roofs, vents (probably in conjunction with importing from other file #53 or subgraphs)

setzer22 commented 2 years ago

Good idea! :thinking: These nodes shouldn't be difficult to implement, but they need to be special cased in the node interpreter. The most difficult thing here is going to be the design, here are some scattered thoughts and doodles:

You mention "do the following nodes X times" or "execute the next node(s) only if", but how can the users define, visually, the scope of the loop? And what is the output of the loop? :thinking:

When it comes to node graphs, my impression is that it's going to be more intuitive to model things in a functional way, since the graph itself is kinda like a big function already. The idea of "repeating a node" is not well specified unless we think in terms of its inputs and outputs.

Instead of a "loop", I would see this as a sort of reduction (we can then rephrase it as a loop to keep things intuitive). Consider the following sketch: image

This feels almost like a regular node, but is actually a higher order function in disguise. The top input is not evaluated once, like every other node, but instead multiple times, for each element in the inputs. And moreover, the output is not discarded, but accumulated. The first time the top branch is run, the Iter node (a special node signaling the start of the iteration / function) gets the start_value as acc, and the first element of inputs as it. For the next iteration, the returned value (the output that is connected to iter_fn) becomes the accumulator, and is fed back into Iter as acc with the next value of inputs passed as it.

I've been thinking about this design for a while and I like it, but there are still a few open questions:

But most importantly, I'm interested about your thoughts @Zireael07 (but also @inact1v1ty since you upvoted, and anyone else reading this!): Does this design feel intuitive? I don't want to come up with a design that feels too academic or technical, since this is a tool mainly aimed at artists, not programmers.

I hate the "for" loop node design in Houdini, and I honestly think we can do better :slightly_smiling_face: with something like this, but I need to know if I'm on the right track.

setzer22 commented 2 years ago

Oh, I didn't mention it but the "if" node is much easier and I already have an idea for it, we'd basically do this: https://www.sidefx.com/docs/houdini/nodes/sop/switch.html

No point in reinventing a good design :smile:

Zireael07 commented 2 years ago

No real ideas about the design (other than imitating what Godot visual scripting or Unreal blueprints do)

(BTW As a weekend experiment, I'm trying my hand at implementing something like Blackjack's UI in Godot itself :P I have never used Godot's graph nodes before, so this will be something totally new for me)

Zireael07 commented 2 years ago

When it comes to the looping, an idea I had was to have a repeater node (inspired by behavior tree e.g. https://gdscript.com/solutions/godot-behaviour-tree/ ) and then have some sort of an end node OR some sort of a way to group/collapse nodes.

setzer22 commented 2 years ago

I also have someting like that in mind, but what I don't see clear about a repeater node is what do do with the outputs of each repetitions :thinking:. The result of the looping would be some kind of object, but how to get that object is not specified. I think in your explanation you are assuming that some fixed operation like "Merge meshes" would be performed each iteration, taking the result of the previous iterations and adding one more mesh.

This works for some use cases, but I'm afraid it's not flexible enough in the general case. Blackjack is designed to allow manipulating different kinds of objects other than the standard mesh. For now there's just the mesh, and the very experimental Terrains (which are a kind of mesh, but not the standard one), but even now you can see how the base looping node needs to handle things other than meshes.

For instance, imagine you have a terrain (made with the Terrain node), and a list of positions, and you want to create a crater at each of the random positions. In this case, the way to combine two iterations has nothing to do with combining meshes. You would "add" the crater's modification on top of the existing terrain. (This terrain addition is something that is not currently supported, but will be).

Even when you do have a mesh, you won't always want to combine the operations in the same way. Maybe what you want is extruding the same face 10 times in a loop, or maybe you want to iteratively delete some faces. These are not great examples because looping would not be the most efficient way to achieve them, but it shows that the "combination" or "aggregation" function is just as important as the repetition itself.