Leadwerks Company Blog

  • entries
    140
  • comments
    865
  • views
    245,859

About this blog

Entries in this blog

Josh

Leadwerks 5 is going to be developed alongside 4.5 with an extended period of beta testing and feedback.  My goal is to build the world's most advanced game design software, tailored to the needs of our community and clients.  Development will first focus on a new programming API updated for C++11 and then a completely new editor using Leadwerks GUI and based on the workflow developed with our current editor.

The first beta will support the following features right away:

These features are already working.  Here is a working build you can try right now:
Leadwerks5.zip

This is the actual source code used to make this example.  Note that C++11 shared pointers have been implemented for all objects and the API has been simplified to make your code more readable:

#include "Leadwerks.h"

using namespace Leadwerks;

int main(int argc, const char *argv[])
{
	auto window = CreateWindow();
	auto context = CreateContext(window);
	auto world = CreateWorld();

	auto camera = CreateCamera(world);
	camera->Move(0, 0, -3);

	auto light = CreateDirectionalLight(world);
	light->SetRotation(35, 35, 0);
	light->SetShadowMode(0);

	auto model = CreateBox(world);
	model->SetColor(0.0, 1.0, 0.92);

	while (not window->Closed())
	{
		if (window->KeyHit(EscapeKey)) break;
		if (model) model->Turn(0, 1, 0);
		if (window->KeyHit(SpaceKey)) model = nullptr;
		world->Update();
		world->Render(context);
	}
	return 0;
}

The beta will be available to developers who want to try the very latest game development technology, with a subscription plan of just $4.99 a month, available through our website.

Josh

Unicode in Leadwerks 5

I've begun implementing unicode in Leadwerks Game Engine 5.  It's not quite as simple as "switch all string variables to another data type".

First, I will give you a simple explanation of what unicode is.  I am not an expert so feel free to make any corrections in the comments below.

When computers first started drawing text we used a single byte for each character.  One byte can describe 256 different values and the English language only has 26 letters, 10 numbers, and a few other characters for punctuation so all was well.  No one cared or thought about supporting other languages like German with funny umlauts or the thousands of characters in the Chinese language.

Then some people who were too smart for their own good invented a really complicated system called unicode.  Unicode allows characters beyond the 256 character limit of a byte because it can use more than one byte per character.  But unicode doesn't really store a letter, because that would be too easy.  Instead it stores a "code point" which is an abstract concept.  Unfortunately the people who originally invented unicode were locked away in a mental asylum where they remain to this day, so no one in the real world actually understands what a code point is.

There are several kinds of unicode but the one favored by nerds who don't write software is UTF-8.  UTF-8 uses just one byte per character, unless it uses two, or sometimes four.  Because each character can be a different length there is no way to quickly get a single letter of a string.  It would be like trying to get a single byte of a compressed zip file; you have to decompress the entire file to read a byte at a certain position.  This means that commands like Replace(), Mid(), Upper(), and basically any other string manipulation commands simply will not work with UTF-8 strings.

Nonetheless, some people still promote UTF-8 religiously because they think it sounds cool and they don't actually write software.  There is even a UTF-8 Everywhere Manifesto.  You know who else had a manifesto?  This guy, that's who:

Karl_Marx.thumb.jpg.1ece92f31b80f2b01f9a2b0bd51b0381.jpg

Typical UTF-8 proponent.

Here's another person with a "manifesto":

Theodore_Kaczynski.jpg.c537d9a7ae01f7b53e773b19a7c0bc49.jpg

The Unabomber (unibomber? Coincidence???)

The fact is that anyone who writes a manifesto is evil, therefore UTF-8 proponents are evil and should probably be imprisoned for crimes against humanity.  Microsoft sensibly solved this problem by using something called a "wide string" for all the windows internals.  A C++ wide string (std::wstring) is a string made up of wchar_t values instead of char values.  (The std::string data type is sometimes called a "narrow string").  In C++ you can set the value of a wide string by placing a letter "L" (for long?) in front of the string:

std::wstring s = L"Hello, how are you today?";

The C++11 specification defines a wchar_t value as being composed of two bytes, so these strings work the same across different operating systems.  A wide string cannot display a character with an index greater than 65535, but no one uses those characters so it doesn't matter.  Wide strings are basically a different kind of unicode called UTF-16 and these will actually work with string manipulation commands (yes there are exceptions if you are trying to display ancient Vietnamese characters from the 6th century but no one cares about that).

For more detail you can read this article about the technical details and history of unicode (thanks @Einlander).

First Pass

At first I thought "no problem, I will just turn all string variables into wstrings and be done with it".  However, after a couple of days it became clear that this would be problematic.  Leadwerks interfaces with a lot of third-party libraries like Steamworks and Lua that make heavy use of strings.  Typically these libraries will accept a chr* value for the input, which we know might be UTF-8 or it might not (another reason UTF-8 is evil).  The engine ended up with a TON of string conversions that I might be doing for no reason.  I got the compiler down to 2991 errors before I started questioning whether this was really needed.

Exactly what do we need unicode strings for?  There are three big uses:

  • Read and save files.
  • Display text in different languages.
  • Print text to the console and log.

Reading files is mostly an automatic process because the user typically uses relative file paths.  As long as the engine internally uses a wide string to load files the user can happily use regular old narrow strings without a care in the world (and most people probably will).

Drawing text to the screen or on a GUI widget is very important for supporting different languages, but that is only one use.  Is it really necessary to convert every variable in the engine to a wide string just to support this one feature?

Printing strings is even simpler.  Can't we just add an overload to print a wide string when one is needed?

I originally wanted to avoid mixing wide and narrow strings, but even with unicode support most users are probably not even going to need to worry about using wide strings at all.  Even if they have language files for different translations of their game, they are still likely to just load some strings automatically without writing much code.  I may even add a feature that does this automatically for displayed text.  So with that in mind, I decided to roll everything back and convert only the parts of the engine that would actually benefit from unicode and wide strings.

Second Try + Global Functions

To make the API simpler Leadwerks 5 will make use of some global functions instead of trying to turn everything into a class.  Below are the string global functions I have written:

std::string String(const std::wstring& s);
std::string Right(const std::string& s, const int length);
std::string Left(const std::string& s, const int length);
std::string Replace(const std::string& s, const std::string& from, const std::string& to);
int Find(const std::string& s, const std::string& token);
std::vector<std::string> Split(const std::string& s, const std::string& sep);
std::string Lower(const std::string& s);
std::string Upper(const std::string& s);

There are equivalent functions that work with wide strings.

std::wstring StringW(const std::string& s);
std::wstring Right(const std::wstring& s, const int length);
std::wstring Left(const std::wstring& s, const int length);
std::wstring Replace(const std::wstring& s, const std::wstring& from, const std::wstring& to);
int Find(const std::string& s, const std::wstring& token);
std::vector<std::wstring> Split(const std::wstring& s, const std::wstring& sep);
std::wstring Lower(const std::wstring& s);
std::wstring Upper(const std::wstring& s);

The System::Print() command has become a global Print() command with a couple of overloads for both narrow and wide strings:

void Print(const std::string& s);
void Print(const std::wstring& s);

The file system commands are now global functions as well.  File system commands can accept a wide or narrow string, but any functions that return a path will always return a wide string:

std::wstring SpecialDir(const std::string);
std::wstring CurrentDir();
bool ChangeDir(const std::string& path);
bool ChangeDir(const std::wstring& path);
std::wstring RealPath(const std::string& path);
std::wstring RealPath(const std::wstring& path);

This means if you call ReadFile("info.txt") with a narrow string the file will still be loaded even if it is located somewhere like "C:/Users/约书亚/Documents" and it will work just fine.  This is ideal since Lua 5.3 doesn't support wide strings, so your game will still run on computers around the world as long as you just use local paths like this:

LoadModel("Models/car.mdl");

Or you can specify the full path with a wide string:

LoadModel(CurrentDir() + L"Models/car.mdl");

The window creation and text drawing functions will also get an overload that accepts wide strings.  Here's a window created with a Chinese title:

Image1.jpg.987ae08e739ae771c6a6bda0bef35ec0.jpg

So in conclusion, unicode will be used in Leadwerks and will work for the most part without you needing to know or do anything different, allowing games you develop (and Leadwerks itself) to work correctly on computers all across the world.

Josh

I have implemented C++11 shared pointers into Leadwerks Game Engine 5 and the following program now works.  When you press the space key the box variable is set to NULL and the visible box on the screen disappears:

#include "Leadwerks.h"

using namespace Leadwerks;

int main(int argc, const char *argv[])
{
	auto window = CreateWindow();
	auto context = CreateContext(window);
	auto world = CreateWorld();

	auto camera = CreateCamera(world);
	camera->Move(0,0,-5);
	camera->SetClearColor(0,0,1);

	auto light = CreateDirectionalLight(world);

	auto box = CreateBoxModel(world);

	while (not window->Closed())
	{
		if (window->KeyHit(SpaceKey)) box = NULL;
		if (box) box->Turn(0, 1, 0);
		world->Update();
		world->Render(context);
	}
	return 0;
}

Shared pointers provide the ease of use of a memory-managed language like C# but run much faster because they aren't using full garbage collection.  The one thing you have to watch out for is circular references.  In the example below, neither a or b would ever be deleted from memory, since they reference each other:

class thing
{
	shared_ptr<thing> friend;
};

auto a = make_shared<thing>();
auto b = make_shared<thing>();
a.friend = b;
b.friend = a;
a = NULL;
b = NULL;

This problem can be solved by using weak pointers:

class thing
{
	weak_ptr<thing> friend;
	shared_ptr<thing> GetFriend();  
};

shared_ptr<thing> thing::GetFriend()
{
	return friend.lock();
}

auto a = make_shared<thing>();
auto b = make_shared<thing>();
a.friend = b;
b.friend = a;
a = NULL;
b = NULL;

When using shared pointers you need to think about hierarchical relationships.  For example, a parent entity stores a list of shared pointers for its child entities, but each child uses a weak pointer to store its parent.  This makes it so children will not be deleted as long as you hold onto the parent entity:

class Entity
{
	std::list<shared_ptr<Entity> > kids;
	weak_ptr<Entity> parent;
};

These relationships are not always immediately obvious.  For example you might think that a world should control when entities are in scope, but that would not allow our first example above.  Instead, entity creation functions return a shared_ptr and the world octree only stores weak_ptr values to entities.  When you want to delete an entity, just set it to NULL.  If you have large collections of entities you can just store them in a list and clear the list when you are ready.  There is no World::Clear() function since the entities are managed by your own code.  I'm not 100% sure on how map loading will work with this, but so far it is working out well.

One thing to watch out for is that Self() or shared_from_this() cannot be accessed in the object constructor or destructor.  This will require some changes to how Leadwerks handles destruction of some objects, since previous versions uses manual deletion and removed lots of list iterators from different parts of the game world.  You might run into situations where you have a lot of weak pointers stored in lists like this:

for (std::list<weak_ptr<Entity> > it = list.begin(); it!=list.end(); it++)
{
	auto entity = (*it).lock();
	//do some stuff
}

Here is how you can modify the loop to handle the situation where an expired weak pointer is encountered (the object was deleted):

std::list<weak_ptr<Entity> > it = list.begin();
while (it != list.end())
{
	auto entity = (*it).lock();
	if (entity == NULL)
	{
		list.erase(it++);
		continue;
	}
	//do some stuff
	it++;
}

This will skip deleted objects and trim the list so that it doesn't grow out of control.

The new paradigm of C++11 shared pointers requires a little bit different approach but is much easier to use and does a good job of preventing mistakes especially when your game gets more complex.

Josh

All classes in Leadwerks are derived from a base Object class.  In Leadwerks 5 we separate simple and complex objects with the new SharedObject class.

Simple objects like a Vec3 ( a three-dimensional vector), an AABB (axis-aligned bounding box), and other items are all derived from the Object class.  Simple objects are created with constructors.  When we make one object equal to another the value is copied from one variable to another, but the two variables are still separate objects.  Below, A and B have the same value but are separate objects:

Vec3 a = Vec3(1,2,3);
Vec3 b = a;

Shared objects are things you don't want to copy, because they involve more than just some numbers.  These always use C++11 shared pointers and use a Create function.  Below, A and B refer to the same object:

shared_ptr<World> a = CreateWorld();
shared_ptr<World> b = a;

The SharedObject class has a couple of functions to make life easier.  Instead of casting pointers with some funny syntax we can use the Cast() method.  Here's an example of casting in Leadwerks 4:

Entity* entity = Model::Load("car.mdl");
Model* model = (Model*)entity;

And here's how it works in Leadwerks 5:

shared_ptr<Entity> entity = LoadModel("car.mdl");
shared_ptr<Model> model = entity->Cast<Model>();

Instead of using "this" inside a class method you can use Self() to get a shared pointer to the object itself:

class MyActor : public Actor
{
	void MyFunction()
	{
		//MyActor* me = this;
		shared_ptr<SharedObject> me = Self();
	}
}

Self() will always return a shared_ptr<SharedObject> value, so you can use Cast() if you need a specific type of object (and match the behavior of "this"):

class MyActor : public Actor
{
	void MyFunction()
	{
		//MyActor* me = this;
		shared_ptr<MyActor> me = Cast<MyActor>();
	}
}

Instead of calling delete or Release to destroy a shared object, all you have to do is set its variable to NULL:

shared_ptr<Model> model = LoadModel("car.mdl");
model = NULL;// poof!

And of course we can always use the auto keyword to make things really simple:

auto model = LoadModel("car.mdl");

Shared objects use automatic reference counting to give you the ease of use of a garbage-collected language, together with the blazing performance of modern C++.  These features are set to make Leadwerks Game Engine 5 the easiest and most cutting-edge development system in the history of game programming.

Josh

Leadwerks Game Engine 5 moves Leadwerks forward into the future with massive performance increases and more advanced features, it also makes game development easier than ever before with three great new programming features.

Shared Pointers

I could write a whole article about the benefits of shared pointers.  Shared pointers are basically a simple form of garbage collection that relieves you from the need to manually delete objects, but doesn't suffer from the slow speed of full garbage collection.like C# uses.

In Leadwerks 4 we need to keep track of object reference counts by using the Release command when we are done with them:

Texture *texture = Texture::Load("Materials/Bricks/brick01.tex");
Material *material = Material::Create()
material->SetTexture(texture);
Model *model = Model::Box();
box->SetMaterial(material);
material->Release();
if (texture) texture->Release();

In Leadwerks 5 we never have to worry about calling Release() or (less commonly used) AddRef() because reference counts of shared pointers are tracked automatically.  The code below would cause a memory leak in Leadwerks 4 but is perfectly fine to use in Leadwerks 5:

auto material = Material::Create()
material->SetTexture(Texture::Load("Materials/Bricks/brick01.tex"));
auto model = Model::Box();
box->SetMaterial(material);

This even works with entities.  As soon as a variable goes out of scope the object is deleted:

if (true)
{
	auto model = Model::Box();
}// automatic deletion occurs here

1303-and-itsgone.jpg.cb17acc011c706142b7d8cf55eecd733.jpg

We can prevent deletion of an object by either keeping the variable in our code, or keeping a container that holds it.  In the case of map loading, the Map:Load() will return a structure that contains all the entity handles so they stay in memory.  Want to delete a camera?  It's easy:

camera = NULL;// poof!

There are still some details to work out but so far this approach is working great.

Another great benefit of shared pointers is that you can completely eliminate the need for big complicated destructors like this one.  All those objects will be automatically deleted when they go out of scope:

OpenGLCamera::~OpenGLCamera()
{
	for (int i=0; i<8; ++i)
	{
		if (shader_ambient[i])
		{
			shader_ambient[i]->Release();
			shader_ambient[i]=NULL;
		}
		for (int n = 0; n < 2; ++n)
		{
			for (int q = 0; q < 2; ++q)
			{
				for (int p = 0; p < 2; ++p)
				{
					if (shader_directional[i][n][q][p])
					{
						shader_directional[i][n][q][p]->Release();
						shader_directional[i][n][q][p] = NULL;
					}
					if (shader_directional_volume[i][n][q][p])
					{
						shader_directional_volume[i][n][q][p]->Release();
						shader_directional_volume[i][n][q][p] = NULL;
					}
					if (shader_point[i][n][q][p])
					{
						shader_point[i][n][q][p]->Release();
						shader_point[i][n][q][p] = NULL;
					}
					if (shader_point_volume[i][n][q][p])
					{
						shader_point_volume[i][n][q][p]->Release();
						shader_point_volume[i][n][q][p] = NULL;
					}
					if (shader_spot_volume[i][n][q][p])
					{
						shader_spot_volume[i][n][q][p]->Release();
						shader_spot_volume[i][n][q][p] = NULL;
					}
					if (shader_environment[i][n][q][p])
					{
						shader_environment[i][n][q][p]->Release();
						shader_environment[i][n][q][p] = NULL;
					}
					for (int usedecal = 0; usedecal < 2; usedecal++)
					{
						if (shader_spot[i][n][q][usedecal][p])
						{
							shader_spot[i][n][q][usedecal][p]->Release();
							shader_spot[i][n][q][usedecal][p] = NULL;
						}
					}
				}
			}
		}
	}
}

That entire function has been commented out in Leadwerks Game Engine 5. :D

Finally, shared pointers eliminate a problem that is the bane of all programmers' existence.  When used correctly, you can you can say goodbye to invalid and uninitialized pointers forever!  Yet shared pointers, unlike garbage collection, are fast to use and don't slow your game down.

Automatic Typing

The use of C++11 in Leadwerks Game Engine 5 gives us automatic typing of variables with the auto keyword.  We can simply something like this:

shared_ptr<Model> model = Model::Box();

To just this:

auto model = Model::Box();

If you have long complicated type names this makes life a million times easier:

for (std::list<shared_ptr<Entity> >::iterator it = list.begin(); it!= list.end(); it++)
{
	shared_ptr<Entity> entity = *it;
	entity->SetPosition(1,2,3);
}

Here's the same code simplified with auto (and range-based loops thanks to @Roland and @thehankinator.  Look how much more readable it is:

for (auto entity : list)
{
	entity->SetPosition(1,2,3);
}

This isn't completely new, but Leadwerks 5 is the first version built exclusively for C++11 features.

More Explicit API

Leadwerks 4 uses several bound states to store a current world and context.  These are global variables that are mostly invisible to the user.  This causes problems with multithreaded programming because two threads might try to change or access the bound state at the same time.

World *world1 = World::Create();
Model *model1 = Model::Box();

World *world2 = World::Create();
Model *model2 = Model::Box();
World::SetCurrent(world1);

Model *model3 = Model::Box();

Quick, which world does "model3" belong to?  Leadwerks 5 gets rid of the concept of bound states and requires you to explicitly specify the object you want to use.  Here's how the above code would look in Leadwerks 5:

auto world1 = World::Create();
auto model1 = Model::Box(world1);

auto world2 = World::Create();
auto model2 = Model::Box(world2);

auto model3 = Model::Box(world1);

Not only is the second example easier to understand, it's also one line shorter.

In a similar manner, the idea of a "current" context will be eliminated.  When you render a world you will need to explicitly specify which context to render to:

world->Render(context)

These programming features will make it easier than ever to code games with Leadwerks.

Josh

Today I am excited to announce plans for the release of the first Leadwerks 5 beta version.  Leadwerks 5 will roll out sooner rather than later, employing an extended beta period during which versions 4 and 5 will live side-by-side, using the same code base, with preprocessor definitions to compile each version.  This allows me to fix small problems without forking the code, while I can implement new changes in version 5.  The first features implemented will be the use of smart pointers for all shared objects, and unicode support for all strings.

A subscription model will be available for access to the Leadwerks 5 beta, at a modest price of just $4.99/month for enthusiasts who want access to the most cutting-edge game development technology as it is developed.  This will be available through the Leadwerks.com site, and will not use Steam (at least at first).  I feel it is important for the company's future to start building a recurring revenue stream, and I want to create something that does not rely on any middleman who may arbitrarily change or discontinue the terms of the service they are providing.  The Leadwerks 5 beta will implement breaking changes as it is developed, and is not meant for use in a production environment, so I do not recommend moving any commercial projects from version 4 to 5.  Leadwerks 4.x will continue to receive updates and new features until the final version 5 is released.

Leadwerks 5 is designed to be the most advanced game engine in the world, combining improved ease of use with massive performance, and a special emphasis on VR.  Thank you for supporting the next generation of game development technology.

Josh

Leadwerks Game Engine 5 is being designed to make use of shared pointers.  This eliminates manual reference counting, which has probably been the most difficult part of programming games with Leadwerks.  Here are three concepts you must understand before you start using smart pointers in Leadwerks 5,

Don't Create Multiple Shared Pointers from One Object

When a shared pointer goes out of scope, it deletes the object it references.  If another smart pointer was created separately that references that object, the other smart pointer will now point to an object that has been deleted!  Set breakpoints in the example below and you will see the problem.  The object is deleted while the second smart pointer still references it.  Any attempt to use the second smart pointer will cause an error:

class Thing
{
public:
	~Thing();
};

Thing::~Thing() {}// <--------- set a breakpoint here

int main(int argc, const char *argv[])
{
	Thing* thing = new Thing;

	shared_ptr<Thing> p1 = shared_ptr<Thing>(thing);
	shared_ptr<Thing> p2 = shared_ptr<Thing>(thing);

	p1 = nullptr;
	int k = 0;// <--------- set a breakpoint here
}

Instead, initialize a smart pointer once and copy it.  Here is the correct way:

class Thing
{
public:
	~Thing();
};

Thing::~Thing() {}// <--------- set a breakpoint here

int main(int argc, const char *argv[])
{
	Thing* thing = new Thing;

	shared_ptr<Thing> p1 = shared_ptr<Thing>(thing);
	shared_ptr<Thing> p2 = p1;

	p1 = nullptr;
	int k = 0;// <--------- set a breakpoint here
}

It's even better to eliminate the new keyword entirely and create object and smart pointer in one step:

class Thing
{
public:
	~Thing();
};

Thing::~Thing() {}// <--------- set a breakpoint here

int main(int argc, const char *argv[])
{
	shared_ptr<Thing> p1 = make_shared<Thing>();
	shared_ptr<Thing> p2 = p1;

	p1 = nullptr;
	int k = 0;// <--------- set a breakpoint here
}

The point is, you create the first smart pointer and thereafter all code should pass that around.  You never need to access the pointer directly.

Of course the use of auto makes everything a lot simpler:

auto p1 = make_shared<Thing>();
auto p2 = p1;

Parent / Child Relationships

If you have an object that is some kind of "child" of a parent object, you probably want that parent to keep a smart pointer to the child that keeps the child from being deleted.  However, if the child has a smart pointer to the parent you are creating a circular reference that will never be deleted from memory.  Think of the parent as the owner of the child.  Something other than the child must keep the parent in memory, but sometimes the child wants to retrieve the parent object in a bit of code.  Therefore, for the child member we will use a shared pointer, and for the parent member we will use a weak pointer:

class Thing
{
public:
	shared_ptr<Thing> child;
	weak_ptr<Thing> parent;
	~Thing();
	shared_ptr<Thing> GetParent();
};

The GetParent() function would look like this:

shared_ptr<Thing> Thing::GetParent()
{
	return parent.lock();
}

You can modify existing functions that access a parent by adding one line of code.  Here is one such function as it would appear in Leadwerks 4, where the parent member is just a regular old stupid pointer:

void Thing::AccessParent()
{
	if (parent != NULL)
	{
		//do some stuff to parent here
	}
}

The updated version for Leadwerks 4 creates a new shared_ptr<Thing> variable (with auto) and locks the weak pointer to make a shared pointer.  The rest of the code works seamlessly:

void Thing::AccessParent()
{
	auto parent = this->parent.lock();
	if (parent != NULL)
	{
		//do some stuff to parent here
	}
}

Class Functions Should Never Returns Themselves

The following code illustrates a problematic issue:

class Thing
{
public:
	shared_ptr<Thing> GetSelf()
};

shared_ptr<Thing> Thing::GetSelf()
{
	return shared_ptr<Thing>(this);
}

int main(int argc, const char *argv[])
{
	shared_ptr<Thing> p1 = make_shared<Thing>();
	shared_ptr<Thing> p2 = p1->GetSelf();
}

The GetSelf() function creates a new shared pointer that has no relation to the first one.  Both of these shared pointers will attempt to delete the object when they go out scope.  Only one will win. :blink:

I did a search throughout the entire Leadwerks Engine project and found only three instances of the phrase "return this;".  The easiest way to fix this problem would be to eliminate this type of behavior altogether by having a parent get the object instead of the object returning itself.  For example if you have a recursive function that is structured like this:

Thing* Thing::FindChild(const std::string& name)
{
	if (name == this->name) return this;
	for (auto it = kids.begin(); it != kids.end(); it++)
	{
		auto child = (*it)->FindChild(name);
		if (child) return child;
	}
	return NULL;
}

You can restructure it like this:

shared_ptr<Thing> Thing::FindChild(const std::string& name)
{
	for (auto it = kids.begin(); it != kids.end(); it++)
	{
		if (name == (*it)->name) return this;
		auto child = (*it)->FindChild(name);
		if (child) return child;
	}
	return NULL;
}

I considered deriving all complex objects from a "SharedObject" class with a weak pointer that referenced itself:

class SharedObject
{
	weak_ptr<SharedObject> self;
};

However, this requires the "self" member to be set when the object is created and is tedious to use.  I think it's easier to just eliminate functions that return the object itself.

Josh

This shows the fundamental difference between shared pointers and manual reference counting.

Leadwerks 4:

void Material::SetTexture(Texture* texture, const int index)
{
	if (this->texture[index] != texture)
	{
		if (this->texture[index]) this->texture[index]->Release();
		this->texture[index] = texture;
		if (this->texture[index]) this->texture[index]->AddRef();
	}
}

Leadwerks 5:

void Material::SetTexture(shared_ptr<Texture> texture, const int index)
{
	this->texture[index] = texture;
}

The second example automatically does the same thing as the first (basically) but you don't have to worry about making mistakes.  When the texture is no longer referred to by any variable, it will be automatically deleted from system and video memory.

Josh

Previously, I talked a little bit about shared pointers in C++11.  These use automatic reference counting to track how many variables are pointing to an object.  When the object is no longer being used, it is automatically deleted.  This is similar to garbage collection, but doesn't involve the massive overhead of garbage-collected systems.  In fact, shared pointers simply automate something we were already doing with the Release() and AddRef() commands in Leadwerks 4.

A weak pointer is like a shared pointer, but having a weak pointer to an object does not prevent the object from being deleted.  Weak pointers are a great way to handle situations that previously could have resulted in an invalid pointer.  Let's say you have an AI class for an enemy in your game that stores an entity it is attacking:

class Monster : public Actor
{
	int health;
	weak_ptr<Entity> target;
  
	virtual void UpdateWorld();
};

To make use of the weak pointer, we convert it into a shared pointer.  At that point, the temporary shared pointer will prevent the object from being deleted:

void Monster::UpdateWorld()
{
	shared_ptr<Entity> enemy = this->target.lock();
	if (enemy != nullptr)
	{
		//do some stuff
	}
}

If something causes the monster's target entity to be deleted, the program will work just fine without having to go through and find all variables that point to that entity.

You can check if a weak pointer's object has been deleted with the expired() command, since creating a new shared pointer involves a certain amount of overhead:

void Monster::UpdateWorld()
{
	if (!this->target.expired())
	{
		shared_ptr<Entity> enemy = this->target.lock();
		if (enemy != nullptr)
		{
			//do some stuff
		}
	}
}

However, you still need to perform the check to see if the resulting shared pointer is nullptr/NULL because theoretically another thread could have caused the object to be deleted right after the expired() call returns false.

If you want to clean up a list of weak pointers as you discover expired pointers, you can structure it like this:

auto it = items.begin();
while (it!=items.end())
{
	auto item = (*it).lock();
	if (item != nullptr)
	{
		item->Update();
		it++;
	}
	else
	{
		items.erase(it++);
	}
}

If we add in the optional expired() check, it gets kind of complicated, but this is not something you should really need to do much:

auto it = items.begin();
while (it!=items.end())
{
	if (!(*it).expired())
	{
		auto item = (*it).lock();
		if (item != nullptr)
		{
			item->Update();
			it++;
		}
		else
		{
			items.erase(it++);
		}
	}
	else
	{
		items.erase(it++);
	}
}

Here's a real example in the Leadwerks source code.  The LEADWERKS_5 preprocessor definition is used to modify behavior in 4/5.  In version 4 the list contains pointers, and in version 5 it contains weak pointers:  Version 4 could get complicated because the entity would have to store an iterator to remove it from this list, in case it is deleted before this routine is called inside the World::Update() function.  In version 5 we just check if the weak pointer is valid and skip it if the entity was deleted.  At the end of the routine, the whole list is cleared, so there are no worries about deleting each iterator.

bool World::UpdateNavigation()
{
	//Invalidate navmesh tiles that intersect moved entities
	for (auto it = updatenavigationlist.begin(); it != updatenavigationlist.end(); it++)
	{
#ifdef LEADWERKS_5
		auto entity = (*it).lock();
		if (entity == nullptr) continue;
#else
		auto entity = (*it);
#endif
		entity->updatenavigationneeded = false;
		for (int i = 0; i<entity->relevantnavtiles.size(); i++)
		{
			entity->relevantnavtiles[i]->Invalidate();
		}
		entity->relevantnavtiles.clear();
		if (entity->navigationmode) navmesh->InvalidateTiles(entity->aabb);
	}
	updatenavigationlist.clear();

	//Update the navmesh
	return navmesh->Update();
}

Weak pointers make game programming easier and less susceptible to errors in Leadwerks Game Engine 5.

Josh

I have proven beyond a shadow of a doubt that I am very bad at shipping posters.  The Winter Game Tournament was completed in January and I have yet to deliver your prizes.  If you have a job opening for someone to ship prizes, or to ship anything at all, I am probably the worst candidate you could ever hope to find to fill said position.  Seriously.

