Jump to content

Josh's Dev Blog

Entries in this blog

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

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

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

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

Digital Reorganization

It's nice to have my big monster computer back, and everything is just the same as I left it. I have a triple-boot machine with Windows 10 and both 32 and 64 bit versions of Ubuntu 16.04. This is easier than trying to set up multi-arch compiling on one install of the OS, though I look forward to the day I no longer have to bother with it. I'm running out of hard drive space on my Windows 10 500 GB SSD so I ordered a 1 TB SSD, and I plan to transfer the 500 GB SSD into my laptop to replace the small SSD it came with. The laptop also contains a terabyte HDD, which was supposed to function as an add-on storage drive, but I am going to install Ubuntu on that. Then I will be able to develop for Windows and Linux on the go, wherever I am. The only thing that would be better than this would be a triple-boot Macbook Pro (maybe someday). Actually, that wouldn't be better. A 17" screen is something I can actually work on, and a 15" screen is no good for programming. So this is the best there is. A 3 TB external HD is being used to back up the Amazon S3 bucket (nearly 60 GB now!), and I copied the entire contents of the 32-bit Ubuntu install onto it as a backup. There was 20 or so "special files" the system could not copy so I don't know if it a true backup, but I think I will be okay. I am attempting to do the same with the 64-bit install files running from the 32-bit OS, but so far I keep getting errors during the copy process. I know you can make an image of the disk, but the disk image is the full size of the hard drive, not just the files it contained, and frankly since it is Linux I expect something else will break in the restore process, so I am not worrying about that. I'm also porting our SVN repositories over to a new service that offers more storage space, although it is quite a lot more expensive. My local Leadwerks working copy has been reverted back to the exact version that was used to release the last build. I'm going to go through manually and re-insert the changes I want from the last six months, because most of the work done was on the new engine. I think there will be less mistakes if I do it this way, since the source got chopped up a lot with #ifdef statements before I finally broke Turbo off from Leadwerks and put it in its own repo. The next Leadwerks update will include bug fixes, a new vehicles system, and peer-to-peer networking through Steam.

Josh

Josh

Discord Server Deleted

Offloading community activity onto Discord does not benefit our community, nor does it further my goal of responsibly obtaining a Lamborghini Huracan (white with orangish leather interior, MSRP $199,000). Therefore I have deleted our Discord server. Please post on our forum here, and if I can do anything to improve the community experience, please let me know.

Josh

Josh

Basic Scene Benchmarks

I did some work optimizing the new renderer to discrete cards and came up with a new benchmark. This scene is interesting because there is not much in the scene, so the culling does not benefit much xrom multithreading, since there is so little work to do. The screen is filled with a spotlight in the foreground, and in the background a point light is visible in the distance. Both engines are running at 1280x720 resolution. This is really a pure test of deferred vs. forward lighting. AMD R9 200 Leadwerks: 448 FPS
Turbo: 648 FPS (44% faster) On this older AMD card we see that forward rendering automatically gives a significant boost in performance, likely due to not having to pass around a lot of big textures that are being written to. GEForce 1070M (Laptop) Leadwerks 4: 120 FPS
Turbo: 400 FPS (333% faster) Here we see an even bigger performance boost. However, these are two very different cards and I don't think it's safe to say yet if one GPU vendor will get more from the new engine. Intel Graphics 4000 Leadwerks 4: 67 FPS  (18% faster)
Turbo: 57 FPS This is an interesting result because integrated graphics tend to struggle with pixel fillrate, but seem to have no problems with texture bandwidth since they are using regular old CPU memory for everything. Although I think the new engine will run faster on Intel graphics (especially newer CPUs) when the scene is loaded down, it seems to run a bit slower in a nearly empty scene. I think this demonstrates that if you build a renderer specifically to take advantage of one type of hardware (discrete GPUs) it will not automatically be optimal on other types of hardware with different architectures. I commented out the lighting code and the framerate shot way up, so it definitely does seem to be a function of the pixel shader, which is already about as optimized as can be. Overall, I think the new engine will show higher relative performance the more you throw at it, but an empty scene may run a bit slower on older Intel chips since the new renderer is optimized very specifically for discrete GPUs. The parallel capabilities of discrete GPUs are relied on to handle complex lighting in a single pass, while texture bandwidth is reduced to a minimum. This is in line with the hardware trend of computing power increasing faster than memory bandwidth. This also shows that low to mid-range discrete GPUs stand to make the most gains.

Josh

Josh

Sample FPS Player Script in New Engine

