Jump to content

Blogs

Real Bindless Textures

Previously I talked about array textures acting as "bindless" textures, but there is an actual OpenGL extension that allows a shader to access any texture without the stupid texture binding / slot convention that limits OpenGL 4.0 shaders to a minimum of 16 textures. Implemenation was surprisingly easy, although Mac hardware apparently does not support this extension. When combined with the multi-draw commands in OpenGL 4.3, and some other tricks, it is possible to render multiple sets of objects in one single draw call. Below you can see six instances of three different objects, with different materials applied to them, all rendered in one single command, for ultimate performance. This is basically the final goal of the whole crazy architecture I've been working on for over a year. I will test this a bit more, release an update for the new engine beta, and then at that point I think it will be time to move everything over to Vulkan / Metal.

Josh

Josh

Bindless Textures

The clustered forward renderer in Leadwerks 5 / Turbo Game Engine required me to implement a texture array to store all shadow maps in. Since all shadow maps are packed into a single 3D texture, the shader can access all required textures outside of the number of available texture units, which only gives 16 guaranteed slots. I realized I could use this same technique to pack all scene textures into a few arrays and completely eliminate the overhead of binding different textures. In order to do this I had to introduce some restrictions. The max texture size, by default, is 4096x4096. Only two texture formats, RGBA and DXT5, are supported. Other texture formats will be converted to RGBA during loading. If a texture is smaller than 1024x1024, it will still take up a layer in the 1024x1024 texture array. This also makes texture lookups in shaders quite a bit more complicated. Before: fragColor *= texture(texture0,texcoords0); After: fragColor *= texture(textureatlases[materialdefs[materialid].textureslot[0]],vec3(texcoords0,materialdefs[materialid].texturelayer[0])); However, making shaders easy to read is not a priority in my design. Performance is. When you have one overarching design goal these decisions are easy to make. Materials can now access up to 256 textures. Or however many I decide to allow. The real reason for this is it will help support my goal to render the entire scene with all objects in just one or a few passes, thereby completely eliminating all the overhead of the CPU/GPU interaction to reach 100% GPU utilization, for ultra maximum performance, to eliminate VR nausea once and for all. Also, I went out walking yesterday and randomly found this item in a small shop. There's something bigger at work here: Most of my money comes through Steam. Valve invented the technology the HTC Vive is based on. Gabe Newell gave me the Gigabyte Brix mini PC that the new engine architecture was invented on. The new engine makes VR performance 10x faster. If this isn't a clear sign that divine providence is on our side, I don't know what is.

Josh

Josh

Fun with Stateless API

Leadwerks 5 / Turbo makes extensive use of multithreading. Consequently, the API is stateless and more explicit. There is no such thing as a "current" world or context. Instead, you explicitly pass these variables to the appropriate commands. One interesting aspect of this design is code like that below works perfectly fine. See if you can work through it and understand what's going on: int main(int argc, const char *argv[]) { //Create a model ;) auto box = CreateBox(nullptr); //Create the world auto world = CreateWorld(); //Create a camera auto camera = CreateCamera(world); camera->Move(0,0,-5); //Create an instance of the model in the new world auto model = box->Instance(world); //Create a window auto window = CreateWindow(); //Create a rendering context auto context = CreateContext(window); while (not window->Closed()) { if (window->KeyDown(KEY_ESCAPE)) window->Close(); world->Update(); world->Render(context); } return 0; }  

Josh

Josh

Loading Binary and Embedded GLTF files

It turns out GLTF is actually three different file formats. 😫 Textures can be loaded from external files, embedded in a binary .glb file, but they can also be saved in an ASCII GLTF files using base64 encoding. Having three different ways to store textures is not a good design decision, but at least it's better than the disaster called Collada. (Note to Khronos: If your file format specification has more pages than a Tom Clancy novel it probably sucks.) Our GLTF loader now supports files with textures embedded in the file, in both binary (.glb) and embedded formats. We also support the Microsoft DDS extension, and support for the Microsoft LOD extension is on the way. The new editor will be able to save loaded models as GLTF files, and we can pack our own information away in our own file extensions, while keeping the file loadable by other applications. Additionally, the new engine now supports textures loaded from DDS, PNG, JPG, BMP, PCX, PSD, GIF, ICO, TIF, EXR, and HDR formats, as well as Leadwerks TEX files. I'd like to get a new update out soon for the new engine, and then continue working on bug fixes for Leadwerks Game Engine 4.6.

Josh

Josh

More Control On Controllers