Part of the problem (although it's still not an excuse) has been that it is actually incredibly difficult to have custom USB keychains made.  These took a long time because my description of black monochrome printing with the text vertical aligned was too apparently too complicated.

MV5BYjBhMjY5YjUtYmYwNC00MmFhLTkxMTktYzMzMTA5ODZmNDRiL2ltYWdlXkEyXkFqcGdeQXVyNjY1OTEzMzc@._V1_.thumb.jpg.e5ac13f4d4d3ac3fdb4d0cfe60a92cdd.jpg

The first proof I got used a beautiful white-on-silver-so-light-it's-practically-white color scheme.

fail.png.347760c26276528eb8485ca387b945f9.png

The second attempt's failure was slightly more subtle.  Slightly.  Is it just me, or does the logo obviously beg to be centered by the vertical center of the text, instead of the image dimensions?  Is this really that hard?:

Image1.jpg.cc444a79c4bd2362b7f852164c59c61d.jpg

At this point I was all like:

3511d9767c96ce281df5a9a69f6b88ed5654af83fe3ca8a1b99b2d225d1ad268.jpg.c43ee36c3195a4028f289f711b9fe7af.jpg

So I drew them a picture of something that to me seemed extremely obvious.  Finally, I got the corrected proof of our glorious USB keychain and have authorized a limited production of this one-of-a-kind item.  I will give props to the graphics designer for choosing the dark gray (subtle blue?) print color, which is much better than #000000:

19957008_10155303550526183_4315704447448976980_o.jpg.c50007531937828de42285fa53b28b89.jpg

All I wanted was a little taste in design.

So here's what I am going to do.  Each entrant in the Winter Games Tournament will receive one limited-edition poster, with one limited-edition Leadwerks USB keychain inside the poster tube, plus one Leadwerks sticker (which I have a ton of).  Because of the delay and the fact that I suck at shipping prizes, I am bumping up the 4GB USB drive to a whopping 16GB.  That's four times the jigglebytes, and enough to back up the entire Leadwerks.com website on.

In other news, the acquisition of American Apparel by Gilden Activewear has actually affected our supply chain for the fabulous Leadwerks line of clothing.  The official Leadwerks hoodie in our signature gray color is currently only available in sizes small and extra small.

ricklol.jpg.951adbf4dd4beb02b2bea1302842905e.jpg

I contacted SpreadShirt.com about the matter and received the following explanation:

Quote

Hello Josh,

Thank you for your email.

Unfortunately this item is currently out of stock in some sizes and colors.

Our product manager has not informed us of when more stock will be arriving as the company American Apparel is currently going through a transition phase while ownership is transferred to the brand Gildan.

The sizes and colors will be available online again once we've received the stock from the new supplier.

Please feel free to contact us if you have any further questions.

Sincerely,

Ariel
Customer Service

The first and most pertinent question is why is the little mermaid working in customer service for SpreadShirt?

character_disneyprincess_ariel_262253c9.jpg.39101c734b08bd50c40ef51132a7ffd0.jpg

The second question is when will the official Leadwerks hoodie become available again?  The garment can be gotten in other colors, but I do not feel that any of these less appealing colors adequately represent the brand of our game engine.  This is most distressing and I will continue to look for a solution.

The 2017 Summer Game Tournament will proceed once I have shipped the prizes out from the previous tournament.  However, as I have proven that I am not a reliable shipper of prizes, the tournament is going back to our original prizeless model.  A roundup of entries from all game developers will be posted at the end and fun will be had by all.

Josh

Leadwerks 4.x will see a few more releases, each remaining backwards compatible with previous versions.  Leadwerks 5.0 is the point where we will introduce changes that break compatibility with previous versions.  The changes will support a faster design that relies more on multi-threading, updates to take advantage of C++11 features, and better support for non-English speaking users worldwide.  Changes are limited to code; all asset files including models, maps, shaders, textures, and materials will continue to be backwards-compatible.

Let's take a look at some of the new stuff.

Shared Pointers
C++11 shared pointers eliminate the need for manual reference counting.  Using the auto keyword will make it easier to update Leadwerks 4 code when version 5 arrives.  You can read more about the use of shared pointers in Leadwerks 5 here.

Unicode Support
To support the entire world's languages, Leadwerks 5 will make use of Unicode everywhere.  All std::string variables will be replaced by std::wstring.  Lua will be updated to the latest version 5.3.  This is not compatible with LuaJIT, but the main developer has left the LuaJIT project and it is time to move on.  Script execution time is not a bottleneck, Leadwerks 5 gains a much longer window of time for your game code to run, and I don't recommend people build complex VR games in Lua.  So I think it is time to update.

Elimination of Bound Globals
To assist with multithreaded programming, I am leaning towards a stateless design with all commands like World::GetCurrent() removed.  An entity needs to be explicitly told which world it belongs to upon creation, or it must be created as a child of another entity:

auto entity = Pivot::Create(world);

I am considering encapsulating all global variables into a GameEngine object:

class GameEngine
{
public:  
  std::map<std::string, std::weak_ptr<Asset> > loadedassets;
  shared_ptr<GraphicsEngine> graphicsengine;
  shared_ptr<PhysicsEngine> physicsengine;
  shared_ptr<NetworkEngine> networkengine;
  shared_ptr<SoundEngine> soundengine;
  shared_ptr<ScriptEngine> scriptengine;//replaces Interpreter class
};

A world would need the GameEngine object supplied upon creation:

auto gameengine = GameEngine::Create();
auto world = World::Create(gameengine);

When the GameEngine object goes out of scope, the entire game gets cleaned up and everything is deleted, leaving nothing in memory.

New SurfaceBuilder Class
To improve efficiency in Leadwerks 5, surfaces will no longer be stored in system memory, and surfaces cannot be modified once they are created.  If you need a modifiable surface, you can create a SurfaceBuilder object.

auto builder = SurfaceBuilder::Create(gameengine);
builder->AddVertex(0,0,0);
builder->AddVertex(0,0,1);
builder->AddVertex(0,1,1);
builder->AddTriangle(0,1,2);
auto surface = model->AddSurface(builder);

When a model is first loaded, before it is sent to the rendering thread for drawing, you can access the builder object that is loaded for each surface:

auto model = Model::Load("Models\box.mdl", Asset::Unique);
for (int i=0; i<model->CountSurfaces(); ++i)
{
	auto surface = model->GetSurface(i);
	shared_ptr<SurfaceBuilder> builder = surface->GetBuilder();
  	if (builder != nullptr)
	{
		for (int v=0; v < surface->builder->CountVertices(); ++v)
		{
			Vec3 v = builder->GetVertexPosition(v);
		}
	}
}

98% of the time there is no reason to keep vertex and triangle data in system memory.  For special cases, the SurfaceBuilder class does the job, and includes functions that were previously found in the Surface class like UpdateNormals().  This will prevent surfaces from being modified by the user when they are in use in the rendering thread.

A TextureBuilder class will be used internally when loading textures and will operate in a similar manner.  Pixel data will be retained in system memory until the first render.  These classes have the effect of keeping all OpenGL (or other graphics API) code contained inside the rendering thread, which leads to our next new feature...

Asynchronous Loading
Because surfaces and textures defer all GPU calls to the rendering thread, there is no reason we can't safely load these assets on another thread.  The LoadASync function will simply return true or false depending on whether the file was able to be opened:

bool result = Model::LoadASync("Models\box.mdl");

The result of the load will be given in an event:

while (gameengine->eventqueue->Peek())
{
	auto event = gameengine->eventqueue->Wait();
	if (event.id == Event::AsyncLoadResult)
	{
		if (event.extra->GetClass() == Object::ModelClass)
		{
			auto model = static_cast<Model>(event.source.get());
		}
	}
}

Thank goodness for shared pointers, or this would be very difficult to keep track of!

Asynchronous loading of maps is a little more complicated, but with proper encapsulation I think we can do it.  The script interpreter will get a mutex that is locked whenever a Lua script executes so scripts can be run from separate threads:

gameengine->scriptengine->execmutex->Lock();
//Do Lua stuff
gameengine->scriptengine->execmutex->Unlock();

This allows you to easily do things like make an animated loading screen.  The code for this would look something like below:

Map::LoadASync("Maps/start.map", world);
while (true)
{
	while (EventQueue::Peek())
	{
		auto event = EventQueue::Wait();
		if (event.id == Event::AsyncLoadResult)
		{
			break;
		}
	}
	
	loadsprite->Turn(0,0,1);
	world->Render();// Context::Sync() might be done automatically here, not sure yet...
}

All of this will look pretty familiar to you, but with the features of C++11 and the new design of Leadwerks 5 it opens up a lot of exciting possibilities.

Josh

C++11 introduces shared pointers, a powerful language feature that provides easy memory management without the overhead of garbage collection.  The example below should look familiar to you:

#include "Leadwerks.h"

using namespace Leadwerks;

int main(int argc, const char *argv[])
{
	shared_ptr<Leadwerks::Window> window = Leadwerks::Window::Create();

	shared_ptr<Context> context = Context::Create(window);

	shared_ptr<World> world = World::Create();

	shared_ptr<Camera> camera = Camera::Create();
	camera->SetRotation(35, 0, 0);
	camera->Move(0, 0, -6);

	shared_ptr<Light> light = DirectionalLight::Create();
	light->SetRotation(35, 35, 0);

	shared_ptr<Model> model = Model::Box();
	model->SetColor(1.0, 0.0, 0.0);
	model->SetPosition(-4, 0, 0);

	while (true)
	{
		if (window->Closed() || window->KeyDown(Key::Escape)) return false;
		
		Leadwerks::Time::Update();
		world->Update();
		world->Render();
		context->Sync();
	}
  
	//Everything will get automatically deleted when we return from this function
	return 0;
}

Using the auto keyword simplifies everything (and it makes this code compatible with Leadwerks 4):

#include "Leadwerks.h"

using namespace Leadwerks;

int main(int argc, const char *argv[])
{
	auto window = Leadwerks::Window::Create();

	auto context = Context::Create(window);

	auto world = World::Create();

	auto camera = Camera::Create();
	camera->SetRotation(35, 0, 0);
	camera->Move(0, 0, -6);

	auto light = DirectionalLight::Create();
	light->SetRotation(35, 35, 0);

	auto model = Model::Box();
	model->SetColor(1.0, 0.0, 0.0);
	model->SetPosition(-4, 0, 0);
	
	while (true)
	{
		if (window->Closed() || window->KeyDown(Key::Escape)) return false;
		
		Leadwerks::Time::Update();
		world->Update();
		world->Render();
		context->Sync();
	}
  
  	//Everything will get automatically deleted when we return from this function
	return 0;
}

Now things get interesting.  This function would normally cause a horrible memory leak, but with shared pointers everything is fine:

void SaveTexture(shared_ptr<Texture> tex)
{
	auto bank = Bank::Create(tex->GetMipmapSize());
	tex->GetPixels(bank->buf);
	bank->Save("pixeldata.dat");
}

Yet shared pointers can still equal nullptr:

auto bank = Bank::Create();
bank.reset();
Debug::Assert(bank==nullptr);

You can even simply set a shared pointer to nullptr, and if that was the last pointer that referenced it, it gets deleted!

auto bank = Bank::Create();
bank = nullptr;// auto deletion here!

How to Delete an Entity
The code below will not delete the entity, because a shared pointer is still stored in the world.

auto entity = Pivot::Create();
entity = nullptr;

The entity must be have its world set to NULL, and the shared pointer must be set to NULL or go out of scope:

entity = Pivot::Create();
entity->SetWorld(nullptr);
entity = nullptr;

Children use a weak pointer to the parent, so they will not cause self-referencing.

Asset Management
You no longer have to worry about calling Release() when loading assets:

auto material = Material::Create();
auto texture = Texture::Load("mytex.tex");
material->SetTexture(texture);
texture = nullptr;

Unused assets will automatically be deleted if they go out of scope:

auto material = Material::Create();
auto texture = Texture::Load("mytex.tex");
material->SetTexture(texture);
texture = nullptr;
material = nullptr;

But if they are in use, they will be retained in memory:

auto material = Material::Create();
auto texture = Texture::Load("mytex.tex");
material->SetTexture(texture);
model->SetMaterial(material);
texture = nullptr;
material = nullptr;

In conclusion, shared pointers automate many of the tasks we have been doing manually with the Leadwerks reference counting system and the AddRef and Release commands.

Admin

 Leadwerks Software today announced the release of version 4.4 of their topselling game engine on Steam.  This version adds a new GUI system, support for inverse kinematics, and enhanced visuals.  The free update goes out today to over 20,000 paid users on Steam.

Leadwerks Game Engine 4.4 sees the introduction of Leadwerks GUI, a system for creating resolution-independent in-game menus.  Custom GUI elements can be created with Lua script, or an item can be selected from a number of pre-built GUI scripts including buttons, tabbed panels, slider controls, and more.  Leadwerks GUI is designed for the future, with support for 4K and 8K displays, as well as VR menus of any resolution.

Screenshots_screenshot261.thumb.jpg.7dcfff75c87b0cbb2de0b29a6cc353eb.jpg

Inverse kinematics are now supported, with two new joints that provide fine control over the orientation of physics bodies.  These can be used to control the end effector in an IK setup, or for precise control of moving objects.  The Leadwerks physics system has been updated to the latest Newton Dynamics 3.14.

Screenshots_screenshot260.thumb.jpg.4f38884321d5acfcc722e70cb98ef1fd.jpg

Post-processing effects have been updated to enhance visual quality.  Bloom, HDR iris adjustment, SSAO, and fog have all been updated for improved graphics.  A new shader has been added to the vegetation system that scattering millions of rocks across a landscape to make terrain more detailed and realistic.

Screenshots_screenshot259.thumb.jpg.7fe75bab83a6be87c1aa01f197efa93a.jpg

The Leadwerks learning materials have been converted into an all-new documentation system.  This allows easier searching and browsing of tutorials, API documentation, and examples.  A new series of tutorials has been added to teach the basics of C++ programming, in addition to the existing lessons on Lua scripting.

Leadwerks Game Engine can be purchased at a discount during the Steam summer sale.  All Leadwerks games have Steamworks support integrated out-of-the-box and are ready to publish through Steam Direct.

Josh

Distance fog is one of the most basic visual effects in 3D graphics, going back to the 1990s.  Here is the effect in the Quake 3 Arena map "Fatal Instinct", which was shrouded in a dense orange fog:

hqdefault.jpg.cf269ad4a7036e236bea5c2c548370bf.jpg

Leadwerks Game Engine 2 had this available as a built-in effect, while the more flexible effects system of Leadwerks 3/4 had several Workshop shaders available to use, including one by Klepto and another one more recently added by myself.  However, this has not been part of the official SDK until version 4.4.  Why is that?

The Problem
When water is rendered in Leadwerks, a low-quality render is performed of the world with the camera scale inverted on the Y axis.  Because the reflection is distorted by ripples, we render to a lower-resolution buffer with all settings on low and effects disabled.  This is important because it gives a faster performance and image quality that is still acceptable.  The water plane also uses occlusion culling so that the extra pass is only rendered if part of the water plane is visible.  All post-processing effects are disabled in the reflection pass, which makes good sense, except in the case of fog.  If the world is shrouded in dense fog but the reflection in the water is clear, it creates an obvious problem.  In the screenshot below, a post-processing effect is applied to the world.  Although the water does have fog applied to it, the reflected image on the water does not have any fog, creating a stark problem, because post-processing effects are disabled in the reflection pass.

screenshot87.thumb.jpg.7b4c13865c90793491607d1c51d97abc.jpg

Pre-Rendering Fog
One option would have been to allow the user to mark some post effects as visible in reflection passes, but that seemed complicated and error-prone.  I came up with the idea build a simple fog calculation into the first ambient or directional lighting pass.  Here it is applied in the directional light pass:

screenshot86.thumb.jpg.935126777fe6969577af35ed90a56ce0.jpg

And here is what happens when the fog effect is applied in the directional light pass in the water reflection as well:

screenshot88.thumb.jpg.06759dc664b3ddc4a83fccb708829251.jpg

We can modify the water shader itself to add the same fog calculation to the surface of the water.  Now our water matches the world.

screenshot89.thumb.jpg.ff8a556c6495748cf6f2a8e29285cf03.jpg

Removing Artifacts
There was still more to do.  We are rendering fog first and other stuff later.  Anything rendered after the main lighting pass has to use the fog calculation to substract its effect from the scene.  For example SSAO will add shaded areas on top of fog, which we definitely don't want.  See the bridge and trees in the distance.

6E2A043636BB6DDB7A8B92E833683FF8D279AE95

The solution is to add the same fog calculation into the SSAO shader:

	float fogeffect = 0.0f;
	if (fogmode==true)
	{
		fogeffect = clamp( 1.0 - (fogrange.y - length(worldCoord - cameramatrix[3].xyz)) / (fogrange.y - fogrange.x) , 0.0, 1.0 );
		fogeffect*=fogcolor.a;
		if (fogeffect==1.0f)
		{
			fragData0 = outputcolor;
			return;
		}
	}

And then use the fog level to lessen the impact of the effect:

fragData0 = outputcolor * fogeffect + outputcolor * (sumao / float(passes)) * (1.0 - fogeffect);

The underwater artifacts are a separate issue that were solved by adding another calculation.  Here is the result:

EB844FB4B1F76913614C81770FA39B54448A32F4

The same fog calculation had to be added to all light shaders, light volumes, and probes, to make sure lights faded into the fog.  Other effects can make use of four new built-in uniforms which will provide all the fog information the shader needs:

uniform bool fogmode;
uniform vec2 fogrange;
uniform vec4 fogcolor;
uniform vec2 fogangle;

On the client side, eight new commands have been added to the camera class to control the fog appearance, which are also available in Lua:

virtual void SetFogColor(const float r, const float g, const float b, const float a);
virtual void SetFogAngle(const float start, const float stop);
virtual void SetFogRange(const float start, const float stop);
virtual void SetFogMode(const bool mode);
virtual Vec4 GetFogColor();
virtual Vec2 GetFogAngle();
virtual Vec2 GetFogRange();
virtual bool GetFogMode();

This was the solution I've had in mind for some time, but I haven't had a chance to implement it until now.  It works really well and provides robust fog that looks correct under a wide variety of settings.

D7E8DF18C44CF0FBA92288DE0A88DE68AD547DF5

Josh

Leadwerks Game Engine 4.4, scheduled for release soon, features some updated and enhanced visual effects.  In this blog I will talk about some of the adjustments I made.  Having "The Zone" scene that Aggror recreated actually helped a lot to see how shaders could be improved.

Bloom / Iris Adjustment / HDR

The bloom and iris adjustment shaders have been updated to give bloom a wider and softer blur.  Iris adjustment is faster and more intense now, which will make the outdoors areas seem very bright when you are indoors, and the indoors areas will look very dark when you are outdoors.

screenshot79.thumb.jpg.d0b7479732e9eebd8327919c94417f97.jpg

screenshot78.thumb.jpg.3b7da31d6aeddeb6bddb70da3203002d.jpg

SSAO
The SSAO shader has multiple passes added to it, resulting in a much smoother yet crisp result. 
78C219DAE8181DD3184E7313F08ABCBB9400F45B

02D895D6B1E7A266EFCED9C564FDBF150809BF36

543598A48A470EB317E09367A88616C7727751D3

Vegetation Shaders

A new vegetation shader called "groundrocks" will make objects align to the terrain.  This lets you easily paint clusters of rocks all across a landscape, resulting in a nice chunky look that breaks up boring heightmap terrain.

50F8CF2FD29EC0F5E43CAB6BB908CA43BF17BDD3

Built-in Fog
Although several fog shaders have been available in the Workshop for some time, version 4.4 adds a built-in distance fog you can use to make spooky scenes.

screenshot80.jpg

The fog is built into several shaders in order to give correct reflections.  It works great with water.  Notice the reflection is also foggy, and the water itself is affected by fog, giving a nice misty look to the far side of the lake.

screenshot81.thumb.jpg.dcffd712d42c95fac07d69de36086904.jpg

You can try Leadwerks Game Engine 4.4 right now by opting into the beta branch on Steam.

Josh

This tutorial demonstrates how to create a high-quality skybox for Leadwerks Game Engine using Vue.

Download

Required Third-Party Programs

Loading the Example

Run Vue and select the File > Open menu item.  Extract the zip file above and open the file "Cloudy Blue Skies.vue".

ccs-1-0-90786400-1443279174_thumb.jpg

Atmosphere and Clouds

You can modify the appearance of the sky with the Atmosphere Editor. Select the Atmosphere > Atmosphere Editor menu item to open this dialog.

ccs-1-0-74507200-1443279390_thumb.jpg

The clouds tab lets you adjust various properties of the cloud layers and add new ones. Skyboxes look best with multiple layers of different kinds of clouds, so don't expect to get the perfect look with just one layer.

ccs-1-0-34038100-1443279521_thumb.jpg

The load button to the right side of the cloud layer list will let you select from a wide range of different cloud types.

ccs-1-0-33700000-1443279536_thumb.jpg

Experiment with different cloud layers to get the look you want. The "Detail amount" setting in particular will really enhance the image, but don't overdo it. You can right-click and drag the mouse to look around in the main panel, so be sure to take a look around to see how the clouds affect the entire sky.

Lighting

To edit the sunlight properties in Vue, select the sunlight object in the World Browser on the right side of the main window.

ccs-1-0-12557900-1443280022.jpg

You can match the exact rotation of the default sunlight angle in Leadwerks to make your skybox line up exactly to the scene lighting. The default sunlight angle in Leadwerks is (55,-35,0). In Vue this corresponds to the values (145,0,215). To get these values we add 90 degrees to the pitch and subtract the yaw from 180. Note in Vue the order of the yaw and roll are switched.

ccs-1-0-03849300-1443279932.jpg

The sun color is very important for the overall composition of our image. In real life we're used to seeing a very high range of light levels in the sky. Computer monitors cannot represent the same range of colors, so images can easily become washed out and lose details. We want to adjust the sun color so we can get the most detail within the spectrum of a 32-bit color display. Like the rotation, the sun color can be modified in the sun properties.

ccs-1-0-14094400-1443280269.jpg

If the sunlight color is too bright, the image will be overexposed and the cloud shape will become washed out.

ccs-1-0-47473100-1443280650_thumb.jpg

If the sunlight is too dark, it will look gray and desaturated.

ccs-1-0-24631500-1443280414_thumb.jpg

The right sun brightness will give a balanced look between bright spots and shadows. This is the part in the process that requires the most artistic sense. It's a good idea to look at some screenshots or photos for comparison as you adjust your settings.

ccs-1-0-79686900-1443284709_thumb.jpg

You will get quite a lot of variation in brightness across the sky, so be sure to take a look around the whole scene when you are adjusting lighting. You can also adjust the main camera's exposure value to vary the brightness of the rendered image.

If you want to hide the sun from view you can do this by setting the "Size of the sun" and "Size of the corona" settings both to zero under the "Sun" tab in the Atmosphere Editor.

Exporting

To export our skybox the exporter module must be installed. Select the File > Export Sky menu item and the export dialog will appear.

ccs-1-0-61001600-1443280903_thumb.jpg

The "Supporting geometry" setting should be set to "Cube". Set the X value to 1536 and the Y value to 2048. This controls the width and height of the saved image. When we press the OK button, the sky will be rendered out into a vertical cube cross with those dimensions. Each face of the cubemap will be 512x512 pixels.

ccs-1-0-89630000-1443281014_thumb.jpg

By default, your skybox will be exported to the file "Documents\e-on software\Vue 2015\Objects\Atmosphere.bmp". The exported cube cross is a nonstandard orientation. To convert this into a cubemap strip ready to load in Leadwerks, use the FixVueCubemap.exe utility posted above.  Drag your exported image file onto the executable, and it will save out a cube strip in PNG format that is ready to load in Leadwerks.

ccs-1-0-34054400-1443281151_thumb.jpg

Importing

To import your skybox into Leadwerks, just drag the cubemap strip PNG file onto the Leadwerks main window. Open the converted texture from the Leadwerks Asset Browser. Set the texture mode to "Cubemap", uncheck the "Generate Mipmaps" checkbox, and check the clamp mode for the X, Y, and Z axes. Press the Save button to reconvert the texture and it will appear in a 3D view.

ccs-1-0-76222900-1443281537_thumb.jpg

You can use the skybox in the current map by selecting it in the scene settings.

ccs-1-0-23907200-1443281670.jpg

Disabling mipmap generation will reduce the size of a 1024x1024 cubemap from 32 to 24 mb. Due to the way the image is displayed, mipmaps aren't needed anyways.

Final Render

For the final render, we want each cubemap face to be 1024x1024 pixels. However, we can get a better quality image if we render at a larger resolution and then downsample the image. In Vue, select the File > Export menu item again to open the export dialog. This time enter 6144 for the X value and 8192 for the Y value. Don't press the OK button until you are ready to take a long break, because the image will take a long time to render. When you're done you will have a huge image file of your skybox with a 2048x2048 area for each cubemap face.

If we resize the image file in a regular paint program, it will create seams along the edges of the cubemap faces. Instead, we're going to pass a parameter to the conversion utility to tell it to downsample the image by a factor of 50%. The "downsample.bat" file is set up to do this, so just double-click on this to launch the executable with the correct parameters. The resulting cubemap strip will be 6144x1024 pixels, with a 1024x1024 area for each face. However, due to the original rendering resolution this will appear less grainy then if we had rendered directly to this resolution.

Import this texture into Leadwerks as before and enjoy your finished high-quality skybox. Always do a low-resolution pass before rendering the final image, as it can take a long time to process.

Josh

An update is up which saves all menu settings into your game's config file.  When your program calls System:SetProperty() the inputted key-value pair is saved in a list of settings.  Your game automatically saves these settings to a file when it closes, located in C:\Users\<USERNAME>\AppData\local\<GAMENAME>\<GAMENAME>.cfg.

The contents of the config file will look something like this:

anisotropicfilter=8
antialias=1
lightquality=1
screenheight=720
screenwidth=1280
session_number=2
terrainquality=1
texturedetail=0
trilinearfilter=1
verticalsync=1
waterquality=1

When your game runs again, these settings will be automatically loaded and applied.  You can override config settings with a command line argument.  However, command lines arguments will not be saved in the config file.

This has been my plan for a long time, and is the reason why your game is not set to use the editor settings.  Setting for running your game in real-time should be separate from editor settings.

Josh

In-Game Menu

Along with Leadwerks GUI, Leadwerks 4.4 adds an in-game menu that is available with the default Lua scripted game.  You can use this to allow your users to adjust settings in the game, or provide a more sophisticated way to quit the game than simply pressing the escape key.

Image1.thumb.jpg.3c376f0b4bad8d43e3c7f6608fbcf18a.jpg

The default window size has been changed to 1280x720 when run from the editor.  Your game will now run in fullscreen mode by default when it is launched outside the editor.

All of these changes are contained in the Main.lua and Menu.lua scripts, and can all be modified or removed to your heart's content.

A full build is available now on the beta branch.

Josh

In Leadwerks 4.3 we integrated GameAnalytics.com into our software, both in the editor and in the engine, as a tool developers can use to track their player statistics.  A number of events were set up in the editor to fire when certain actions were performed, in order to gain better insight into how people were using Leadwerks.  Here are the results.

The most popular primitives

Unsurprisingly, boxes are by far the most popular primitive created in Leadwerks Editor.  The community has created more than 20,000 boxes since analytics were enabled.  What blows my mind is that cylinders are actually the second-most commonly used primitive, rather than wedges.  Users created 1753 cylinders but only 1358 wedges in the given time period!  Even more shocking is that spheres are more popular than cones, with 985 spheres created versus just 472 cone primitives.

This causes me to question my assumptions of how people use Leadwerks, and how items in the interface should be prioritized.

primitives.jpg

The Workshop Store is used heavily

Leadwerks users installed more than 5000 items from the Workshop.  It also looks like people install many items, as sometimes the number of installs exceed the number of times the Workshop interface is opened.

workshop.jpg

People buy Workshop items directly through the Steam Client

Although users are buying Workshop items in high quantities, it appears that their primary route is through the Steam store interface, rather than the built-in Workshop browser.  People browse the items in the Steam client and then use the Workshop browser to install their purchased items.  The numbers for Workshop Store purchases are much higher than the number of people clicking the buy button in the editor.

If you're interested in selling your 3D models or textures through the Leadwerks Workshop Store, it's easy to get started and you can earn money directly from Steam.  Contact us to learn more or stop by the forum.

Josh

Leadwerks Game Engine 5 is a restructuring of Leadwerks Game Engine 4 to adapt to the demands of virtual reality and leverage the full capabilities of modern and future hardware.

Basically, the main idea with VR is that if you don't maintain a steady 90 FPS in virtual reality, you will throw up.  Nausea may be the worst physiological feeling you can experience.  In fact, nausea has been rated by cancer patients as worse than pain.  Being sensitive to motion sickness myself, this is a problem I am very motivated to solve.

In a conventional renderer, running both your game logic and rendering at 60 hz (frames per second) seems reasonable.  However, when we up the framerate to the 90 hz required for fluid virtual reality experiences, it seems like an excessive demand on the game code.  Game logic normally handles AI, player input, and various other tasks, and those things don't have to be updated quite that often.

Distributing Tasks
The solution I have come up with is to decouple the game loop from the renderer.  In the diagram below, the game loop is running at only 30 hz, while the physics, culling, and rendering loops are running at independent frequencies.

multithread1.png.9a5b81c1fd8ac31eac590a913b23894c.png

Think of this as like gears on a bicycle.  Your pedals move slowly, but your wheels spin very fast.  The game logic loop is like your pedals, while the rendering loop is like the rear wheel it is connected to.

gears.jpg.2a420b3c735ed09ebaad61597144f6b8.jpg

Previously, your game logic needed to execute in about 8 milliseconds or it would start slowing down the framerate.  With my design here, your game code gets more than 32 milliseconds to execute, a lifetime in code execution time, while a steady framerate of 90 or 60 FPS is constantly maintained.

I actually came up with this idea on my own, but upon further research I found out this is exactly what Intel recommends.  The technique is called Free Step Mode.  The diagram below does not correspond to our engine design, but it does illustrate the idea that separate systems are operating at different speeds:

7951-2.jpg.a224d98361a9c2890ece33362afa653f.jpg

If all threads are set to execute at the same frequency, it is called Lock Step Mode.

7952.jpg.913116238ba12998a0b351549d732e16.jpg

Data Exchange
Data in the game loop is exchanged with the physics and navmesh threads, but is passed one-way on to the culling loop, where it is then passed in a single direction to the rendering loop.  This means there will be a slight delay between when an event occurs and when it makes its way to the rendering thread and the final screen output, but we are talking times on the level of perhaps 10 milliseconds, so it won't be noticeable.  The user will just see smooth motion.

multithread2.png.5f514bfdc821b4e6404455b91c10248a.png

Rather than risk a lot of mutex locks, data is going to be passed one-way and each thread will have a copy of the object.  The game loop will have the full entity class, but the rendering threads will only have a stripped-down class, something like this:

class RenderObject
{
public:
	Mat4 matrix;
	AABB aabb;
	std::vector<Surface*> surfaces;
};

The original entity this object corresponds to can be modified or deleted, without fear of affecting downstream threads.  Again, Intel confirmed what I already thought would be the best approach:

Quote

In order for a game engine to truly run parallel, with as little synchronization overhead as possible, it will need to have each system operate within its own execution state with as little interaction as possible to anything else that is going on in the engine. Data still needs to be shared however, but now instead of each system accessing a common data location to say, get position or orientation data, each system has its own copy. This removes the data dependency that exists between different parts of the engine. Notices of any changes made by a system to shared data are sent to a state manager which then queues up all the changes, called messaging. Once the different systems are done executing, they are notified of the state changes and update their internal data structures, which is also part of messaging. Using this mechanism greatly reduces synchronization overhead, allowing systems to act more independently.

-Designing the Framework of a Parallel Game Engine, Jeff Andrews, Intel
https://software.intel.com/en-us/articles/designing-the-framework-of-a-parallel-game-engine

But wait, isn't latency a huge problem in VR, and I just described a system that adds latency to the renderer?  Yes and no.  The rendering thread will constantly update the headset and controller orientations, every single frame, at 90 hz.  The rest of the world will be 1-2 frames behind, but it won't matter because it's not connected to your body.  You'll get smooth head motion with zero delays while at the same time relieving the renderer of all CPU-side bottlenecks.

Even for non-VR games, I believe this design will produce a massive performance boost unlike anything you've ever seen.

Josh

Leadwerks Game Engine 4.4 features an upgrade to the latest version of Newton Dynamics, along with a bunch of new features to enhance physics.

Kinematic Controller
The new kinematic controller is a joint that lets you specify a position, rotation (Euler or quaternion), or a 4x4 matrix to orient the body to.  You can set the maximum linear and angular force the joint may use to orient the entity.  This allows you to create a kinematic controller that only affects position, only affects rotation, or one that controls both at once.  In the video below I am using a kinematic controller to create a simple IK system with two hinge joints.  The end effector is controlled by the mouse position, while the base entity stays in place, since it has zero (infinite) mass:

The kinematic controller provides much more stable collisions than the Entity PhysicsSetPosition() and PhysicsSetRotation() commands, and should be used in place of these.  In fact, these commands will be removed from the documentation and should not be used anymore, although they will be left in the engine to ensure your code continues to work.  The FPS player script will be updated to use a kinematic control for objects you are holding, which will eliminate the energetic collisions the script currently produces if you pick up a crate and push it into the wall.

The new joint commands are as follows:

static Joint* Kinematic(Entity* entity, const Vec3& position);
virtual void SetTargetMatrix(const Mat4& mat);
virtual void SetTargetPosition(const float x, const float y, const float z, const float blend = 0.5);
virtual void SetTargetPosition(const Vec3& pos, const float blend = 0.5);
virtual void SetTargetRotation(const float pitch, const float yaw, const float roll, const float blend = 0.5);
virtual void SetTargetRotation(const Vec3& rotation, const float blend = 0.5);
virtual void SetTargetRotation(const Quat& rotation, const float blend = 0.5);

For improved constistency in the API, the joint SetAngle function will be renamed SetTargetAngle, but a copy of the old command will remain in the engine:

virtual void SetTargetAngle(const float angle);

Joint Friction
Hinge joints can now accept a friction value to make them more resistant to swinging around.  I used this in the example below to make the joints less "loose", while a kinematic controller positions the green box:

New Vehicle Model
Newton 3.14 features a new vehicle model with a realistic simulation of a slip differential.  Power is adjusted to each wheel according to the resistance on each tire.

17634312_10154987760341183_2545546855562571217_n.png.e07fcedc55d8782cb027516383cf8f0f.png

Watch closely as the wheels below act just like a real car does when its tires slip:

The realistic vehicle models gives vehicles a much more visceral and fun feeling.  The new vehicle also uses actual bodies for the tires, instead of convex raycasts, so the sudden bouncing the old vehicles could exhibit if the chassis didn't encompass the tires is eliminated.

Springs

Slider and hinge joints now have optional spring behavior you can enable with one command.  Use this to make our own custom suspension system, or anything else you need.

void SetSpring(const float spring)

These changes will be available next week on the beta branch on Steam.

Josh

A new easy-to-use networking system is coming soon to Leadwerks Game Engine.  Built on the Enet library, Leadwerks networking provides a fast and easy way to quickly set up multiplayer games.  Each computer in the game is either a server or a client.  The server hosts the game and clients can join and leave the game at will.  On the other hand, when the server leaves the game, the game is over!

1491250148_client_server.png.df3eb728a7f045355f151f4ec1e5cc6a.png

Creating a Client

You can soon create a client with one command in Leadwerks:

client = Client:Create()

To connect to a server, you need to know the IP address of that computer:

client:Connect("63.451.789.3")

To get information from the other computer, we simply update the client and retrieve a message:

local message = client:Update()
if message.id == Message.Connected then
  	print("Connected to server")
elseif message.id == Message.Disconnected then
  	print("Disconnected from server")
elseif message.id == Message.Chat then
  	print("New chat message: "..message.stream:ReadString());
end

You can even send messages, consisting of a simple message ID, a string, or a stream.

client:Send(Message.Chat,"Hello, how are you today?")

There are two optional flags you can use to control the way your messages are sent.  If you specify Message.Ordered, your packets will arrive in the order they were sent (they won't necessarily otherwise).  You can use this for updating the position of an object, so that the most recent information is always used.  The Message.Reliable flag should be used for important messages that you don't want to miss.  UDP packets are not guaranteed to ever arrive at their destination, but messages sent with this flag are.  Just don't use it for everything, since it is slower!

When we're ready to leave the game, we can do that just as easily:

client:Disconnect()

A dedicated server does not have anyone playing the game.  The whole computer is used only for processing physics and sending and receiving information.  You can create a dedicated server, but it's better to let your players host their own games.  That way there's always a game to join, and you don't have to buy an extra computer and keep it running all the time.

Creating a Server

Your game should be able to run both as a client and as a server, so any player can host or join a game.  Creating the game server is just as easy.

local server = Server:Create(port)

Once the server is created, you can look up your IP address and ask a friend to join your game.  They would then type the IP address into their game and join.

The server can send and receive messages, too.  Because the server can be connected to multiple clients, it must specify which client to send the message to.  Fortunately, the Message structure contains the Peer we received a message from.  A peer just means "someone else's computer".  If your computer is the client, the server you connect to is a peer.  If your computer is the server, all the other clients are peers:

local message = client:Update()
if message.id == Message.Connected then
  	player2 = message.peer
end

You can use the peer object to send a message back to that computer:

server:Send(peer, Message.Chat, "I am doing just great! Thanks for asking.")

If you want to boot a player out of your game, that's easy too:

server:Disconnect(peer)

The broadcast command can be used to send the same message out to all clients:

server:Broadcast(Message.Chat, "I hope you are all having a great time in my cool chat program!")

Public Games

You can make your game public, allowing anyone else in the world who has the game to play with you.  You specify a name for your game, a description of your server, and call this command to send a message to the Leadwerks server:

server:Publish("SuperChat","My SuperChat Server of Fun")

All client machines, anywhere in the world, can retrieve a list of public games and choose one to join:

for n=0,client:CountServers("SuperChat")-1 do
	local remotegame = client:GetServer(n)
  	print(remotegame.address)
  	print(remotegame.description)
end

This is a lot easier than trying to type in one person's IP address.  For added control, you can even host a games database on your own server, and redirect your game to get information from there.

Josh

After a lot of research and development, Leadwerks GUI is almost ready to release.  The goal with this system was to create an in-game GUI that was customizable, extendable, and could also serve as a windowed GUI for application development in the future.

Widgets

The GUI system is based on the Widget class.  Once a GUI is created on a rendering context you can add widgets to it.  Each widget is a rectangular container with a padding area.  The widgets can be arranged in a hierarchy and their bounds will clip the contents of their child widgets, both for rendering and mouse interaction.

The GUI is automatically rendered onto the screen inside the call to Context:Sync(),

Widget Scripts

Each widget has a Lua script to control rendering and behavior, similar to the way Lua scripts work in our entity system.  The script assigned to a widget controls what type of widget it is, how it looks, and how it interacts with mouse and keyboard input.  A set of widget scripts are provided to create a variety of controls including buttons, checkboxes, text entry boxes, list boxes, text display areas, choice boxes, sliders, and more.

You can create your own widget scripts to add new types of controls, like for an RPG interface or something else.  The script below shows how the tabber widget is implemented.

--Styles
if Style==nil then Style={} end
if Style.Panel==nil then Style.Panel={} end
Style.Panel.Border=1
Style.Panel.Group=2

--Initial values
Script.indent=1
Script.tabsize = iVec2(72,28)
Script.textindent=6
Script.tabradius=5

function Script:Start()	
	self.widget:SetPadding(self.indent,self.indent,self.tabsize.y+self.indent,self.indent)
end

function Script:MouseLeave()
	if self.hovereditem~=nil then
		self.hovereditem = nil
		local scale = self.widget:GetGUI():GetScale()
		local pos = self.widget:GetPosition(true)
		local sz = self.widget:GetSize(true)
		self.widget:GetGUI():Redraw(pos.x,pos.y,sz.width,self.tabsize.y*scale+1)
		--self.widget:Redraw()
	end
end

function Script:Draw(x,y,width,height)
	local gui = self.widget:GetGUI()
	local pos = self.widget:GetPosition(true)
	local sz = self.widget:GetSize(true)
	local scale = self.widget:GetGUI():GetScale()
	local n
	local sel =  self.widget:GetSelectedItem()
	
	--Draw border
	gui:SetColor(0)
	gui:DrawRect(pos.x,pos.y+self.tabsize.y*scale,sz.width,sz.height-self.tabsize.y*scale,1)
	
	--Draw unselected tabs
	for n=0,self.widget:CountItems()-1 do
		if n~=sel then
			self:DrawTab(n)
		end
	end
	
	--Draw selected tab
	if sel>-1 then
		self:DrawTab(sel)
	end
	
	---Panel background
	gui:SetColor(0.25)
	gui:DrawRect(pos.x+1,pos.y+self.tabsize.y*scale+1,sz.width-2,sz.height-self.tabsize.y*scale-2)
end

function Script:DrawTab(n)
	local gui = self.widget:GetGUI()
	local pos = self.widget:GetPosition(true)
	local sz = self.widget:GetSize(true)
	local scale = self.widget:GetGUI():GetScale()
	local s = self.widget:GetItemText(n)
	
	local textoffset=2*scale
	if self.widget:GetSelectedItem()==n then
		textoffset=0
	end
	
	local leftpadding=0
	local rightpadding=0
	if self.widget:GetSelectedItem()==n then
		gui:SetColor(0.25)
		if n>0 then
			leftpadding = scale*1
		end
		rightpadding = scale*1
	else
		gui:SetColor(0.2)
	end
	gui:DrawRect(-leftpadding+pos.x+n*(self.tabsize.x)*scale,textoffset+pos.y,rightpadding+leftpadding+self.tabsize.x*scale+1,self.tabsize.y*scale+self.tabradius*scale+1,0,self.tabradius*scale)
	gui:SetColor(0)
	gui:DrawRect(-leftpadding+pos.x+n*(self.tabsize.x)*scale,textoffset+pos.y,rightpadding+leftpadding+self.tabsize.x*scale+1,self.tabsize.y*scale+self.tabradius*scale+1,1,self.tabradius*scale)
	
	if self.widget:GetSelectedItem()~=n then
		gui:SetColor(0)
		gui:DrawLine(pos.x+n*self.tabsize.x*scale,pos.y+self.tabsize.y*scale,pos.x+n*self.tabsize.x*scale+self.tabsize.x*scale,pos.y+self.tabsize.y*scale)
	end
	if self.hovereditem==n and self.widget:GetSelectedItem()~=n then
		gui:SetColor(1)
	else
		gui:SetColor(0.7)
	end
	gui:DrawText(s,pos.x+(n*self.tabsize.x+self.textindent)*scale,textoffset+pos.y+self.textindent*scale,(self.tabsize.x-self.textindent*2)*scale-2,(self.tabsize.y-self.textindent*2)*scale-1,Text.VCenter+Text.Center)

end

function Script:MouseDown(button,x,y)
	if button==Mouse.Left then
		if self.hovereditem~=self.widget:GetSelectedItem() and self.hovereditem~=nil then
			self.widget.selection=self.hovereditem
			local scale = self.widget:GetGUI():GetScale()
			local pos = self.widget:GetPosition(true)
			local sz = self.widget:GetSize(true)
			self.widget:GetGUI():Redraw(pos.x,pos.y,sz.width,self.tabsize.y*scale+1)
			EventQueue:Emit(Event.WidgetAction,self.widget,self.hovereditem)
		end
	elseif button==Mouse.Right then
		if self.hovereditem~=self.widget:GetSelectedItem() and self.hovereditem~=nil then
			EventQueue:Emit(Event.WidgetMenu,self.widget,self.hovereditem,x,y)		
		end
	end
end

function Script:KeyDown(keycode)
	if keycode==Key.Right or keycode==Key.Down then
		local item = self.widget:GetSelectedItem() + 1
		if item<self.widget:CountItems() then
			self.widget.selection=item
			local scale = self.widget:GetGUI():GetScale()
			local pos = self.widget:GetPosition(true)
			local sz = self.widget:GetSize(true)
			self.widget:GetGUI():Redraw(pos.x,pos.y,sz.width,self.tabsize.y*scale+1)
			EventQueue:Emit(Event.WidgetAction,self.widget,item)
		end
	elseif keycode==Key.Left or keycode==Key.Up then
		local item = self.widget:GetSelectedItem() - 1
		if item>-1 and self.widget:CountItems()>0 then
			self.widget.selection=item
			local scale = self.widget:GetGUI():GetScale()
			local pos = self.widget:GetPosition(true)
			local sz = self.widget:GetSize(true)
			self.widget:GetGUI():Redraw(pos.x,pos.y,sz.width,self.tabsize.y*scale+1)
			EventQueue:Emit(Event.WidgetAction,self.widget,item)
		end
	elseif keycode==Key.Tab then
		local item = self.widget:GetSelectedItem() + 1
		if item>self.widget:CountItems()-1 then
			item=0
		end
		if self.widget:CountItems()>1 then
			self.widget.selection=item
			local scale = self.widget:GetGUI():GetScale()
			local pos = self.widget:GetPosition(true)
			local sz = self.widget:GetSize(true)
			self.widget:GetGUI():Redraw(pos.x,pos.y,sz.width,self.tabsize.y*scale+1)
			EventQueue:Emit(Event.WidgetAction,self.widget,item)
		end		
	end
end

function Script:MouseMove(x,y)
	local prevhovereditem = self.hovereditem
	self.hovereditem = nil
	local scale = self.widget:GetGUI():GetScale()
	local sz = self.widget:GetSize(true)
	if x>=0 and y>=0 and x<sz.width and y<self.tabsize.y*scale then
		local item = math.floor(x / (self.tabsize.x*scale))
		if item>=0 and item<self.widget:CountItems() then
			self.hovereditem=item
		end
	end
	if self.hovereditem==self.widget:GetSelectedItem() and prevhovereditem==nil then
		return
	end
	if self.hovereditem==nil and prevhovereditem==self.widget:GetSelectedItem() then
		return
	end
	if prevhovereditem~=self.hovereditem then
		local pos = self.widget:GetPosition(true)
		local sz = self.widget:GetSize(true)
		self.widget:GetGUI():Redraw(pos.x,pos.y,sz.width,self.tabsize.y*scale+1)
	end
end

Widget Rendering

Widgets are buffered and rendered with an advanced system that draws only the portions of the screen that need to be updated.  The GUI is rendered into a texture, and then the composite image is drawn onscreen.  This means you can have very complex interfaces rendering in real-time game menus with virtually no performance cost.

By default, no images are used to render the UI so you don't have to include any extra files in your project.

Widget Items

Each widget stores a list of items you can add, remove, and edit.  These are useful for list boxes, choice boxes, and other custom widgets.

GUI Events

Leadwerks 4.4 introduces a new concept into your code, the event queue.  This stores a list of events that have occurred.  When you retrieve an event it is removed from the stack:

	while EventQueue:Peek() do
		local event = EventQueue:Wait()
		if event.source == widget then
			print("OK!")
		end
	end

Resolution Independence

Leadwerks GUI is designed to operate at any resolution.  Creation and positioning of widgets uses a coordinate system based on a 1080p monitor, but the GUI can use a global scale to make the interface scale up or down to accommodate any DPI including 4K and 8K monitors.  The image below is rendering the interface at 200% scaling on a 4K monitor.

gui.thumb.png.cdc37a2be840446845db2915d57a754c.png

A default script will be included that you can include from Main.lua to build up a menu system for starting and quitting games, and handling common graphical features and other settings.

Image2.thumb.jpg.86e408079029dc5f8e15be9ee8487159.jpg

Leadwerks GUI will be released in Leadwerks Game Engine 4.4.

Josh

vr_main.jpg.35954ceccc3a0ff84cb31c21c99541bf.jpg

Back around February I started working on a website update that included the following:

  • Responsive design everywhere.
  • SSL everywhere.
  • Visual improvement of website.
  • Updated documentation system.
  • Tutorials for C++ programming basics.
  • Update forum software to new major version.
  • Forum moved to new URL.

All of that is now pretty much done.  These changes improve the online Leadwerks experience and are independent from the software itself, so it was a good idea to get them done now.

Since September I've had more time to think about Leadwerks Game Engine 5, and although I am not completely sold on Vulkan, I think it's a good plan.

Leadwerks 5 is all about performance and user experience with VR as a prime target.

Multithreaded Architecture

Separate threads for navmesh updating, physics, game logic, culling, and rendering.  The rendering thread loops at a constant 60 or 90 (for VR) frames per second regardless of what your game is doing.  This gives your game logic four times more time to run, while independently maintaining a constant framerate.  The design I have in mind will make Leadwerks 5 the fastest game engine, ever, and choosing Leadwerks for VR will be a no-brainer.

Leadwerks Editor 5

A new editor will be written in C++ using Leadwerks GUI, which will give us the same appearance on Windows, Linux, and Mac.  Functionality will be pretty close to the existing editor, but with more room to grow and a few improvements.  Because it's written in C++ parts of the editor can be exposed to Lua, and an editor API will be provided for making Lua mods and plugins.  By default, it will use a dark theme to be easy on the eyes.  A standalone script editor may be provided as well.

PBR Material System with Substance Support

The lighting model will use a more advanced lighting equation and substance PBR materials (metalness and roughness) will be loaded natively.

Shared Pointers

The reference counting system in the Object class will be replaced with C++11 shared pointers.  This gives you the performance of C++ with ease of use like a garbage-collected language.

64-bit

The engine and editor will be released as a 64-bit build only.

Game Templates

More game templates will be provided.  Fortunately we can add these now and updates for Leadwerks 5 will be minimal.

Open-Source Components

Source code to some parts of the engine and editor may be provided on the Leadwerks GitHub account.  For example, I may make a standalone open-source script editor or publish some of the engine classes for the community to play with.

Platforms

Leadwerks 5 will launch on Windows, Linux, and Mac.  The improved compatibility of Leadwerks 5 means we could do crazy things like run the editor on an iPad, but I'm going to stick with what I know sells.

Enterprise Edition

A standalone version that does not use Steam will be sold in bundles to companies that require this.

Pricing

A monthly plan may be introduced at around $5-20 per month.  Pricing for a perpetual license for the standard and pro editions would most likely be the same as now ($99-199), with a discount for early adopters / upgrades.  The enterprise version would probably be about $1000 per seat with a discount for schools.

If you found this blog interesting, please consider using the social media share buttons below to share it.

Admin

An update for version 4.4 beta is now available. The Newton Dynamics library has been updated to the current version. Vehicles are temporarily unavailable, but everything else should work. The Newton DLLs have been moved into external DLLs, which allows the author of Newton to debug his own physics code in Leadwerks from Visual Studio.

 

You can get the update by opting into the beta branch on Steam.