Bright Life is an upcoming 3D flora generation toolkit to allow indie game developers to create flourishing ecosystems instantly.

Post news Report RSS Bright Life Devlog – Building a Node Editor in .NET

Complete breakdown of how Bright Life's node editor was created using WinForms .NET and Telerik and the challenges along the way.

Posted by on

Node editors are becoming increasingly popular.

While they can sometimes turn into spaghetti junctions, nodes provide a far more intuitive and visual way to interact with game development software.

However, unlike a button or spinbox, there’s no default node editor component in WinForms or Telerik.

Node Editor

There are a handful of open-source node editor projects on GitHub using OpenGL that I could use as a template. But, creating a second OGL context within WinForms is a bit of a pain. Not to mention, running two separate render loops is hardly good for performance.

Luckily dotNet has all the functionality needed to create a node editor. And since WinForms’ paint events are only called when something is updated, the performance will also be immensely superior.

The concept is pretty straightforward. Draw a bunch of nodes onto a canvas with lines connecting them, and capture any mouse clicks to trigger interaction functions.

Creating a canvas is as simple as overriding the paint event on a default UI component. I could do this with a simple panel. However, instead, I’ve opted for a PictureBox.

That’s because, just like the 3D viewport, a PictureBox is double-buffered, while panels only use a single buffer.

Double Buffered Painting

Doubling buffering is a technique to show a previously rendered frame while the next frame is still being calculated. In other words, while the node editor is being re-rendered in one buffer, the previously rendered result stored in the other buffer is shown on screen.

By comparison, if the node editor were being rendered inside a single buffer, then the rendered result would be shown as it’s still being drawn. This creates flickering artefacts that will quickly drive any artist insane.

Most editors use rectangular nodes with multiple input and output plugs flowing from left to right. But for Bright Life, the node tree will flow from the bottom to the top.

I’m also taking a page from SpeedTree’s book and going with circular nodes rather than rectangular ones.

The first step is to create a Node class. This will contain all the resources needed for drawing, as well as properties, which will be displayed in the Properties panel once a node is selected.

Each node will also have a numerical ID, a Type, a Label, a list of pointers to all of its children, and a pointer to its parent.

These pointers will be essential when stepping through the node tree during the 3D model generation stage later on.

Project Creation

With a basic template for the node class in place, I need to build a project creation, loading and saving procedure before continuing.

Creating a new project file using the SaveFileDialog class in dotNet is relatively straightforward. Artists select a destination and project name, and providing that the dialogue isn’t cancelled, Bright Life can go ahead and create the project.

A project will consist of two files. The first is the actual metadata containing all the settings and parameters created by the artist. The second is a 64 by 64 thumbnail image.

Whenever a project is updated, a screenshot of the rendered plant will be stored inside this image.

That way, when the project is loaded up again in the future, artists will be able to see a snapshot of the generated 3D plant rather than just a default file icon with a name.

Creating a custom file format

When it comes to creating a project system, I need to decide on an internal file structure.

The usual go-to for most software is Extensible Markup Language or XML. It’s a standardised format that hasn’t had a major update since 2006, and yet its flexibility is why it’s still widely used today.

The problem is that XML files have a habit of becoming bloated, leading to long loading times. And for cloud computing, it’s largely been replaced by JavaScript Object Notation files.

But for Bright Life, I decided to create a custom file structure based on keyword lines.

Each line within the project file will start with a keyword that tells Bright Life what type of data it can expect to find.

The first line in every project will start with the keyword “Info”. And as the name suggests, it will contain information about the project.

The second line will start with the keyword “Common”. This will contain various global settings of the generated plant model itself, such as the random number seed.

The rest of the file will consist of data lines, each starting with alternative keywords.

For the time being, the only type of data line currently being used is for nodes, and will therefore start with the keyword “Node”.

It’s then followed by the properties and variables set by the user, including the Type, ID, and the IDs of any children.

To keep things organised, I created a Plant Loader class. This takes the file path of a project and parses the contents.

The parser first checks the keyword of each line and processes them depending on what it finds. For lines with the “Node” keyword, the loader also checks the Type to determine what settings it should expect to find.

Each setting is stored as its own sub-keyword followed by an equals sign followed by the value. The parser splits the sub-keyword and value using the equal sign and stores both inside a C++ Map as strings.

Then, when loading a specific node type, the value of a setting in the Map can be queried by its sub-keyword. The value can then be converted into the correct data type, such as an integer, float or vector.

The advantage of this approach is that if a query is made and no such value exists within the Map, a default value can be assigned instead.

This works wonders in undoing file corruption and solves a host of compatibility problems.

Once loaded, the Plant Loader class returns a list of the nodes within the project. This list is then stored inside another list within the Bright Life editor, creating a new tab at the top of the interface.

Storing a list within another list isn’t the most efficient approach. However, by doing so, artists can open multiple plant projects simultaneously and swap between them by switching tabs. It also means data can be easily transferred between projects.

Whenever a change is made to a project or a tab is switched, Bright Life will also automatically save the project. This means artists don’t have to worry about remembering to save their work. And should the software ever crash, nothing will be lost.

Building a Node Network

With the project creation, loading, and saving systems in place, it’s time to return to the node editor.

Bright Life will feature a variety of different node types. However, every plant always begins with just one.

The Seed node will determine how many plant placements should be generated, the minimum distance between each placement, and over what area they should be scattered. This means artists can generate entire patches of vegetation instead of just generating a single plant at a time.