The Leadwerks API has standard Boolean functions that detect when the end user has pressed a key. While this is very simple and easy to understand, the issue comes when you wish to support binding of actions. Instead calling functions when a certain key was pressed or held, a better way to detect key events is to assign a key to an action. (e.g: Is the Jump key pressed). In Luawerks, I've written an action script in which returns the window call if the key defined in the user's settings is hit. The downsides is that you had to do this for every action in your game and that this was a system written for Luawerks so it wasn't very portable for non-lua/Leadwerks projects. function Action:MoveForward() if self.suspend==true then return false end local key_forward = GetKeyInt(System:GetProperty("key_forward","w")) return window:KeyDown(key_forward) end For those who don't know, SDL is an Open Source library that comes in handy for things like Window management, input, and such. So over the weekend, I've decided to sit down and create my own Input System in which would be allow for action bindings, portable for any type of project while supporting Game Pads outside of Steam. The first step was to manually poll all inputs and treat them all the same. For the mouse and keyboard, this was just seeing if a key was hit, and registering that event to a map. namespace RInput_KM { void SimulateButton(const Sint32& pKey, bool bDown); const float ButtonDown(const Sint32& pKey); void ShowMouse(); void HideMouse(); void SetMousePosition(const int x, const int y, bool bGlobal = false); void ModMousePosition(const int x, const int y, bool bGlobal = false); void UpdateMousePosition(); void SimulateMouse(const int x, const int y); const int GetMouseX(); const int GetMouseY(); void SetMouseWheelPosition(const int y); const bool OnMouseWheelUp(); const bool OnMouseWheelDown(); // Returns the long to a character. const char* GetButtonName(const Sint32& pButton); // Returns the character to a long const Sint32 GetButtonIndex(const char* pButton); void Enable(); void Disable(); void FlushKeyboard(); void FlushMouse(); void Flush(); } When it comes to buttons, it doesn't matter if the mouse button was pressed or a key stroke. What matters is that a button on the keyboard and mouse combo was pressed. I treat the keyboard and mouse as one controller. You can also "Turn off" the mouse and keyboard if you want. namespace RInput_GamePad { typedef enum { ENUM_GAMEPAD_NULL = -1, ENUM_GAMEPAD_ONE, ENUM_GAMEPAD_TWO, ENUM_GAMEPAD_THREE, ENUM_GAMEPAD_FOUR, ENUM_GAMEPAD_MAX = ENUM_GAMEPAD_FOUR } GamePadIndex; typedef struct { SDL_GameController* controller; const char* pszDeviceName; bool bEnabled; std::map<Uint8, bool> mControllerButtons; } gamepad_t; void Connect(const Sint32& pWhich); void Disconnect(const Sint32& pWhich); gamepad_t GetDeviceFromPort(const GamePadIndex& pPort); // Digital input: void SimulateButton(const Sint32& pWhich, const Uint8& pButton, bool bDown); const float ButtonDown(const Uint8& pButton, const GamePadIndex& iIndex = ENUM_GAMEPAD_ONE); const char* GetButtonName(const Uint8& pButton); const Uint8 GetButtonIndex(const char* pButton); // Analog input: void UpdateAxisMotions(const Sint32& pWhich, const Uint32& pAxis); const Sint16 GetAxisValue(const Sint32& pWhich, const Uint32& pAxis, bool bFlip = false); const float GetAxisFloat(const Sint32& pWhich, const Uint32& pAxis, bool bFlip = false); void Flush(const Sint32& pWhich); void FlushAll(); } Next was the game pads. which were a bit more challenging as you need to consider multiple game pads and the valves of analog inputs. If you were paying attention, You would notice that the ButtonDown functions for both the keyboard+mouse and the game pad are returning floats. While it may be limiting for certain applications, I've created "fake buttons" for events for when the trigger is pressed, or if the left stick is to the left. All input returns a float ranging from 0.0 to 1.0. For digital inputs like buttons, this will be a 1 or 0, but for analog the value depending the range from the resting point to the max will called. So if the left stick is all the way to the left, you'd get 1; half way 0.5. This is much better then dealing with direct Uint32 values. Last was to merge both controllers into one entry point in which we use in our app to register our actions, and check to see if they are being called. namespace RInput { typedef enum { CONTROLLER_KEYBOARDMOUSE, CONTROLLER_GAMEPAD } Controllers_t; void Init(SDL_Window* pWindow); void InitSDL(const void *data); void SetActiveDevice(const Controllers_t& pDevice); Controllers_t GetActiveDevice(); const char* GetActiveDeviceAsString(); const char* GetGamePadDeviceAsString(const int pPort); const Sint8 GetGamePadCount(); void TestEvents(const SDL_Event& pEvent); void PollEvents(); // <- Use this function if you're not using SDL event polling. void Flush(const Controllers_t& pController); void FlushAll(); //==================================================================== typedef struct { Sint32 key; Uint8 button; bool bDown; bool bHit; } action_t; const float GetActionInput(action_t& pButton); void RegisterAction(const std::string& pActionName, Sint32 iKey, Uint8 iButton, bool bConistant); void ModifyAction(const std::string& pActionName, Sint32 iKey, Uint8 iButton); action_t& GetAction(const std::string& pActionName); bool LoadActionsFromFile(const char* pszPath); void UpdateGamePadStickAsMouse(const Sint32& pWhich, const Sint8& pAxis); } Actions can be bind to a with key and a button. bDown is used to check if the action is being held down while bHit is a flag to check if this is something that should be called once per pressed (Like a key to pick up a box or something.) One top of all this, Actions can have their key/button bindings changed via an XML file. Here's an example how to use this with Leadwerks. #include "Leadwerks.h" using namespace Leadwerks; #include "rinput/rinput.h" int main() { Leadwerks::Window* window = Window::Create(); RInput::InitSDL(window->hwnd); Leadwerks::Context* context = Context::Create(window); World* world = World::Create(); Camera* camera = Camera::Create(); camera->SetRotation(35, 0, 0); camera->Move(0, 0, -6); Light* light = DirectionalLight::Create(); light->SetRotation(35, 35, 0); light->SetShadowMode(0); Model* model = Model::Box(); model->SetColor(1.0, 0.0, 0.0); model->SetPosition(0, 0, 0); model->SetShadowMode(0); RInput::RegisterAction("moveforward", KEYBOARD_W, GAMEPAD_BUTTON_LSTICK_UP, true); RInput::RegisterAction("movebackward", KEYBOARD_S, GAMEPAD_BUTTON_LSTICK_DOWN, true); RInput::RegisterAction("moveleft", KEYBOARD_A, GAMEPAD_BUTTON_LSTICK_LEFT, true); RInput::RegisterAction("moveright", KEYBOARD_D, GAMEPAD_BUTTON_LSTICK_RIGHT, true); RInput::RegisterAction("jump", KEYBOARD_SPACE, GAMEPAD_BUTTON_A, false); RInput::LoadActionsFromFile("controller.xml"); while (window->Closed() == false) { RInput::PollEvents(); float x = 0; float z = 0; float u = RInput::GetActionInput(RInput::GetAction("moveforward")); float d = RInput::GetActionInput(RInput::GetAction("movebackward")); float l = RInput::GetActionInput(RInput::GetAction("moveleft")); float r = RInput::GetActionInput(RInput::GetAction("moveright")); if (u != 0) { z += 0.05 + (u /10); } if (d != 0) { z -= 0.05 + (d / 10); } if (l != 0) { x -= 0.05 + (l / 10); } if (r != 0) { x += 0.05 + (r / 10); } model->Move(x, 0, z); x = 0; z = 0; Leadwerks::Time::Update(); world->Update(); world->Render(); context->DrawStats(0, 0, false); context->Sync(false); } return 0; }   The Result:   There are many things I didn't add for the sake of time or I wasn't sure how to implement like supporting action for multiple controllers on the front end. But this does what I was set out to do, and although I haven't tried, I'm confident that with little to no work, this code will work on macOS and Linux. As long as there is SDL2 on the platform, this will work. Now no matter if the project is Leadwerks, Turbo or just an SDL2 project, I now have a portable input system with controller support which you can find here.

reepblue

reepblue

My Return

"My Return" would imply that I've been away for some time. To be honest here, I've been using the Leadwerks editor since before it was an official "game engine". Before you would use it to create your items to then import into another game engine. I've been always interested in creating games, since the late 90's. I remember using a few other older editors whose names I've long forgotten with the sands of time. I have always wanted to create an RPG, as that's my favorite genre of games. So the name of this blog implies that the Demurian empire has long survived and thus, a sort of resurgence of this project might happen. I figure, a blog is a great way to stay motivated and to in a way, focus you on developing a game. Many of us "hobbyists" suffer a disease of not finishing our projects that we started with. You see this in every forum out there, for any of these game engines. There is always a small group of dedicated users of the engine, but the majority of the time you see shells of what once was. Ideas that lay dormant for someone in the future to come across. I just want to thank the developer of this engine, he has done an amazing job and with my return, I imagine that this engine should still continue into the future. The website looks wonderful, and I like the way that everything integrates with Steam. So as I sit here typing, my mind is moving fast with ideas and I figure it's time to go back to the engine and start working on something. Something to brush the dust off, to maybe utilize the assets I've purchased as DLC on Steam. Well, I hope you'll join me on this journey, and let's see what happens in 2019 here.

Sharlenwar

Sharlenwar

Development Days

Some of the Leadwerks Game Engine design was originally developed to run on PC and mobile. In order to supported multiple renderers (OpenGL and OpenGLES) I implemented a system that uses an abstract base class with an API-specific class derived from that: Texture OpenGLTexture All OpenGL code was contained in the OpenGLTexture class. This worked fine, and theoretically it would have allowed us to support multiple renderers within one build, like OpenGL and DirectX. In practice it's a feature that was never used, and created a lot of complicate class hierarchies, with functionality split between the base and derived classes. In the new engine, all rendering code is completely separated in a separate thread, and we have a separate class that is a stripped-down representation of the object the programmer interfaces with: Texture RenderTexture When the programmer calls a command that makes a change to the Texture object, an instruction is added to a queue of commands that is sent to the rendering thread, and their change is also made on that RenderTexture object, although not instantaneously. Right now I am stripping out the derived classes and turning classes that were previously abstract into full classes. It's quite a big job to restructure a complex program like this but it needs to be done. Even when we switch over to Vulkan / Metal I don't see us every supporting multiple APIs within a single build, and I am glad to get rid of this aspect of the engine. I'm also doing the same thing for physics. An Entity object in the main thread can have a PhysicsNode object that lives in the physics thread. However, this does not get created unless there is some physics command performed on the entity, like setting the mass or adding a collision shape. Other stuff I want to change: Get rid of GetClass() / GetClassName() method. Get rid of Object::ModelClass etc. constants. Replace all static class constants with global variables, i.e. WINDOW_FULLSCREEN instead of Window::Fullscreen. It's actually best to declare constants with enum because then they get evaluated as a constant and can be used in array declarations and switch statements. It only needs to be declared once, in the header, but unlike a macro it stays contained within the namespace it is declared in: enum { MAX_PHYSICS_THREADS = 32 }; The next step is to create a usable programming SDK with models, lights, scene loading, scripting, and physics. This will allow beta testers to actually start developing games. The lack of a visual editor is a challenge, but at the same time we are now using more standard file formats like DDS and GLTF, which gives us better consistency with the output of various modeling programs. I'd like to start looking at a Lua debugger for Visual Studio Code soon. There seems to be some debuggers out there already, but I have no idea how the communication between the debugger and the game is supposed to work. I invented my own network data protocol in Leadwerks and there isn't any standard I am aware of. 2D graphics in the new engine are quite different from in Leadwerks, which used drawing commands to control what gets displayed on screen. Since the rendering all occurs asynchronously on another thread, this approach does not make sense at all. I also had a problem with the GUI in this design. The GUI system uses a script with a drawing command to redraw each widget, but we don't want any Lua code running in the rendering thread. The solution is to make 2D graphical elements persistent objects: auto window = CreateWindow(); auto context = CreateContext(window); auto world = CreateWorld(); //Create some 2D graphics auto rect = CreateRect(context,10,10,200,75,true); rect->SetColor(0,0,1); auto line = CreateLine(context,10,10,200,200); line->SetColor(1,0,0); auto text = CreateText(context,"Hello!",0,0,200,75,TEXT_CENTER); text->SetPosition(10,10) text->SetFont(LoadFont("Fonts/arial.ttf",18); while (not window->Closed()) { world->Render(context); } Just like with an entity, you can set the variable to null to stop drawing the element. while (not window->Closed()) { if (window->KeyHit(KEY_SPACE)) rect = nullptr; world->Render(context); } 2D elements can have a hierarchy, so you can create one element that gets drawn on top of another: auto rect = CreateRect(context,10,10,200,75,true); rect->SetColor(0,0,1); auto rect2 = CreateRect(rect,4,4,rect.size.x-8,rect.size.y-8,true); rect2->SetColor(1,1,1); We need the GUI working for some VR projects I want to use the new engine in soon. Once the items above are all working, that will give us everything we need to start working on the new editor.

Josh

Josh

Loading Assets from Streams

Since the GLTF file format can pack textures into a single file with the model, I needed to implement asset loading directly from a stream: auto stream = ReadFile("image.png"); auto tex = LoadTexture(stream); This was interesting because I needed to add a check for each supported image type so the loader can determine the file type from the contents instead of the file path extension. Most file formats include a string or "magic number" at the beginning of the file format to indicate what type of file it is: //BMP check pos = stream->GetPos(); if (stream->GetSize() - pos >= 2) { if (stream->ReadString(2) == "BM") isbmp = true; } stream->Seek(pos); The TGA file format is weird though because it does not have one of these. It just launches straight into a header of information, already assuming the file is a TGA file. So what you have to do is read some of the values and see if they are reasonable. With a little help from the BlitzMax source code, I was able to do this: //TGA check pos = stream->GetPos(); tgahdr hdr; if (stream->GetSize() - pos >= sizeof(hdr)) { const int TGA_NULL = 0; const int TGA_MAP = 1; const int TGA_RGB = 2; const int TGA_MONO = 3; const int TGA_RLEMAP = 9; const int TGA_RLERGB = 10; const int TGA_RLEMONO = 11; const int TGA_COMPMAP = 32; const int TGA_COMPMAP4 = 33; stream->Read(&hdr, sizeof(hdr)); if (hdr.colourmaptype == 0) { if (hdr.imgtype == TGA_MAP or hdr.imgtype == TGA_RGB or hdr.imgtype == TGA_RLERGB) { if (hdr.psize == 15 or hdr.psize == 16 or hdr.psize == 24 or hdr.psize == 32) { if (hdr.width > 0 and hdr.width <= 163284 * 2) { if (hdr.height > 0 and hdr.height <= 163284 * 2) istga = true; } } } } } stream->Seek(pos); In fact the whole idea of having a list of loaders that read the file contents to determine if they are able to load the file is an idea I pulled from the design of BlitzMax. It is strange that so many good tech products have fallen away yet we are growing.

Josh

Josh

GLTF Materials

I'm now able to load materials from GLTF files. These can use external textures or they can use textures packed into a GLTF binary file. Because we have a standardized material specification, this means you can download GLTF files from SketchFab or Turbosquid, and your model materials will automatically be loaded, all the time. There's no more generating materials or messing around trying to figure out which texture is the normal or specular map. An extension exists for DDS texture support, fortunately. Here are the preliminary results.  

Josh

Josh

Back in Action

So, most of December was eaten up on some NASA VR projects. There was a conference last week in Seattle that I attended for a couple of days. Then I had meetings in northern California and Arizona. Unfortunately, I can't really talk much about what I am doing with those. Rest assured I am working on a plan to grow the company so we can provide better products and support for you. I'm taking a hit on productivity now in order to make a bigger plan happen. Today is my first day back home after all that, and I now have time to focus on the software. Thanks for your patience while I get this all sorted out.

Josh

Josh

Luawerks Updated

Luawerks has been updated to 1.2.7, making some small adjustments and fixes to the system. If you have previously purchased Luawerks, this update is available for free on the Leadwerks Marketplace. Please note: Luawerks is not ready for Leadwerks 4.6 and it's recommended to use Luawerks with Leadwerks 4.5. Following changes include: Added an optional Map selection menu. "New Game" will default call a panel in which all the maps in your Maps directory will be listed, Note: This will only work if the maps are not included in your zip as raw Lua functions are called. This will also not work if the Lua sandbox is enabled. Re-located error.mdl to a sub directory "Models/Developer" Added widget.mdl and axis.mdl for assistance for visualizing matrix results. When in-game, "Exit to Menu" will show instead of "Quit." This will release the world and the player will be returned to the main menu. Arrange locations of various functions. The names remain the same. Added various functions needed for this update.   About Luawerks Luawerks is a Lua framework for video games developed on the Leadwerks Game Engine. It supplies developers with additional functions, handles the game loop and allows them to debug their code with a developers console. With all that out of the way, you can focus on making your game fun! You can purchase Luawerks from the Leadwerks Marketplace for $9.99. For documentation and bug reporting, please visit the GitHub page.

reepblue

reepblue

Loading GLTF Files

With an understanding of JSON for Modern C++ (an excellent library) I was able to dive into the new GLTF 2.0 file format and create a model loader. Here's what I've got so far: Although the file format is JSON-based, vertex and indice data is loaded from either a binary .bin file, or packed away at the end of the ASCII file. You can open GLTF files in NotePad and edit them, but GLB files are a mix of ASCII and binary. The model above loads in about 16 milliseconds. I did find some big shortcomings in the file format, however, and I am going to list them here. 1. It's needlessly complicated The way data is stored is ridiculously complex. Accessors, bufferviews, and buffers? Why not just provide an array of values. The designers of this spec must have been on some serious drugs when they came up with this. I understand why they did this, they are trying to make resource reuse as efficient as possible, and allow the file format to dictate how data is stored internally, because they are writing this from the perspective of a graphics API designer. Guess what? NO ONE is going to use it like that. Everyone is just going to load the data into their own mesh structures and send it to the GPU however they want. Allowing the file format to control how data is stored internally in the engine is a level of stupid I can barely conceive of. Why does this matter? Well, if you are trying to get people to adopt your file format you make it as simple as possible. Collada was a disaster, and I personally know one programmer who nope'd out of there after taking one look at the GLTF specification due to bad experiences with Collada. 2. It's not a model file format GLTF is a scenes (plural, we'll get to that later) file format and it's being shoehorned into something else. There is no guarantee of a single top-level node, so my loader has to create a root model to parent multiple nodes to if they are encountered. 3. It's not meant for games GLTF stands for "GL Transmission Format" which sounds like an STD but is meant for serving up 3D scenes in a web browser. Thus, it uses JPEG and PNG files for textures and does not support the most common texture format for games, DDS. It doesn't even support Khronos' own KTX texture format that no one uses. Why does this matter? Widespread support for GLTF is one of the compelling reasons to use it. When you select a GLTF file in Windows 10 Explorer, it shows the model in a 3D preview window. Really nice! But if the model stores a DDS texture, it will fail to load! (It still loads successfully in Microsoft's 3D object viewer included in Windows 10.) I plan on using DDS textures in the new engine, but if all our 3D models won't display in Explorer, why use the format? There's also no support for or concept of material files stored in external files. All materials are packed into the model file. I'm not sure how we are going to handle this. 4. It even sucks at what it's supposed to be The design of the GLTF format is a giant catch-22. Check this out: All model and material data is stored in the file format, and there is no loading of models from external files (like other GLTF files). But it's a scene file format, right? Most games have more than one setting. You don't want to have all your models copied to all your different scene files redundantly. GLTF solves this by including multiple scenes in the file, with a value to indicate the first "map" to load. So all your models, textures, and scenes are getting packed into a single file! Here's the kicker: There is no support for model compression, and this is supposed to be a file format for sending to web browsers. No problem, let's just use some ZIP compression to compress the whole file, right? Ha! Now you have to decompress ONE GIANT FILE containing EVERY MODEL AND SCENE used in your ENTIRE GAME just to load one scene! The concept of having editable asset files and a separate file format for the published game is straight out of 1994. We're not going back to WAD files and we're not going to pack all our game content into one giant uncompressed file. I actually have a soft spot for WAD files. Leadwerks 6? But Waitaminute... Okay, so those criticisms aside, it's still pretty good. We have a fast-loading open-source 3D model format with widespread support. It's designed with the concept of PBR materials built-in. And with extensions, this file format that was not designed for game models is being shoehorned into a standard that is already ahead of FBX. So in the new engine we are going to try to use GLTF as the main model format, not just as an intermediate format we convert models from. There are still some unanswered questions that will be challenging to resolve, but I think this is going to give us the best workflow in the future. Oh, and GLTF destroys OpenGEX based on one simple factor: binary data. You do not want to load vertex arrays from text as it will cause very slow load times. The whole concept of an "exchange format" is pretty dumb. It never works and it isn't what people want. People want an editable game-ready format, and GLTF while not perfect is pretty close to that.

Josh

Josh

Driving car experience with 4.6

That's what I obtained for driving experience with 4.6. For me it's ok, just the distance of the wheels to the cars are growing too much while getting speed. Sounds have to get upgraded too   I add a video that show car driving possibilities in the down-scaled world I want to create with an own scaled character controller. The idea is to obtain with this a bigger world lol So everything is under scaled, the car too. It's not GTA 6 but it's very ok for what I expect because I like when the car you drive is shaking on the road, as too much polished is not realistic enough, as you could not feel the ruggedness of the world you are playing in . I think the cars in gta 5 are definitvly too polished as you cannot reverse them for example, making the game a way too sanitized.

        Progress of this update 02/2019:                

Marcousik

Marcousik

Fun with JSON

I realized there are two main ways a plugin is going to be written, either as a Lua script or as a DLL. So I started experimenting with making a JSON file that holds the plugin info and tells the engine where to load it from: { "plugin": { "title": "Game Analytics", "description": "Add analytics to your game. Visit www.gameanalytics.com to create your free account.", "author": "© Leadwerks Software. All Rights Reserved.", "url": "https://www.turboengine.com", "library": "GameAnalytics.dll" } } { "plugin": { "title": "Flip Normals", "description": "Reverse faces of model in Model Editor.", "author": "© Leadwerks Software. All Rights Reserved.", "url": "https://www.turboengine.com", "scriptfile": "FlipNormals.lua" } } I like this because the plugin info can be loaded and displayed in the editor without actually loading the plugin. I also wanted to try using a JSON file to control script properties. For example, this file "SlidingDoor.json" goes in the same folder as the script and contains all the properties the engine will create when the script is attached to an entity: { "script": { "properties": { "enabled": { "label": "Enabled", "type": "boolean", "value": true, "description": "If disabled the door will not move until it is enabled." }, "distance": { "label": "Distance", "type": "float", "value": [1,0,0], "description": "Distance the door should move, in global space." }, "opensound": { "label": "Open Sound", "type": "sound", "value": null, "description": "Sound to play when door opens." }, "closedelay": { "label": "Close Delay", "type": "integer", "value": 2000, "minvalue": 0, "description": "The number of milliseconds a door will stay open before closing again. Set this to 0 to leave open." } } } } I like that it is absolutely explicit, and it allows for more information than the comments allow in Leadwerks 4. There is actually official tools for validating the data. The json data types map very closely to Lua. However, it is more typing than just quickly writing a line of Lua code. While we're at it, let's take a look at what a JSON-based scene file format might look like: { "scene": { "entities": [ { "type": "Model", "name": "main door", "id": "1c631222-0ec1-11e9-ab14-d663bd873d93", "path": "Models/Doors/door01.gltf", "position": [0,0,0], "euler": [90,0,0], "rotation": [0,0,0,1], "scale": [1,1,1], "matrix": [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]], "mass": 10, "color": [1,1,1,1], "static": false, "scripts": [ { "path": "Scripts/Objects/Doors/SlidingDoor.lua", "properties": { "distance": [1,0,0], "movespeed": 5 } }, { "path": "Scripts/Objects/Effects/Pulse.lua" } ] } ] } }  

