I wanted to create some more samples before releasing this, but I'm spending my life in VR headsets lately so I just needed to get this out there with some explanation. The block has twelve tutorials. Each one builds off the last and incrementally introduces features and concepts to ease you into it. There is only one sample project right now, a cross-platform audio player, which demonstrates how to make a 2D GUI system. While the sample and tutorials are 2D, this works with 3D interfaces.
I can't live without this library now. It's sped up my development time in Cinder like crazy. It's both thorough and flexible. I use it for everything weird experimental interfaces to 3D games to high-performance prototypes and more. It's basically a shortcut to doing many things. Pull it and check out the tutorials and hopefully it will click what I'm doing here. I'll go over a few features:
IT'S A TREE! The UiTree's primary feature is that it contains a map of children which are also UiTrees. It knows its parent, root, etc. This elegantly brings the parent-child relationship into C++ interface objects.
FAST 2D/3D LAYOUTS AND ANIMATION The UiTree has built-in transform properties. When you set the position, rotation, and scale, you may optionally pass in a "speed" parameter which performs linear interpolation. There are also velocity variants for any sort of transform. The UiTree is Timeline-friendly, as demonstrated in one of the tutorials.
FAST AND POWERFUL NODE FINDING Every node in a UiTree structure has a unique ID. You can set these or let the UiTree increment these for you. If you know a node's ID, you can pull it up from anywhere in the hierarchy which is less deep than it. There is a also a query method which returns a flat list of nodes using a boolean method (kind of like sorting). For example, you can use query to return all the nodes on the top half of the screen, regardless of their depth in the tree.
NODES CAN REPRESENT ANYTHING The UiTreeT<T> can store arbitrary data using a template argument. For example, if you have a Data structure which holds things like color, text, audio, etc, you set that as the tree's template argument. Now the UiTree has an instance of your Data structure which you can easily read and write. Optionally, you can rely on UiTree's unique ID system to store data externally and easily link back to it.
INTERACTIVITY AND EVENTS The UiTree is a mini-program which can hook up to pretty much any event Cinder broadcasts, as well as some unique events around visibility. Additionally, UiTree's can be made aware of their shape in 2D and 3D. This enables mouse/touch over and out events, as well as built-in collision testing.
PORTABLE UiTree is a single header file. That's it.
This tutorial demonstrates how to pass the UI tree through recursive methods to create a pattern with minimal code:
Pointing out a few advantages of the UI tree in a code snippet:
The audio player sample demonstrates how to use the UiTree to make a cross-platform music player:
The software used in this Leap-based installation app uses the UiTree to create a 2D physics system which displays thoughts and ideas submitted by employees:
An in-development 3D shooter which uses a UiTree for all its gameplay and GUI:
The best way to put it is that this isn't a scene graph, but it's the building block to create a scene graph. Or HTML renderer. Or video game. Or node-based synthesizer. Or data visualizer. Basically anything which could benefit from a hierarchal structure.
I felt like any time I used an existing scene graph, there was a certain amount of work I would have to put in to "undo" some of it to make it more flexible and customizable. UiTree is built up to the point to where I usually tear a scene graph down. It's purpose is to provide the animation and interaction elements common in any UI and let you build the presentation layer on top of it.
If I just needed a quick GUI for editing parameters on something that wasn't public facing, I would use a more complete, existing UI library. UiTree is my tool of choice for production / public-facing projects.
Thanks Ban, this is a very clean and concise block. I also really like its single-header format.
I was wondering if you could elaborate on your recommendation of keeping complex data, such as Batches or Vbos, outside of the Node classes.
Other Scene graph models I've seen, like Paul's sample or Potion's poScene, use inheritance and allow each node to have all the objects it needs in order to "draw itself". In your examples, instead, the main class has a drawTree function that I imagine can get very complex for big scenes. How do you work around that?
You absolutely can make a more complex class to handle the drawing inside either an event handler or the T class you pass to the UiTreeT<T>. If you run threads inside your T class, you may run into issues if your tree resizes dynamically. There are ways to make it work, though. It's just my personal preference, and generally a good practice, to separate the renderer from the data. This makes it possible for me to use the same UiTree with a DX, GL, or Vulkan renderer depending in my target device.
I pretty much always do instanced drawing, so this separation is required. What I usually do is keep a geometry library (ie, gl::BatchRef) separate from the UiTree. Each mesh has a VBO in which to store instanced model matrix attribute data. Each UiTree instance's data contains some type of identifier pointing back to the model. On update(), I create a vector<mat4> for each mesh, using the UiTreeT<T>::calcModelMatrix() method to get the mat4. These are buffered the VBO. On draw(), I only have to make one draw call for each model instead of making a draw call for each UiTree instance. It makes for a huge performance boost when drawing a lot of objects.
I'll also store color and some other data in the instance attributes, usually by creating an Instance class in C++ with attributes mirroring the class members in the shader.