Jump to content

Josh

Staff
  • Content Count

    15,992
  • Joined

  • Last visited

Blog Entries posted by Josh

  1. Josh
    I'm back from I/ITSEC. This conference is basically like the military's version of GDC. VR applications built with Leadwerks took up about half of Northrop Grumman's booth. There were many interesting discussions about new technology and I received a very warm reception. I feel very positive about our new technology going forward.

    I am currently reworking the text field widget script to work with our persistent 2D objects. This is long and boring but needs to be done. Not much else to say right now.
  2. Josh
    Here are some screenshots showing more complex interface items scaled at different resolutions. First, here is the interface at 100% scaling:

    And here is the same interface at the same screen resolution, with the DPI scaling turned up to 150%:

    The code to control this is sort of complex, and I don't care. GUI resolution independence is a complicated thing, so the goal should be to create a system that does what it is supposed to do reliably, not to make complicated things simpler at the expense of functionality.
    function widget:Draw(x,y,width,height) local scale = self.gui:GetScale() self.primitives[1].size = iVec2(self.size.x, self.size.y - self.tabsize.y * scale) self.primitives[2].size = iVec2(self.size.x, self.size.y - self.tabsize.y * scale) --Tabs local n local tabpos = 0 for n = 1, #self.items do local tw = self:TabWidth(n) * scale if n * 3 > #self.primitives - 2 then self:AddRect(iVec2(tabpos,0), iVec2(tw, self.tabsize.y * scale), self.bordercolor, false, self.itemcornerradius * scale) self:AddRect(iVec2(tabpos+1,1), iVec2(tw, self.tabsize.y * scale) - iVec2(2 * scale,-1 * scale), self.backgroundcolor, false, self.itemcornerradius * scale) self:AddTextRect(self.items[n].text, iVec2(tabpos,0), iVec2(tw, self.tabsize.y*scale), self.textcolor, TEXT_CENTER + TEXT_MIDDLE) end if self:SelectedItem() == n then self.primitives[2 + (n - 1) * 3 + 1].position = iVec2(tabpos, 0) self.primitives[2 + (n - 1) * 3 + 1].size = iVec2(tw, self.tabsize.y * scale) + iVec2(0,2) self.primitives[2 + (n - 1) * 3 + 2].position = iVec2(tabpos + 1, 1) self.primitives[2 + (n - 1) * 3 + 2].color = self.selectedtabcolor self.primitives[2 + (n - 1) * 3 + 2].size = iVec2(tw, self.tabsize.y * scale) - iVec2(2,-1) self.primitives[2 + (n - 1) * 3 + 3].color = self.hoveredtextcolor self.primitives[2 + (n - 1) * 3 + 1].position = iVec2(tabpos,0) self.primitives[2 + (n - 1) * 3 + 2].position = iVec2(tabpos + 1, 1) self.primitives[2 + (n - 1) * 3 + 3].position = iVec2(tabpos,0) else self.primitives[2 + (n - 1) * 3 + 1].size = iVec2(tw, self.tabsize.y * scale) self.primitives[2 + (n - 1) * 3 + 2].color = self.tabcolor self.primitives[2 + (n - 1) * 3 + 2].size = iVec2(tw, self.tabsize.y * scale) - iVec2(2,2) if n == self.hovereditem then self.primitives[2 + (n - 1) * 3 + 3].color = self.hoveredtextcolor else self.primitives[2 + (n - 1) * 3 + 3].color = self.textcolor end self.primitives[2 + (n - 1) * 3 + 1].position = iVec2(tabpos,2) self.primitives[2 + (n - 1) * 3 + 2].position = iVec2(tabpos + 1, 3) self.primitives[2 + (n - 1) * 3 + 3].position = iVec2(tabpos,2) end self.primitives[2 + (n - 1) * 3 + 3].text = self.items[n].text tabpos = tabpos + tw - 2 end end  
  3. Josh
    I did a little experiment with FPS Creator Pack #75 by upsampling the images with Gigapixel, which uses deep learning to upsample images and infer details that don't appear in the original pixels. The AI neural network does a pretty impressive job, generating results that are look better than a simple sharpen filter: I doubled the size of the textures to 1024x1024. Then I generated normal maps from the high-res images using AMD's TGA to DOT3 tool, and saved the normal maps with BC5 DDS compression. The diffuse textures were saved with BC7 DDS compression. The images below are using a 4x magnification to demonstrate the difference.


    As you can see, the image that is upsampled with deep learning looks normal and the resized image looks like there is butter on the lens! It's hard to believe the above images came from a 256x128 section of an image.
    The workflow was pretty tedious, as I had to convert images to TGA, then to uncompressed or BC5 DDS, and then to BC7 in Visual Studio. Each BC7 texture took maybe 5-10 minutes to compress! So while this set represents the optimum quality for 2019 game tech, and the format for assets we want to use in LE5, the workflow has a lot of room for improvement.
    You can download the full package here:
    FPSCPack75TexturesHD.zip
  4. Josh
    A new beta is available with the following changes:
    Script prefixes are now changed to lowercase entity:Update(), entity:Start(), etc., as well as widget:Draw(), etc. This is because Entity() and Widget() are going to be functions to cast an object to that type. Sprites are now created on a sprite layer object. A sprite layer is created in a world and added to a camera. This allows control over what camera sees what set of sprites. See the examples for details. GUI system is partially working. Resizing the window won't reposition items correctly. Only four widget types are supported, panel, button, hyperlink, and label. Example in the FPSGame demo. The game menu system is packed into an entity script and works really nicely. Widget scripts are a little harder to develop now since they use a system of persistent objects, but performance is very much better than LE4. An interesting detail is that you get free interpolation of position and color at a rate of 60 hz. A lot of work was done to improve the Lua binding system. See details here. Error reporting and handling is much improved. No work was done on sound. No work has been done to the multicam demo, which some people had trouble with. Actors crashing / Lua stack error bug fixed. Changed .bat files to use release version of EXE instead of debug. New commands EmitEvent() and GetEvent(). If the returned event type is EVENT_NONE, there is no event. EVENT_QUIT can be emitted anywhere in the program to signal the main loop to exit. Typical usage is like this: while window:Closed() == false do while true do local event = GetEvent() if event.id == EVENT_QUIT then return end if event.id == EVENT_NONE then break end if event.id == EVENT_WINDOW_CLOSE and Window(event.source) == window then return end--you don't need this when Window:Closed() is being checked for already end world:Update() world:Render(framebuffer) end  
  5. Josh
    DPI scaling and the 2D drawing and GUI system were an issue I was a bit concerned about, but I think I have it worked out. This all goes back to the multi-monitor support that I designed back in September. Part of that system allows you to retrieve the DPI scale for each display. This gives you another piece of information in addition to the raw screen resolution. The display scale gives you a percentage value the user expects to see vector graphics at, with 100% being what you would expect with a regular HD monitor. If we scale our GUI elements and font sizes by the display scale we can adjust for screens with any pixel density.
    This shot shows 1920x1080 fullscreen with DPI scaling set to 100%:

    Here we see the same resolution, with scaling set to 125%:

    And this is with scaling set to 150%:

    The effect of this is that if the player is using a 4K, 8K, or any other type of monitor, your game can display finely detailed text at the correct size the user expects to see. It also means that user interfaces can be rendered at any resolution for VR.
    Rather than trying to automatically scale GUI elements I am giving you full control over the raw pixels. That means you have to decide how your widgets will be scaled yourself, and program it into the game interface, but there is nothing hidden from the developer. Here is my code I am working with now to create a simple game menu. Also notice there is no CreatePanel(), CreateButton(), etc. anymore, there is just one widget you create and set the script for. I might add an option for C++ actors as well, but since these are operating on the main logic thread there's not really a downside to running the code in Lua.
    local window = ActiveWindow() if window == nullptr then return end local framebuffer = window:GetFramebuffer() if framebuffer == nil then return end self.gui = CreateGUI(self.guispritelayer) --Main background panel self.mainpanel = CreateWidget(self.gui,"",0,0,framebuffer.size.x,framebuffer.size.y) self.mainpanel:SetScript("Scripts/GUI/Panel.lua", true) local scale = window.display.scale.y local w = 120 local h = 24 local sep = 36 local x = framebuffer.size.x / 6 local y = framebuffer.size.y / 2 - sep * 3 self.resumebutton = CreateWidget(self.mainpanel,"RESUME GAME",x,y,w,h) self.resumebutton:SetScript("Scripts/GUI/Hyperlink.lua", true) self.resumebutton:SetFontSize(14 * window.display.scale.y) y=y+sep*2 self.label2 = CreateWidget(self.mainpanel,"OPTIONS",x,y,w,h) self.label2:SetScript("Scripts/GUI/Hyperlink.lua", true) self.label2:SetFontSize(14 * window.display.scale.y) y=y+sep*2 self.quitbutton = CreateWidget(self.mainpanel,"QUIT", x,y, w,h) self.quitbutton:SetScript("Scripts/GUI/Hyperlink.lua", true) self.quitbutton:SetFontSize(14 * window.display.scale.y) w = 400 * scale h = 550 * scale self.optionspanel = CreateWidget(self.mainpanel,"QUIT", (framebuffer.size.x- w) * 0.5, (framebuffer.size.y - h) * 0.5, w, h) self.optionspanel:SetScript("Scripts/GUI/Panel.lua", true) self.optionspanel.color = Vec4(0.2,0.2,0.2,1) self.optionspanel.border = true self.optionspanel.radius = 8 * scale self.optionspanel.hidden = true  
  6. Josh
    Previously I talked about the technical details of hardware tessellation and what it took to make it truly useful. In this article I will talk about some of the implications of this feature and the more advanced ramifications of baking tessellation into Turbo Game Engine as a first-class feature in the 
    Although hardware tessellation has been around for a few years, we don't see it used in games that often. There are two big problems that need to be overcome.
    We need a way to prevent cracks from appearing along edges. We need to display a consistent density of triangles on the screen. Too many polygons is a big problem. I think these issues are the reason you don't really see much use of tessellation in games, even today. However, I think my research this week has created new technology that will allow us to make use of tessellation as an every-day feature in our new Vulkan renderer.
    Per-Vertex Displacement Scale
    Because tessellation displaces vertices, any discrepancy in the distance or direction of the displacement, or any difference in the way neighboring polygons are subdivided, will result in cracks appearing in the mesh.

    To prevent unwanted cracks in mesh geometry I added a per-vertex displacement scale value. I packed this value into the w component of the vertex position, which was not being used. When the displacement strength is set to zero along the edges the cracks disappear:

    Segmented Primitives
    With the ability to control displacement on a per-vertex level, I set about implementing more advanced model primitives. The basic idea is to split up faces so that the edge vertices can have their displacement scale set to zero to eliminate cracks. I started with a segmented plane. This is a patch of triangles with a user-defined size and resolution. The outer-most vertices have a displacement value of 0 and the inner vertices have a displacement of 1. When tessellation is applied to the plane the effect fades out as it reaches the edges of the primitive:

    I then used this formula to create a more advanced box primitive. Along the seam where the edges of each face meet, the displacement smoothly fades out to prevent cracks from appearing.

    The same idea was applied to make segmented cylinders and cones, with displacement disabled along the seams.


    Finally, a new QuadSphere primitive was created using the box formula, and then normalizing each vertex position. This warps the vertices into a round shape, creating a sphere without the texture warping that spherical mapping creates.

    It's amazing how tessellation and displacement can make these simple shapes look amazing. Here is the full list of available commands:
    shared_ptr<Model> CreateBox(shared_ptr<World> world, const float width = 1.0); shared_ptr<Model> CreateBox(shared_ptr<World> world, const float width, const float height, const float depth, const int xsegs = 1, const int ysegs = 1); shared_ptr<Model> CreateSphere(shared_ptr<World> world, const float radius = 0.5, const int segments = 16); shared_ptr<Model> CreateCone(shared_ptr<World> world, const float radius = 0.5, const float height = 1.0, const int segments = 16, const int heightsegs = 1, const int capsegs = 1); shared_ptr<Model> CreateCylinder(shared_ptr<World> world, const float radius = 0.5, const float height=1.0, const int sides = 16, const int heightsegs = 1, const int capsegs = 1); shared_ptr<Model> CreatePlane(shared_ptr<World> world, cnst float width=1, const float height=1, const int xsegs = 1, const int ysegs = 1); shared_ptr<Model> CreateQuadSphere(shared_ptr<World> world, const float radius = 0.5, const int segments = 8); Edge Normals
    I experimented a bit with edges and got some interesting results. If you round the corner by setting the vertex normal to point diagonally, a rounded edge appears.

    If you extend the displacement scale beyond 1.0 you can get a harder extended edge.

    This is something I will experiment with more. I think CSG brush smooth groups could be used to make some really nice level geometry.
    Screen-space Tessellation LOD
    I created an LOD calculation formula that attempts to segment polygons into a target size in screen space. This provides a more uniform distribution of tessellated polygons, regardless of the original geometry. Below are two cylinders created with different segmentation settings, with tessellation disabled:

    And now here are the same meshes with tessellation applied. Although the less-segmented cylinder has more stretched triangles, they both are made up of triangles about the same size.

    Because the calculation works with screen-space coordinates, objects will automatically adjust resolution with distance. Here are two identical cylinders at different distances.

    You can see they have roughly the same distribution of polygons, which is what we want. The same amount of detail will be used to show off displaced edges at any distance.

    We can even set a threshold for the minimum vertex displacement in screen space and use that to eliminate tessellation inside an object and only display extra triangles along the edges.

    This allows you to simply set a target polygon size in screen space without adjusting any per-mesh properties. This method could have prevented the problems Crysis 2 had with polygon density. This also solves the problem that prevented me from using tessellation for terrain. The per-mesh tessellation settings I worked on a couple days ago will be removed since it is not needed.
    Parallax Mapping Fallback
    Finally, I added a simple parallax mapping fallback that gets used when tessellation is disabled. This makes an inexpensive option for low-end machines that still conveys displacement.

    Next I am going to try processing some models that were not designed for tessellation and see if I can use tessellation to add geometric detail to low-poly models without any cracks or artifacts.
  7. Josh
    For finer control over what 2D elements appear on what camera, I have implemented a system of "Sprite Layers". Here's how it works:
    A sprite layer is created in a world. Sprites are created in a layer. Layers are attached to a camera (in the same world). The reason the sprite layer is linked to the world is because the render tweening operates on a per-world basis, and it works with the sprite system just like the entity system. In fact, the rendering thread uses the same RenderNode class for both.
    I have basic GUI functionality working now. A GUI can be created directly on a window and use the OS drawing commands, or it can be created on a sprite layer and rendered with 3D graphics. The first method is how I plan to make the new editor user interface, while the second is quite flexible. The most common usage will be to create a sprite layer, attach it to the main camera, and add a GUI to appear in-game. However, you can just as easily attach a sprite layer to a camera that has a texture render target, and make the GUI appear in-game on a panel in 3D. Because of these different usages, you must manually insert events like mouse movements into the GUI in order for it to process them:
    while true do local event = GetEvent() if event.id == EVENT_NONE then break end if event.id == EVENT_MOUSE_DOWN or event.id == EVENT_MOUSE_MOVE or event.id == EVENT_MOUSE_UP or event.id == EVENT_KEY_DOWN or event.id == EVENT_KEY_UP then gui:ProcessEvent(event) end end You could also input your own events from the mouse position to create interactive surfaces, like in games like DOOM and Soma. Or you can render the GUI to a texture and interact with it by feeding in input from VR controllers.

    Because the new 2D drawing system uses persistent objects instead of drawing commands the code to display elements has changed quite a lot. Here is my current button script. I implemented a system of abstract GUI "rectangles" the script can create and modify. If the GUI is attached to a sprite layer these get translated into sprites, and if it is attached directly to a window they get translated into system drawing commands. Note that the AddTextRect doesn't even allow you to access the widget text directly because the widget text is stored in a wstring, which supports Unicode characters but is not supported by Lua.
    --Default values widget.pushed=false widget.hovered=false widget.textindent=4 widget.checkboxsize=14 widget.checkboxindent=5 widget.radius=3 widget.textcolor = Vec4(1,1,1,1) widget.bordercolor = Vec4(0,0,0,0) widget.hoverbordercolor = Vec4(51/255,151/255,1) widget.backgroundcolor = Vec4(0.2,0.2,0.2,1) function widget:MouseEnter(x,y) self.hovered = true self:Redraw() end function widget:MouseLeave(x,y) self.hovered = false self:Redraw() end function widget:MouseDown(button,x,y) if button == MOUSE_LEFT then self.pushed=true self:Redraw() end end function widget:MouseUp(button,x,y) if button == MOUSE_LEFT then self.pushed = false if self.hovered then EmitEvent(EVENT_WIDGET_ACTION,self) end self:Redraw() end end function widget:OK() EmitEvent(EVENT_WIDGET_ACTION,self) end function widget:KeyDown(keycode) if keycode == KEY_ENTER then EmitEvent(EVENT_WIDGET_ACTION,self) self:Redraw() end end function widget:Start() --Background self:AddRect(self.position, self.size, self.backgroundcolor, false, self.radius) --Border if self.hovered == true then self:AddRect(self.position, self.size, self.hoverbordercolor, true, self.radius) else self:AddRect(self.position, self.size, self.bordercolor, true, self.radius) end --Text if self.pushed == true then self:AddTextRect(self.position + iVec2(1,1), self.size, self.textcolor, TEXT_CENTER + TEXT_MIDDLE) else self:AddTextRect(self.position, self.size, self.textcolor, TEXT_CENTER + TEXT_MIDDLE) end end function widget:Draw() --Update position and size self.primitives[1].position = self.position self.primitives[1].size = self.size self.primitives[2].position = self.position self.primitives[2].size = self.size self.primitives[3].size = self.size --Update the border color based on the current hover state if self.hovered == true then self.primitives[2].color = self.hoverbordercolor else self.primitives[2].color = self.bordercolor end --Offset the text when button is pressed if self.pushed == true then self.primitives[3].position = self.position + iVec2(1,1) else self.primitives[3].position = self.position end end This is arguably harder to use than the Leadwerks 4 system, but it gives you advanced capabilities and better performance that the previous design did not allow.
  8. Josh
    What's new
    EAX audio effects for supported hardware. Source class renamed to "Speaker". Plane joint for 2D physics, so now you can make Angry Birds with Vulkan graphics. Fixed DPI issues with fullscreen mode. Added impact noise to barrels, fixed Lua collision function not being called. Script functions now start with "Entity:" instead of "Script:", i.e. Entity:Update() instead of Script:Update(). Additionally, four examples can be run showing various functionality. Double-click on the .bat files to launch a different demo:
    First-person shooter game. 2D physics demonstration. Advanced 2D drawing with text, rotation, and scaling. Multi-camera setup.
  9. Josh
    I've been doing some work on the sound system in Leadwerks 5 beta, and I added EAX effects in. If you have a dedicated sound card this can be used to add some nice reverb effects that make your sound environment sound a lot more real:
    Here's the simplest usage:
    auto fx = LoadSoundEffect("Sound/FX/sewerpipe.json"); auto listener = CreateListener(world); listener->SetEffect(fx); This will apply the effect to all mono sources. Stereo sources are assumed to be music or GUI noises, and will be unaffected. Eventually, the way I see this being used is a script attached to a CSG brush that changes the listener's EAX effect when the player enters and leaves the volume, but the above shows the API approach.
    I exported all the EAX presets into JSON files like so. You can load one of the existing files, or if you are feeling really creative you can try making your own:
    { "AirAbsorptionGainHF": 0.99426, "DecayHFLimit": 0, "DecayHFRatio": 0.89, "DecayLFRatio": 0.41, "DecayTime": 2.76, "Density": 1.0, "Diffusion": 0.82, "EchoDepth": 0.17, "EchoTime": 0.13, "Gain": 0.316228, "GainHF": 0.281838, "GainLF": 0.0891251, "HFReference": 2854.4, "LateReverbGain": 0.891251, "LateReverbPan": [0.0, 0.0, 0.0], "LFReference": 107.5, "LateReverbDelay": 0.02, "ModulationDepth": 0.0, "ModulationTime": 0.25, "ReflectionsDelay": 0.029, "ReflectionsGain": 0.354813, "ReflectionsPan": [0.0, 0.0, -0.0], "RoomRolloffFactor": 0.0 } Here's the full list of available presets:
    CastleSmallroom CastleMediumroom CastleLongpassage CastleLargeroom CastleHall CastleCupboard CastleCourtyard CastleAlcove FactoryAlcove FactoryShortPassage FactoryMediumRoom FactoryLongPassage FactoryLargeRoom FactoryHall FactoryCupboard FactoryCourtyard FactorySmallRoom IcepalaceAlcove IcepalaceShortPassage IcepalaceMediumRoom IcepalaceLongPassage IcepalaceLargeroom IcepalaceHall IcepalaceCupboard IcepalaceCourtyard IcepalaceSmallRoom SpacestationAlcove SpacestationMediumRoom SpacestationShortpassage SpacestationLongPassage SpacestationLargeRoom SpacestationHall SpacestationCupboard SpacestationSmallRoom WoodenAlcove WoodenShortPassage WoodenMediumRoom WoodenLongPassage WoodenLargeRoom WoodenHall WoodenCupboard WoodenSmallRoom WoodenCourtyard SportEmptyStadium SportSquashCourt SportSmallSwimmingPool SportLargeSwimmingPool SportGymnasium SportFullStadium SportStadiumTannoy Workshop SchoolRoom PractiseRoom Outhouse Caravan Dome Tomb PipeSmall DomeSaintPauls PipeLongThing PipeLarge PipeResonant OutdoorsBackyard OutdoorsRollingPlains OutdoorsDeepCanyon OutdoorsCreek OutdoorsValley MoodHeaven MoodHell MoodMemory DrivingCommentator DrivingPitGarage DrivingInCarRacer DrivingInCarSports DrivingFullGrandstand DrivingEmptyGrandstand DrivingTunnel CityStreets CitySubway CityMuseum CityLibrary CityUnderpass Dustyroom Chapel SmallWaterRoom I might consider implementing Steam Audio in the future (formerly Phonon) but for now OpenAL does everything I want.
  10. Josh
    An update for Leadwerks 5 is now available.
    The Vulkan data transfer system has been revised and is now simpler but uses more memory. Data is likely to be quadruple-buffered, but it's a fairly small amount of data and this isn't a big concern. 
    I fixed a bad bug where multiple threads were accessing a global variable in the Mat4::GetQuaternion function. This fixes the object flashing glitch that was visible in previous builds.
    The engine is updated to the latest version of Newton Dynamics and kinematic joints work correctly now. The upvector joint is removed and a plane joint has been added for 2D physics, but I don't think it will work yet. Object picking up is implemented in the player controller script.
    I switched out the default scene with a new one using some of @TWahl 's materials.
    Added an FPSWeapon script that loads a gun and makes it sway.
    Entity::AddScript() can now be called in the Start() function of another script with no problems.
    Fullscreen windows are now working.
    The window / display system is changed a bit. New display commands:
    std::vector<shared_ptr<Display> > ListDisplays() std::vector<iVec2> Display::GraphicsModes() You must pass a display object in the window creation command now. Here's how I do it in Lua:
    --Get the primary display local displaylist = ListDisplays() local display = displaylist[1]; --Get the display's highest resolution graphics mode gfxmodes = display:GraphicsModes() gfx = gfxmodes[#gfxmodes] --Create a window local fullscreenmode = false local window if fullscreenmode then window = CreateWindow(display, "My Game", 0, 0, gfx.x, gfx.y, WINDOW_FULLSCREEN) else window = CreateWindow(display, "My Game", 0, 0, 1280 * display.scale.x, 720 * display.scale.y, WINDOW_CENTER + WINDOW_TITLEBAR) end And in C++:
    const bool fullscreenmode = false; //Get the primary display auto displays = ListDisplays(); auto display = displays[0]; //Create a window shared_ptr<Window> window; if (fullscreenmode) { auto gfxmodes = display->GraphicsModes(); auto gfx = gfxmodes[gfxmodes.size() - 1]; window = CreateWindow(display, L"My Game", 0, 0, gfx.x, gfx.y, WINDOW_FULLSCREEN); } else { Vec2 displayscale = display->GetScale(); window = CreateWindow(display, L"My Game", 0, 0, 1280 * displayscale.x, 720 * displayscale.y, WINDOW_TITLEBAR | WINDOW_RESIZABLE | WINDOW_CENTER); } The speed of the point light shadow updating is unbelievably fast. Point light shadows in Leadwerks 4 are very expensive to update because they require six different render passes for each of the six cubemap faces, but in Leadwerks 5 beta with Vulkan they are basically free. I'm sure it will slow down if I add enough points lights and have them all constantly updating, but I don't see any difference at all in the framerate right now when shadows are active. If you are having any trouble with their appearance you can set the global variable MULTIPASS_CUBEMAP to false in C++ at the very beginning of your program.

    This script can be used to display performance statistics. At this time it only shows the framerate but I can expand on this in the future.
    function Script:Start() self.statsEnabled = true self.textcache = {} self.font = LoadFont("Fonts/arial.ttf") self.fontsize = 16 self.textalignment = TEXT_LEFT self.world:EnableStats(self.statsEnabled) self:BindKey(KEY_F11, self.ToggleStats) end function Script:ToggleStats() self.statsEnabled = not self.statsEnabled self.world:EnableStats(self.statsEnabled) end function Script:Update() --Return if disabled or font missing if self.statsEnabled == false or self.font == nil then return end --Hide previously used sprite if self.displayfps ~= nil then self.displayfps:Hide() end --Retrieve the framerate and convert to string --Convert to integer to limit the amount of different string values local fps = tostring(math.ceil(self.world.renderstats.framerate - 0.5)).." FPS" --Check for cached version and create it if it doesn't exist if self.textcache[fps] == nil then self.textcache[fps] = CreateText(self.world, self.font, fps, self.fontsize, self.textalignment, 1) self.textcache[fps]:SetPosition(4,4) self.textcache[fps]:SetColor(0,1,0,0.75) end --Set current sprite and show self.displayfps = self.textcache[fps] self.displayfps:Show() end It may seem like a lot of code just to draw a bit of text onscreen, but the benefit is extreme performance. Instead of drawing one character at a time like the Leadwerks renderer does, this creates persistent text objects and reuses them when needed. That cuts the performance cost of displaying text down to basically zero, making it great for complex GUIs and game interfaces.
  11. Josh
    Leadwerks 3 / 4 was aimed at beginners who were completely new to game development. Since we were the first game engine on Steam, this made a lot of sense at the time, and the decision resulted in a successful outcome. However, in the next engine we are taking a different approach. (This is a direct result of Steam Direct.)
    Enterprise is a new market I am pursuing, and we have a lot of interest from aerospace and defense VR developers. The fact that I am American also helps here. There are special features these customers need that aren't necessarily needed by game developers, but I think you will like some of the possibilities this unlocks.
    For game developers, I have been moving back to an approach more like Leadwerks 2 where we focus on extreme high-end PC game technology, so that comes down to graphical quality and performance. I think most people here will be pretty happy with that direction. We're going to sell on Steam, Humble Store, Amazon, Microsoft store, Mac App Store, and direct from our website. Less importance will be attached to Steam, as they are just one more storefront we sell through. We're not going to use Steam Workshop.
    For pricing of the non-enterprise version, I am thinking $59.99 / $99.99 standard / pro with a monthly subscription option at $4.99 / $9.99. This is actually cheaper than the pricing of Leadwerks, but I think keeping things under $100 is better for the consumer market.
    The paid beta subscription is going to end before the end of the year, and it will be replaced with an open beta. The reason I did this was because I wanted a very small group of people testing the early betas (really more of an alpha), and I wanted to test out our subscriptions system. During that time we found and fixed a couple of small issues, so this was a good idea. A big thanks to all who participated and bought me many espressos.  ☕
    Finally, we are not going to call Leadwerks 5 "Turbo Game Engine". The name just isn't sticking for me, and I don't think we can get rid of the :"retro" connotations it has. My new technology has developed quite a lot since then, and speed is not the only advantage it brings to the table. I do have a new name in mind, but I am not ready to announce it yet. Until then, I will refer to the new engine as "Leadwerks 5 beta".
  12. Josh
    The best way to test the new engine is to use it to make something. I am messing around with the beginnings of a new first-person shooter template. I'm telling everyone involved "We are remaking Doom, but a little differently" and it actually works really well. Everyone understand what it should look like, and there is no need to establish a new visual style. We can tell when we have it right, and when we have it wrong. And the original game gives us a sort of benchmark for quality and performance we can measure against.
    I have two scripts for the player. One is for basic movement and interaction with objects. Another one handles the display and movement of the player's weapon. I opted to use entirely programmatic motion for the weapon sway, and I don't plan on showing any hands. These will work together because Leadwerks 5 allows you to add multiple scripts to any entity. Here is the current weapon script:
    function Script:Start() self.modelfile = "Models/Weapons/Ronan Rifle/scene.gltf" self.weaponposition = Vec3(0.12, -0.4, 0.42) self.weaponswayspeed = 0.25 self.weaponswayticks = 0 --Load weapon self.weapon = LoadModel(self.world, self.modelfile) if self.weapon == nil then return end self.weapon:CastShadows(false) self.weapon:SetPosition(self.weaponposition) self.weaponrotation = self.weapon:GetRotation() end function Script:Update() if self.weapon == nil then return end --Parent to camera if not already done if self.weapon.parent == nil then if type(self.camera) == "userdata" then self.weapon:SetParent(self.camera,false) else DebugWarning("FPSWeapon: self.camera must be an entity.") end end --Adjust speed based on ground velocity self.weaponswayspeed = 0.1 if self:GetAirborne() == false then local speed = self.velocity.xz:Length() self.weaponswayspeed = self.weaponswayspeed + 0.75 * math.min(speed / 3, 1) end --Weapon sway self.weaponswayticks = self.weaponswayticks + 16.666666 * self.weaponswayspeed local pitch = math.cos(self.weaponswayticks / 100) * 0.25 local yaw = math.sin(self.weaponswayticks / 100) * 0.25 local sway = math.sin(self.weaponswayticks / 100) * 0.004 local bob = -math.cos(self.weaponswayticks / 50) * 0.002 self.weapon:SetRotation(self.weaponrotation + Vec3(pitch, yaw, 0)) self.weapon:SetPosition(self.weaponposition + Vec3(sway, bob, 0)) end An interesting bit is the swizzle and properties, which makes Lua more flexible than in the past:
    local speed = self.velocity.xz:Length() In Leadwerks 4 we would have had to type all this:
    local speed = self.entity:GetVelocity():xz():Length() Here is the very first pass, with some materials courtesy of @TWahl. I wanted much faster movement with this one, so the player runs by default and you hold the shift key to walk more slowly.
     
  13. Josh
    This is something I typed up for some colleagues and I thought it might be useful info for C++ programmers.
    To create an object:
    shared_ptr<TypeID> type = make_shared<TypeID>(constructor args…) This is pretty verbose, so I always do this:
    auto type = make_shared<TypeID>(constructor args…) When all references to the shared pointer are gone, the object is instantly deleted. There’s no garbage collection pauses, and deletion is always instant:
    auto thing = make_shared<Thing>(); auto second_ref = thing; thing = NULL; second_ref = NULL;//poof! Shared pointers are fast and thread-safe. (Don’t ask me how.)
    To get a shared pointer within an object’s method, you need to derive the class from “enable_shared_from_this<SharedObject>”. (You can inherit a class from multiple types, remember):
    class SharedObject : public enable_shared_from_this<SharedObject> And you can implement a Self() method like so, if you want:
    shared_ptr<SharedObject> SharedObject::Self() { return shared_from_this(); } Casting a type is done like this:
    auto bird = dynamic_pointer_cast<Bird>(animal); Dynamic pointer casts will return NULL if the animal is not a bird. Static pointer casts don’t have any checks and are a little faster I guess, but there’s no reason to ever use them.
    You cannot call shared_from_this() in the constructor, because the shared pointer does not exist yet, and you cannot call it in the destructor, because the shared pointer is already gone!
    Weak pointers can be used to store a value, but will not prevent the object from being deleted:
    auto thing = make_shared<Thing>(); weak_ptr<Thing> thingwptr = thing; shared_ptr<Thing> another_ref_to_thing = thingwptr.lock(); //creates a new shared pointer to “thing” auto thing = make_shared<Thing>(); weak_ptr<Thing> thingwptr = thing; thing = NULL; shared_ptr<Thing> another_ref_to_thing = thingwptr.lock(); //returns NULL! If you want to set a weak pointer’s value to NULL without the object going out of scope, just call reset():
    auto thing = make_shared<Thing>(); weak_ptr<Thing> thingwptr = thing; thingwptr.reset(); shared_ptr<Thing> another_ref_to_thing = thingwptr.lock(); //returns NULL! Because no garbage collection is used, circular references can occur, but they are rare:
    auto son = make_shared<Child>(); auto daughter = make_shared<Child>(); son->sister = daughter; daughter->brother = son; son = NULL; daughter = NULL;//nothing is deleted! The problem above can be solved by making the sister and brother members weak pointers instead of shared pointers, thus removing the circular references.
    That’s all you need to know!
  14. Josh
    A new build is available on the beta branch on Steam.
    Updated to Visual Studio 2019. Updated to latest version of OpenVR and Steamworks SDK. Fixed object tracking with seated VR mode. Note that the way seated VR works now is a little different, you need to use the VR orientation instead of just positioning the camera (see below). Added VR:SetRotation() so you can rotate the world around in VR. The VRPlayer script has rotation added to the left controller. Press the touchpad to turn left and right. Any arbitrary rotation will work, including roll and pitch. Here are the offset commands:
    static void VR::SetOffset(const Vec3& position); static void VR::SetOffset(const float x, const float y, const float z); static void VR::SetOffset(const float x, const float y, const float z, const float pitch, const float yaw, const float roll); static void VR::SetOffset(const Vec3& position, const Vec3& rotation); static void VR::SetOffset(const Vec3& position, const Quat& rotation); static Vec3 VR::GetOffset(); static Vec3 VR::GetRotation(); static Quat VR::GetQuaternion();  
  15. Josh
    An update is available for the Leadwerks 5 beta.
    Shadow updating is fixed so the lights no longer turn black during the update whenever a shadow changes.
    We're now using multiview to draw all six faces of a cube shadow map in one single pass! Point light shadow updates are something that used to be a considerable bottleneck in Leadwerks 4, and their performance impact is now very insignificant. Thanks to Sascha Williams for his excellent Vulkan examples.
    Joints are finished, a new upvector joint is added to lock the Y axis to a vector, all collision shape types are now supported, and body mass centers are fixed for rigid body physics will work correctly.
  16. Josh
    Previously, we saw how the new renderer can combine multiple cameras and even multiple worlds in a single render to combine 3D and 2D graphics. During the process of implementing Z-sorting for multiple layers of transparency, I found that Vulkan does in fact respect rasterization order. That is, objects are in fact drawn in the same order you provide draw calls to a command buffer.
    Furthermore, individual primitives (polygons) are also rendered in the order they are stored in the indice buffer:
    Now if you were making a 2D game with 1000 zombie sprites onscreen you would undoubtedly want to use 3D-in-2D rendering with an orthographic camera. Batching and depth discard would give you much faster performance when the number of objects goes up. However, the 2D aspect of most games is relatively simple, with only a dozen or so 2D sprites making up the user interface. Given that 2D graphics are not normally going to be much of a bottleneck, and that the biggest performance savings we have achieved was in making text a static object, I decided to rework the 2D rendering system into something that was a little simpler to use.
    Sprites are no longer a 3D entity, but are a new type of pure 2D object. They act in a similar way as entities with position, rotation, and scale commands, but they only use 2D coordinates:
    //Create a sprite auto sprite = CreateSprite(world,100,100); //Make blue sprite->SetColor(0,0,1); //Position in upper-left corner of screen sprite->SetPosition(10,10) Sprites have a handle you can set. By default this is in the upper-left corner of the sprite, but you can change it to recenter them. Sprites can also be rotated around the Z axis:
    //Center the handle sprite->SetHandle(0.5,0.5); //Rotation around center sprite->SetRotation(45); SVG vector images are great for 2D drawing and GUIs because they can scale for different display resolutions. We support these as well, with an optional scale value the image can be rasterized at.
    auto sprite = LoadSprite(world, "tiger.svg", 0, 2.0);
    Text is now just another type of sprite:
    auto text = CreateSprite(world, font, L"Hello, how are you today?\nI am fine.", 72, TEXT_LEFT); These sprites are all displayed within the same world as the 3D rendering, so unlike what I previously wrote about...
    You do not have to create extra cameras or worlds just to draw 2D graphics. (If you are doing something advanced then the multi-camera method I previously described is a good option, but you have to have very demanding needs for it to make a difference.) Regular old screen coordinates you are used to will be used (coordinate [0,0] is top-left). By default sprites will be drawn in the order they are created. However, I definitely see a need for additional control here and I am open to ideas. Should there be a sprite order value, a MoveToFront() method, or a system of different layers? I'm not sure yet.
    I'm also not sure how per-camera sprites will be controlled. At this time sprites are stored in a per-world list, but we will want some 2D elements to only appear on some cameras. I am not sure yet how this will be controlled.
    I am going to try to get an update out soon with these features so you can try them out yourself.
  17. Josh
    Current generation graphics hardware only supports up to a 32-bit floating point depth buffer, and that isn't adequate for large-scale rendering because there isn't enough precision to make objects appear in the correct order and prevent z-fighting.

    After trying out a few different approaches I found that the best way to support large-scale rendering is to allow the user to create several cameras. The first camera should have a range of 0.1-1000 meters, the second would use the same near / far ratio and start where the first one left off, with a depth range of 1000-10,000 meters. Because the ratio of near to far ranges is what matters, not the actual distance, the numbers can get very big very fast. A third camera could be added with a range out to 100,000 kilometers!
    The trick is to set the new Camera::SetClearMode() command to make it so only the furthest-range camera clears the color buffer. Additional cameras clear the depth buffer and then render on top of the previous draw. You can use the new Camera::SetOrder() command to ensure that they are drawn in the order you want.
    auto camera1 = CreateCamera(world); camera1->SetRange(0.1,1000); camera1->SetClearMode(CLEAR_DEPTH); camera1->SetOrder(1); auto camera2 = CreateCamera(world); camera2->SetRange(1000,10000); camera2->SetClearMode(CLEAR_DEPTH); camera2->SetOrder(2); auto camera3 = CreateCamera(world); camera3->SetRange(10000,100000000); camera3->SetClearMode(CLEAR_COLOR | CLEAR_DEPTH); camera3->SetOrder(3); Using this technique I was able to render the Earth, sun, and moon to-scale. The three objects are actually sized correctly, at the correct distance. You can see that from Earth orbit the sun and moon appear roughly the same size. The sun is much bigger, but also much further away, so this is exactly what we would expect.

    You can also use these features to render several cameras in one pass to show different views. For example, we can create a rear-view mirror easily with a second camera:
    auto mirrorcam = CreateCamera(world); mirrorcam->SetParent(maincamera); mirrorcam->SetRotation(0,180,0); mirrorcam=>SetClearMode(CLEAR_COLOR | CLEAR_DEPTH); //Set the camera viewport to only render to a small rectangle at the top of the screen: mirrorcam->SetViewport(framebuffer->GetSize().x/2-200,10,400,50); This creates a "picture-in-picture" effect like what is shown in the image below:

    Want to render some 3D HUD elements on top of your scene? This can be done with an orthographic camera:
    auto uicam = CreateCamera(world); uicam=>SetClearMode(CLEAR_DEPTH); uicam->SetProjectionMode(PROJECTION_ORTHOGRAPHIC); This will make 3D elements appear on top of your scene without clearing the previous render result. You would probably want to move the UI camera far away from the scene so only your HUD elements appear in the last pass.
  18. Josh
    Previously I described how multiple cameras can be combined in the new renderer to create an unlimited depth buffer. That discussion lead into multi-world rendering and 2D drawing. Surprisingly, there is a lot of overlap in these features, and it makes sense to solve all of it at one time.
    Old 2D rendering systems are designed around the idea of storing a hierarchy of state changes. The renderer would crawl through the hierarchy and perform commands as it went along, rendering all 2D elements in the order they should appear. It made sense for the design of the first graphics cards, but this style of rendering is really inefficient on modern graphics hardware. Today's hardware works best with batches of objects, using the depth buffer to handle which object appears on top. We don't sort 3D objects back-to-front because it would be monstrously inefficient, so why should 2D graphics be any different?
    We can get much better results if we use the same fast rendering techniques we use for 3D graphics and apply it to 2D shapes. After all, the only difference between 3D and 2D rendering is the shape of the camera projection matrix. For this reason, Turbo Engine will use 2D-in-3D rendering for all 2D drawing. You can render a pure 2D scene by setting the camera projection mode to orthographic, or you can create a second orthographic camera and render it on top of your 3D scene. This has two big implications:
    Performance will be incredibly fast. I predict 100,000 uniquely textured sprites will render pretty much instantaneously. In fact anyone making a 2D PC game who is having trouble with performance will be interested in using Turbo Engine. Advanced 3D effects will be possible that we aren't used to seeing in 2D. For example, lighting works with 2D rendering with no problems, as you can see below. Mixing of 3D and 2D elements will be possible to make inventory systems and other UI items. Particles and other objects can be incorporated into the 2D display.
    The big difference you will need to adjust to is there are no 2D drawing commands. Instead you have persistent objects that use the same system as the 3D rendering.
    Sprites
    The primary 2D element you will work with is the Sprite entity, which works the same as the 3D sprites in Leadwerks 4. Instead of drawing rectangles in the order you want them to appear, you will use the Z position of each entity and let the depth buffer take care of the rest, just like we do with 3D rendering. I also am adding support for animation frames and other features, and these can be used with 2D or 3D rendering.

    Rotation and scaling of sprites is of course trivial. You could even use effects like distance fog! Add a vector joint to each entity to lock the Z axis in the same direction and Newton will transform into a nice 2D physics system.
    Camera Setup
    By default, with a zoom value of 1.0 an orthographic camera maps so that one meter in the world equals one screen pixel. We can position the camera so that world coordinates match screen coordinates, as shown in the image below.
    auto camera = CreateCamera(world); camera->SetProjectionMode(PROJECTION_ORTHOGRAPHIC); camera->SetRange(-1,1); iVec2 screensize = framebuffer->GetSize(); camera->SetPosition(screensize.x * 0.5, -screensize.y * 0.5); Note that unlike screen coordinates in Leadwerks 4, world coordinates point up in the positive direction.

    We can create a sprite and reset its center point to the upper left hand corner of the square like so:
    auto sprite = CreateSprite(world); sprite->mesh->Translate(0.5,-0.5,0); sprite->mesh->Finalize(); sprite->UpdateBounds(); And then we can position the sprite in the upper left-hand corner of the screen and scale it:
    sprite->SetColor(1,0,0); sprite->SetScale(200,50); sprite->SetPosition(10,-10,0);
    This would result in an image something like this, with precise alignment of screen pixels:

    Here's an idea: Remember the opening sequence in Super Metroid on SNES, when the entire world starts tilting back and forth? You could easily do that just by rotating the camera a bit.
    Displaying Text
    Instead of drawing text with a command, you will create a text model. This is a series of rectangles of the correct size with their texture coordinates set to display a letter for each rectangle. You can include a line return character in the text, and it will create a block of multiple lines of text in one object. (I may add support for text made out of polygons at a later time, but it's not a priority right now.)
    shared_ptr<Model> CreateText(shared_ptr<World> world, shared_ptr<Font> font, const std::wstring& text, const int size) The resulting model will have a material with the rasterized text applied to it, shown below with alpha blending disabled so you can see the mesh background. Texture coordinates are used to select each letter, so the font only has to be rasterized once for each size it is used at:

    Every piece of text you display needs to have a model created for it. If you are displaying the framerate or something else that changes frequently, then it makes sense to create a cache of models you use so your game isn't constantly creating new objects. If you wanted, you could modify the vertex colors of a text model to highlight a single word.

    And of course all kinds of spatial transformations are easily achieved.

    Because the text is just a single textured mesh, it will render very fast. This is a big improvement over the DrawText() command in Leadwerks 4, which performs one draw call for each character.
    The font loading command no longer accepts a size. You load the font once and a new image will be rasterized for each text size the engine requests internally:
    auto font = LoadFont("arial.ttf"); auto text = CreateText(foreground, font, "Hello, how are you today?", 18); Combining 2D and 3D
    By using two separate worlds we can control which items the 3D camera draws and which item 2D camera draws: (The foreground camera will be rendered on top of the perspective camera, since it is created after it.) We need to use a second camera so that 2D elements are rendered in a second pass with a fresh new depth buffer.
    //Create main world and camera auto world = CreateWorld(); auto camera = CreateCamera(world); auto scene = LoadScene(world,"start.map"); //Create world for 2D rendering auto foreground = CreateWorld() auto fgcam = CreateCamera(foreground); fgcam->SetProjection(PROJECTION_ORTHOGRAPHIC); fgcam->SetClearMode(CLEAR_DEPTH); fgcam->SetRange(-1,1); auto UI = LoadScene(foreground,"UI.map"); //Combine rendering world->Combine(foreground); while (true) { world->Update(); world->Render(framebuffer); } Overall, this will take more work to set up and get started with than the simple 2D drawing in Leadwerks 4, but the performance and additional control you get are well worth it. This whole approach makes so much sense to me, and I think it will lead to some really cool possibilities.
    As I have explained elsewhere, performance has replaced ease of use as my primary design goal. I like the results I get with this approach because I feel the design decisions are less subjective.
  19. Josh
    A big update is now available for beta subscribers!
    Multipass Rendering
    You can use several cameras to increase the depth range or mix 2D and 3D graphics.
    2D Graphics
    As discussed earlier, 2D graphics are now supported using persistent 2D objects.
    Depth Sorting
    Multiple layers of transparency will now render in correct order, with lighting in each layer. Note that I used an empty script called "null.lua" to prevent the glass surfaces here from being collapsed at load.

    Coroutine Sequences
    Lua coroutine sequences are working now. Lua coroutines can be added to an entity and they will be updated each frame, in order:
    function Start() self:AddCoroutine(self.MoveToPoint,10,0,0) self:AddCoroutine(self.MoveToPoint,10,0,10) self:AddCoroutine(self.MoveToPoint,0,0,10) end Key / Action Bindings
    Key bindings are now working for Lua scripts or C++ actors. You supply a default key code and an action name. Action name is not implemented yet but later it will allow the player to remap their keys.
    Localization
    Along with text rendering and Unicode support, you can now load language definition sets to automatically replace your text when you call CreateText.
    auto lang = LoadLanguage("Config/Localization/German.json"); SetLanguage(lang); Language files are just a bunch of JSON key pairs:
    { "Hello": "Guten Tag", "How are you?": "Wie gehts?", "I must have an apple.": "Ich muss einen Apfel haben." } Vertical fonts are not currently supported.
    Notification Boxes and File Dialogs
    In anticipation of our new editor, these have been added.
  20. Josh
    I'm happy to say the physics joint class in the new engine is completed. I made all the members that are not meant to be accessed private. One interesting part is the parent and child public members, which are constant pointers to private members. This should result in a read-only member in C++. A sol property is used to expose this to Lua in a read-only manner.
    The upvector joint will align an object's Y axis to any vector you set, but still allow rotation around the axis. This is perfect for making 2D games with physics. You can just set the axis to point along the Z world axis, and you will have 2D physics locked to the XY plane.
    class Joint : public SharedObject { std::list<shared_ptr<Joint> >::iterator bodylink[2]; Mat4 entitymatrix[2]; bool uselimits; Vec2 limits; Vec3 origin; PhysicsJoint* physicsjoint; bool motorenabled; bool limitsenabled; float angle; float motorspeed; float motorpower; float stiffness; float spring; Vec2 friction; Mat4 targetmatrix; shared_ptr<Entity> parent_; shared_ptr<Entity> child_; public: Joint(); virtual ~Joint(); const shared_ptr<Entity>& parent; const shared_ptr<Entity>& child; virtual void Break(); virtual void SetSpring(const float relaxation, const float spring, const float damper); virtual float GetSpring(); virtual void SetLimits(const float limits0, const float limits1); virtual Vec2 GetLimits(); virtual void SetStiffness(const float stiffness); virtual float GetStiffness(); virtual void SetMotorSpeed(const float speed); virtual void SetMotorPower(const float power); virtual float GetMotorPower(); virtual float GetMotorSpeed(); virtual void EnableLimits(); virtual void EnableMotor(const bool enabled); virtual void DisableLimits(); virtual void DisableMotor(); virtual bool LimitsEnabled(); virtual bool MotorEnabled(); virtual void SetFriction(const float angularfriction, const float linearfriction); virtual Vec2 GetFriction(); virtual void SetTargetPosition(const Vec3& pos, const float blend = 1.0f); virtual void SetTargetPosition(const float x, const float y, const float z, const float blend = 1.0f); virtual void SetTargetRotation(const Quat& rotation, const float blend = 1.0f); virtual void SetTargetRotation(const Vec3& rotation, const float blend = 1.0f); virtual void SetTargetRotation(const float pitch, const float yaw, const float roll, const float blend = 1.0f); virtual void SetTarget(const float target); virtual void SetTarget(const Mat4& target); friend shared_ptr<Joint> CreateUpVectorJoint(const Vec3&, shared_ptr<Entity>); friend shared_ptr<Joint> CreateKinematicJoint(const float, const float, const float, shared_ptr<Entity>); friend shared_ptr<Joint> CreateSliderJoint(const float, const float, const float, const float, const float, const float, shared_ptr<Entity>, shared_ptr<Entity>); friend shared_ptr<Joint> CreateBallAndSocketJoint(const float, const float, const float, shared_ptr<Entity>, shared_ptr<Entity>); friend shared_ptr<Joint> CreateHingeJoint(const float, const float, const float, const float, const float, const float, shared_ptr<Entity>, shared_ptr<Entity>); }; extern shared_ptr<Joint> CreateUpVectorJoint(const Vec3& pin, shared_ptr<Entity> child); extern shared_ptr<Joint> CreateUpVectorJoint(const float pinx, const float piny, const float pinz, shared_ptr<Entity> child); extern shared_ptr<Joint> CreateKinematicJoint(const Vec3& pos, shared_ptr<Entity> child); extern shared_ptr<Joint> CreateKinematicJoint(const float posx, const float posy, const float posz, shared_ptr<Entity> child); extern shared_ptr<Joint> CreateSliderJoint(const Vec3& pos, const Vec3& pin, shared_ptr<Entity> parent, shared_ptr<Entity> child); extern shared_ptr<Joint> CreateSliderJoint(const float x, const float y, const float z, const float px, const float py, const float pz, shared_ptr<Entity> child, shared_ptr<Entity> parent); extern shared_ptr<Joint> CreateBallAndSocketJoint(const Vec3& pos, shared_ptr<Entity> parent, shared_ptr<Entity> child); extern shared_ptr<Joint> CreateBallAndSocketJoint(const float posx, const float posy, const float posz, shared_ptr<Entity> child, shared_ptr<Entity> parent); extern shared_ptr<Joint> CreateHingeJoint(const Vec3& pos, const Vec3& pin, shared_ptr<Entity> parent, shared_ptr<Entity> child); extern shared_ptr<Joint> CreateHingeJoint(const float x, const float y, const float z, const float pinx, const float piny, const float pinz, shared_ptr<Entity> child, shared_ptr<Entity> parent); And this is what the Lua binding code looks like:
    L->new_usertype<Joint> ( "Joint", sol::base_classes, sol::bases<SharedObject>(), sol::meta_function::index, &SharedObject::dynamic_get, sol::meta_function::new_index, &SharedObject::dynamic_set, "parent", sol::property([](Joint& j) { return j.parent; }), "child", sol::property([](Joint& j) { return j.child; }), "Break", &Joint::Break, "SetSpring", &Joint::SetSpring, "GetSpring", &Joint::GetSpring, "SetLimits", &Joint::SetLimits, "GetLimits", &Joint::GetLimits, "EnableLimits", &Joint::EnableLimits, "DisableLimits", &Joint::DisableLimits, "LimitsEnabled", &Joint::LimitsEnabled, "EnableMotor", &Joint::EnableMotor, "DisableMotor", &Joint::DisableMotor, "MotorEnabled", &Joint::MotorEnabled, "SetFriction", &Joint::SetFriction, "GetFriction", &Joint::GetFriction, "SetStiffness", &Joint::SetStiffness, "GetStiffness", &Joint::GetStiffness, "SetMotorSpeed", &Joint::SetMotorSpeed, "GetMotorSpeed", &Joint::GetMotorSpeed, "SetMotorPower", &Joint::SetMotorPower, "GetMotorPower", &Joint::GetMotorPower, "SetTargetRotation", sol::overload( sol::resolve<void(const Vec3&, const float)>(&Joint::SetTargetRotation), sol::resolve<void(const Quat&, const float)>(&Joint::SetTargetRotation), sol::resolve<void(const float, const float, const float, const float)>(&Joint::SetTargetRotation) ), "SetTargetPosition", sol::overload( sol::resolve<void(const Vec3&, const float)>(&Joint::SetTargetPosition), sol::resolve<void(const float, const float, const float, const float)>(&Joint::SetTargetPosition) ), "SetTarget", sol::overload( sol::resolve<void(const float)>(&Joint::SetTarget), sol::resolve<void(const Mat4&)>(&Joint::SetTarget) ) ); L->set_function("CreateHingeJoint", sol::overload( sol::resolve<shared_ptr<Joint>(const Vec3&, const Vec3&, shared_ptr<Entity>, shared_ptr<Entity>)>(&CreateHingeJoint), sol::resolve<shared_ptr<Joint>(const float, const float, const float, const float, const float, const float, shared_ptr<Entity>, shared_ptr<Entity>)>(&CreateHingeJoint) )); L->set_function("CreateSliderJoint", sol::overload( sol::resolve<shared_ptr<Joint>(const Vec3&, const Vec3&, shared_ptr<Entity>, shared_ptr<Entity>)>(&CreateSliderJoint), sol::resolve<shared_ptr<Joint>(const float, const float, const float, const float, const float, const float, shared_ptr<Entity>, shared_ptr<Entity>)>(&CreateSliderJoint) )); L->set_function("CreateBallAndSocketJoint", sol::overload( sol::resolve<shared_ptr<Joint>(const Vec3&, shared_ptr<Entity>, shared_ptr<Entity>)>(&CreateBallAndSocketJoint), sol::resolve<shared_ptr<Joint>(const float, const float, const float, shared_ptr<Entity>, shared_ptr<Entity>)>(&CreateBallAndSocketJoint) )); L->set_function("CreateKinematicJoint", sol::overload( sol::resolve<shared_ptr<Joint>(const Vec3&, shared_ptr<Entity>)>(&CreateKinematicJoint), sol::resolve<shared_ptr<Joint>(const float, const float, const float, shared_ptr<Entity>)>(&CreateKinematicJoint) )); L->set_function("CreateUpVectorJoint", sol::overload( sol::resolve<shared_ptr<Joint>(const Vec3&, shared_ptr<Entity>)>(&CreateUpVectorJoint), sol::resolve<shared_ptr<Joint>(const float, const float, const float, shared_ptr<Entity>)>(&CreateUpVectorJoint) )); A big thanks goes out to the developers of sol and Newton Dynamics for providing excellent technology for me to build on.
  21. Josh
    The following changes have been made to the GLTF model loader:
    Correctly loaded rotations and orientations. Mesh collision caching for faster loading. Supports transforms with negative scale and correct face orientation. Support for adjustable alpha cutoff value. Support for KHR_materials_unlit extension (full bright materials). This model from SketchFab was useful for testing because it uses so many features of the GLTF format:
    https://sketchfab.com/3d-models/ftm-0970f30574d047b1976ba0aa6f2ef855
    This scene uses alpha masking, transparency, and the full-bright extension, since it already includes lightmaps. The cartoon outline you see on the edges is actually mesh-based.
    Update is available now for beta subscribers. At this point, if you find a GLTF file that does not load correctly, or if there is an extension you would like to see support added for, I would like to hear it.
    I'm also proposing standalone GLTF material files here:
    https://github.com/KhronosGroup/glTF/issues/1420

  22. Josh
    There are two aspects of GLTF files that have non-optimal loading speed. First, the vertex data is not stored in the same exact layout and format as our vertex structure. I found the difference in performance for this was pretty small, even for large models, so I was willing to let it go. Tangents can take a bit longer to build, but those are usually included in the model if they are needed.
    The second issue is the triangle tree structure which is used for raycasting (the pick commands). I found the difference in debug mode was really significant when using a large (two million poly) mesh. Here are the numbers. The big concern is building the pick tree in debug mode, which takes two minutes using this model.
    GLTF Loading Speed (milliseconds)
    Release Mode
    Vertices only: 1249 Vertices + build tangents: 1276 Vertices + build collision: 5563 Debug Mode
    Vertices only: 3758 Vertices + build tangents: 12453 Vertices + build collision: 120,000 I was able to improve this speed significantly by saving vertex and pick tree data to a cache file after processing.
    Release Mode
    Cached data: 1233 Debug Mode
    Cached data: 2707 This is sort of a halfway step between using GLTF natively and converting to our faster-loading GMF2 file format.
    I like the idea of keeping all files in easily-accessible formats like DDS and GLTF, but there are some big questions. Where and how should the mesh cache files be stored? A published game will not necessarily have write access for files in its own install folder, so any files written should be in the user Documents or AppData folders. How should the GLTF file be identified? By checksum? By full file path? The full file path will change when the game is installed on another computer. Should the mesh cache files be shipped with the game, or generated the first time it is run? Do we even need to worry about caching when release mode is used? It seems like only debug mode has a significant performance problem, so maybe this should be something that only gets used during development?j I need to think about this.
  23. Josh
    I'm putting together ideas for a racing game template to add to Leadwerks. We already support vehicles. The challenge is to put together that looks and feels slick and professional, like a real game people want to play. The finished demo will be submitted to Greenlight, GameJolt, IndieDB, itch.io, etc.
     
    Gameplay
    First, I wanted to think about what style of racing I want this to be. I don't want street racing because it's kind of boring, and the level design is more involved. I don't want spintires-style technical offroading because it's too specialized. I want some fun medium-paced 4x4 racing like in
    , but modern. 


     
    This single-player game will pit you against seven computer-controlled components. You win by coming in the top three places. A time-trial option will allow you to compare your scores to other players via Steam leaderboards.
     
    The HUD will display a speedometer, your place in the race, current lap, and total and current lap time.
     
    Cars will be 4x4 trucks, identical except with a different texture.
     
    The player can turn headlights on and off, honk their horn, and drive. The transmission will always be automatic.
     
    Pressing the C key will alternate between views, including 3rd person, 3rd person further away, first-person (in-car), and a free third person camera that doesn't rotate with the car.
     
    Checkpoints will be placed throughout the level, with a sound when you pass through.
     
    After the race is complete, a replay will be performed from data recorded during the race, and scores will be shown on the screen.
     
    Environment
    I want the environment to be scrubby arid desert with big dramatic crags in the background.
     

     
    Roads will be painted on with a dirt texture, and decals will be used to add tire tracks sporadically. Decals will fade out at a fairly close distance, as I plan on having lots of them in the map.
     
    The game will allow you to set the time of day and weather. I have not decided if the weather and time of day will change as the race progresses. Time of day includes night, morning, afternoon, and evening.
     
    Weather can also be set, with options for sunny, rainy, and snowy. Snow will use a post-processing effect to add snow on all upwards-facing surfaces. Tire grip will be reduced in snowy and rainy conditions.
     
    The vehicles will throw up a cloud of dirt, mud, water, or other material, based on the primary texture of the terrain where they are contacting. Dirt, water, raindrops, ice, snow, and other effects will hit the camera and remain for a moment before fading.
     
    Screen-space reflection will be showcased heavily on the vehicle bodies.
     
    One song will play for the menu and one for the race. The song will sound something like this at 0:44 because it sounds modern:


     
    Or maybe this:

     
    Scope Limits
    The game is single-player only.
    I'm not going to bother with changes to the terrain or vehicles leaving tread marks.
    There will be no arms visible when the camera is inside the car.
    The environment will be static. There will be no destruction of the environment, and no moving objects or physically interactive items except for the cars.
    I am not going to implement an overhead map.
    I am not going to implement vehicle damage.
    Other than finishing the game GUI, I do not want to implement any new features in Leadwerks to complete this.
    The game will not attempt to be realistic or follow any real-world racing events.
    The race will not portray an audience or people standing around.
    No weapons.

  24. Josh
    Still a lot of things left to do. Now that I have very large-scale rendering working, people want to fill it up with very big terrains. A special system will be required to handle this, which adds another layer to the terrain system. Also, I want to resume work on the voxel GI system, as I feel these results are much better than the performance penalty of ray-tracing. There are a few odds and ends like AI navigation and cascaded shadow maps to finish up.
    I am planning to have the engine more or less finished in the spring, and begin work on the new editor. Our workflow isn't going to change much. The new editor is just going to be a more refined version of what we already have, although it is a complete new program written from scratch, this time in C++.
    It's kind of overwhelming but I have confidence in the whole direction and strategy of this new product.
  25. Josh
    If were a user of BlitzMax in the past, you will love these convenience commands in Turbo Engine:
    int Notify(const std::wstring& title, const std::wstring& message, shared_ptr<Window> window = nullptr, const int icon = 0) int Confirm(const std::wstring& title, const std::wstring& message, shared_ptr<Window> window = nullptr, const int icon = 0) int Proceed(const std::wstring& title, const std::wstring& message, shared_ptr<Window> window = nullptr, const int icon = 0)
×
×
  • Create New...