Josh

Josh

Plugins Expanded

This month I was working on a lot of NASA projects using Leadwerks. My goal with this is to build up enough business that I can start hiring more people to help. It's nice to make money using my own tools, and it gives me a lot of ideas for the new engine. This morning I felt like experimenting with the design of plugins in the new editor a bit. I came up with two types of plugins. One will add an new menu item to the model editor that flips the normals of the current model. The ProcessEvent() function intercepts a GUI event in the editor and performs new functionality: Plugin.title = "Flip Model Normals" Plugin.description = "Reverses the faces of all triangles in the model editor." function Plugin:Load() local menu = Editor.modelEditor.menu:FindMenu("Plugins") if menu~=nil then self.menuitem = menu:AddItem("Flip Normals") end end function Plugin:Unload() if self.menuitem~=nil then self.menuitem:Free() end end function Plugin:ProcessEvent(event) if event.id = EVENT_MENUACTION then if event.source = self.menuitem then if Editor.modelEditor.model~=nil then self:FlipNormals(Editor.modelEditor.model) Editor.modelEditor:Redraw() return false end end end return true end function Plugin:FlipNormals(model) local n,k,lod,mesh,i,a,b,c for n=0,#model.lods do lod = model.lods[n] for k=0,#lod.meshes do mesh = lod.meshes[k] mesh:Unlock() for i=0,#mesh.indices/3 do a = mesh.indices[i*3+0] b = mesh.indices[i*3+1] c = mesh.indices[i*3+2] mesh.indices[i*3+0] = c mesh.indices[i*3+2] = a end mesh:Lock() end end end Another type of plugin adds some new functionality to your game. This one is different because it loads a function from a dynamically linked library and passes the Lua state to a function: In this case, I decided to try separating analytics off into its own plugin, simply because it is a self-contained system that doesn't interfere with the rest of the engine: Plugin.name = "Example" Plugin.title = "Example Plugin" Plugin.description = "This is an example plugin." function Plugin:GetPath() local ext="" if GetOS()=="Windows" then ext = "dll" elseif GetOS()=="Linux" then ext = "so" elseif GetOS()=="MacOS" ext = "dylib" end return self.name..ext end function Plugin:Load() local f = package.loadlib(self:GetPath(),"Load") if type(f)=="function" then f(GetLuaState()) end end function Plugin:Unload() local f = package.loadlib(self:GetPath(),"Unload") if type(f)=="function" then f() end end The source code for the dynamic library would be something like this: #include "GameAnalytics.h" bool Load(sol::state* L) { L->set_function("EnableAnalytics", EnableAnalytics); } void Unload() { } bool EnableAnalytics(const bool state) { return true; } Once the plugin is loaded, the Lua state would be passed to the DLL (or .so, or .dylib) and the new commands would get inserted into the Lua state. So what does a plugin consist of? A ,lua or precompiled .luac file is required. If the plugin uses C++ code, a .dll, .so, and .dylib are required. Optional source code and project(s) for the dynamically linked library. And what can a plugin do? Add new features to the editor. Add new Lua commands to use in your game. That's all for now.

