Visual Scripting – Final Post

Good progress has been made continually throughout the project, I have run into a few problems but I have  prioritised more important features rather than grinding away at the same problem wasting precious time. I am happy that the tool in its current state can produce a natural looking result and shows a lot of potential for further work.

The project is available on my Github here.

To Open the project from Github on your computer you will need the following:

  • Unreal Engine 4, Version 4.18.3
  • Visual Studio 17
    • Windows 10 SDK
    • Windows 8.1 SDK
    • .NET desktop development
    • Desktop development with C++
    • Game Development with C++

I would have liked to have created a Windows build to use as a demo however I got stuck trying to fix a compile error that was not present when using the Compile button in the editor but was present in both Visual Studio and when packaging for windows. In the end I decided that it would be a waste of time spending hours debugging it rather than making sure it’s finished.

Further work

It’s very slow to generate so I would like to optimise everything, specifically getting the mesh data from the terrain and generating the normal.

Currently all of my code is in the Level blueprint so I would like to put it into it’s own blueprint class so that it’s easier to use in multiple levels.

I would like to add support for more biomes with differing settings and even moisture simulation to procedurally determine the location of biomes as per my initial proposal.

I would like to explore the possibility of creating my own heightmaps using Fractal noise, building on what I learned from implementing it in Monogame in my spare time, see Visual Scripting – Spawning Trees.

Finally I would like to use Noise to determine where vegetation should spawn as opposed to the current random number generator; this could possibly allow for me to further speed up the generation time as well as allowing more control to the user for where vegetation should appear.

Asset Ownership

Tree Mesh, Created in 3DS Max

Materials, Created by playing around in the material editor
• Snow Material: Noise node based off world position
• Ground Material: Picks Stone or grass texture dependant on the slope

Sourced Textures, From http://www.Textures.com:
• Grass0057_1_S
• TexturesCom_Cliffs0356_1_seamless_S
WaterPlain0017_1_S

Visual Scripting – UI Implementation

This week I added Menu to the tool so that the user can configure various Generation settings with sliders and such, the settings are:

  • Seed
  • Maximum Slope
  • Snowy biome height
  • Water Height
  • Tree Density

It is a little slow to generate as there is lots of room for optimisation however for demonstration purposes it’s fine.

Here is some footage of it working in the editor.

Most of my time getting it working was spent trying to communicate between the HUD blueprint and the Level blueprint I settled on having a reference to the HUD on the Camera object and searching for the camera in the level blueprint. From there I used an event dispatcher to allow the Menu Widget on the HUD to call the generate event. Figuring out how to set up the event dispatcher took a while as most of the forum posts explaining them would contain two screenshots of the node setup but not which blueprint should contain what.

Visual Scripting – Notable Problems

This post covers a few of the most notable problems I have had with UE4 regarding random crashes and compiling issues. I am less so concerned about the crashes themselves but more of the lack of supporting documentation surrounding fixes for what to me seems like a fairly common use of the engine.

There was a point where I had spent the day trying to debug a function that did not appear to be getting called. I went through my usual process of placing Breakpoints only to find that none of my variables had been declared, upon further investigation it became clear that the Compiler had taken issue with some pointer arrays, decided the function changed nothing and optimised it all away. There were no errors showing that there was an issue and it took an entire day to figure out the source of the problem and to fix it.

I wanted to move all of my generation code from the level blueprint and into its own class, either a blueprint or a C++ class but I just ended up spending hours trying to fix obscure linker and compile errors informing me that functions I had previously using did not exist. Also, whenever I compiled the code using the button in the editor it would crash without warning. The Documentation says nothing about fixing what I would assume are common errors nor how to properly set up the build file or even which header files should be included when working with certain classes. Furthermore it’s incredibly difficult to debug your code as it is not easy to tell if the error is coming from C++ or from Unreal Engine. It is also often the case that errors will be reported after one compile that will be gone after the next compile without a trace which is possibly more infuriating.

