Jump to content

Josh

Staff
  • Posts

    23,222
  • Joined

  • Last visited

Blog Entries posted by Josh

  1. Josh
    Leadwerks 4 supports compressed and encrypted game files in ZIP format, but there are many different custom package formats different games use. The plugin system in Leadwerks 5 allows us to create importers for these different package formats so we can access content directly from commercial games you have installed on your computer. The example below shows how to load a VTF texture from the game Half-Life 2 directly from the game's install files.
    First we need to load some plugins to deal with these weird file formats.
    --Load VPK package loader plugin local vpkloader = LoadPlugin("Plugins/VPK.dll") --Load VTF texture loader plugin local vtfloader = LoadPlugin("Plugins/VTF.dll") Next we will load a single VPK package directly from the Half-Life 2 install directory. If you have Steam installed somewhere other than the default path you can change the value of the STEAM_PATH variable to load the package from somewhere else.
    --Steam installation path local STEAM_PATH = "C:/Program Files (x86)/Steam" --Load package (Half-Life 2 must be installed) local pkg = LoadPackage(STEAM_PATH.."/steamapps/common/Half-Life 2/hl2/hl2_textures_dir.vpk") Now we can load content directly from the Half-Life 2 game files. Although the file specified below does not actually exist in the game folder, it will be loaded as if it was because of the VPK package we previously loaded.
    --Load texture local tex = LoadTexture("materials/building_template/buildingset040a.vtf") Here is the result when I run the program:

    Now if you wanted to load all the HL2 data files when your game starts, that is easy to do too. If you place this script in the "Scripts/Start" folder all the content will be automatically made available:
    local STEAM_PATH = "C:/Program Files (x86)/Steam/" local HL2_PATH = STEAM_PATH.."steamapps/common/Half-Life 2/hl2/" local dir = LoadDir(HL2_PATH) local k,v LoadedPackages = {} for k,v in ipairs(dir) do local ext = Lower(ExtractExt(v)) if ext == "vpk" then local pkg = LoadPackage(v) if pkg ~= nil then table.insert(LoadedPackages, pkg) end end end What is this good for? You can browse and view all the content in your installed games and use some assets as placeholders. Valve is pretty lenient with their IP so if your game is only published on Steam, you could probably use all the Source games textures and models. You could make a mod that replaces the original game engine but requires the game to be installed to run. It would also not be too hard to integrate these features into the new editor so that it can be used as a modding tool for different games and allow you to create new game levels.
    You can get the source for this and other plugins on Github.
  2. Josh
    I've restructured the plugin SDK for our new engine and created a new repository on Github here:
    https://github.com/Leadwerks/PluginSDK
    The GMF2 format will only be used as an internal data transfer protocol for model loader plugins. Our main supported file format will be GLTF.
    As of now, the plugin system can be used to write texture loaders for different file formats, model loaders, or to modify behavior of particles in the new particle system. The FreeImage texture loader has been moved out of the core engine and into a plugin so you no longer have to include the FreeImage DLL unless you want to use it. The main supported texture format will be DDS, but the FreeImage plugin supports many common image file formats.
  3. Josh
    The Leadwerks 5 beta will soon be updated with particle emitters and an example particle system plugin. Previously, I showed some impressive results with physically interactive particles that collide with and exert forces on the environment. I decided to use the plugin system for controlling particle behavior, as this offers the best performance and can be run on the physics thread. 
    A particle system plugin uses some predefined structures and functions to modify the behavior of particles when they are emitted or as they are updated. This allows for unlimited features to be added to the particle system, because anything you want can be added with a plugin. A system for sending settings to the plugin will be implemented in the future so you can adjust the plugin settings and see the results. The default particle settings and features will probably stay pretty barebones and I will just use the plugin system to add any advanced functionality since it is so flexible.
    void EmitParticle(ParticleModifier* mod, ParticleSystem* particlesystem, Particle* particle) { if (mod->emissionshape == EMISSION_SHAPE_BOX) { particle->position[0] = Random(-mod->area[0], mod->area[0]); particle->position[1] = Random(-mod->area[1], mod->area[1]); particle->position[2] = Random(-mod->area[2], mod->area[2]); } else if (mod->emissionshape == EMISSION_SHAPE_CYLINDER) { particle->position[0] = Random(-mod->area[0], mod->area[0]); particle->position[1] = Random(-mod->area[1], mod->area[1]); particle->position[2] = Random(-mod->area[2], mod->area[2]); auto l = sqrt(particle->position[0] * particle->position[0] + particle->position[1] * particle->position[1] + particle->position[2] * particle->position[2]); if (l > 0.0f) { particle->position[0] /= l; particle->position[1] /= l; particle->position[2] /= l; } } particle->position[0] += particlesystem->matrix[12]; particle->position[1] += particlesystem->matrix[13]; particle->position[2] += particlesystem->matrix[14]; } There are three other new Lua examples included. Coroutines.lua shows how a sequence of actions can be added to an entity before the game starts, and the actions will be executed in order:
    --Create model local model = CreateBox(world) --Add some behaviors to be executed in order model:AddCoroutine(MoveToPoint, Vec3(3,0,0), 2) model:AddCoroutine(MoveToPoint, Vec3(-3,0,0), 2) model:AddCoroutine(MoveToPoint, Vec3(0,0,0), 2) --Main loop while window:Closed() == false do world:Update() world:Render(framebuffer) end This is great for setting up cut scenes or other sequences of events.
    An example showing how to enable tessellation is also included. Tessellation is now a per-camera setting.
    camera:SetTessellation(10) The number you input is the size in pixels of the tessellated primitives. Use zero to disable tessellation. Tessellation is disabled by default on all cameras.
    Finally, an example showing how to use a texture loader plugin is included. All you have to do is load the plugin and after that textures can be loaded in VTF format:
    local vtfloader = LoadPlugin("Plugins/VTF.dll") local tex = LoadTexture("Materials/wall01.vtf")  
  4. Josh
    I made some changes to the design of the particle system. I am less concerned with the exact behavior of particles as they move around and move interested right now in building a system with good performance and deep physics interactions. Although I want particle behavior to be customizable, I don't think scripts are the right tool for the job. C++ plugins are better suited for this for two reasons.
    C++ is much faster, and particles are a system that will make heavy use of that. Lua scripts can't be run on separate threads. In Leadwerks Engine 4 we have basic particle collisions, but I wanted something more interactive in the new system. I move the particle update code into the physics thread. I implemented collision as well as the ability for particles to exert forces on other objects. Here's what happens when some slow-moving smoke particles interact with a scene: The lower platform rotates freely while the upper platform is motorized.
    When the particle velocity is increase they start to behave like a stream of water:
    Best of all, the speed is surprisingly fast. 4000 particles with collision update in just 2 milliseconds. The code scales well across cores so if you have a lot of CPU cores simulations with 100,000 particles are possible.
    Right now particles are processed in the physics thread, and get sent to the rendering thread for display, but right now the main thread actually never sees the individual particles.
    This is fast enough I think particles will default to full physics. Instead of just being a dumb visual effect we are going to have fully interactive fluids and gases. Flamethrowers can fill a room with fire and it will creep around corners to fill a space.
  5. Josh
    I wanted to work on something a bit different, and this sure is different. I've got a framework of a new particle system worked out. What's really special about this system is the amount of interactivity the particles will allow.
    Particle-world collisions. Particle-particle collisions (repulsion) Particle-particle cohesion (fluids with surface tension) Instead of just being a visual effect, I want our new particles to be fully interactive with physics so that particles can exert forces on objects. This will allow you to simulate fluids, smoke, and other effects in a realistic manner, not just dumb collision of particles bounding off walls. It should even be possible to simulate hydrophobic and hydrophillic liquids if you mix two together with different cohesion values.
    Basically what I want is something like Nvidia Flow on the CPU and exerting forces on the world. So if you had water falling on a water wheel the wheel would move because of the forces, or a blast of wind could knock objects over without any special force fields or other fake effects.
    I also have a technique worked out that will allow lighting of clouds and other masses of gas, with back-scattering.
    Emitters can be instanced so if you have one really high-quality torch effect, for example, you can instance it and use it as much as you like without any additional computational cost per instance.
    Particle emitters can be controlled with a Lua script or C++ actor. Two new functions are available, UpdateParticle() and EmitParticle(). Here is a script that controls particle behavior over time:
    entity.particleVelocity = Vec3(0,0,1) entity.particleAcceleration = Vec3(0,-1,0) entity.inverseSquareFalloff = true entity.particleRadiusBegin = 0.1 entity.particleRadiusEnd = 0.2 entity.particleColorBegin = Vec4(1,1,1,1) entity.particleColorEnd = Vec4(1,1,1,0) entity.particleMass = 1 entity.particleSpin = 5 function entity:Start() self.particleColorBeginHSL = HSL(self.particleColorBegin.rgb) self.particleColorEndHSL = HSL(self.particleColorEnd.rgb) local emitter = Emitter(self) if emitter == nil then return end local n for n = 1, #emitter.particles do emitter.particles[n].mass = self.particleMass emitter.particles[n].falloff = (n-1) / (#emitter.particles - 1) end end function entity:EmitParticle(index) local emitter = Emitter(self) if emitter == nil then return end emitter.particles[index].position = self:GetPosition(true) emitter.particles[index].velocity = TransformVector(self.particleVelocity,self,nil) emitter.particles[index].radius = self.particleRadiusBegin emitter.particles[index].color = self.particleColorBegin end function entity:UpdateParticle(index) local emitter = Emitter(self) if emitter == nil then return end emitter.particles[index].velocity = emitter.particles[index].velocity + self.particleAcceleration / 60 local falloff = emitter.particles[index].falloff if self.inverseSquareFalloff then falloff = falloff * falloff end emitter.particles[index].color.rgb = RGB(self.particleColorBeginHSL * (1 - falloff) + self.particleColorEndHSL * falloff) emitter.particles[index].color.a = self.particleColorBegin.a * (1 - falloff) + self.particleColorEnd.a * falloff emitter.particles[index].radius = self.particleRadiusBegin * (1 - falloff) + self.particleRadiusEnd * falloff emitter.particles[index].rotation = emitter.particles[index].rotation + self.particleSpin / 60 end A different script could be used to make particles emit from vertices of a model, to make the model appear to be on fire, or other effects. This will allow infinite customization to create any behavior you want.
    Particle physics will be calculated on the physics thread so I expect them to be very fast.
  6. Josh
    Putting all the pieces together, I was able to create a GUI with a sprite layer, attach it to a camera with a texture buffer render target, and render the GUI onto a texture applied to a 3D surface. Then I used the picked UV coords to convert to mouse coordinates and send user events to the GUI. Here is the result:

    This can be used for GUIs rendered onto surfaces in your game, or for a user interface that can be interacted with in VR. This example will be included in the next beta update.
  7. Josh
    I have been working on 2D rendering off and on since October. Why am I putting so much effort into something that was fairly simple in Leadwerks 4? I have been designing a system in anticipation of some features I want to see in the GUI, namely VR support and in-game 3D user interfaces. These are both accomplished with 2D drawing performed on a texture. Our system of sprite layers, cameras, and sprites was necessary in order to provide enough control to accomplish this.
    I now have 2D drawing to a texture working, this time as an official supported feature. In Leadwerks 4, some draw-to-texture features were supported, but it was through undocumented commands due to the complex design of shared resources between OpenGL contexts. Vulkan does not have this problem because everything, including contexts (or rather, the VK equivalent) is bound to an abstract VkInstance object.

    Here is the Lua code that makes this program:
    --Get the primary display local displaylist = ListDisplays() local display = displaylist[1]; if display == nil then DebugError("Primary display not found.") end local displayscale = display:GetScale() --Create a window local window = CreateWindow(display, "2D Drawing to Texture", 0, 0, math.min(1280 * displayscale.x, display.size.x), math.min(720 * displayscale.y, display.size.y), WINDOW_TITLEBAR) --Create a rendering framebuffer local framebuffer = CreateFramebuffer(window); --Create a world local world = CreateWorld() --Create second camera local texcam = CreateCamera(world) --Create a camera local camera = CreateCamera(world) camera:Turn(45,-45,0) camera:Move(0,0,-2) camera:SetClearColor(0,0,1,1) --Create a texture buffer local texbuffer = CreateTextureBuffer(512,512,1,true) texcam:SetRenderTarget(texbuffer) --Create scene local box = CreateBox(world) --Create render-to-texture material local material = CreateMaterial() local tex = texbuffer:GetColorBuffer() material:SetTexture(tex, TEXTURE_BASE) box:SetMaterial(material) --Create a light local light = CreateLight(world,LIGHT_DIRECTIONAL) light:SetRotation(55,-55,0) light:SetColor(2,2,2,1) --Create a sprite layer. This can be shared across different cameras for control over which cameras display the 2D elements local layer = CreateSpriteLayer(world) texcam:AddSpriteLayer(layer) texcam:SetPosition(0,1000,0)--put the camera really far away --Load a sprite to display local sprite = LoadSprite(layer, "Materials/Sprites/23.svg", 0, 0.5) sprite:MidHandle(true,true) sprite:SetPosition(texbuffer.size.x * 0.5, texbuffer.size.y * 0.5) --Load font local font = LoadFont("Fonts/arial.ttf", 0) --Text shadow local textshadow = CreateText(layer, font, "Hello!", 36 * displayscale.y, TEXT_LEFT, 1) textshadow:SetColor(0,0,0,1) textshadow:SetPosition(50,30) textshadow:SetRotation(90) --Create text text = textshadow:Instantiate(layer) text:SetColor(1,1,1,1) text:SetPosition(52,32) text:SetRotation(90) --Main loop while window:Closed() == false do sprite:SetRotation(CurrentTime() / 30) world:Update() world:Render(framebuffer) end I have also added a GetTexCoords() command to the PickInfo structure. This will calculate the tangent and bitangent for the picked triangle and then calculate the UV coordinate at the picked position. It is necessary to calculate the non-normalized tangent and bitangent to get the texture coordinate, because the values that are stored in the vertex array are normalized and do not include the length of the vectors.
    local pick = camera:Pick(framebuffer, mousepos.x, mousepos.y, 0, true, 0) if pick ~= nil then local texcoords = pick:GetTexCoords() Print(texcoords) end Maybe I will make this into a Mesh method like GetPolygonTexCoord(), which would work just as well but could potentially be useful for other things. I have not decided yet.
    Now that we have 2D drawing to a texture, and the ability to calculate texture coordinates at a position on a mesh, the next step will be to set up a GUI displayed on a 3D surface, and to send input events to the GUI based on the user interactions in 3D space. The texture could be applied to a computer panel, like many of the interfaces in the newer DOOM games, or it could be used as a panel floating in the air that can be interacted with VR controllers.
  8. Josh
    In Leadwerks 4, render-to-texture was accomplished with the SetRenderTarget command, which allowed a camera to draw directly to a specified texture, while hiding the underlying framebuffer object (FBO). In the new engine we have a bit more explicit handling of this behavior. This is largely in part due to the use of Vulkan's bindless design, which greatly improves the context-binding design of OpenGL. The Leadwerks "Buffer" class was never documented or officially supported because the underlying OpenGL functionality made the system pretty messy, but the design of Vulkan simplifies this aspect of graphics.
    We have seen that the Framebuffer classes replaces the LE4 context. I've added a TextureBuffer class which can be created similarly:
    shared_ptr<TextureBuffer> CreateTextureBuffer(const int width, const int height, const int colorcomponents = 1, const bool depthcomponent = true, const int samples = 0); Once a TextureBuffer is created, you can set a camera to target it for rendering:
    camera->SetRenderTarget(texbuffer); You can also apply its color component(s) to a material:
    material->SetTexture(texbuffer->GetColorBuffer(0), TEXTURE_BASE); You could also retrieve the depth buffer and apply that to a material, rendering the scene from the top down and using the depth in a rain or snow shader, for example.
    This functionality will later be used to render the GUI system to a texture for use in VR or with in-game menus painted onto 3D surfaces.
    Like everything with Vulkan, this involved a very long process of figuring out everything we need to use, discarding the things we don't, and packaging it up in a structure that is actually usable by the end user. However, once all that is done we have a very powerful system that is optimized for exactly the way modern GPUs work. Here is a small sample of some of my code, just to give you an idea of how complicated this stuff is:
    for (auto pair : visset->cameravislists) { auto cam = pair.first; clear[1].color = { cam->clearcolor.r, cam->clearcolor.g, cam->clearcolor.b, cam->clearcolor.a }; auto light = dynamic_pointer_cast<RenderLight>(cam); if (light == nullptr and cam->rendertarget == nullptr) continue; renderpass[0] = device->shadowpass; renderpass[1] = device->renderpass[CLEAR_COLOR | CLEAR_DEPTH]; int faces = 1; if (light) { if (light->description.type == LIGHT_POINT) faces = 6; } if (MULTIPASS_CUBEMAP) faces = 1; for (int face = 0; face < faces; ++face) { renderPassBeginInfo.clearValueCount = 2; if (light) { renderPassBeginInfo.renderPass = device->shadowpass->pass; if (light->description.type == LIGHT_POINT and MULTIPASS_CUBEMAP == true) { renderPassBeginInfo.renderPass = device->cubeshadowpass->pass; } renderPassBeginInfo.framebuffer = light->shadowbuffer[face]->framebuffer; renderPassBeginInfo.renderArea.extent.width = light->shadowbuffer[face]->size.width; renderPassBeginInfo.renderArea.extent.height = light->shadowbuffer[face]->size.height; } else { renderpass[0] = device->renderpass[CLEAR_COLOR | CLEAR_DEPTH]; int cc = cam->rendertarget->CountColorTextures(); renderPassBeginInfo.renderPass = device->rendertotexturepass[cc][int(cam->rendertarget->depthtexture != nullptr)]->pass; renderPassBeginInfo.framebuffer = cam->rendertarget->framebuffer; renderPassBeginInfo.renderArea.extent.width = cam->rendertarget->size.width; renderPassBeginInfo.renderArea.extent.height = cam->rendertarget->size.height; } vkCmdBeginRenderPass(commandbuffers[currentFrame]->commandbuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); RecordDraw(currentFrame, cam, pair.second, renderpass[0], face); commandbuffers[currentFrame]->EndRenderPass(); if (light) commandbuffers[currentFrame]->BindResource(light->shadowbuffer[face]); //Copy output to render texture if (cam->rendertarget) { for (int n = 0; n < cam->rendertarget->colortarget.size(); ++n) { if (cam->rendertarget->colortarget[n] != nullptr) { commandbuffers[currentFrame]->TransitionImageLayout(pair.first->rendertarget->colortexture[n], VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, -1); commandbuffers[currentFrame]->TransitionImageLayout(pair.first->rendertarget->colortarget[n], VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, -1); VkImageCopy regions = {}; regions.dstOffset = {0u,0u,0u}; regions.extent = { uint32_t(cam->rendertarget->colortarget[n]->size.x), uint32_t(cam->rendertarget->colortarget[n]->size.y), 1u}; regions.srcOffset = regions.dstOffset; regions.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; regions.dstSubresource.baseArrayLayer = 0; regions.dstSubresource.layerCount = 1; regions.dstSubresource.mipLevel = 0; regions.srcSubresource = regions.dstSubresource; vkCmdCopyImage(commandbuffers[currentFrame]->commandbuffer, cam->rendertarget->colortexture[n]->vkimage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, cam->rendertarget->colortarget[n]->vkimage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &regions); commandbuffers[currentFrame]->TransitionImageLayout(pair.first->rendertarget->colortarget[n], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, -1); } } } } } Below is a simple Lua program that sets up a scene with two cameras, and renders one camera to a texture buffer which is displayed on the middle box itself.
    --Get the primary display local displaylist = ListDisplays() local display = displaylist[1]; if display == nil then DebugError("Primary display not found.") end local displayscale = display:GetScale() --Create a window local window = CreateWindow(display, "Render to Texture", 0, 0, math.min(1280 * displayscale.x, display.size.x), math.min(720 * displayscale.y, display.size.y), WINDOW_TITLEBAR) --Create a rendering framebuffer local framebuffer = CreateFramebuffer(window); --Create a world local world = CreateWorld() --Create second camera local texcam = CreateCamera(world) texcam:SetClearColor(1,0,1,1) --Create a camera local camera = CreateCamera(world) camera:Move(0,0,-2) camera:SetClearColor(0,0,1,1) --Create a texture buffer local texbuffer = CreateTextureBuffer(512,512,1,true) texcam:SetRenderTarget(texbuffer) --Create scene local box = CreateBox(world) local cone = CreateCone(world) cone:SetPosition(2,0,0) cone:SetColor(1,0,0,1) local sphere = CreateSphere(world) sphere:SetPosition(-2,0,0) sphere:SetColor(0,1,0,1) --Create render-to-texture material local material = CreateMaterial() local tex = texbuffer:GetColorBuffer() material:SetTexture(tex, TEXTURE_BASE) box:SetMaterial(material) --Create a light local light = CreateLight(world,LIGHT_DIRECTIONAL) light:SetRotation(35,-55,0) --Main loop while window:Closed() == false do texcam:SetPosition(0,0,0) texcam:Turn(0,1,0) texcam:Move(0,0,-2) world:Update() world:Render(framebuffer) end Here is the result. Look how simple it is to control this powerful system!

  9. Josh
    The GUIBlock structure now has an iVec4 "clipregion" member. This allows you to set a clipping region around a block to prevent it from drawing outside the region. The region will automatically be shrunk to show only the visible area of the widget, within the visible area of its parent. The purpose of this is to prevent text from overflowing outside of the widget. I found this necessary because the multi-line text area widget involves the dynamic creation of different persistent 2D elements, and keeping the outer border on top was becoming a problem.
    The Sprite class now has a SetClipRegion(iVec4) method, which is what gets used to display the above effect when the GUI is rendered with 3D graphics.
    Precompiled headers are enabled for the Visual Studio project. The first build will still be prohibitively slow but subsequent builds should be faster.
    Vulkan headers and lib are now included in the project, so you don't have to download the Vulkan SDK separately.
    Some other new things are included, so be sure to take a look.
  10. Josh
    An update is available for Leadwerks Game Engine 5 beta. The GUI system is now working with support for the following items:
    Panel Button Tabber Label Hyperlink Text field (editable, single line) Text area (multiline, read-only, allows text selection) The GUI scripts use a system of "blocks". You can add a solid rectangle, a wire rectangle, (both support rounded corners) or a text block.
    The drawing hierarchy is not yet respected, so if you type a long line of text in a text field, for example, the text will overrun the container.
    Some weirdness exists when GUI blocks are resized, can be seen in the tabbed panel tabs.
    Alpha anti-alias is not currently implemented so curved edges will be a bit pixellated. Has to do with pre-multiplied alpha the engine now uses by default.
    It's better to move, resize, show and hide, and change the text of blocks than to clear the widget and recreate them. There was some difficult stuff here with the culling and render tweening system I had to work out.
  11. 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.
  12. 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  
  13. 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  
  14. 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
  15. 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  
  16. 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.
  17. 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.
  18. 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.
  19. 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.
  20. 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.
     
  21. 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.
  22. 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".
  23. 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.
  24. 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

  25. 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.
×
×
  • Create New...