Josh

Josh

Leadwerks Game Engine 4.6 Beta Update

The beta branch on Steam is now updated. Version 4.6 introduces peer-to-peer multiplayer capabilities with a new multiplayer game template. Check out the new features in the documentation: P2P Lobby Voice The physics library is updated to the latest version of Newton. The editor has some enhancements as well: Model editor view range is calculated from model extents, so if you load a model that is huge it won't be invisible. Model editor displays number of limbs as well as vertices and triangles. Settings file is now saved any time changes are made in the project manager or the options editor. Menu added for Leadwerks Marketplace. Paid Workshop items are no longer accepted. Menu item added for Discord chat. Please test this out and report any errors in the bug reports forum, as this is pretty close to the final version 4.6.

Admin

Admin

Vehicle Development

I've made progress with the new vehicle system and it is shaping up nicely. The vehicle wheels consist of a slider joint with a spring (the strut) connected to a pivot, connected to the wheel by a hinge joint (the axle). If the wheel can be steered, an additional pivot is inserted between the strut and axle, with a motorized hinge to control steering. There were two big problems in addition to this that need to be solved in order to make a vehicle that is stable at high speeds. First, the mass matrix of the tires needs to be spherical. The mass matrix is the distribution of mass across an object. A brick and a 2x4 piece of lumber probably have about the same mass, but have a different mass matrix. Therefore the brick should spin more easily than the lumber. If you don't make the mass matrix for the tires spherical you will get bad wobbling at high speeds, like this video shows: When the mass matrix is fixed this problem goes away. The vehicle gets up to 90 MPH, and although there are other issues, there is no tire wobble. The other issue that needs to be solved can be seen in the video above. At high speeds the tire collisions become inaccurate and the vehicle bounces a lot. We need to replace the default collision with a volume raycast coming from the top position the wheel can sit on the shock, going down to the extended position of the strut. This is the part I haven't done yet, but I know it can be done. I think the new vehicle system will offer a lot of flexibility and possibilities for future features since it is mostly made with the standard physics features.

Josh

Josh

Vehicle WIP in Leadwerks 4.6

Here's a look at the new vehicle system that is being developed. The API has been simplified so you simply create a vehicle, add as many tires as you want, and start using it. The AddTire() command now accepts a model and the dimensions of the tire are calculated from the bounds of the mesh. class Vehicle { int AddTire(Model* model, bool steering=false, const float spring=500.0f, const float springrelaxation = 0.1f, const float springdamping = 10.0f); void SetGas(const float accel); void SetBrakes(const float brakes); void SetSteering(const float angle); static Vehicle* Create(Entity* entity); }; A script will be provided which turns any vehicle model into a ready-to-use playable vehicle. The script searches for limb names that start with "wheel" or "tire" and turns those limbs into wheels. If they are positioned towards the front of the car, the script assumes the wheels can be turned with steering. You can also reverse the orientation of the vehicle if the model was designed backwards. There is one big issue to solve still. When a vehicle drives on flat terrain the tires catch on the edges of the terrain triangles and the whole vehicle bounces around badly. I'm looking into how this can be solved with custom collision overrides. I do not know how long this will take, so it might or might not be ready by Christmas.

Josh

Josh

Peer-to-peer Networking in Leadwerks Game Engine 4.6

I'm wrapping up the new multiplayer capabilities for Leadwerks 4.6, and I am very excited about what this offers developers. We saw previously how the lobby system is used to create or join games, and how to retrieve Steam IDs for messaging. Once we have joined a game we can start sending messages with the P2P::Send command, which includes a few overloads: static bool P2P::Send(uint64 steamid, const int messageid, const int channel = 0, const int flags = 0); static bool P2P::Send(uint64 steamid, const int messageid, std::string& data, const int channel = 0, const int flags = 0); static bool P2P::Send(uint64 steamid, const int messageid, Bank* data, const int channel = 0, const int flags = 0); static bool P2P::Send(uint64 steamid, const int messageid, Stream* data, const int channel = 0, const int flags = 0); static bool P2P::Send(uint64 steamid, const int messageid, const void* data, const int size, const int channel = 0, const int flags = 0); Each message has an ID and can be followed by additional data in the form of a string, bank, or stream. Voice chat is handled automatically, but the rest is up to you to decide what data the messages should contain. I provide examples for text chat, joining and leaving a game, and movement. Receiving messages from other players is extremely simple: static Message* P2P::Receive(const int channel = 0); The message object has information contained within it: class Message { public: int id; Stream* stream; uint64 userid }; We can evaluate messages based on their ID. For example, here is a text chat message being received: auto message = P2P::Receive() if (message) { if (message->id == MESSAGE_CHAT && message->stream != nullptr) { Print(message->stream->ReadString()); } } A new multiplayer game template will be included, with out-of-the-box support for text and voice chat, public servers, and player movement. You can download the test app and try it out here: Thanks to everyone who has helped me test this!