I also created a Stem Node that, as the name suggests, will generate the stem of a plant. It will contain settings for shape modifications and variations, stiffness for wind simulation, along with its own set of local scale and rotation parameters.

As things stand, neither node type is fully fleshed out since that will happen when I get started on actual 3D geometry generation.

In terms of styling, each node is represented as a circle with a coloured background and a clearly defined icon.

The colour and icon are unique to each node type. And to further improve clarity, an editable label is displayed underneath.

I also added a subtle drop shadow to give more depth.

Each node is then connected by a straight line which shows the parent-child relationship within the tree.

Node Positioning

When it comes to positioning, most node editors give artists the freedom to move things around to their heart’s content.

However, in my experience, this quickly leads to a confusing mess, especially for less experienced game developers and artists that apparently hate being organised.

Therefore, I’ve decided to eliminate this source of frustration from the equation by making Bright Life automatically position each node.

The first iteration of this algorithm was fairly straightforward. I created a recursive function that would step through the node tree and position each node based on the number of nodes on each layer.

At first glance, this approach worked like a charm. However, problems started to arise when a node tree contains multiple branches. Child nodes would overlap because the positioning algorithm couldn’t directly access how many children are on each layer.

To solve this, I split the process into two stages.

The first stage steps through the node tree and keeps track of how many nodes are present on each layer. The second stage is identical to the original algorithm but uses the results from the first stage to determine the horizontal spacing between nodes. It also keeps track of how many nodes on each layer have been positioned at any time to apply the correct horizontal offset.

Node Selection

With positioning done, it’s time to integrate a selection system.

Determining whether a user has clicked on a node is a simple process requiring a basic MouseDown event trigger. However, introducing multi-selection capabilities is where things get more complicated.

Checks are run to see whether the SHIFT or CTRL key is being held down whenever the left mouse button is pressed. Holding the SHIFT key will add a node to an existing selection, while the CTRL key will remove it.

While multiple nodes can be selected at one time, only one can be the primary node, which is always the last one to have been selected. The key difference is that the settings of the primary node will be displayed in the Property panel, where the user can make changes.

The border colours are updated to blue and yellow, respectively, to clearly highlight the selected and primary selected nodes.

Beyond a basic click-select system, I also added a box tool. Artists can click and drag inside the canvas to generate a box. And upon releasing the left mouse button, all the nodes side of the box will be selected.

Dynamic Properties Panel

But whenever a selection is made, the Properties panel needs to be updated.

In WinForms, the easy approach to building a dynamic properties panel is to just drag and drop UI components into a table grid layout and then toggle the visibility as needed. However, this isn’t scalable.

Apart from having to add potentially hundreds of UI components, each with ValueChanged event, whenever the visibility state of a table is updated, the paint event of each component inside that table is called one by one.

Needless to say, this isn’t very efficient. And if too many UI components are within a table layout, the interface begins to visibly lag.

That’s why I’m using something called a Property Grid instead. This effectively achieves the same result in terms of functionality but has the added advantage of drawing everything in a single paint event.

Apart from increased performance, Property Grids allow artists to sort settings by category and alphabetically. They also come with a pre-built search bar to filter for a specific parameter. To top things off, a small description panel is appended to the bottom, displaying basic in-built documentation for each parameter.

For Bright Life, I’m using Telerik’s version of the WinForms Property Grid since it has more default functionality. However, it’s still not perfect and requires some additional work.

Property grids contain basic UI editors like a textbox and spinbox for editing strings and numerical values, respectively. But when working with properties like Scale, which has multiple dimensions, the default spinbox editor doesn’t cut it.

I could split each dimension into its own setting. However, this quickly creates clutter that goes against the principles of a SIC UI.

Therefore, I created a custom Vector data type that can store two float values. I also whipped together a type converter that can parse user input into a vector.

The last step is to override the EditorRequired and EditorInitialised events called by the Property Grid. Whenever an artist begins to edit a setting, BrightLife will check what data type is associated with it and determine what type of editor should be created.

Node Buttons

To complete the node editor, I added three buttons to each node that appear when hovering over them.

The first is marked with a small plus symbol. And clicking on it opens a popup menu where artists can browse through the entire collection of available nodes within Bright Life.

Each node within the menu is categorised by plant type and appears in alphabetical order. That way, if an artist is looking to generate a 3D mushroom, they know to use the nodes under the Fungus menu item.

However, artists can combine different nodes from different plant types to create crazy and wonderful vegetation.

For example, there will be nothing stopping someone from creating a giant mushroom with glow-in-the-dark watermelons dangling from its cap.

The limit will be an artist’s imagination, not the software.

The second node button is simply a visibility toggle to enable or disable the effects of a node and its children in the 3D viewport.

While the third button will allow artists to delete nodes. This will also remove any children, and a prompt will be made before completing the action to prevent accidental deletions.

I also linked the node removal function to the delete key so that artists can select a group of nodes and delete them all in a single key press rather than having to do it one by one.

Outro

With the node editor working near perfectly, it’s time to do some tidying up. I eliminated some early unused code and also spent some time improving the visuals.

Hovering and selecting nodes changes the drop shadow colour to create a small glow. It’s a subtle effect but makes Bright Life feel a bit more responsive.

I also swapped out the straight lines connecting nodes with bezier curves.

I’d love to hear your thoughts in the comments below, and be sure to subscribe to get your free copy of Bright Life once it’s finished.

Thanks for tuning in, and I’ll see you in the next one.

Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: