Jump to content

Josh

Staff
  • Posts

    23,222
  • Joined

  • Last visited

Blog Entries posted by Josh

  1. Josh

    Articles
    When it comes to complex projects I like to focus on whatever area of technology causes me the most uncertainty or worry. Start with the big problems, solve those, and as you continue development the work gets easier and easier. I decided at this stage I really wanted to see how well Vulkan graphics work on Mac computers.
    Vulkan doesn't run natively on Mac, but gets run through a translation library called MoltenVK. How well does MoltenVK actually work? There was only one way to find out...
    Preparing the Development Machine
    The first step was to set up a suitable development machine. The only Mac I currently own is a 2012 MacBook Pro. I had several other options to choose from:
    Use a remote service like MacInCloud to access a new Mac remotely running macOS Big Sur. Buy a new Mac Mini with an M1 chip ($699). Buy a refurbished Mac Mini ($299-499). What are my requirements?
    Compile universal binaries for use on Intel and ARM machines. Metal graphics. I found that the oldest version of Xcode that supports universal binaries is version 12.2. The requirements for running Xcode 12.2 are macOS Catalina...which happens to be the last version of OSX my 2012 MBP supports! I tried upgrading the OS with the Mac App Store but ran into trouble because the hard drive was not formatted with the new-ish APFS drive format. I tried running disk utility in a safe boot but the option to convert the file system to APFS was disabled in the program menu, no matter what I did. Finally, I created a bootable install USB drive from the Catalina installer and did a clean install of the OS from that.
    I was able to download Xcode 12.2 directly from Apple instead of the App Store and it installed without a hitch. I also installed the Vulkan SDK for Mac and the demos worked fine. The limitations on this Mac appear to be about the same as an Intel integrated chip, so it is manageable (128 shader image units accessible at any time). Maybe this is improved in newer hardware. Performance with this machine in macOS Catalina is actually great. I did replace the mechanical hard drive with an SSD years ago, so that certainly helps.
    Adding Support for Universal Binaries
    Mac computers are currently in another big CPU architecture shift from Intel x64 to arm64. They previously did this in 2006 when they moved from PowerPC to Intel processors, and just like now, they previously used a "universal binary" for static and shared libraries and executables.
    Compiling my own code for universal binaries worked with just one change. The stat64 structure seems to be removed for the ARM builds, but changing this to "stat" worked without any problems. The FreeImage texture loader plugin, on the other hand, required a full day's work before it would compile. There is a general pattern that when I am working with just my own code, everything works nicely and neatly, but when I am interfacing with other APIs productivity drops ten times. This is why I am trying to work out all this cross-platform stuff now, so that I can get it all resolved and then my productivity will skyrocket.
    macOS File Access
    Since Mojave, macOS has been moving in a direction of requiring the developer to explicitly request access the parts of the file system, or for the user to explicitly allow access. On one hand, it makes sense to not allow every app access to all your user files. On the other hand, this really cripples the functionality of Mac computers. ARM CPUs do no in and of themselves carry any restrictions I am aware of, but it does look like the Mac is planned to become another walled garden ecosystem like iOS.
    I had to change the structure of user projects so that the project folders are included in the build. All files and subfolders in the blue folders are packaged into your Xcode application automatically, and the result is a single app file (which is really a folder) ready to publish to the Mac App Store.

    However, this means the Leadwerks-style "publish" feature is not really appropriate for the new editor. Maybe there will be an optional extension that allows you to strip unused files from a project?
    There are still some unanswered questions about how this will work with an editor that involves creating and modifying large numbers of files, but the behavior I have detailed above is the best for games and self-contained applications.
    MacOS is now about as locked down as iOS. You cannot run code written on another machine that is not signed with a certificate, which means Apple can probably turn anyone's program off at any time, or refuse to grant permission to distribute a program. So you might want to think twice before you buy into the Mac ecosystem.

    MoltenVK
    Integration with the MoltenVK library actually went pretty smoothly. However, importing the library into an Xcode project will produce an error like "different teamID for imported library" unless you add yet another setting to your entitlements ilst, "com.apple.security.cs.disable-library-validation" and set it to YES.
    I was disappointed to see the maximum accessible textures per shader are 16 on my Nvidia GEForce 750, but a fallback will need to be written for this no matter what because Intel integrated graphics chips have the same issue.
    Finally, after years of wondering whether it worked and months of work, I saw the beautiful sight of a plain blue background rendered with Metal:

    It looks simple, but now that I have the most basic Vulkan rendering working on Mac things will get much easier from here on out.
  2. Josh

    Articles
    I have procrastinated testing of our new 3D engine on AMD hardware for a while. I knew it was not working as-is, but I was not too concerned. One of the promises of Vulkan is better support across-the-board and fewer driver bugs, due to the more explicit nature of the API. So when I finally tried out the engine on an R9 200 series card, what would actually happen? Would the promise of Vulkan be realized, or would developers continue to be plagued by problems on different graphics cards? Read on to find out how Vulkan runs on AMD graphics cards.
    To test how Vulkan on AMD graphics cards, the first thing I had to do was run the new engine on a machine with an AMD graphics card. I removed the Nvidia card from my PC tower and inserted an AMD R9 200 series graphics card into the PCI-E slot of the motherboard. Then I turned on the computer's power by pressing the power button and powering up my computer with an AMD graphics card. What would happen? Would the AMD graphics card run Vulkan successfully?
    The first error I encountered while running the new 3D engine with Vulkan on an AMD graphics card was that the shadowmap texture format had been explicitly declared as depth-24 / stencil 8, and it should have checked the supported formats to find a depth format the AMD graphics card supported for Vulkan. That was easily fixed.
    The second issue was that my push constants structure was too big. There is a minimum limit of 128 bytes for the push constants structure in Vulkan 1.1 and 1.2. I never encountered this limit before, but on the AMD graphics card it was 128. I was able to eliminate an unneeded vec4 value to bring the structure size down to 128 bytes from 144 bytes.
    With these issues fixed, the new engine with Vulkan ran perfectly on my AMD graphics card. The engine also works without a hitch on Intel graphics, with the exception that the number of shader image units seems to be a hardware-limited feature on those chipsets. We've also seen very reliable performance on Intel chips, although the number of shader image units is severely restricted on that hardware. Overall it appears the promise of fewer driver bugs under Vulkan is holding true, although there is very wide variability in the hardware capabilities that requires rendering fallbacks.
  3. Josh
    One month ago I began work to investigate what it would take to bring Ultra App Kit, the foundation for our new generation of game development tools, to Linux. Today I am happy to share my progress with you and discuss some of the things I have learned.
    Developed by MIT in the year 1984, X11 is an interesting beast that is easy to start with, but can become quite difficult once you get into the details. (Wayland support is of course an obvious step in the not-too-distant future but I have to work with what exists here now today and Ubuntu 20.04 still uses X by default.) The single hardest part had to do with calls to XSetInputFocus on windows that had not yet been mapped. X has a weird asynchronous design, yet XSetInputFocus does not seem to get added to the command queue and instead depends on the mapping (visible) state of a window right now. That means that is you create or show a window and then immediately activate it, an error will occur that looks something like this:
    Error of failed request: BadMatch (invalid parameter attributes) Major opcode of failed request: 42 (X_SetInputFocus) Serial number of failed request: 12 Current serial number in output stream: 12 The way around this is to call XMapWindow and then wait on the event queue until a MapNotify event for that window occurs, adding all other events into a list that can be evaluated on the next call to WaitEvent(). The time elapsed is checked inside the loop in case something weird happens and the desired event is never received:
    void Window::Show() { if (!Hidden()) return; XMapWindow(display->GetHandle(),xwindow); XMoveResizeWindow(display->GetHandle(), GetHandle(),m_position.x,m_position.y,size.x,size.y); XFlush(display->GetHandle()); XSync(display->GetHandle(),false); auto engine = GameEngine::Get(); XEvent ev = {}; auto start = Millisecs(); while (xhidestate) { XNextEvent(display->GetHandle(),&ev); if (ev.type == MapNotify and ev.xany.window == GetHandle()) { xhidestate = false; return; } if (engine) engine->storedxevents.push_back(ev); Sleep(1); if (Millisecs() - start > 5000) { Warn("MapNotify is taking a long time to be received. This may cause window errors."); return; } } } POSIX timers are strange creatures that seem to follow rules all their own. A timer callback gets triggered during any call to sleep() when a timer tick has occurred, but a mutex lock inside the callback will freeze the program. I ended up using the much simpler timerfd interface.
    Double-buffering, good text rendering, and alpha blending are all different extensions built on top of the base X11 system. Getting all of this to work together took a lot of trial and error. However, I think you will agree based on the screenshots below that this work has been worthwhile.


    Multi-select draggable treeview with insertion between nodes
     

    Multi-line text display with optional word wrapping
     

    Hierarchical menu system with real popup windows
     
    For the final leg of development I have set up a small Kickstarter campaign. If you haven't gotten Ultra App Kit yet this is a good opportunity to grab it before the Linux build is released. Ultra App Kit can also be purchase in our store or on Steam.
     
  4. Josh

    Articles
    I recently fired up an install of Ubuntu 20.04 to see what the state of development on Linux is now, and it looks like things have improved dramatically since I first started working with Linux in 2013. Visual Studio Code looks and works great on Linux, and I was able to load the Ultra Engine source code and start compiling, although there is still much work to do. The debugger is fantastic, especially after using the Code::Blocks debugger on Linux, which was absolutely sadistic. (They're both using GDB under the hood but the interface on Code::Blocks was basically unusable.) Regardless of whatever their motivation was, Microsoft (or rather, the developers of the Atom editor VS Code was based on) has made a huge contribution to usability on the Linux desktop. 

    Settings up C++ compilation in Visual Studio Code is rather painful. You have to edit a lot of configuration files by hand to specify the exact command line GCC should use, but it is possible:
    { "tasks": [ { "type": "cppbuild", "label": "C/C++: g++ build active file", "command": "/usr/bin/g++", "args": [ "-g", "-D_ULTRA_APPKIT", "./Libraries/Plugin SDK/GMFSDK.cpp", "./Libraries/Plugin SDK/MemReader.cpp", "./Libraries/Plugin SDK/MemWriter.cpp", "./Libraries/Plugin SDK/TextureInfo.cpp", "./Libraries/Plugin SDK/Utilities.cpp", "./Libraries/Plugin SDK/half/half.cpp", "./Libraries/freeprocess/freeprocess.c", "./Libraries/s3tc-dxt-decompressionr/s3tc.cpp", "./Libraries/stb_dxt/stb_dxt.cpp", "./Classes/Object.cpp", "./Classes/Math/Math_.cpp", "./Classes/Math/Vec2.cpp", "./Classes/Math/Vec3.cpp", "./Classes/Math/Vec4.cpp", "./Classes/Math/iVec2.cpp", "./Classes/Math/iVec3.cpp", "./Classes/Math/iVec4.cpp", "./Classes/String.cpp", "./Classes/WString.cpp", "./Classes/Display.cpp", "./Classes/IDSystem.cpp", "./Classes/JSON.cpp", "./Functions.cpp", "./Classes/GUI/Event.cpp", "./Classes/GUI/EventQueue.cpp", "./Classes/Language.cpp", "./Classes/FileSystem/Stream.cpp", "./Classes/FileSystem/BufferStream.cpp", "./Classes/FileSystem/FileSystemWatcher.cpp", "./Classes/GameEngine.cpp", "./Classes/Clock.cpp", "./Classes/Buffer.cpp", "./Classes/BufferPool.cpp", "./Classes/GUI/Interface.cpp", "./Classes/GUI/Widget.cpp", "./Classes/GUI/Panel.cpp", "./Classes/GUI/Slider.cpp", "./Classes/GUI/Label.cpp", "./Classes/GUI/Button.cpp", "./Classes/GUI/TextField.cpp", "./Classes/GUI/TreeView.cpp", "./Classes/GUI/TextArea.cpp", "./Classes/GUI/Tabber.cpp", "./Classes/GUI/ListBox.cpp", "./Classes/GUI/ProgressBar.cpp", "./Classes/GUI/ComboBox.cpp", "./Classes/GUI/Menu.cpp", "./Classes/Window/LinuxWindow.cpp", "./Classes/Timer.cpp", "./Classes/Process.cpp", "./Classes/FileSystem/StreamBuffer.cpp", "./Classes/Multithreading/Thread.cpp", "./Classes/Multithreading/Mutex.cpp", "./Classes/Multithreading/ThreadManager.cpp", "./Classes/Loaders/Loader.cpp", "./Classes/Loaders/DDSTextureLoader.cpp", "./Classes/Assets/Asset.cpp", "./Classes/Plugin.cpp", "./Classes/Assets/Font.cpp", "./Classes/FileSystem/Package.cpp", "./Classes/Graphics/Pixmap.cpp", "./Classes/Graphics/Icon.cpp", "./AppKit.cpp" ], "options": { "cwd": "${workspaceFolder}" }, "problemMatcher": [ "$gcc" ], "group": { "kind": "build", "isDefault": true }, "detail": "Task generated by Debugger." } ], "version": "2.0.0" } Our old friend Steam of course looks great on Linux and runs perfectly.

    But the most amazing thing about my install of Ubuntu 20.04 is that it's running on the Windows 10 desktop:

    Hyper-V makes it easy to create a virtual machine and run Linux and Windows at the same time. Here are some tips I have found while working with it:
    First, make sure you are allocating enough hard drive space and memory, because you are unlikely to be able to change these later on. I tried expanding the virtual hard drive, and after that Ubuntu would not load in the virtual machine, so just consider these settings to be locked. I gave my new VM 256 GB hard drive space and used the dynamic memory setting to allocate memory as-needed.
    Second, by default Ubuntu is only going to give you a 1024x768 window. You can change this but it requires a little work. Open the terminal in Ubuntu and type this command:
    sudo nautilus /etc/default/grub This will open the file browser and select the GRUB file, which stores system settings. Open the file with a text editor. Since the file was opened from a nautilus window with super-user permissions, the text editor will also be launched with permissions.
    Find this line of text:
    GRUB_CMDLINE_LINUX_DEFAULT="quiet splash" Change it to this. You can set the screen resolution to whatever value you prefer:
    GRUB_CMDLINE_LINUX_DEFAULT="quiet splash video=hyperv_fb:1920x1080" Close the text editor and the nautilus window. In the terminal, run this command:
    sudo update-grub Restart the virtual machine and your new screen resolution will work. However, the DPI scaling in Ubuntu seems to be not very reliable. My laptop uses a 1920x1080 screen, and at 17" this really needs to be scaled up to 125%. I could not get "fractional scaling" to work. The solution is to set your Windows desktop resolution to whatever value feels comfortable at 100% scaling. In my case, this was 1600x900. Then set Ubuntu's screen resolution to the same. When the window is maximized you will have a screen that is scaled the way you want.

    Checkpoints are a wonderful feature that allow you to easily walk back any changes you make to the virtual machine. You can see the steps I went through on to install all the software I wanted to work on Linux:

    If I ever mess anything up, I can easily revert back to whatever point I want. You can probably transfer the virtual machine between computers very easily, too, although I have not tried this yet. Changing the VM disk size, however, requires that you delete all checkpoints. So like I said, don't do that. My disk size is set to 256 GB, but in reality the whole VM is only taking up about 25 GB on my real hard drive.
    The "type clipboard text" feature in Hyper-V does not work at all with Linux, so don't even try.
    Pro tip: If you ever get stuck in the virtual machine, you can press Ctrl + Alt + Left arrow to bring the focus back to the Windows desktop.
    Hyper-V isn't going to allow you to play games with Vulkan or OpenGL, but for other software development it seems to work very well. It's a bit slow, but the convenience of running Linux on the Windows desktop and being able to switch back and forth instantly more than makes up for it.
    Super Bonus Tip
    I found that regardless of how much disk space you allocate for the virtual machine, the Windows default install of Ubuntu 20.04 will still only use 12 GB on the main drive partition. If you try to resize this in the built-in tools, an error will occur, something like "Failed to set partition size on device. Unable to satisfy all constraints on the partition."
    To resize the partition you must install gparted:
    sudo apt install gparted When you run the application it will look like this. Right-click on the ext4 partition and select the Resize/Move menu item:

    Set the new partition size and press the Resize button. 
    Extreme Double Bonus Tip
    If at any time you are running gparted you see an error like "Not all of the space available to /dev/sdb appears to be used, you can fix the GPT to use all of the space" you should press the Fix button. If you don't do this, the partition resize won't work and will display some errors.
    After the partition resize, the VM files on Windows are still only using about 21 GB, so it looks like Hyper-V doesn't allocate more disk space than it has to.
    Crazy Extra Tip
    Both VS Code and Brave browser (and probably Chrome) have an option to use a much better looking "custom titlebar" instead of the ugly GTK+ titlebar buttons.
  5. Josh

    Articles
    I want to streamline some of this website. We went through a lot of changes since the release on Steam in 2014 and learned what works and what does not.
    The "Marketplace" is just called the default "Downloads" name now, and that literally is what it is for. It's a place to keep a permanent copy of your files. Paid files are still supported, and any purchased items are still available to download, but I do not have any aspirations of building this up unless it just happens spontaneously. Instead I will focus on DLCs and on just making the file format loading features as easy as possible, so you can just buy something on cgtrader.com and not have any conversion step at all. I have hidden all paid items that did not have any sales, but I will keep them on the server for now.
    Most of your games have been moved back to the downloads area. I was the one who wanted everyone to move to the game launcher on Steam, so I will move them back for you. It would have been a great idea if Steam brought in traffic. There was some success with that, but it was not at a level that it justifies all the complication of going through that system.
    I am finding that the gallery and videos do a much better job of keeping people's attention than having a bunch of custom game pages they have to click around to find something interesting, so I intend to move all the games, videos, and screenshots out of the games database and eventually phase that out. You are welcome to put links in your screenshots and videos, and it will bring traffic to your page.
    One thing that really got me thinking this way was I have noticed some people post screenshots here regularly. I see them posting the same images on some other more general gamedev websites, and they seem to come here because they get more attention and feedback than they do on the bigger sites. I have even seen people post videos here and get more views than they do on YouTube (assuming YouTube views and comments are even real, which they might very well not be).
    In general I am finding that pushing all the content together and serving it up at 25 dopamine hits per page works really well. I want to move the content out of the projects area and phase that out too, for this reason.
    The chat feature I installed last week is a massive success and fills a huge need without sending all our activity away somewhere else.
    I want the website to be more focused because that helps me think. I don't want any more changes. I don't really want to try any new ideas this year because I don't think it's necessary.
    Going forward, we know now what our strategy is. On the consumer side, I just need to listen to you, the users, about what features you need, and deliver an improved overall quality over what we did with Leadwerks Engine. Although you don't have to do VR, focusing on VR gives me a niche of customers who I can help achieve some very interesting games, and it is gives you guys a way to sell in a market that has a lot of demand and isn't saturated. Imagine if there were 100 indie VR games available in the Downloads area right now. That would be quite popular.
    On the business side, I plan to give them something they cannot say no to, which is why I am been spending so long developing this killer new technology.
    In the end, Steam didn't have anything to offer other than another place to sell software, and it made me do some really weird things. There is no advantage to using any of the features built into Steam, except maybe the P2P networking system if you need it.
    I don't think more "innovation" is really needed now. Mostly I just need focus on giving you the features you need with good quality and documentation. Of course, the new engine allows me to do that in a way I could not before.
  6. Josh

    Articles
    You probably have noticed that our entire community has been migrated from the leadwerks.com domain to our new ultraengine.com domain. In this article I am going to describe recent changes to our website, the process by which these decisions were made, and the expected outcomes. These are not just superficial changes, but rather it is a fundamental restructuring of the way our business works. This is the execution of a plan I have had for several years.
    When we first starting selling on Steam and I saw the revenue it was bringing in, my reaction was to focus entirely on Steam and try to align everything we did to that. The idea was that if I could bring that advantage to you, we would all benefit from the massive gaming traffic that goes through Steam. I replaced many of our community features with Steam-centric equivalents. Our website gallery, videos, and downloads were replaced by the Steam equivalent. To view this content on our website I used the Steam web API to hook into our application ID and retrieve content.
    The screenshots and videos were a success. Leadwerks Editor allows publishing a screenshot directly to Steam, and we have gained a very big collection of screenshots and videos on Steam. However, there are two problems that have caused me rethink this approach:
    First, when it came to monetization, Steam seems to be a one-trick pony. Try as I could, Steam users just don't buy things through Steam Workshop. Our own sales in the web-based Marketplace are outperforming Steam.

    Second, Steam has flooded their marketplace with new titles. This incentivizes competition for digital shelf space. Instead of investing in Steam features for one application ID, it makes more sense to release multiple products on Steam and not tie yourself to one Steam application ID. Valve's position seems to be that you are responsible for driving traffic to your game, but if that is the case why are they still charging 30%? Our situation on Steam is still good, but I am not throwing everything 100% into Steam in the future and I prefer to drive traffic to our store on our own website.

    Based on what I have seen, it makes sense to move our center of activity back to our own website. Recent technological advances have made this easier. Cheap file storage backend systems have eliminated the expenses and technical problems of storing large amounts of user data. RSS feed importers have allowed us to hook into the Steam web API to feed all our Steam screenshots and videos into our own system.
    Videos
    We have a new video section on our site. You can paste a YouTube link through our web interface or upload a video file directly. Any videos you publish on Steam will be automatically fed in as well. You will notice in the tutorials section I am now hosting tutorial videos on our own site. They are also uploaded on YouTube, but I am not relying on YouTube anymore for video hosting.

    In the future I plan to support user-created paid video tutorials, with the same rules as paid items in the Marketplace.
    Gallery
    A new screenshot gallery is up, with images hosted on our own site again. I hope to find a way to migrate all our content on Steam into this system, like we did with videos. I also want to bulk upload all our old screenshots from before Steam.
    The Steam-based gallery and videos can still be viewed on the leadwerks.com website, as well as the Leadwerks documentation.
    Marketplace Games
    The Marketplace we have now is a 2.0 version of our original system before Steam, with a new Games category. Back in the days before Steam it always amazed me that Furious Frank had over 20,000 downloads. This was from the days before itch.io and gamejolt, and there was a big appetite for indie games. The Games database of our website never reached that level, and I think the reason was that we should have focused on the content. If people want to watch videos they will go to the videos section. If people want to download free games they will go to the Games category in the Marketplace. Having a customized page on our website with a lot of information and links all in one place is about as pointless as having a Facebook fan page. There's no reason for it, all it does is slow down the delivery of the actual content. It looks kind of cool, but I think the viewer just wants to get to the content (download the game, watch videos, view screenshots) instead of wading through a lot of custom CSS pages. If you want to drive traffic to your website or to your Steam page, post a video and put links in the description where you want the viewer to go next.
    In addition to uploading free games, you can now sell your games in the Marketplace. I have plans to attract a lot of new traffic to the site in 2021, so maybe your games can get more sales at the same time. The same 70/30 split we use for Marketplace assets applies to games.
    Furious Frank is starting over from zero and I realize he would have been enjoyed by over 100,000 players had I not pushed our traffic towards Steam so hard.
    Email Marketing (and leaving social media behind)
    At a startup event I attended years ago, one of the speakers told me that email was actually their best marketing tool. I was kind of surprised because to me it seemed archaic, but that conversation stuck in my mind. According to conventional wisdom, if you want to get the word out about your product you should crete an Instagram account, upload your images, and then when you invariably get no traffic you should blame yourself because your content sucks. Who is pushing this "conventional wisdom"? It is being pushed by giant tech companies that seek to absorb and monetize all your content, and a network of parasitical "gurus" who want to sell you useless advice and then blame you when it fails. This is not the way online customer acquisition actually works.
    I started looking at traffic on our social media accounts and comparing it to email and web traffic, and the results are shocking. Our email newsletters regularly result in 30x more clicks than anything I write on social media. Not only are they failing to bring in an acceptable level of traffic, Twitter and even Steam are actively preventing us from reaching our customers by censoring our content.
    Email is the only direct line of communication you have with your own customers. All third-party middlemen have the potential to abuse their position. They will flood their marketplace with products, change their algorithms, arbitrarily censor or shadowban content. The only thing that will provide a counterweight to that abuse is a good BATNA. If you don't have that you can expect to be treated badly. (It's really a miracle that email was invented early enough to become a common open standard. This would never happen today. "Blue Sky" is probably a sabotage effort.)
    In that light, the advice I got makes a lot of sense. Once I realized this I stopped posting on Facebook and Twitter and just left a pinned message directing people to the mailing list:

    Our new website also features a mailing list sign-up form featured prominently in the website footer.

    Instead of wasting time shouting into the wind on (anti)social media I am going to instead focus on writing quality articles that will be posted on our site and sent out to the email list.
    Store
    Valve has made it clear that game developers should not rely on Steam alone to drive traffic to their game. Okay, well if I am responsible for bringing in traffic, I am going to direct it to my own store, not to Steam.
    The MBA in me realizes two things:
    Getting a user's email address is good and has a quantifiable value. Getting a user's credit card system stored in our system is even better. (It doesn't actually store credit cards on our server, it stores a token that can only be used with our domain.) These two steps are serious hurdles to overcome for any web store. Now that I am selling Ultra App Kit directly on our own site, I have seen an increase in sales of items in our Marketplace. This is not a coincidence. People buy one thing and then it becomes a habit. A subscription option will be available for future software products. All new software I release is going to require an initial sign-in to your forum account. We have tens of thousands of users on Steam that I have no email addresses for or ability to contact, and that is not going to work going forward. (I will make sure an override is built into the software that can be activated by a dead man switch.)

    This system gives me the ability to offer software products under a subscription model for the first time ever. This is preferable from my point of view, but I understand it's not for everyone and a conventional software license will also be available.
    We also have an automated system to send out Steam keys, so I am now able to sell Steam keys directly in our store. When you order you will receive an email with a link to retrieve your key. Once you enter the key in Steam it is added to your Steam account just as if you bought it on Steam.
    To make payments easier we are now accepting PayPal and cryptocurrency payments, in addition to credit cards.
    (Valve clearly recognizes a problem with visibility on Steam and is desperately trying to convince you to stay engaged in their system so they can take their 30%. I don't mean to say they are bad guys, I am just saying in any partnership both parties will have some divergent interests and must stick up for themselves. The day that mine is the only company selling products on Steam is when I will consider going Steam-exclusive again. What indie developers should be doing right now is selling their own games on their own websites, in addition to third-party stores like Steam.)
    Rolling Out Ultra Engine (Leadwerks 5)
    Breaking off the GUI features of the new engine and releasing it early as the standalone product Ultra App Kit was a great idea because it allows me to implement all these things early, test them out, and ensures a smoother release of the finished 3D engine later this year. The basic theme we see here is that these social and gaming platforms are no longer doing their job effectively and we must build something of our own. Withdrawing our content from them and building up our own website only makes sense if there is a way to drive more traffic here. If Steam is flooded and social media is saturated, how can we attract traffic? Email is good for engaging customers you have already made contact with, but how do you attract new people in large numbers? I have an answer to that, but it really deserves a whole article itself.
    Conclusion
    Here is where we were before:
    Domain name no one could pronounce or spell. It was literally impossible to tell someone to go to our website without explaining the spelling. User content hosted on Steam and YouTube Almost all sales through Steam Ineffective outreach through social media Single product for sale with perpetual free upgrades No ability to collect customer information Here is where we are now:
    Domain name that can easily spread by word of mouth Most user content hosted on our own website Direct sales through our own website and many third-party stores Effective outreach through email (and other means I will talk about later) Ability to sell annual paid updates or subscription model Growing customer database of people I can directly contact I hope I have shown how all these changes were not just random decisions I made, but are part of a "holistic" strategy, for lack of a better word.
  7. Josh

    Articles
    Breaking the new engine up into sub-components and releasing them in several stages is something new. The reason for trying this was twofold:
    Steam now incentives competition for digital shelf space, unlike when Leadwerks was first released on Steam in 2014, when I was trying to build up my quality of presence and promote one app ID on Steam. I would like to get some new software out early before the full engine is finished. The beautiful thing is that Ultra App Kit all consists of things I had to get done anyways. It was not a distraction from the development of the 3D engine.  Here are some of the features I was able to finish and document. All of these will be part of the new 3D engine when it comes out:
    New documentation system using markdown pages on Github, integrated search, sitemap generation, caching, copy button in code boxes. New project creation wizard Finalize display and window classes GUI system Math Memory Process Multithreading Strings Pixmap class Icons System Dialogs In addition, this allows me to establish a lot of my plans for marketing of the new engine and perform a "dry run" before the final release:
    Community system migrated to new website License Management system collects Steam customer emails, allows subscription license model. New videos section of site with a category for official video tutorials. Affiliate / referral system Partnerships with video creators, bloggers, and advertisers. The initial release will support C++ programming, but subsequent releases for Lua and C# will allow me to judge how much interest there is in these languages relative to one another.
    Maybe we will see Ultra 2D Kit this summer?
  8. Josh

    Articles
    A new update is available on Steam for Ultra App Kit.
    A TEXTFIELD_PASSWORD style flag has been added and is used for the password entry form:

    A WINDOW_CHILD style flag has been added. I found this was necessary while implementing a Vulkan 3D viewport in a GUI application. You can read more about that here.
    Pressing the Tab key will now switch the focus between widgets.
    The "Learn" tab in the project manager has been moved in front of the "Community" tab.
    The Visual Studio project is now using a property sheet to store the location of the headers and libs, the way Leadwerks 4 does.
    Build times have been sped up using incremental linking, /Debug.fastlink, multi-processor builds (which finally work with precompiled headers), and other tricks. Build times will typically be less than one second now if you are just modifying your own code. (This blog article from Microsoft was very helpful.)
    The precompiled header has been changed to "UltraEngine.h" which I find more intuitive and compatible than "pch.h".
    Ultra App Kit lets you easily build desktop GUI applications. If you don't have it already, you can get access to the beta by purchasing it now in our store.
  9. Josh

    Articles
    Before finalizing Ultra App Kit I want to make sure our 3D engine works correctly with the GUI system. This is going to be the basis of all our 3D tools in the future, so I want to get it right before releasing the GUI toolkit. This can prevent breaking changes from being made in the future after the software is released.
    Below you can see our new 3D engine being rendered in a viewport created on a GUI application. The GUI is being rendered using Windows GDI+, the same system that draws the real OS interface, while the 3D rendering is performed with Vulkan 1.1. The GUI is using an efficient event-driven program structure with retained mode drawing, while Vulkan rendering is performed asynchronously in real time, on another thread. (The rendering thread can also be set to render only when the viewport needs to be refreshed.)

    The viewport resizes nicely with the window:

    During this process I learned there are two types of child window behavior. If a window is parented to another window it will appear on top of the parent, and it won’t have a separate icon appear in the Windows task bar. Additionally, if the WS_CHILD window style is used, then the child window coordinates will be relative to the parent, and moving the parent will instantly move the child window with it. We need both types of behavior. A splash screen is an example of the first, and a 3D viewport is an example of the second. Therefore, I have added a WINDOW_CHILD window creation flag you can use to control this behavior.
    This design has been my plan going back several years, and at long last we have the result. This will be a strong foundation for creating game development tools like the new engine's editor, as well as other ideas I have.
    This is what "not cutting corners" looks like.
  10. Josh
    We now accept popular cryptocurrencies in our store and Marketplace through Coinbase Commerce. That's right, you can now buy software like Ultra App Kit using Bitcoin, Ethereum, Litecoin, Dai, or Bitcoin Cash (if you can figure it out!). Right now it's a novelty, but it's worth trying. Maybe by 2022 it will be your only option?

    Sadly, Dogecoin is not one of the currently supported coins. Soon?
  11. Josh
    Note: This article contains some referral links for affiliate systems that I added after writing it. My purpose for including them is so that I can learn how these systems work by participating in them, because I am interested in possibly implementing one of our own in the future. The article was written because these are all things I am using and recommend and I am very bored with the same old things.
    Except for VR, the last decade of technology has been pretty yawn-inducing. I think Silicon Valley effectively killed the entire tech industry by showering a few companies with unlimited free money, but the scam is running out and you can't hold back innovation forever. In this blog I will talk about three technologies you might not have heard of and explain what each one can do for you.
    Proton Mail
    Proton Mail is like the new Gmail, except better because it uses end-to-end encryption so your email is never visible on the company's servers. You can read your email by logging into the website, using the smartphone app, or by installing the bridge application to decrypt mail on your PC and integrating with Outlook (requires the paid plan, but that is only $4.99 a month). There's also a free VPN available so you can have secure encrypted connections on the coffee shop wifi.
    A quick search of our mailing list shows that a lot of you have already switched to Proton Mail, making Gmail the new AOL.

    It is possible to use Proton Mail's secure system with your own domain, which gives you the ability to separate your email from your website. This is pretty nice for a couple of reasons. First, if your website is down temporarily for maintenance, email keeps flowing without a hitch. Second, if some type of theoretical security breach were to hit your site, email would be unaffected by it, or vice-versa. I think it is also possible to set up directly with your DNS so you can have email on your own domain with no web hosting at all.
    Why you need it:
    End-to-end encryption makes your email secure. No data harvesting to show you annoying ads. It just looks cool. Brave Browser
    On the surface, Brave browser looks nearly identical to Chrome, but underneath the hood it is a different beast. This is the browser that is really pushing new technology with features like built-in bit torrent, crypto currency, tor, and support for the peer-to-peer IPFS Internet protocol. Not all these technologies are going to pan out, but the possibilities of doing away with the DNS system or revolutionizing online commerce are tantalizing, and some of them are going to work out. With 20 million users Brave now has the size to benefit from network effects so it will be interesting to see what kinds of things are possible with this browser.

    I feel I should warn you. Once you start using Brave it is difficult to go back, especially once you understand what that would involve.

    Why you need it
    Built-in ad blocker. No data harvesting. Innovative new features. Works all with Chrome extensions. BackBlaze B2
    BackBlaze B2 is a cloud storage system similar to Amazon S3. These types of systems are critical for us because it allows a separate of website files and content files. Our website is only a few gigabytes and lives on our server, while all the user-generated files including images, attachments, avatars, and any other uploaded content is all stored on a backend file system. This makes backups to the website very small and as our content grows our core site stays the same size. Invision Power Board does not yet support BackBlaze B2 but the moment it does I plan to switch and cut 75% off our file storage costs.

    Why you need it:
    Like Amazon S3, at 25% the cost. Bonus Tip: KeyCDN
    KeyCDN is a content distribution network like Cloudflare, and is another Swiss company. You can set this up to deliver images or other files faster to the user, and the basic plan lets you get started for free. I plan to integrate this into our site to serve up images and other files faster in the future.

    Why you need it:
    Try a CDN for free. Very inexpensive paid plan. In Summary
    When we continue to rely on old familiar technologies and services we block ourselves from getting involved in new things. I had no idea what IPFS was until I started using Brave, and now I want to add support for it with our website. What are some of the interesting technologies, products, services, or websites you have seen popping up? I feel like a new era has begun.
  12. Josh

    Articles
    I'm finalizing the Ultra App Kit API, which is going to be the basis of the new engine's API. Naming commands themselves is always a bit of an art unto itself. Some things are named a certain way because it is common convention, like Stream::Seek() instead of Stream::SetPosition() and ACos() instead of ArcCosine().
    Other times we have holdovers from old APIs or BASIC syntax like Stream::EOF() or String::Mid().
    Should a class that has no SetSize() method use GetSize() or Size() for a method that returns the size? I have to decide these things now and live with the repercussions for years.
    Take a look through the new documentation and tell me if you have any suggestions, or forever hold your peace.
  13. Josh

    Articles
    There are three new features in the upcoming Ultra Engine (Leadwerks 5) that will make game input better than ever before.
    High Precision Mouse Input
    Raw mouse input measures the actual movement of the mouse device, and has nothing to do with a cursor on the screen. Windows provides an interface to capture the raw mouse input so your game can use mouse movement with greater precision than the screen pixels. The code to implement this is rather complicated, but in the end it just boils down to one simple command expose to the end user:
    Vec2 Window::MouseMovement(const int dpi = 1000); What's really hilarious about this feature is it actually makes mouse control code a lot simpler. For first-person controls, you just take the mouse movement value, and that is your camera rotation value. There's no need to calculate the center of the window or move the mouse pointer back to the middle. (You can if you want to, but it has no effect on the raw mouse input this command measures.)
    The MousePosition() command is still available, but will return the integer coordinates the Windows mouse cursor system uses.
    High Frequency Mouse Look
    To enable ultra high-precision mouse look controls, I have added a new command:
    void Camera::SetFreeLookMode(const bool mode, const float speed = 0.1f, const int smoothing = 0) When this setting is enabled, the mouse movement will be queried in the rendering thread and applied to the camera rotation. That means real-time mouse looking at 1000 FPS is supported, even as the game thread is running at a slower frequency of 60 Hz. This was not possible in Leadwerks 4, as mouse looking would become erratic if it wasn't measured over a slower interval due to the limited precision of integer coordinates.
    XBox Controller Input
    I'm happy to say we will also have native built-in support for XBox controllers (both 360 and One versions). Here are the commands:
    bool GamePadConnected(const int controller = 0) bool GamePadButtonDown(const GamePadButton button, const int controller = 0) bool GamePadButtonHit(const GamePadButton button, const int controller = 0) Vec2 GamePadAxisPosition(const GamePadAxis axis, const int controller = 0) void GamePadRumble(const float left, const float right, const int controller = 0) To specify a button you use a button code:
    GAMEPADBUTTON_DPADUP GAMEPADBUTTON_DPADDOWN GAMEPADBUTTON_DPADLEFT GAMEPADBUTTON_DPADRIGHT GAMEPADBUTTON_START GAMEPADBUTTON_BACK GAMEPADBUTTON_LTHUMB GAMEPADBUTTON_RTHUMB GAMEPADBUTTON_LSHOULDER GAMEPADBUTTON_RSHOULDER GAMEPADBUTTON_A GAMEPADBUTTON_B GAMEPADBUTTON_X GAMEPADBUTTON_Y GAMEPADBUTTON_RTRIGGER GAMEPADBUTTON_LTRIGGER And axes are specified with these codes:
    GAMEPADAXIS_RTRIGGER GAMEPADAXIS_LTRIGGER GAMEPADAXIS_RSTICK GAMEPADAXIS_LSTICK These features will give your games new player options and a refined sense of movement and control.
  14. Josh
    An update for Ultra App Kit beta on Steam is now available. This finishes the user interface scaling to support HD, 4K, 8K, and other resolutions. My original plan was to force an application restart if the scale setting was changed, but I found a way to dynamically resize the interface in a manner that gives natural results, so it now supports dynamic rescaling. That is, if the user changes the Windows DPI setting, or if a window is dragged to a monitor with a different DPI setting, the application will receive an event and rescale the interface dynamically.
    Below you can see a sample interface scaled at 100% and 150%:


    Ultra App Kit can be pre-ordered in our store now. You will receive the finished software when it is complete, and a Steam key now to access the beta:
     
  15. Josh

    Articles
    An update is available for the Ultra App Kit beta on Steam.
    Menu open / close behavior is finished and is now working bug-free. Fixed problem where list boxes were only showing the first item. A submenu item is demonstrated in the example program. A progress bar widget is added in the example program. A label widget is added in the example program. A second radio button is added in the example program. Still to do:
    Work out some scaling issues. Light theme. Some small details with some widget styles. Finish documentation. Project wizard / manager application.
  16. Josh

    Articles
    2020 was the most intellectually challenging year in my career. Many major advancements were invented, and 2021 will see those items refined, polished, and turned into a usable software product. Here is a partial list of things I created:
    Streaming hierarchal planet-scale terrain system with user-defined deformation and texture projection. Vulkan post-processing stack and transparency with refraction. Vulkan render-to-texture. Major progress on voxel ray tracing. Porting/rewrite of Leadwerks GUI with Implementation in 3D, rendered to texture, and using system drawing. Plugin system for loading and saving textures, models, packages, and processing image and mesh data. Lua debugger with integration in Visual Studio Code. Pixmap class for loading, modifying, compressing, and saving texture data. Vulkan particle system with physics. Implemented new documentation system. Lua and C++ state serialization system with JSON. C++ entity component system and preprocessor. (You don't know anything about this yet.) Not only was this a year of massive technical innovation, but it was also the year when my efforts were put to the test to see if my idea of delivering a massive performance increase for VR was actually possible, or if I was in fact, as some people from the Linux community have called me, "unbelievably delusional". Fortunately, it turned out that I was right, and an as-of-yet-unreleased side-by-side benchmark showing our performance against another major engine proves we offer significantly better performance for VR and general 3D graphics. More on this later...
    Additionally, I authored a paper on VR graphics optimization for a major modeling and simulation conference (which was unfortunately canceled but the paper will be published at a later time). This was another major test because I had to put my beliefs, which I mostly gain from personal experience, into a more quantifiable defensible scientific format. Writing this paper was actually one of the hardest things I have ever done in my life. (I would like to thank Eric Lengyel of Terathon Software for providing feedback on the final paper, as well as my colleagues in other interesting industries.)
    I'm actually kind of floored looking at this list. That is a massive block of work, and there's a lot of really heavy-hitting items. I've never produced such a big volume of output before. I'm expecting 2021 to be less about groundbreaking research and more about turning these technologies into usable polished products, to bring you the benefits all these inventions offer.
  17. Josh
    The beta testers and I are are discussing game programming in the new engine. I want to see C++, Lua, and C# all take a near-identical approach that steals the best aspects of modern game programming and ditches the worst, to create something new and unique. To that end, we are developing the same simple game several times, with several different methodologies, to determine what really works best. One thing I realized quickly was we really need a way to load prefabs from files.
    I started implementing a JSON scene format using the wonderful nlohmann::json library, and found the whole engine can easily serialize all information into the schema. You can save a scene, or save a single entity as a prefab. They're really the same thing, except that prefabs contain a single top-level entity.
    { "scene": { "entities": [ { "angularVelocity": [ 0.0, 0.0, 0.0 ], "castShadows": true, "collisionType": 0, "color": [ 0.7529412508010864, 1.2352941036224365, 1.5, 1.0 ], "floatPrecision": 32, "guid": "53f8d368-24da-4fba-b343-22afb4237d1b", "hidden": false, "light": { "cacheShadows": true, "coneAngles": [ 45.0, 35.0 ], "range": [ 0.019999999552965164, 12.0 ], "type": 0 }, "mass": 0.0, "matrix": 52, "name": "Point Light 2", "physicsMode": 1, "pickMode": 0, "position": 0, "quaternion": 24, "rotation": 12, "scale": 40, "static": false, "velocity": [ 0.0, 0.0, 0.0 ] } } } } For fast-loading binary data I save an accompanying .bin file. The values you see for position, rotation, etc. are offsets in the binary file where the data is saved.
    From there, it wasn't that much if a stretch to implement Lua State serialization. Lua tables align pretty closely to JSON tables. It's not perfect, but it's close enough I would rather deal with the niggles of that than implement an ASCII data structure that doesn't show syntax highlighting in Visual Studio Code.
    "luaState": { "camera": "ab65bd91-153d-47fb-a11b-ff40c19cd8f4", "cameraheight": 1.7, "camerarotation": "<Vec3>::-4.2,39.1,0", "carriedobject": "9daf54a7-c3b5-4b4b-979b-6b034d6b80fd", "carriedobject_damping": "<Vec2>::0.1,0.1", "carriedobject_gravitymode": true, "carryposition": "<Vec3>::-0.259477,-0.372455,1.95684", "carryrotation": "<Quat>::0.0635833,0.755824,-0.649477,-0.0535437", "interactionrange": 2.5, "listener": "45c93683-ca3b-493a-ad06-b16fe14e4175", "looksmoothing": 2.0, "lookspeed": 0.1, "maxcarrymass": 10.0, "modelfile": "Models/Weapons/Ronan Rifle/scene.gltf", "mouselost": false, "mousemovement": "<Vec2>::-5.00474e-19,3.1102e-22", "movespeed": 5.0, "throwforce": 1500.0, "weapon": "2f8e3d74-26be-4828-9053-a455a9fd05fd", "weaponposition": "<Vec3>::0.12,-0.4,0.42", "weaponrotation": "<Vec3>::-89.9802,-0,0", "weaponswayspeed": 0.1, "weaponswayticks": 2443.5362155621197 } As a result, we now have the ability to easily add quick save of any game, and loading of the game state, automatically, without any special code. The only exception is for entities that are created in code, since they do not have a GUID to trace back to the original loaded scene. This is easily handled with a LoadState() function that gets executed in Lua after a saved game is loaded. In my FPSPlayer script I create a kinematic joint to make the player carry an object when they select it by looking at it and pressing the E key. Since this joint is created in code, there is no way to trace it back to the original scene file. So what I do is first remove the existing joint, if an object is currently being picked up, and then create a new joint, if one has been loaded in the game save file.
    function entity:LoadState() if self.carryjoint ~= nil then self.carryjoint.child:SetGravityMode(self.carriedobject_gravitymode) self.carryjoint.child:SetDamping(self.carriedobject_damping.x, self.carriedobject_damping.y) self.carryjoint:Break() self.carryjoint = nil end if self.carriedobject ~= nil then local pos = TransformPoint(self.carryposition, self.camera, nil) self.carryjoint = CreateKinematicJoint(pos, self.carriedobject) self.carryjoint:SetFriction(1000,1000) end end Here is the result. The player rotation, camera angle, and other settings did not have to be manually programmed. I just saved the scene and reloaded the entity info, and it just works. You can see even the weapon sway timing gets restored exactly the way it was when the game is reloaded from the saved state.
    For most of your gameplay, it will just work automatically. This is a game-changing feature because it enables easy saving and loading of your game state at any time, something that even AAA games sometimes struggle to support.
  18. Josh
    Light is made up of individual particles called photons. A photon is a discrete quantum of electromagnetic energy. Photons are special because they have properties of both a particle and a wave. Photons have mass and can interact with physical matter. The phenomenon of "solar pressure" is caused by photons bombarding a surface and exerting force. (This force actually has to be accounted for in orbital mechanics.) However, light also has a wavelength and frequency, similar to sound or other wave phenomenon.
    Things are made visible when millions of photons scatter around the environment and eventually go right into your eyes, interacting with photoreceptor cells on the back surface of your interior eyeball (the retina). A "grid" of receptors connect into the optic nerve, which travels into your brain to the rear of your skull, where an image is constructed from the stimulus, allowing you to see the world around you.
    The point of that explanation is to demonstrate that lighting is a scatter problem. Rendering, on the other hand, is a gather problem. We don't care about what happens to every photon emitted from a light source, we only care about the final lighting on the screen pixels we can see. Physically-based rendering is a set of techniques and equations that attempt to model lighting as a gather problem, which is more efficient for real-time rendering. The technique allows us to model some behaviors of lighting without calculating the path of every photon.
    One important behavior we want to model is the phenomenon of Fresnel refraction. If you have ever been driving on a desert highway and saw a mirage on the road in the distance, you have experienced this. The road in the image below is perfectly dry but appears to be submerged in water.

    What's going on here? Well, remember when I explained that every bit of light you see is shot directly into your eyeballs? Well, at a glancing angle, the light that is most likely to hit your eyes is going to be bouncing off the surface from the opposite direction. Since you have more light coming from one single direction, instead of being scattered from all around, a reflection becomes visible.
    PBR models this behavior using a BRDF image (Bidirectional reflectance distribution function). These are red/green images that act as a look-up table, given the angle between the camera-to-surface vector and the incoming light vector. They look something like this:

    You can have different BRDFs for leather, plastic, and all different types of materials. These cannot be calculated, but must be measured with a photometer from real-world materials. It's actually incredibly hard to find any collection of this data measured from real-world materials. I was only able to find one lab in Germany that was able to create these. There are also some online databases available that these as a text table. I have not tried converting any of these into images.
    Now with PBR lighting, the surrounding environment plays a more important role in the light calculation than it does with Blinn-Phong lighting. Therefore, PBR is only as good as the lighting environment data you have. For simple demos it's fine to use a skybox for this data, but that approach won't work for anything more complicated than a single model onscreen. In Leadwerks 4 we used environment probes, which create a small "skybox" for separate areas in the scene. These have two drawbacks. First, they are still 2D projections of the surrounding environment and do not provide accurate 3D reflections. Second, they are tedious to set up, so most of the screenshots you see in Leadwerks are not using them.

    Voxel ray tracing overcomes these problems. The 3D voxel structure provides better reflections with depth and volume, and it's dynamically constructed from the scene geometry around the camera, so there is no need to manually create anything.

    I finally got the voxel reflection data integrated into the PBR equation so that the BRDF is being used for the reflectance. In the screenshot below you can see the column facing the camera appears dull.

    When we view the same surface at a glancing angle, it becomes much more reflective, almost mirror-like, as it would in real life:

    You can observe this effect with any building that has a smooth concrete exterior. Also note the scene above has no ambient light. The shaded areas would be pure black if it wasn't for the global illumination effect. These details will give your environments a realistic lifelike look in our new engine.
  19. Josh

    Articles
    Our new editor is being designed to support user-created extensions written in Lua. I want Lua to work in our new editor the way MaxScript works in 3ds Max, to allow an endless assortment of new tools you can create and use.
    Now that the editor GUI system is well underway, I want to start thinking about how user-created extensions will work with our new editor. I'm going to lay out some theoretical code for how a road creation tool might integrate into the editor.
    First we declare a start function that is run when the extension is loaded. This will add a toolbar and menu item so the tool can be selected, as well as create a new event listen function:
    function extension:Start() --Load the tool icon local icon = LoadPixmap("Icons/RoadTool.svg") --Add a toolbar button self.toolbarbutton = application.mainwindow.toolbar:InsertButton(icon) --Add a menu button self.menuitem = application.mainwindow.menu["Tools"]:InsertItem("Road Tool") self.menuitem:SetPixmap(icon) --Listen for events. EVENT_NONE will process all events: ListenEvent(EVENT_NONE, application.viewportgrid.viewport[1], self.ProcessEvent, self) ListenEvent(EVENT_NONE, application.viewportgrid.viewport[2], self.ProcessEvent, self) ListenEvent(EVENT_NONE, application.viewportgrid.viewport[3], self.ProcessEvent, self) ListenEvent(EVENT_NONE, application.viewportgrid.viewport[4], self.ProcessEvent, self) end Now we need to declare a function to process events. If the function returns false, the event will not be further processed, so the default mouse tool will be overridden.
    function extension:ProcessEvent(event) --Return if the road tool is not active if self.toolbarbutton:GetState() == false then return true end --Evaluate widget events - keep the menu and toolbar button in sync if event.id == EVENT_WIDGETACTION then if event.source == self.menu then self.toolbarbutton:SetState(event.data) elseif event.source == self.toolbarbutton self.menuitem:SetState(event.data) end --Evaluate mouse events elseif event.id == EVENT_MOUSEDOWN then local viewport = Viewport(event.source) if viewport ~= nil then local pickinfo = PickInfo() if viewport.camera:Pick(viewport.framebuffer, event.x, event.y, pickinfo, 0, true) then self:AddNode(pickinfo.position) end return false end --Evaluate key hits elseif event.id == EVENT_KEYDOWN then if event.data == KEY_ENTER then if #self.splinepoints > 1 then --Create our road self:CreateRoad() --Update the undo system application:CreateUndoStep() --Tell the editor the scene is modified application:ModifyScene() --Refresh the viewports application.viewportgrid:Redraw() return false end end end return true end  
  20. Josh

    Articles
    A new update is available for beta testers. This update focuses on the GUI capabilities, mostly for interfaces created directly on a window, rather than 3D interfaces. We are moving from a Lua-driven GUI system to one coded more explicitly in C++. This will provide us with better documented behavior, easier portability, and a more responsive interface.
    Changes:
    GUI widgets no longer use Lua scripts. Each widget type has a C++ class, a creation command, and style constants. A new ComboBox widget is implemented, with a dropdown list that can extend beyond the parent window's borders. A lot of work was done on GUI behavior. The "Actor" class is renamed to "Component". The Entity::AddActor and AddScript methods are now both called AddComponent. The "Scripts/Objects" folder is renamed to "Scripts/Components". The "Scripts/Functions" folder is not really needed and has been eliminated. I have gone into quite a lot of detail getting all the little window and widget behaviors right, since I plan to use this for our new editor interface. The result should feel like the Visual Studio or 3ds Max interface.
    If there was a existing suitable GUI library out there I would not be creating this, but there really isn’t. It is very strange that millions of programmers are making websites, blockchain applications, and games, but no one is paying attention to the most basic functionality of a computer. I will have an announcement soon about something I am doing to fix this situation.
  21. Josh

    Articles
    Our forum software and theme have been updated. The new theme is just the default Invision Power Board skin, with our own header and footer added.
    In the past I put a lot of effort into getting the forum to look exactly the way I wanted, and I don't think that effort was a very good use of time. Each forum update requires us to redo the skin, and I just don't feel like it is that important. I can fine-tune the appearance of the user interface in the Leadwerks Editor, but this web stuff is out of my hands. The fact that Google is forcing everyone to use "responsive design" just means web design is always going to be a compromise. So I am just going with a direction that requires minimal maintenance.
    In the future, I would like to develop a dark theme and use that as the default. Again, it would just be the default theme with the colors changed, and no attempt to change the layout.
  22. Josh

    Articles
    The terrain streaming / planet rendering stuff was the last of the feature creep. That finishes out the features I have planned for the first release of the new engine. My approach for development has been to go very broad so I could get a handle on how all the features work together, solve the hard problems, and then fill in the details when convenient.
    The hard problems are all solved so now it's just a matter of finishing things, Consequently, I don't think my blogs are going to make any more groundbreaking feature announcements, but rather are going to show steady improvement of each subsystem as we progress towards a finished product.
    The GUI is something I wanted to spend some more cycles on. The initial release of the new engine will be a pure programming SDK with GUI support, but the GUI I am implementing is also going to be the basis of the new editor, when that time comes. I decided that using Lua scripts to control widgets was a bad idea because when operating at-scale I think this will cause some small slowdown in the UI. My goals for the new editor are for it to load fast and be very snappy and responsive, and that is my highest priority. It is nice to have overarching design goals because then you know what you must do.
    I've started the process of converting our Lua widget scripts into C++ code. The API now has functions like CreatePanel(), CreateButton(), etc. and is much more formalized than the flexible-but-open-ended GUI system in Leadwerks 4. For customization, I am implementing a color system. We have a bunch of color constants like this:
        enum WidgetColor     {         WIDGET_COLOR_BACKGROUND,         WIDGET_COLOR_BORDER,         WIDGET_COLOR_FOREGROUND,         WIDGET_COLOR_SELECTION,         WIDGET_COLOR_HIGHLIGHT,         WIDGET_COLOR_AUX0,         WIDGET_COLOR_AUX1,         WIDGET_COLOR_AUX2,         WIDGET_COLOR_AUX3,     }; There is a Widget::SetColor() command that lets you set any of the above values. Now, this is not a complete set of colors. The GUI system uses a lot more colors than that. But these colors are generated by multiplying the defined color by some value to make it a little darker or a little lighter.
    This means I am making a decision to reduce the flexibility of the system in favor of more formalized feature support, better documentation, and better performance.
    I think we will be able to load a color scheme from a JSON file and that will allow enough customization that most things people want to do will be possible. For custom widget behavior, I think either an actor or a DLL plugin could be used. There are enough options for future extensibility that I feel like we will be okay deferring that decision for now, and I am not coding myself into a corner.
    Here's a shot of the current state of things:

    I probably have enough GUI code ahead of me I could just go silent for a month and stay busy with this. I don't really want to think about that for the rest of today. Goodnight.
  23. Josh

    Articles
    An update is available for Leadwerks 5 beta on Steam that adds a World::SetSkyColor() command. This allows you to set a gradient for PBR reflections when no skybox is in use.
    I learned with Leadwerks 4 that default settings are important. The vast majority of screenshots people show off are going to use whatever default rendering settings I program in. We need a good balance between quality and performance for the engine to use as defaults. Therefore, the engine will use SSAO and bloom effects by default, a gentle gradient will be applied to PBR reflections, and the metal / roughness values of new materials will each be 0.5. Here is the result when a simple box is created with a single directional light:

    And here is what a more complex model looks like, without any lights in the scene:

    You can use World::SetSkyColor() to change the intensity of the reflections:

    Or you can change the colors to get an entirely different look:

    A Lua example using this command is available in the "Scripts/Examples" folder.
    These feature will help you to get better graphics out of the new engine with minimal effort.
  24. Josh
    In games we think of terrain as a flat plane subdivided into patches, but did you know the Earth is actually round? Scientists say that as you travel across the surface of the planet, a gradual slope can be detected, eventually wrapping all the way around to form a spherical shape! At small scales we can afford to ignore the curvature of the Earth but as we start simulating bigger and bigger terrains this must be accounted for. This is a big challenge. How do you turn a flat square shape into a sphere? One way is to make a "quad sphere", which is a subdivided cube with each vertex set to the same distance from the center:

    I wanted to be able to load in GIS datasets so we could visualize real Earth data. The problem is these datasets are stored using a variety of projection methods. Mercator projections are able to display the entire planet on a flat surface, but they suffer from severe distortion near the north and south poles. This problem is so bad that most datasets using Mercator projections cut off the data above and below 75 degrees or so:

    Cubic projections are my preferred method. This matches the quad sphere geometry and allows us to cover an entire planet with minimal distortion. However, few datasets are stored this way:

    It's not really feasible to re-map data into one preferred projection method. These datasets are enormous. They are so big that if I started processing images now on one computer, it might take 50 years to finish. We're talking thousands of terabytes of data that can be streamed in, most of which the user will never see even if they spend hours flying around the planet.
    There are many other projection methods:

    How can I make our terrain system handle a variety of projection methods ti display data from multiple sources? This was a difficult problem I struggled with for some time before the answer came to me.
    The solution is to use a user-defined callback function that transforms a flat terrain into a variety of shapes. The callback function is used for culling, physics, raycasting, pathfinding, and any other system in which the CPU uses the terrain geometry:
    #ifdef DOUBLE_FLOAT void Terrain::Transform(void TransformCallback(const dMat4& matrix, dVec3& position, dVec3& normal, dVec3& tangent, const std::array<double, 16>& userparams), std::array<double, 16> userparams) #else void Terrain::Transform(void TransformCallback(const Mat4& matrix, Vec3& position, Vec3& normal, Vec3& tangent, const std::array<float, 16>& userparams), std::array<float, 16> userparams) #endif An identical function is used in the terrain vertex shader to warp the visible terrain into a matching shape. This idea is similar to the vegetation system in Leadwerks 4, which simultaneously calculates vegetation geometry in the vertex shader and on the CPU, without actually passing any data back and forth.
    void TransformTerrain(in mat4 matrix, inout vec3 position, inout vec3 normal, inout vec3 tangent, in mat4 userparams) The following callback can be used to handle quad sphere projection. The position of the planet is stored in the first three user parameters, and the planet radius is stored in the fourth parameter. It's important to note that the position supplied to the callback is the terrain point's position in world space before the heightmap displacement is applied. The normal is just the default terrain normal in world space. If the terrain is not rotated, then the normal will always be (0,1,0), pointing straight up. After the callback is run the heightmap displacement will be applied to the point, in the direction of the new normal. We also need to calculate a tangent vector for normal mapping. This can be done most easily by taking the original position, adding the original tangent vector, transforming that point, and normalizing the vector between that and our other transformed position.
    #ifdef DOUBLE_FLOAT void TransformTerrainPoint(const dMat4& matrix, dVec3& position, dVec3& normal, dVec3& tangent, const std::array<double, 16>& userparams) #else void TransformTerrainPoint(const Mat4& matrix, Vec3& position, Vec3& normal, Vec3& tangent, const std::array<float, 16>& userparams) #endif { //Get the position and radius of the sphere #ifdef DOUBLE_FLOAT dVec3 center = dVec3(userparams[0], userparams[1], userparams[2]); #else Vec3 center = Vec3(userparams[0], userparams[1], userparams[2]); #endif auto radius = userparams[3]; //Get the tangent position before any modification auto tangentposition = position + tangent; //Calculate the ground normal normal = (position - center).Normalize(); //Calculate the transformed position position = center + normal * radius; //Calculate transformed tangent auto tangentposnormal = (tangentposition - center).Normalize(); tangentposition = center + tangentposnormal * radius; tangent = (tangentposition - position).Normalize(); } And we have a custom terrain shader with the same calculation defined below:
    #ifdef DOUBLE_FLOAT void TransformTerrain(in dmat4 matrix, inout dvec3 position, inout dvec3 normal, inout dvec3 tangent, in dmat4 userparams) #else void TransformTerrain(in mat4 matrix, inout vec3 position, inout vec3 normal, inout vec3 tangent, in mat4 userparams) #endif { #ifdef DOUBLE_FLOAT dvec3 tangentpos = position + tangent; dvec3 tangentnormal; dvec3 center = userparams[0].xyz; double radius = userparams[0].w; #else vec3 tangentpos = position + tangent; vec3 tangentnormal; vec3 center = userparams[0].xyz; float radius = userparams[0].w; #endif //Transform normal normal = normalize(position - center); //Transform position position = center + normal * radius; //Transform tangent tangentnormal = normalize(tangentpos - center); tangentpos = center + tangentnormal * radius; tangent = normalize(tangentpos - position); } Here is how we apply a transform callback to a terrain:
    #ifdef DOUBLE_FLOAT std::array<double, 16> params = {}; #else std::array<float, 16> params = {}; #endif params[0] = position.x; params[1] = position.y; params[2] = position.z; params[3] = radius; terrain->Transform(TransformTerrainPoint, params); We also need to apply a custom shader family to the terrain material, so our special vertex transform code will be used:
    auto family = LoadShaderFamily("Shaders/CustomTerrain.json"); terrain->material->SetShaderFamily(family); When we do this, something amazing happens to our terrain:

    If we create six terrains and position and rotate them around the center of the planet, we can merge them into a single spherical planet. The edges where the terrains meet don't line up on this planet because we are just using a single heightmap that doesn't wrap. You would want to use a data set split up into six faces:
    All our terrain features like texture splatting, LOD, tessellation, and streaming data are retained with this system. Terrain can be warped into any shape to support any projection method or other weird and wonderful ideas you might have.
  25. Josh
    A new update is available for Leadwerks 5 beta. This adds the ability for to use post-processing effects together with render-to-texture. The SpriteLayer class has been renamed to Canvas and the Camera::AddSpriteLayer method has been renamed to Camera::AddCanvas.
    The beta has been moved to Steam and updates will be distributed there from now on. Beta testers were sent keys to install the program on their Steam accounts.
×
×
  • Create New...