Josh

Josh

Discord Server is Back!

After using it for a few weeks, I previously deleted our Discord server for the following reasons: Community activity was being taken from our site to Discord. They weren't really providing anything of value. There have been some problems with our Cometchat integration lately. I have noticed new messages are not popping up like they should, and selecting the link to view a user's profile does not work. So I set to fixing these issues. Upgrading CometChat to the latest version my account has access to did not solve these issues, and it introduced a new problem where the chat bar was not being hidden when the user logs out. The company has changed their pricing to a much more expensive subscription plan. With minimum features I would be paying $50 monthly, and I still don't know if these issues are fixed in the latest version. More expensive versions also support audio and video chat, but in the last when I have tried these they have always been dodgy. In order to add support for these additional features, which I don't know have improved, it would cost $99/mo. total. At this point I decided that Discord is in fact providing a valuable service for us and I am going to rely on it from now on. Our built-in chat system is now disabled and I encourage you to join me on Discord. This time it is here to stay: https://discord.gg/ukkSVnF

Josh

Josh

Introducing Voice Chat in Leadwerks 4.6

The next update will include a new networking system with built-in voice chat for multiplayer games. Voice communication is built into your game and can be enabled simply by turning it on when a specific key is pressed: void Voice::SetRecording(window->KeyDown(Key::T)) You can selectively filter out users so your voice only gets sent to your own team or to a specific player: void Voice::SetFilter(const uint64 steamid, const bool state) When another player sends their voice data to you, the engine will automatically receive and play the sound. You can also retrieve a sound source for a specific player and make adjustments to it. This can be used to enable 3D spatialization and position the sound source where the player is, for VR voice chat. Source* Voice::GetPlayerSource(const uint64_t steamid) When I first implemented this the sound was sometimes choppy. I added some automatic adjustment of the pitch to slow down the sound a bit if new data is not received yet, and to speed it up if it falls too far behind. This seems to work really well and I'm sure it must be a common technique in applications like Twitch and Skype. A new multiplayer game template will be provided that shows how to set up the framework for a multiplayer game. Here's a video preview of the voice communication in action.  