Here is a script for a first-person player. All of this is actually working now. It's very interesting that physics, rendering, culling, and Lua game code are all executing at the same time on different threads. CPU usage in a simple scene and some physics is about 35% on a quad core processor, which is good. I think the most interesting thing here is that to break the kinematic joint used to control an object the player picks up, you simply set the variable to nil. However, I did run into one problem with the garbage collector. If you do not call collectgarbage() after setting the joint variable to nil, the joint will not get deleted, and it will still affect the entity. This is not good. I got around this by placing a call to collectgarbage() immediately after getting rid of the joint, but I don't think I like that.  I could just make the engine call the Lua garbage collector once before the world update and once before the world render. Originally, I thought Lua would be a big problem for VR applications but since the Lua game code gets its own thread, with a maximum execution time of about 16 milliseconds just for your game code, that is plenty of time to be wasteful with the GC, and I think we will be just fine even with high-performance VR games! Remember, the rendering thread runs at 90 hz but the game logic thread only runs at 60 or even 30 hz, so it's no big deal. However, I am leaning towards adding a Destroy() function that frees everything it can from an object without actually deleting it. Also note that window and context are global variables. There is no "current" window or context, so this code expect that these have been created in Lua or C++ and set as global variables. Script.lookspeed=0.1--float Script.movespeed=3--float Script.maxcarrymass=10--float Script.throwforce=100--float function Script:Start() --Player physics if self.mass==0 then self.mass=70 end self:EnablePhysics(PHYSICS_PLAYER) self.collisiontype=COLLISION_PLAYER --Set up camera self.camera=CreateCamera(self:GetWorld()) local cx=Round(context:GetWidth()/2) local cy=Round(context:GetHeight()/2) window:HideMouse() window:SetMousePosition(cx,cy) self.camerarotation=self:GetRotation(true) end function Script:Update() --Mouse look local cx = Round(context:GetWidth()/2) local cy = Round(context:GetHeight()/2) local mousepos = window:GetMousePosition() window:SetMousePosition(cx,cy) local mx = mousepos.x - cx local my = mousepos.y - cy self.camerarotation.x = self.camerarotation.x + my * self.lookspeed self.camerarotation.y = self.camerarotation.y + mx * self.lookspeed self.camerarotation.x = Clamp(self.camerarotation.x,-90,90) self.camera:SetRotation(self.camerarotation,true) --Player movement local jump=0 if window:KeyDown(KEY_SPACE) then if self:GetAirborne()==false then jump=6 end end if self:GetAirborne() then jump = 0 end local move=0 if window:KeyDown(KEY_W) then move=move+1 end if window:KeyDown(KEY_S) then move=move-1 end local strafe=0 if window:KeyDown(KEY_D) then strafe=strafe+1 end if window:KeyDown(KEY_A) then strafe=strafe-1 end self:SetPlayerInput(self.camerarotation.y, move*self.movespeed, strafe*self.movespeed, jump, false,0,0,true,0) self.camera:SetPosition(self:GetPosition(true),true) self.camera:Translate(0,1.7,0,true) --Update carried object if self.carryjoint~=nil then --Update kinematic joint local position=TransformPoint(self.carryposition,self.camera,nil) local rotation=TransformRotation(self.carryrotation,self.camera,nil) self.carryjoint:SetTarget(position,rotation) --Throw if window:MouseHit(MOUSE_LEFT) then self.carryjoint.child:AddForce(TransformNormal(0,0,1,self.camera,nil)*self.throwforce) self.carryjoint.child:EnableGravity(true) self.carryjoint=nil collectgarbage() end end --Interact with environment if window:KeyHit(KEY_E) then if self.carryjoint~=nil then --Drop carried object self.carryjoint.child:EnableGravity(true) self.carryjoint=nil collectgarbage() else --Find new object to interact with local pickinfo = PickInfo() if self.camera:Pick(0,0,10,pickinfo,0,true,0) then local entity=pickinfo.entity if entity.Activate~=nil then --Activate the object entity:Activate(self) else --Pick it up if its light enough if entity.mass>0 and entity.mass<=self.maxcarrymass then if self.carryjoint~=nil then --Drop carried object self.carryjoint=nil collectgarbage() end local p=entity:GetPosition(true) entity:EnableGravity(false) self.carryjoint=CreateKinematicJoint(p.x,p.y,p.z,entity) self.carryposition=TransformPoint(entity:GetPosition(true),nil,self.camera) self.carryrotation=TransformRotation(entity:GetQuaternion(true),nil,self.camera) end end end end end end  

Josh

Josh

Sample Sliding Door Script in Turbo / Leadwerks 5

I've successfully converter the sliding door script over to the new API. This will all look very familiar, but there are some cool points to note. The entity mass can be retrieved and set with a property as if it was just a regular variable. Underneath the hood, the engine is making method calls. I decided to prefix most field names with "slidingdoor_" to prevent other scripts from accidentally interfering. The "enabled" value, however, is meant to be shared. The Entity is the script object. No more "entity.script.entity.script..." stuff to worry about. All of this actually works in the new engine. Entity.enabled=true--bool "Enabled" Entity.slidingdoor_openstate=false--bool "Start Open" Entity.slidingdoor_distance=Vec3(1,0,0)--Vec3 "Distance" Entity.slidingdoor_movespeed=1--float "Move speed" 0,100,3 Entity.slidingdoor_opensoundfile=""--path "Open Sound" "Wav File (*wav):wav|Sound" Entity.slidingdoor_closesoundfile=""--path "Close Sound" "Wav File (*wav):wav|Sound" Entity.slidingdoor_loopsoundfile=""--path "Loop Sound" "Wav File (*wav):wav|Sound" Entity.slidingdoor_manualactivation=false--bool "Manual activate" Entity.slidingdoor_closedelay=2000--int "Close delay" function Entity:Start() self:EnableGravity(false) if self.mass==0 then self.mass=10 end --In Leadwerks 4: --if self.entity:GetMass()==0 then self.entity:SetMass(10) end -- :) self.slidingdoor_sound={} self.slidingdoor_sound.open = LoadSound(self.slidingdoor_opensoundfile) self.slidingdoor_sound.loop = LoadSound(self.slidingdoor_loopsoundfile) self.slidingdoor_sound.close = LoadSound(self.slidingdoor_closesoundfile) if self.slidingdoor_sound.loop~=nil then self.slidingdoor_loopsource = CreateSource() self.slidingdoor_loopsource:SetSound(self.slidingdoor_sound.loop) self.slidingdoor_loopsource:SetLoopMode(true) self.slidingdoor_loopsource:SetRange(50) end --if self.slidingdoor_manualactivation==false then self.Use=nil end self.slidingdoor_opentime=0 --Create a motorized slider joint local position = self:GetPosition(true) --You could also do this: --local position = self.mat[3].xyz local pin=self.slidingdoor_distance:Normalize() self.slidingdoor_joint=CreateSliderJoint(position.x,position.y,position.z,pin.x,pin.y,pin.z,self) if self.openstate then self.slidingdoor_openangle=0 self.slidingdoor_closedangle=self.slidingdoor_distance:Length() else self.slidingdoor_openangle=self.slidingdoor_distance:Length() self.slidingdoor_closedangle=0 end self.slidingdoor_joint:EnableMotor(true) self.slidingdoor_joint:SetMotorSpeed(self.slidingdoor_movespeed) end function Entity:Use() self:Open() end function Entity:Toggle() if self.enabled then if self.slidingdoor_openstate then self:Close() else self:Open() end end end function Entity:Open() if self.enabled then self.slidingdoor_opentime = CurrentTime() if self.slidingdoor_openstate==false then if self.slidingdoor_sound.open then self:EmitSound(self.slidingdoor_sound.open) end self.slidingdoor_joint:SetTarget(self.slidingdoor_openangle) self.slidingdoor_openstate=true if self.slidingdoor_loopsource~=nil then self.slidingdoor_loopsource:SetPosition(self:GetPosition(true)) if self.slidingdoor_loopsource:GetState()==SOURCE_STOPPED then self.slidingdoor_loopsource:Play() end end end end end function Entity:Close() if self.enabled then if self.slidingdoor_openstate then if self.slidingdoor_sound.close then self:EmitSound(self.slidingdoor_sound.close) end self.slidingdoor_joint:SetTarget(self.slidingdoor_closedangle) self.slidingdoor_openstate=false if self.slidingdoor_loopsource~=nil then self.slidingdoor_loopsource:SetPosition(self:GetPosition(true)) if self.slidingdoor_loopsource:GetState()==0 then self.slidingdoor_loopsource:Play() end end end end end function Entity:Disable() self.enabled=false end function Entity:Enable() self.enabled=true end function Entity:Update() --Disable loop sound if self.slidingdoor_sound.loop~=nil then local angle if self.slidingdoor_openstate then angle = self.slidingdoor_openangle else angle = self.slidingdoor_closedangle end if math.abs(self.slidingdoor_joint:GetAngle()-angle)<0.1 then if self.slidingdoor_loopsource:GetState()~=SOURCE_STOPPED then self.slidingdoor_loopsource:Stop() end else if self.slidingdoor_loopsource:GetState()==SOURCE_STOPPED then self.slidingdoor_loopsource:Resume() end end if self.slidingdoor_loopsource:GetState()==SOURCE_PLAYING then self.slidingdoor_loopsource:SetPosition(self:GetPosition(true)) end end --Automatically close the door after a delay if self.slidingdoor_closedelay>0 then if self.slidingdoor_openstate then if CurrentTime()-self.slidingdoor_opentime>self.slidingdoor_closedelay then self:Close() end end end end  

Josh

Josh

Leadwerks 4.6 Development

With Christmas approaching I am now turning my attention to finishing Leadwerks Game Engine 4.6. The major features planned are peer-to-peer networking and a new vehicles system, as well as miscellaneous bug fixes. A beta build will be made available early on Steam for testing.

Josh

Josh

Turbo Game Engine Beta Update

A big update for the beta of the upcoming Turbo Game Engine is now available, adding support for VR and Lua script! VR Rendering Turbo Game Engine now supports VR rendering, with support for true single-pass stereoscopic rendering on Nvidia GPUs. Other hardware will use a double-rendering path that is still faster than Leadwerks. To turn VR on simply call EnableVR(). Controllers are not yet supported, just rendering. Lua Scripting Lua script is now supported in Turbo! The amount of classes and functions is limited, but the foundation for full Lua support is in. Here are some of the great features in the new system. No Script Object All scripts operate on the entity itself. Instead of this: function Script:Update() self.entity:Turn(1,0,0) end You type this: function Object:Update() self:Turn(1,0,0) end This is especially nice when it comes to functions that retrieve another entity since you don't have to type "entity.script" to get Lua values and functions: function Object:Collision(entity,position,normal,speed) entity:Kill() end Smart Pointers Lua garbage collection now works together with C++11 smart pointers. You will never have to deal with invalid pointers or object deletion again. There is no Release() anymore. Just set your variable to nil to delete an object: local box = CreateBox(world) box = nil If you want the object to be collected immediately, you can force a GC step like this: local box = CreateBox(world) box = nil collectgarbage() Best of all, because Lua runs on the game logic thread separate from the rendering thread, it's perfectly fine to use Lua high-performance applications, even in VR. A pause in the game execution for Lua garbage collection will not pause the rendering thread. Multiple Scripts You can add any number of scripts to an object with the AddScript command: entity->AddScript("Scripts/Object/test.lua") Scripts on Any Object (Experimental) Now all object types can have scripts, not just entities: material->AddScript("Scripts/Object/Appearance/Pulse.lua") Set and Get Script Values in C++ You can easily set and get values on any object, whether or not it has had a script added to it: entity->SetValue("health",100); Print(entity->GetNumberValue("health")); Vector Swizzle Using getters and setters I was able to implement vector swizzles. If you write shaders with GLSL you will be familiar with this convenient  feature: local a = Vec3(1,2,3) local b = a.xz --equivalent to Vec2(a.x,a.z) In Lua you can now return any combination of vector elements, using the XYZW or RGBA names. The code below will swap the red and blue elements of a color: local color = Vec4(1,0,0.5,1) local color = color.bgra Not only can you retrieve a value, but you can assign values using the swizzle: local v = Vec3(1,2,3) v.zy = Vec2(1,2) Print(v) --prints 1,2,1 Note there are presently only two engine script hooks that get called, Start() and Update(). Simpler Uber Shaders I decided to do away with the complicated #ifdef macros in the shaders and use if statements within a single shader: //Diffuse Texture if (texturebound[0]) { color *= texture(texture0,texcoords0); } This makes internal shader management MUCH simpler because I don't have to load 64 variations of each shader. I am not sure yet, but I suspect modern GPUs will handle the branching logic with no performance penalty. It also means you can do things like have a material with just a color and normal map, with no need for a diffuse map. The shader will just adjust to whatever textures are present. A C++ Turbo program now looks like this: #include "Turbo.h" using namespace Turbo; int main(int argc, const char *argv[]) { //Create a window auto window = CreateWindow("MyGame", 0, 0, 1280, 720); //Create a rendering context auto context = CreateContext(window); //Create the world auto world = CreateWorld(); //This only affects reflections at this time world->SetSkybox("Models/Damaged Helmet/papermill.tex"); shared_ptr<Camera> camera; auto scene = LoadScene(world, "Maps/start.map"); //Create a camera if one was not found if (camera == nullptr) { camera = CreateCamera(world); camera->Move(0, 1, -2); } //Set background color camera->SetClearColor(0.15); //Enable camera free look and hide mouse camera->SetFreeLookMode(true); window->HideMouse(); while (window->KeyHit(KEY_ESCAPE) == false and window->Closed() == false) { //Camera movement if (window->KeyDown(KEY_A)) camera->Move(-0.1, 0, 0); if (window->KeyDown(KEY_D)) camera->Move(0.1, 0, 0); if (window->KeyDown(KEY_W)) camera->Move(0, 0, 0.1); if (window->KeyDown(KEY_S)) camera->Move(0, 0, -0.1); //Update the world world->Update(); //Render the world world->Render(context); } return 0; } If you would like to try out the new engine and give feedback during development, you can get access now for just $5 a month. I am now going to turn my attention to Leadwerks 4.6 and getting that ready for the Christmas season. The next step in Turbo development will probably be physics, because once that is working we will have a usable game engine.

Josh

Josh

Using Multiple Entity Scripts in Turbo Game Engine

During development of Leadwerks Game Engine, there was some debate on whether we should allow multiple scripts per entity or just associate a single script with an entity. My first iteration of the scripting system actually used multiple scripts, but after using it to develop the Darkness Awaits example I saw a lot of problems with this. Each script used a different classname to store its variables and functions in, so you ended up with code like this: function Script:HurtEnemy(amount) if self.enemy ~= nil then if self.enemy.healthmanager ~= nil then if type(self.enemy.healthmanager.TakeDamage)=="function" then self.enemy.healthmanager.TakeDamage(amount) end end end end I felt this hurt script interoperability because you had to have a bunch of prefixes like healthmanager, ammomanager, etc. I settled on using a single script, which I still feel was the better choice between these two options: function Script:HurtEnemy(amount) if self.enemy ~= nil then if type(self.enemy.TakeDamage)=="function" then self.enemy.TakeDamage(amount) end end end Scripting in Turbo Game Engine is a bit different. First of all, all values and functions are attached to the entity itself, so there is no "script" table. When you access the "self" variable in a script function you are using the entity object itself. Here is a simple script that makes an entity spin around its Y axis: function Entity:Update() self:Turn(0,0.1,0) end Through some magic that is only possible due to the extreme flexibility of Lua, I have managed to devise a system for multiple script attachments that makes sense. There is no "component" or "script" objects itself, adding a script to an entity just executes some code that attached values and functions to an entity. Adding a script to an entity can be done in C++ as follows: model->AttachScript("Scripts/Objects/spin.lua"); Or in Lua itself: model:AttachScript("Scripts/Objects/spin.lua"); Note there is no concept of "removing" a script, because a script just executes a bit of code that adds values and functions to the entity. Let's say we have two scripts named "makeHealth100 and "makeHealth75". MakeHealth100.lua Entity.health=100 MakeHealth75.lua Entity.health=75 Now if you were to run the code below, which attaches the two scripts, the health value would first be set to 100, and then the second script would set the same value to 75, resulting in the number 75 being printed out: model->AttachScript("Scripts/Objects/MakeHealth100.lua"); model->AttachScript("Scripts/Objects/MakeHealth75.lua"); Print(entity->GetNumber("health")); Simple enough, right? The key point here is that with multiple scripts, variables are shared between scripts. If one scripts sets a variable to a value that conflicts with another script, the two scripts won't work as expected. However, it also means that two scripts can easily share values to work together and create new functionality, like this health regeneration script that could be added to work with any other scripts that treat the value "health" as a number. HealthRegen.lua Entity.healthregendelay = 1000 function Entity:Start() self.healthregenupdatetime = CurrentTime() end function Entity:Update() if self.health > 0 then if CurrentTime() - self.healthregenupdatetime > self.healthregendelay then self.health = self.health + 1 self.health = Min(self.health,100) end end end What about functions? Won't adding a script to an entity overwrite any functions it already has attached to it? If I treated functions the same way, then each entity could only have one function for each name, and there would be very little point in having multiple scripts! That's why I implemented a special system that copies any added functions into an internal table. If two functions with the same name are declared in two different scripts, they will both be copied into an internal table and executed. For example, you can add both scripts below to an entity to make it both spin and make the color pulse: Spin.lua function Entity:Update() self:Turn(0,0.1,0) end Pulse.lua function Entity:Update() local i = Sin(CurrentTime()) * 0.5 + 0.5 self:SetColor(i,i,i) end When the engine calls the Update() function, both copies of the function will be called, in the order they were added. But wait, there's more. The engine will add each function into an internal table, but it also creates a dummy function that iterates through the table and executes each copy of the function. This means when you call functions in Lua, the same multi-execution feature will be available. Let's consider a theoretical bullet script that causes damage when the bullet collides with something: function Entity:Collision(entity,position,normal,speed) if type(entity.TakeDamage) == "function" then entity:TakeDamage(20) end end If you have two (or more) different TakeDamage functions on different scripts attached to that entity, all of them would get called, in order. What if a function returns a value, like below?: function Entity:Update() if self.target ~= nil then if self.target:GetHealth() <= 0 then self.target = nil --stop chasing if dead end end end If multiple functions are attached that return values, then all the return values are returned. To grab multiple returned values, you can set up multiple variables like this: function foo() return 1,2,3 end a, b, c = foo() print(a) --1 print(b) --2 print(c) --3 But a more practical usage would be to create a table from the returned values like so: function foo() return 1,2,3 end t = { foo() } print(t[1]) --1 print(t[2]) --2 print(t[3]) --3 How could this be used? Let's say you had a script that was used to visually debug AI scripts. It did this by checking to see what an entity's target enemy was, by calling a GetTarget() function, and then creating a sprite and aligning it to make a line going from the AI entity to its target it was attacking: function Entity:UpdateDisplay() local target = self:GetTarget() self.sprite = CreateSprite() local p1 = self.entity:GetPosition() local p2 = target:GetPosition() self.sprite:SetPosition((p1 + p2) * 0.5) self.sprite:AlignToVector(p2 - p1) self.sprite:SetSize(0.1,(p2-p1):Length()) end Now let's imagine we had a tank with a main gun as well as an anti-aircraft gun that would ward off attacks from above, like this beauty I found on Turbosquid: Let's imagine we have two different scripts we attach to the tank. One handles AI for driving and shooting the main turret, while the other just manages the little machine gun. Both the scripts have a GetTarget() function, as the tank may be attacking two different enemies at once. We can easily modify our AI debugging script to handle multiple returned values as follows: function Entity:UpdateDisplay() local targets = { self:GetTarget() } --all returned values get put into a table for n,target in ipairs(targets) do local sprite = CreateSprite() self.sprites.insert(sprite) local p1 = self.entity:GetPosition() local p2 = target:GetPosition() sprite:SetPosition((p1 + p2) * 0.5) sprite:AlignToVector(p2 - p1) sprite:SetSize(0.1,(p2-p1):Length()) end end However, any scripts that are not set up to account for multiple returned values from a function will simply use the first returned value, and proceed as normal. This system supports both easy mix and match behavior with multiple scripts, but keeps the script code simple and easy to use. Scripts have easy interoperability by default, but if you want to make your function and variable names unique to the script it is easy to do so. Let me know if you have any other ideas for scripting in Turbo Game Engine.

Josh

Josh

True Single-Pass Stereo Rendering in Turbo Game Engine

Virtual reality rendering is very demanding on hardware for two reasons. First, the refresh rate of most VR headsets is 90 hz instead of the standard 60 hz refresh rate most computer monitors operate at. This means that rendering must complete in about 11 milliseconds instead of 16. Second, we have to render the scene twice with two different views, one for each eye. Without any special optimizations, this roughly means that we have to pack 16 milliseconds worth of rendering code into five milliseconds. There are a few optimizations we can make to improve efficiency over a naive implementation. Although VR rendering requires two different views to be rendered, the two views are only a few centimeters apart. We can perform culling for both views at once by simply widening the camera frustum a little bit so that both eyes are included the camera volume. For rendering, Nvidia provides an extension for single-pass stereo rendering. This doubles up the geometry and renders to two different viewports at once, ensuring that the engine makes the same number of draw calls when rendering in stereo or normal mode. (The same can be done using a geometry shader with Intel graphics, although it has yet to be determined if VR will be possible on this hardware.) Here is the result: Combined with all the other optimizations I've packed into Turbo, like clustered forward rendering and a multithreaded rendering architecture designed specifically for VR, this makes VR rendering blazingly fast. How fast is it? Well, it's so fast that SteamVR diagnostics thinks that it is going backwards in time. Take a look at the timer in the lower left corner here: The obvious conclusion is that we have successfully broken the speed of light barrier and time travel will be possible shortly. Start thinking about what items or information you want to gift your past self with now, so that you can be prepared when I start offering temporal tourism packages on this site.

Josh

Josh

Trademark for "Turbo Game Engine"

About six months ago I filed a trademark for the term "Turbo Game Engine". Today I heard back from the US Trademarks Office. I sent the requested reply. So it looks like we will get a trademark for the name "Turbo Game Engine" which means no one else can legally use it. 👍 This was the whole point of me announcing the name and putting it up for sale as a commercial product six months ago.

Josh

Josh

How to Expose C++ Classes to Lua in Turbo Game Engine