Finally I’d like to mention for anyone else who wishes to work with landscapes in the future. The editor will crash if you are creating a blueprint and try to set a child actor’s class to a Landscape;  the editor will freeze for a second and then crash and present the error reporting window, the same as the compile bug mentioned earlier. If you have a lightning fast computer this might not be that much of a problem but if like me it takes you the best part of 5 minutes to launch UE4 this can really eat up your time and it certainly drained my motivation to continue.

Visual Scripting – Spawning Trees

This week I started actually spawning trees on my landscape instead of my debug cubes. I did this like before by using the position array and spawning one at each point, however I used the normals that I generated previously to determine the slope angle  (dot product of the normal and a vector pointing straight up) and only spawning the tree if the angle was less than a certain value

I have also included a water plane which determines the minimum height that the trees can be placed at.

TreesSlopesWater

There are now far fewer objects being spawned on the terrain so the performance is quite bearable while using a lower LOD, so i can now spawn the trees using a greater detail mesh meaning that the Forrest I can generate looks far more realistically dense.

TreesSlopesWater_before

Next I used the highest detail mesh to spawn the trees however i used a seeded Random number generator to only spawn a tree at 10% of the points. I also varied the sizes and orientations that the trees spawn at. All together this leads to a far more realistic and pleasing forest being generated.

TreesSlopesWater_Improvement

I also created a Material for the terrain that applies either a grass or a stone texture depending on the current slope of the terrain. I did this both as an experiment but also so that it is easier to see where the trees should be spawning. It also has the added benefit of making it easier to see the shape of the terrain.

TreesSlopesWater_after

Going forward, I would like to use Noise to determine the tree distribution instead of what i am doing currently because random number generation can look unnatural whereas a noise algorithm such as value or Perlin noise will give a far more natural and configurable look.

As an experiment I implemented Terrain generation in Monogame using fractal value noise. This was useful as it allowed me to have an attempt at implementing a noise generator; Unreal Engine does not have a built in noise generator like other engines do so when i come to use noise for the tree distribution i already know how to implement it.

SpawningTrees_HeightMap

Visual Scripting – Generating Normals

I got rather carried away with actually developing this next feature so this post covers 2 weeks of development . I have since completed more work which i’ll cover in the next post.

Generating Normals

The task for the next feature was to start adding trees to my landscape but only where the slope of the landscape below was suitable, this requires knowing the normals of the mesh so i started here.

My plan was to look at all of the points adjacent to another and use the cross product with 2 adjacent points at a time and then average them. In the first week my attempt was very problem filled, at one point the compiler had decided that the entire function had no purpose and optimised all of it away, making debugging the problem rather difficult. I also had a problem where the normals that were returned to me were just incorrect.

What did not help was that I had decided to use a complicated method to get the normals with the hope that it would run fast. This was a mistake and i should have started with a simpler approach and optimised it later if needed. I do this often and in future i’ll be assuring i don’t do it. KISS: Keep It Simple, Stupid.

My second approach worked immediately, further hammering down the KISS “methodology”. First i initialise my blank array of normals to face along the z axis, this is so that all of the edge cases are handled. Then for each point that is not on an edge i calculate the normal for the positions above and right, then the positions below and left giving me two normals, then i can average these out. I only need to work out the two opposing normals as all four adjacent positions are factored into the calculation.

TArray<FVector> UMyFunctionLibrary::GenerateNormals(const TArray<FVector> positions)
{
    // Output array
    TArray<FVector> Normals;

    // Set every vector to point up
    for (size_t i = 0; i < positions.Num(); i++)
        Normals.Add(FVector(0.f, 0.0f, 1.0f));

    size_t size = sqrt(positions.Num());

    // For each point that is not on the edge
    for (size_t y = 1; y < size - 1; y++)
    {
        for (size_t x = 1; x < size - 1; x++)
        {

            FVector thisPos = positions[y * size + x];

            // Up and Right
            FVector up = thisPos - positions[(y + 1) * size + x];
            up.Normalize();
            FVector right = thisPos - positions[y * size + (x + 1)];
            right.Normalize();

            FVector normalA = FVector::CrossProduct(up, right);

            // Down and Left
            FVector down = thisPos - positions[(y - 1) * size + x];
            down.Normalize();
            FVector left = thisPos - positions[y * size + (x - 1)];
            left.Normalize();

            FVector normalB = FVector::CrossProduct(down, left);

            // Mean of both normals
            FVector normalAvg = (normalA + normalB) / 2;
            normalAvg.Normalize();

            // Set ths normal in the array
            // Negated because Unreal uses an inverted y axis
            Normals[y * size + x] = -normalAvg;
        }
    }

    return Normals;
}

Here’s some renders using the line renderer to visualise the normals.

Normals_AboveNormals_Side

Visual Scripting – Sorting things out

Before I am able to place vegetation on the terrain I need to know the slope angle at each point so I can determine whether it is too steep or what kind of plants i want to appear in those circumstances. I could get the tangent/normal data from the FRawMesh from last week however i that would require removing duplicates again which would be both slow and I could become a pain if they don’t match up after the sorting process. I decided to go the easier route and calculate them by sampling the adjacent points.

The first issue is that blueprints only support 1D arrays but i need to address elements with 2D Coordinates, To help with this i wrote a macro that uses the formula i = y * width + x which will do just that.

Sorting_2DindexingSorting_Indexing0

To test that it worked I set the cube at that index to a red material, sadly I do not have a screenshot of this but there were red cubes.

Sorting_ColouringTest

In the testing, the cubes at (0, 0) and (width, height) were in the correct positions in the array; however, due to the way I was removing the duplicate vertices, they were not all sorted causing some rows to be jumbled with the next. I changed the debug cube to also have a Text Renderer that I show the position in the array on.

Sorting_UnsortedDefaultOrder.PNG

My original instinct was to implement my own sorting algorithm in blueprint or C++, in theory i would be able to use the same indexing formula as my comparison for checking, however this would be very time consuming. Instead i found that TArray already has the function TArray::Sort() so I wrote a C++ function that would use it and return the sorted array.
Sorting_UsingSortFunction

TArray UMyFunctionLibrary::Sort(TArray points)
{
    // use TArray::Sort using my predicate to sort the array
    points.Sort(SortPredicate);
    return points;
}

// Function that TArray::Sort can use to compare FVectors
bool UMyFunctionLibrary::SortPredicate(const FVector & a, const FVector & b)
{
    if (a.Y < b.Y)
    {
        // if a's y is less we know it has to be before b
        return true;
    }
    else if (a.Y == b.Y && a.X < b.X)
    {
        // if they're at equal y, if a's x is less than b's x, true
        return true;
    }
    else
    {
        // else b is before a
        return false;
    }
}

It’s worth noting that for simplicity’s sake I used multiple if statements in my predicate function and not the more elegant formula. It is possible that using the formula could be faster but it was quicker to write and is more easily read when using this solution.

Sorting_sorted

Here you can see the successfully sorted array, ready for me to start calculating the slope data.

Optimising The Debug Cubes

As I mentioned in the last post, Spawning cubes using the Vertex list was very slow at the higher detail levels due to there being multiple vertices overlapping, such is the nature of meshes. I Will likely not be using the raw vertices for very long however as a small exercise I wrote a small function to remove duplicate vectors from the array, and the results were very successful.

The function runs through all of the vectors in the input array, Checks whether the new array already contains it, if not it gets added to the new list. The new list is then returned.

VertListOptimisation

I also tidied up the level blueprint when adding in the new function by using a sequence, this serves no purpose other than to make the code look nicer and easier to read. I also added a “Print Text” node to write the number of vectors in the list to the screen.

OptimisedCubeSpawning

Firstly I ran the level with the optimisation node disconnected so that I could see both how long it took to run and note down the number of vectors, Then I reconnected the node and ran again, taking note of the number of vectors.

The results were really impressive, Without the optimisation it took ages to start the level and it was incredibly laggy, this was down to the 55,296 cubes that were spawned in, many of which were overlapping each other. With the optimisation it started almost immediately with only a little frame lag and an impressive 9,409 cubes spawned. I will also make it clear that there were the same number of cubes visible so I had only removed duplicate cubes.