Josh

Josh

Introducing the Lobby System in Leadwerks 4.6

The new Lobby system in Leadwerks 4.6 allows you to create a public listing of your multiplayer game for others to join. This uses the Steamworks library. You can tap into the Steamworks lib by piggybacking on the default "SpaceWar" example game, which uses the Steam app ID 480. This is set up by default when you create a new Leadwerks project. Now you might think of a lobby as a place where people hang out and chat before the game starts. You can treat it like this, but it's best to keep the lobby up as the game is running. This will allow other people to find the game to join, if it isn't full or if someone leaves the game. So a lobby is better described as a publicly advertised game others can join. Creating a new lobby to advertise our game is easy. We just call a command to create it, and then we will set two string values. The game title is set so that we can later retrieve only lobbies that are running this particular game. (This should be done if you are using the SpaceWar app ID instead of your own Steam ID.) We also add a short description that can display information about the game. Lobby* mylobby = Lobby::Create(); mylobby->SetKey("game", "MyGameTitle"); mylobby->SetKey("description", "Here is my lobby!"); It's also easy to retrieve a list of lobbies: int count = Lobby::Count(); for (int i = 0; i < count; ++i) { Lobby* lobby = Lobby::Get(i); } We can use GetKey() to look for lobbies that are running the same game we are: if (lobby->GetKey("game") == "MyGameTitle") We can retrieve the owner of the lobby with this command that will return a Steam ID: uint64 steamid = lobby->GetOwner(); Once you have that Steam ID, that is all you need to start sending messages through the new P2P networking system. More on that later. And we can also retrieve a list of all members in the lobby: int count = lobby->CountMembers(); for (int k = 0; k < count; ++k) { uint64 steamid = lobby->GetMember(k); } After a lobby is created, it will have one member, which will be the same Steam ID as the owner. Joining a lobby is simple enough: if (lobby->Join()) System::Print("Joined lobby!"); And leaving is just as easy: lobby->Leave() Now here's the magical part: When the owner of the lobby leaves, the lobby is not destroyed. Instead, the system will automatically choose another player to become the owner of that lobby, so the multiplayer game can keep going! The lobby will not be destroyed until everyone leaves the game.

Josh

Josh

×