I'm using the excellent sol2 library to interface C++ and Lua in the upcoming Turbo Game Engine. I've decided not to create an automatic header parser like I did for tolua++ in Leadwerks 4, for the following reasons: There are a lot of different options and special cases that would probably make a header parser a very involved task with me continually discovering new cases I have to account for. sol2 is really easy to use. Each class I want available to Lua will have a static function the Lua virtual machine code can call when a new Lua state is created. The new_usertype method is able to expose a C++ class to Lua in a single command. At a minimum, the name of the class and the base class should be defined. This method can accept a lot of arguments, so I am going to break it up over several lines. void Vec3::InitializeClass(sol::state* luastate) { //Class luastate->new_usertype<Vec3> ( //Name "Vec3", //Hierarchy sol::base_classes, sol::bases<Object>() ); } We can export members to Lua very easily just by adding more arguments in the call to new_usertype: //Members "x", &Vec3::x, "y", &Vec3::y, "z", &Vec3::z, Metamethods are special operations like math operands. For example, adding these arguments into the method call will set all the metamethods we want to use. //Metamethods sol::meta_function::to_string, &Vec3::ToString, sol::meta_function::index, [](Vec3& v, const int index) { if (index < 0 or index > 2) return 0.0f; return v[index]; }, sol::meta_function::new_index, [](Vec3& v, const int index, double x) { if (index < 0 or index > 2) return; v[index] = x; }, sol::meta_function::equal_to, &Vec3::operator==, sol::meta_function::less_than, &Vec3::operator<, sol::meta_function::subtraction, sol::resolve<Vec3(const Vec3&)>(&Vec3::operator-), sol::meta_function::addition, sol::resolve<Vec3(const Vec3&)>(&Vec3::operator+), sol::meta_function::division, sol::resolve<Vec3(const Vec3&)>(&Vec3::operator/), sol::meta_function::multiplication, sol::resolve<Vec3(const Vec3&)>(&Vec3::operator*), sol::meta_function::unary_minus, sol::resolve<Vec3()>(&Vec3::operator-), sol::meta_function::modulus, &Vec3::operator%, And then finally the class methods we want to use can be exposed as follows: //Methods "Length", &Vec3::Length, "Cross", &Vec3::Cross, "Normalize", &Vec3::Normalize, "Inverse", &Vec3::Inverse, "Distance", &Vec3::DistanceToPoint In C++ you can not retrieve a pointer to a function, so we are going to create a quick Lambda expression and expose it as follows: //Constructor luastate->set_function("Vec3", [](float x, float y, float z) {return Vec3(x, y, z); } ); This allows us to create a Vec3 object in Lua the same way we would with a constructor in C++. Here is the complete Vec3 class initialization code, which makes Lua recognize the class, exposes the members, adds math operations, and exposes class methods: void Vec3::InitializeClass(sol::state* luastate) { //Class luastate->new_usertype<Vec3> ( //Name "Vec3", //Hierarchy sol::base_classes, sol::bases<Object>(), //Members "x", &Vec3::x, "y", &Vec3::y, "z", &Vec3::z, "r", &Vec3::x, "g", &Vec3::y, "b", &Vec3::z, //Metamethods sol::meta_function::to_string, &Vec3::ToString, sol::meta_function::index, [](Vec3& v, const int index) { if (index < 0 or index > 2) return 0.0f; return v[index]; }, sol::meta_function::new_index, [](Vec3& v, const int index, double x) { if (index < 0 or index > 2) return; v[index] = x; }, sol::meta_function::equal_to, &Vec3::operator==, sol::meta_function::less_than, &Vec3::operator<, sol::meta_function::subtraction, sol::resolve<Vec3(const Vec3&)>(&Vec3::operator-), sol::meta_function::addition, sol::resolve<Vec3(const Vec3&)>(&Vec3::operator+), sol::meta_function::division, sol::resolve<Vec3(const Vec3&)>(&Vec3::operator/), sol::meta_function::multiplication, sol::resolve<Vec3(const Vec3&)>(&Vec3::operator*), sol::meta_function::unary_minus, sol::resolve<Vec3()>(&Vec3::operator-), sol::meta_function::modulus, &Vec3::operator%, //Methods "Length", &Vec3::Length, "Cross", &Vec3::Cross, "Normalize", &Vec3::Normalize, "Inverse", &Vec3::Inverse, "Distance", &Vec3::DistanceToPoint ); //Constructor luastate->set_function("Vec3", [](float x, float y, float z) {return Vec3(x, y, z); } ); } To add your own C++ classes to Lua in Turbo, you will create a similar function as above and call it at the start of the program.

Josh

Josh

Vector Image Support

Building on the Asset Loader class I talked about in my previous blog post, I have added a loader to import textures from SVG files. In 2D graphics there are two types of image files. Rasterized images are made up of a grid of pixels. Vector images, on the other hand, are made up of shapes like Bezier curves. One example of vector graphics you are probably familiar with are the fonts used on your computer. SVG files are a vector image format that can be created in Adobe Illustrator and other programs: Because SVG images are resolution-independent, when you zoom in on these images they only become more detailed: This makes SVG images perfect for GUI elements. The Leadwerks GUI can be rendered at any resolution, to support 4K, 8K, or other displays. This is also great for in-game VR GUIs because you can scale the image to any resolution. SVG images can be loaded as textures to be drawn on the screen, and in the future will be able to be loaded as GUI images. You can specify a DPI to rasterize the image to, with a default setting of 96 dots per inch.

Josh

Josh

Asset Loader Class

There's a discussion on the forum that sort of veered into talking about Khronos' GLTF file format specification: Some of this gave me some ideas for changes in the art pipeline in Turbo Game Engine. I was not feeling very concentrated today so I decided to do some easy work and implement a loader class: class Loader : public SharedObject { public: std::vector<wstring> extensions; virtual bool Reload(shared_ptr<Stream> stream, shared_ptr<SharedObject> o, const int flags = 0)=0; }; Then I created a TEX texture loader class: class TEXTextureLoader : public Loader { public: virtual bool Reload(shared_ptr<Stream> stream, shared_ptr<SharedObject> o, const int flags = 0)=0; }; When the engine is initialized it creates a TEXTexLoader object and saves it in a list of texture loaders: GameEngine::Initialize() { textureloaders.push_back(make_shared<TEXTextureLoader>()); } And the class constructor itself creates an array of file extensions the loader supports: TEXTextureLoader::TEXTextureLoader() { extensions = { "tex" }; } When a texture is loaded, the engine looks for a texture loader that matches the extension or the loaded file path. At this point, all we have done is restructured the engine to do the same thing it did before, with more code. But now we can add new loaders for other file formats. And that is exactly what I did. You can now load textures directly from PNG files: class PNGTextureLoader : public Loader { public: virtual bool Reload(shared_ptr<Stream> stream, shared_ptr<SharedObject> o, const int flags = 0)=0; }; I've done the same thing for model file loading as well, although I have not yet added support for any other file types. If we can add a C++ loader, we should be able to add one with Lua too. I haven't worked out the details yet, but it could work something like this: function LoadModelObj( stream, model ) while stream:EOF() == false do -------------- end end AddModelLoader(LoadModelObj) So with that, you could download a Lua script that would add support for loading a new file format. I think in the new engine we will support all common image formats, including Photoshop PSD, as well as DDS, KTX, and our existing TEX files. Textures loaded from image formats will be less optimal because mipmaps will be generated on loading. Settings like clamping and filter modes will be stored in the associated .meta files, which means these files will start getting published along with your game asset files. This is a change from the Leadwerks 4 way of doing things.

Josh

Josh

More Work on Shadows

I found and fixed the cause of the cubemap seams in variance shadow maps so we now have nice soft seamless shadows. I also changed the engine so that point lights use six 2D textures instead of a separate cubemap texture array. This means that all light types are sharing one big 2D array texture, and it frees up one texture slot. I am not sure if I want to have a hard limit on number of shadow-casting lights in the scene, or if I want to implement a system that moves lights in and out of a fixed number of shadowmap slots.

Josh

Josh

Three Massive improvements the new engine will make in your life

As I work with the new engine more and more I keep finding new ways it makes life happy and productive. Smart Pointers I have talked about how great these are at length, but I keep finding new reasons I love them. The behind-the-scenes design has been a lot of fun, and it's so cool to be able to write lines of code like this without any fear of memory leaks: LoadSound("Sound/Music/fully_loaded_60.wav")->Play(); What do you think that code does? It plays a sound, keeps it in memory, and then unloads it when the sound finishes playing (assuming it is not loaded anywhere else). Smart Pointers make the new API almost magical to work with, and they don't have the performance overhead that garbage collection would, and they work great with Lua script. User Interface Leadwerks GUI will be used in our new editor, which allows me to innovate in many new ways. But we're also using Visual Studio Code for the script editor, which gives you a sleek modern scripting environment. Better Scene Management Cached shadow maps.are a feature in Leadwerks 4 that separate geometry into static and dynamic shadow-casting types. Static shadows are rendered into a cache texture. When the shadow updates only the dynamic objects are redrawn on top of the saved static cache. This requires that you set the light shadow mode to Dynamic|Static|Buffered. In the new engine this will be automatic. By default lights will use a shadow cache, and if the light moves after the first shadow render, the cache will be disabled. Any geometry can be marked as static in the new editor. Static objects are more optimal for lighting, navigation, and global illumination, and will not respond to movement commands. (This can also be used to mark which brushes should get merged when the scene is loaded). If you don't explicitly select whether an object in the scene should be static or not, the engine will guess. For example, any object with non-zero mass or a script attached to it should not be automatically marked as static. If you didn't understand any of that, don't worry! Just don't do anything, and your scene will already be running efficiently, because the engine makes intelligent choices based on your game's behavior. It's all turning out really nice.

Josh

Josh

Next Steps

I've got the basic GI algorithm working but it needs a lot of work to be correct. I tend to do very well when the exact outcome is well-defined, but I am not as good at dealing with open-ended "artistic" programming. I may end up outsourcing the details of the GI shader to someone else, but the underlying data management is solid enough that I am not scared of it anymore. There's a lot of aspects of the design I'm not scared of anymore. We worked out smart pointers (including Lua integration), physically-based rendering, and most importantly the crazy ideas I had for the super efficient architecture work really well. At this point I think I am going to put the GI on hold, since I could play around with that endlessly, and focus on getting a new build out to the beta subscribers. We're going to just use a single skybox for ambient and specular reflections right now, and when it's ready GI and environment probes will provide that.  After that I think I will focus on the physics and navigation systems, exposing the entire API to Lua, and getting some of the outsourced work started. There's a few things I plan to farm out: Visual Studio Code Lua debugger GI details Weather system Water and clouds systems Everything else is pretty well under my control. This started out as an idea for an impossible design, but everything has fallen into place pretty neatly.

Josh

Josh

×