Jump to content

Josh

Staff
  • Posts

    23,227
  • Joined

  • Last visited

Blog Entries posted by Josh

  1. Josh
    I have Steam peer-to-peer networking commands implemented into Leadwerks 4.6. Here are the commands. Note this complex system has been boiled down to just three simple commands:
    class P2P { public: #ifdef LEADWERKS_5 static bool Send(uint64 steamid, const int messageid, shared_ptr<BankStream> data, const int channel = 0, const int flags = 0); static bool Send(uint64 steamid, const int messageid, shared_ptr<Bank> data, const int channel = 0, const int flags = 0); static shared_ptr<Message> Receive(uint64 steamid, const int channel = 0); #else static bool Send(uint64 steamid, const int messageid, BankStream* data, const int channel = 0, const int flags = 0); static bool Send(uint64 steamid, const int messageid, Bank* data, const int channel = 0, const int flags = 0); static Message* Receive(uint64 steamid, const int channel = 0); #endif static bool Send(uint64 steamid, const int messageid, const int channel = 0, const int flags = 0); static bool Send(uint64 steamid, const int messageid, std::string& data, const int channel = 0, const int flags = 0); static bool Send(uint64 steamid, const int messageid, const void* data, const int size, const int channel = 0, const int flags = 0); static bool Disconnect(const uint64 steamid); }; There isn't really any concept of "making a connection" here. You just start sending packets to any Steam user you want, and when you are done you can call Disconnect() to clean up some stuff. This system is completely peer-to-peer, so any player in a game can directly message any other player, although you probably want one central computer in charge of the game. NAT punch-through is supported, and in the event that it fails then messages will automatically be sent through a relay server.
    This is only part of the solution though. We still need a publicly readable list of what games are available to join.
    In a system like ENet, which is what our existing networking is built on, you have one server and multiple clients. The server can be hosted on one of the player's machines, or it can be a dedicated server. A dedicated server is a computer running the game that doesn't have a player on it. It will also skip rendering graphics so that it can relay messages between players faster. A dedicated server is always on, but these take a lot of extra work to develop and maintain, and if the dedicated server ever goes down the game is unplayable.
    A game hosted on one of the player's machines is nice because you don't have to set up a dedicated server for your game. Players themselves can create a new game with whatever settings they want, and other players can join them. However, when the player that created the game leaves, the game is terminated.
    Lobbies
    The Steam networking API supports a feature called lobbies. In games like Left 4 Dead, players will congregate in a lobby until it is full. A dedicated server is selected. The lobby is destroyed and the game moves over to a dedicated server to play. Lobbies can be selected by a player either through automatic matchmaking (I'm not a fan of this) or a list of lobbies can be retrieved.

    The name "lobby" is a little misleading because that is only one possible way this feature can be used. There is no reason a lobby can't be left open while the game is playing, in which case they function more like a regular public game server list. One interesting feature is that if the lobby creator leaves the game, a new lobby leader is automatically selected and the lobby lives on. This means that one user can create a game, other players can join it, and the original creator can leave the game without it shutting down. The new lobby leader can be used as the server that synchronizes the game state between all players, creating a sort of "floating server" that only disappears when all players leave the game.
    This is a huge improvement over both dedicated and hosted servers. A floating server isn't tied to a single machine, whether that be one player's computer or a dedicated server that has to be maintained constantly. This is the best solution to quickly get your multiplayer game up and running, and it will be available in Leadwerks Game Engine 4.6.
  2. Josh
    Crowd navigation is built into Leadwerks3D. You just set a destination, and your characters will automatically travel wherever you tell them to go. I think having navigation built into the engine will give us better results than a third party add-on would. We can automatically recalculate sections of the map that change, AI script functions can be called when certain events occur, and the code to control characters is extremely simple.
     


  3. Josh
    Current generation graphics hardware only supports up to a 32-bit floating point depth buffer, and that isn't adequate for large-scale rendering because there isn't enough precision to make objects appear in the correct order and prevent z-fighting.

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

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

    Want to render some 3D HUD elements on top of your scene? This can be done with an orthographic camera:
    auto uicam = CreateCamera(world); uicam=>SetClearMode(CLEAR_DEPTH); uicam->SetProjectionMode(PROJECTION_ORTHOGRAPHIC); This will make 3D elements appear on top of your scene without clearing the previous render result. You would probably want to move the UI camera far away from the scene so only your HUD elements appear in the last pass.
  4. Josh
    Based on the excellent sales of the SciFi Interior Model Pack, I was able to get a deal to sell a new pack of high-quality first-person weapons. The following items are included:
    Pistol
    Combat shotgun
    MP5 Machne Gun
    M4 Rifle
    Grenade
    Machete

     
    Weapons are included as animated visual weapons, and as models that can be placed throughout the map to pick up.
     

     
    Instead of just distributing raw models, I want to offer game-ready items that can just be dropped into your game. This requires me to add sounds and perform some scripting to make the FPS player handle picking weapons up and switching between weapons. It also requires some new scripting for the new weapon types, like grenades and melee weapons. It's a challenge to design the scripts in a general-purpose manner, the benefit is everyone gets game-ready weapons they can simply drop into their map for the player to find and use. Plus, I get to design some cool explosion effects.
     
    The pack will be priced at $29.99, includes sounds, scripts, and special effects, and will be available in October.
     

  5. Josh
    Compiling Leadwerks on Linux using the Code::Blocks IDE wasn't very hard. I particularly like the way Code::Blocks handles files in projects. Instead of creating your own "fake" file system in the project explorer, you just add the folder or files you want, and they are organized in a hierarchy that matches the file system.
     
    I found the X windowing system to be extremely easy to work with. I've implemented windowed OpenGL contexts on Windows, OSX, iOS, and Android, and Linux was by far the most straightforward and simple.
     
    One problem I ran into was that some Leadwerks classes conflict with Linux classes. Our Windows, Font, and Time classes match other classes in the Linux headers. I got around this by prefixing the classes with the "Leadwerks::" namespace, but some better solution should be found.
     
    Upon my first attempt to run Leadwerks, all I got was a blue screen. I fell back from OpenGL 4 to OpenGL 2 and started playing with the OpenGL matrix commands (a relic of the fixed-function pipeline I never even use anymore). When I called glOrtho() with the context width and height, I produced an OpenGL INVALID_VALUE error. From there it wasn't hard to figure out the cause...
     

    int Window::GetClientWidth) { return 0;/*don't forget to add this later*/ }
     
    Once I implemented the missing function, everything worked fine. So I am pleased to unveil the very first screenshot in the history of the universe of Leadwerks running in Linux using OpenGL 4:
     

    Behold, the green square of triumph!
     
    Though this is a simple example, we have a full 3D engine with deferred lighting and a really unique terrain system, so I don't think it will be long now before we can start showing off some great Linux graphics. Linux has the fastest possible performance for 3D rendering, so it will be interesting to see what we can do with it.
  6. Josh
    As I finish up the FPS Weapons Pack, I am struck by just how much work it takes to build a game, beyond the engine programming. I wrote Leadwerks, and it still took me several days to script the behavior of these weapons. I also had the advantage of being able to add new entity classes, as I needed two new entity types to complete the task: LensFlares and Sprites.
     
    I like the flexibility of Leadwerks to make any type of game, and I think having an abstract 3D command set to do anything is really powerful. However, I feel like we're still missing something, and it's not more graphical features.
     
    The output of the community can be seen on the games page and in the Leadwerks Workshop. There are a lot more Leadwerks 3 games than Leadwerks 2 games, which is a good sign. In most cases, I think the bottleneck is on content. I don't simply mean 3D models. Those are fairly easy to obtain, although it is very hard to find a large collection of artwork with consistent art direction. The problem is there is a big gap between having a 3D model and having a scripted game-ready item you can drop into the map and start playing. Considering how much work it has taken me just to set up the first two DLCs, it's ridiculous to expect each individual to reproduce all that work themselves.
     
    You can categorize most game technology into low, mid, and high-level features. Leadwerks 2 was only strong in the lowest level category. Leadwerks 3 adds very strong support for the mid-level category, thanks to the editor. Traditionally, everything beyond that has been left up to the user to implement.
     

     
    This is something that needs to change. Low-level game technology is not very valuable anymore. I think the success of Leadwerks will be based on connecting games with players on Steam. To make that happen, we need more games. I want to make the development process a little more like modding a game, with more game-ready content ready to use. Anyone should be able to make a game without any programming at all. When you're ready to dig into Lua or C++, it's there, but you should be able to get up and running without it.
     


     
    Fortunately, the community has responded positively to the first DLC and I think the FPS Weapons Pack will sell well. I've started production of our own game assets using just a few artists. Why are we producing our own stuff, rather than reselling existing work?
    There's a ton of content out there already, but most of it is very bad quality.
    Even the good quality stuff doesn't feel consistent because it comes from different sources.
    Interactive objects like characters and weapons still require a ton of work to make game-ready. Often times animations have all kinds of trouble and glitches.
    It takes a ton of work getting things ready for use. Why not just make it to spec from the start, instead of scrounging for content that often times is not suited for purpose?
    Since sales cover the cost of production, I'd rather just sell them myself and make a profit. (Two revenue streams are better than one because it gives you more options.)

     
    Right now I have my environment artist, my character artist, and will likely rely on the same person for future weapon packs. All Leadwerks content will come from the same artists, so it will all look like it fits together. The first character pack is in production now.
     
    So I think you can expect that I will start taking on more responsibility for gameplay stuff that has in the past been left up to the end user. I won't attempt to create every game under the sun, but I think Leadwerks should have more default gameplay stuff the user can modify to suit their own needs.
  7. Josh
    With the help of @martyj I was able to test out occlusion culling in the new engine. This was a great chance to revisit an existing feature and see how it can be improved. The first thing I found is that determining visibility based on whether a single pixel is visible isn't necessarily a good idea. If small cracks are present in the scene one single pixel peeking through can cause a lot of unnecessary drawing without improving the visual quality. I changed the occlusion culling more to record the number of pixels drawn, instead just using a yes/no boolean value:
    glBeginQuery(GL_SAMPLES_PASSED, glquery); In OpenGL 4.3, a less accurate but faster GL_ANY_SAMPLES_PASSED_CONSERVATIVE (i.e. it might produce false positives) option was added, but this is a step in the wrong direction, in my opinion.
    Because our new clustered forward renderer uses a depth pre-pass I was able to implement a wireframe rendering more that works with occlusion culling. Depth data is rendered in the prepass, and the a color wireframe is drawn on top. This allowed me to easily view the occlusion culling results and fine-tune the algorithm to make it perfect. Here are the results:
    As you can see, we have pixel-perfect occlusion culling that is completely dynamic and basically zero-cost, because the entire process is performed on the GPU. Awesome!
  8. Josh
    Three years ago I realized we could safely distribute Lua script-based games on Steam Workshop without the need for a binary executable.  At the time this was quite extraordinary.
    http://www.develop-online.net/news/update-leadwerks-workshop-suggests-devs-can-circumvent-greenlight-and-publish-games-straight-to-steam/0194370
    Leadwerks Game Launcher was born.  My idea was that we could get increased exposure for your games by putting free demos and works in progress on Steam.  At the same time, I thought gamers would enjoy being able to try free indie games without the possibility of getting viruses.  Since then there have been some changes in the market.
    Anyone can publish a game to Steam for $100. Services like itch.io and GameJolt have become very popular, despite the dangers of malware. Most importantly, the numbers we see on the Game Launcher just aren't very high.  My own little game Asteroids3D is set up so the user automatically subscribes to it when the launcher starts.  Since March 2015 it has only gained 12,000 subscribers, and numbers of players for other games are much lower.  On the other hand, a simple game that was hosted on our own website a few years back called "Furious Frank" got 22,000 downloads.  That number could be much higher today if we had left it up.
    So it appears that Steam is good for selling products, but it is a lousy way to distribute free games.  In fact, I regularly sell more copies of Leadwerks Game Engine than I can give away free copies of Leadwerks Game Launcher.
    This isn't to say Game Launcher was a failure.  In many cases, developers reported getting download counts as high or higher than IndieDB, GameJolt, and itch.io.  This shows that the Leadwerks brand can be used to drive traffic to your games.
    On a technical level, the stability of Leadwerks Game Engine 4 means that I have been able to upgrade the executable and for the most part games seamlessly work with newer versions of the engine.  However, there are occasional problems and it is a shame to see a good game stop working.  The Game Launcher UI could stand to see some improvement, but I'm not sure it's worth putting a lot of effort into it when the number of installs is relatively low.
    Of course not all Leadwerks games are written in Lua.  Evayr has some amazing free C++ games he created, and we have several commercial products that are live right now, but our website isn't doing much to promote them.  Focusing on distribution through the Game Launcher left out some important titles and split the community.
    Finally, technological advancements have been made that make it easier for me to host large amounts of data on our site.  We are now hooking into Amazon S3 for user-uploaded file storage.  My bill last month was less than $4.00.
    A New Direction
    It is for these reasons I have decided to focus on refreshing our games database and hosting games on our own website.  You can see my work in progress here.
    https://www.leadwerks.com/games
    The system is being redesigned with some obvious inspiration from itch.io and the following values in mind:
    First and foremost, it needs to look good. Highly customizable game page. Clear call to action. There are two possible reasons to post your game on our site.  Either you want to drive traffic to your website or store page, or you want to get more downloads of your game.  Therefore each page has very prominent buttons on the top right to do exactly this.
    Each game page is skinnable with many options.  The default appearance is sleek and dark.

    You can get pretty fancy with your customizations.

    Next Steps
    The templates still need a lot of work, but it is 80% done.  You can begin playing around with the options and editing your page to your liking.  Comments are not shown on the page yet, as the default skin has to be overridden to match your page style, but they will be.
    You can also post your Game Launcher games here by following these steps:
    Find your game's file ID in the workshop.  For example if the URL is "http://steamcommunity.com/sharedfiles/filedetails/?id=405800821" then the file ID is "405800821". Subscribe to your item, start Steam, and navigate to the folder where Game Launcher Workshop items are stored:
    C:\Program Files (x86)\Steam\steamapps\workshop\content\355500 If your item is downloaded there will be a subfolder with the file ID:
    C:\Program Files (x86)\Steam\steamapps\workshop\content\355500\405800821 Copy whatever file is found in that folder into a new folder on your desktop.  The file might be named "data.zip" or it could be named something like "713031292550146077_legacy.bin".  Rename the file "data.zip" if it is. Copy the game launcher game files located here into the same folder on your desktop:
    C:\Program Files (x86)\Steam\steamapps\common\Leadwerks Game Launcher\Game When you double-click "game.exe" (or just "game" on Linux) your game should now run.  Rename the executable to your game's name, including the Linux executable if you want to support Linux. Now zip up the entire contents of that folder and upload it on the site here. You can also select older versions of Game Launcher in the Steam app properties if you want to distribute your game with an older executable.
    Save the Games
    There are some really great little games that have resulted from the game tournaments over the years, but unfortunately many of the download links in the database lead to dead links in DropBox and Google Drive accounts.  It is my hope that the community can work together to preserve all these fantastic gems and get them permanently uploaded to our S3 storage system, where they will be saved forever for future players to enjoy.
    If you have an existing game, please take a look at your page and make sure it looks right.
    Make any customizations you want for the page appearance. Clean up formatting errors like double line breaks, missing images, or dead links. Screenshots should go in the screenshot field, videos should go in the video field, and downloads should go in the downloads field. Some of the really old stuff can still be grabbed off our Google drive here.
    I appreciate the community's patience in working with me to try the idea of Game Launcher, but our results clearly indicate that a zip download directly from our website will get the most installs and is easiest for everyone.
  9. Josh
    In Leadwerks 4.3 we integrated GameAnalytics.com into our software, both in the editor and in the engine, as a tool developers can use to track their player statistics.  A number of events were set up in the editor to fire when certain actions were performed, in order to gain better insight into how people were using Leadwerks.  Here are the results.
    The most popular primitives
    Unsurprisingly, boxes are by far the most popular primitive created in Leadwerks Editor.  The community has created more than 20,000 boxes since analytics were enabled.  What blows my mind is that cylinders are actually the second-most commonly used primitive, rather than wedges.  Users created 1753 cylinders but only 1358 wedges in the given time period!  Even more shocking is that spheres are more popular than cones, with 985 spheres created versus just 472 cone primitives.
    This causes me to question my assumptions of how people use Leadwerks, and how items in the interface should be prioritized.

    The Workshop Store is used heavily
    Leadwerks users installed more than 5000 items from the Workshop.  It also looks like people install many items, as sometimes the number of installs exceed the number of times the Workshop interface is opened.

    People buy Workshop items directly through the Steam Client
    Although users are buying Workshop items in high quantities, it appears that their primary route is through the Steam store interface, rather than the built-in Workshop browser.  People browse the items in the Steam client and then use the Workshop browser to install their purchased items.  The numbers for Workshop Store purchases are much higher than the number of people clicking the buy button in the editor.
    If you're interested in selling your 3D models or textures through the Leadwerks Workshop Store, it's easy to get started and you can earn money directly from Steam.  Contact us to learn more or stop by the forum.
  10. Josh
    Here's what's next for Leadwerks Game Engine.
     
    Game Launcher
    Leadwerks Game Launcher will be released in early preview mode when 50 games are reached. Right now we have 13. Filling the content up is a high priority, and the Summer Game Tournament will bring us closer to that goal.
     
    I am mentioning this first because I want to emphasize that the number of games posted is my measure of how well I'm doing. Every action should be considered in terms of how it helps you guys publish more games.
     
    Usability Improvements
    Since the learning process for Leadwerks is now very well defined, we're able to identify and improve minor hiccups in the workflow. I'm getting a lot of great feedback here. The tutorials have helped y giving us a common reference point we can use to communicate how things should be done, and what can be improved.
     
    Documentation will continue to be improved as we gain more experience. I am holding off on recording any videos until we get the materialize finalized.
     
    Here are some of the things I want to modify or improve:
    New icon set
    Improve 3D control widget
    Use name in script entity field instead of drag+drop
    World:FindEntity() Lua command
    Revert material editor texture list to old style
    Implement web browser in Linux GUI for welcome page and Workshop.
    Add built-in help window for better documentation layout (will still pull data from online pages)
    Improve project manager and new project window, using web interface.
    Research is being performed on improvements to the script editor.

     
    Game Templates
    The 2D and 3D platformer templates are the easiest to make and are good examples for learning. These will likely come next. A racing game template is fun and simple, and may come after that.
     
    DLC
    A soldier pack would add a lot of new options to gameplay. We saw with the zombie pack that many people were able to use these items in a lot of different ways, and it added a lot of depth to gameplay. I think a soldier pack with characters that can fight against or with the player, or against zombies, each other, etc. would be a great addition.
     
    New Features
    I make decisions on new features based on the following question: What will result in more games? The things I think are important are a vegetation system like Leadwerks 2 had (but better), decals, and some built-in GUI commands. It is likely that the vegetation system will be the next big feature.
     
    Built-in Model Store
    We have some setbacks on this, but we're researching another way to make this happen.
     
    That's quite a lot of different stuff.
     
    As always, any of these items are subject to change.
  11. 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.
  12. Josh
    The Leadwerks 2 vegetation system used a grid of "cells" that each contained a quantity of vegetation instances. If the cell was close enough, each instance was checked against the camera frustum like a regular entity and then all visible instances were rendered in a batch. Cells that were further away were collapsed into a single surface consisting of a quad for each instance. This is how we were able to draw massive amounts of foliage in the distance at a fast framerate:
     


     
    This was good, but limited by the scene management performed by the CPU. It also used very large amounts of memory to store all the 4x4 matrices of the individual instances. The above map used about half a gigabyte of data to store all positions for each tree int he scene.
     
    The Leadwerks 3 vegetation system is being designed around modern hardware capabilities, taking advantage of the features of OpenGL 4. This has two goals:
    Remove the overhead of scene management performed on the CPU.
    Reduce memory usage of vegetation data.


    Transform Feedback
    One of the most interesting things OpenGL 4 supports is transform feedback, which allows you to render data to an arbitrary buffer. This can be combined with a geometry shader to selectively output information in ways a normal render-to-texture can't. This schematic from RasterGrid's OpenGL blog shows how the system works: 

     
    Below you can see my implementation working. The scene consists of 91 million polygons, with about 28 million visible at any given time. (This number will be reduced through the use of billboards.) The edges of the camera frustum are pulled in to make the culling visible.
     


     
    Use of the transform feedback feature in OpenGL 4 relieves the CPU from the expensive overhead of large-scale scene management.

    Tiling Matrices
    The need for individual instance matrices is being totally eliminated by using a tiling grid of 4x4 matrices. This creates a repeating pattern of rotations across the scene. I think when the trees are placed on a hilly terrain the tiling appearance won't be visible, but there are other tricks I can use to increase the randomness. The physics system will work by dynamically retrieving the relevant 4x4 matrices of nearby instances, totally eliminating the need to store any of this massive data in memory. The fact that no instance data is being sent over the PCIE bridge also means this system will be much faster than Leadwerks 2. 
    Vegetation in Leadwerks 3 will be a virtually zero-overhead system and allow for much greater volumes of geometry than Leadwerks 2 could render, at effectively no cost on most hardware.
  13. Josh
    Leadwerks Game Engine 4.1 beta is now available on the beta branch on Steam. This is the final version 4.1, barring any bug fixes that are made before the release later this month. I am going to focus on just resolving bug reports and adding new documentation for the next two weeks.
     

     
    4.1 adds environment probes, which have been covered previously, and volumetric lighting. Point and spot lights can display a volumetric effect by adjusting the "Volume strength" setting, found under Light properties in the editor. You can also use the new commands Light::SetVolumetricStrength() and Light::GetVolumetricStrength(). At this time, directional lights should use the godray shader instead, but I might try this effect on directional lights as well before the release.
     
    Volumetric lighting is an expensive effect to render, but my Intel 5200 is running it at a fast speed, and high-end cards should have no problem with it. Lights with a large range are more expensive than lights with a small range. The performance will be exactly the same for any intensity level above zero (the default). The effect is disabled when the low-quality light setting is in use.
     
    Volumetric lighting looks best in dark scenes with lots of contrast. Spotlights tend to look a little better, as point lights don't have the same well-defined shape. Use these sparingly for dramatic lighting effects in key positions. It looks especially nice when you have a fence or grate in front of the light to create lots of interesting light beams.
     
    The bloom effect has also been improved, and SSAO, godrays, and iris adjustment post-processing effects have been added. The bloom effect is using the mixing equation from Leadwerks 2, along with an improved Gaussian blur function from the RasterGrid blog.
     

     
    New entity icons courtesy or reepblue have been created.
     
    Have fun with the new features and let me know if you have any questions.
  14. Josh
    A new build is available on the beta branch. This changes the model picking system to use a different raycasting implementation under-the-hood. Sphere picking (using a radius) will also now correctly return the first hit triangle. You will also notice much faster loading times when you load up a detailed model in the editor!
    Additional parameters have been added to the Joint::SetSpring command:
    void Joint::SetSpring(const float spring, const float relaxation = 1.0f, const float damper = 0.1f) The classes for P2P networking, lobbies, and voice communication have been added but are not yet documented and may still change.
  15. Josh
    A new update to Leadwerks 3.0 is now available. Registered developers can run the Leadwerks updater to download and install the patch. This update adds terrain, bug fixes, and a few small feature enhancements.
    Our new terrain system, described in our Kickstarter campaign to bring Leadwerks to the Linux operating system, is based on a unique "dynamic megatextures" approach. This technique renders sections of the terrain into virtual textures and places them around the camera. The terrain presently allows a maximum size of 1024 meters and 16 texture layers, but these constraints can be lifted in the future once it's been thoroughly tested. You can see an example terrain the the "terrain.map" scene included in the example project folder.

    With the increased scene geometry terrain brings, I found it necessary to precalculate navmeshes in the editor. To calculate a navmesh for a map, select the Tools > Build NavMesh menu item to being up the Build NavMesh Dialog. The navigation data will be saved directly into your map file for pathfinding. Two values have been exposed to control the navmesh calculation and the appearance of the navmesh has been improved to allow easier visual debugging. Additionally, the new World::BuildNavMesh command lets you calculate navigation meshes in code.
    The bug report forum contains info about recently fixed problems. The most notable fix was for character controller physics. Some frame syncing issues were fixed which were causing entities to sometimes pass through walls and floors. This problem was very apparent in the recent game demo GreenFlask,
    A new command World::SetPhysicsDetail allows you to balance the speed and accuracy of the physics simulator.
    The Transform::Plane command has been enhanced to work with Lua, which had trouble understanding the syntax of the command.
  16. Josh
    Day/night cycles are something I have thought about for a long time. There's several possible approaches to simulating these, but it wasn't until today that I decided which is best. Here are some options I considered:
     
    1. Shader-based skies with particle clouds.
    This is the method Crysis employs. A subsurface scattering shader creates the sky background. The mathematics are pretty complex, but the results for an empty blue sky look great. Particle clouds are used to place sparse clouds in the sky.
     
    Pros: Clear skies look great.
    Cons: Not very good for overcast skies, clouds look worse than a skybox, expensive to render.
     
    2. Animated skyboxes.
    The idea is to animate a looping sequence of skybox animations for a 24-hour cycle, and interpolate between the nearest two frames at all times.
     
    Pros: Potentially very high quality results, with a low cost of rendering.
    Cons: Long rendering times to generate skybox textures, no ability to tweak and adjust skies.
     
    The solution I finally arrived at is similar to the day/night cycles in STALKER: Call of Pripyat. A skybox with uniform lighting is used, so that it is not apparent from which direction the sunlight should be coming. The ambient light level, directional light color, fog, and skybox color are adjusted according to a set of colors for a 24-hour cycle. A large sun corona can be used to simulate the sun moving across the sky, and the directional light angle can be synced with the sun.
     
    Pros: Adjustable and dynamic, easy to add new skyboxes, low cost to render. Skyboxes can be changed to alter weather.
    Cons: Skyboxes with a distinct direction of lighting won't look good.
     
    Here are some of my results using this method:



    Of course, you are still free to implement any method you choose, but I am pretty satisfied with this approach for my own work, and I will make a scripted entity available to control these settings.
  17. Josh
    I have the basis of CSG editing written into the engine. It was a bit challenging to figure out just how to structure the Brush class. We have a lightweight ConvexHull entity, which is used for the camera frustum. This is an invisible volume that changes frequently, and is used to test whether objects intersect the camera's field of view. I didn't want to bloat this class with a lot of renderable surfaces, texture coordinates, vertex normals, etc. I initially thought I would derive the Brush class from the ConvexHull class, and make brushes a more complex renderable "thing" in the world.
     
    I didn't want to create multiple rendering pathways in the octree system, so it made sense to derive the Brush class from the Entity class, so all the entity rendering pathways could be used to tell whether a brush should be drawn. However, both the Entity and ConvexHull classes are derived from the base class in Leadwerks3D, the Object class. This creates the diamond inheritance problem, where there are two Objects a Brush is derived from:

     
    In the end, I derived the Brush class from the Entity class, and just gave it a ConvexHull for a member. This meets the needs of the system and is simple:

    class Brush : public Entity { ConvexHull* convexhull; ... };
     
    So with that out of the way, it's on to editing. There are three types of objects you can create in the Leadwerks3D editor. You can drag a model into the scene from the asset browser. You can create and modify brushes like in 3D World Studio. You can also create special entities like lights, particle emitters, sound emitters, and other items.
     
    I'm ready to start CSG editing, but the thing that strikes me right away is I have no idea how object creation should work. You can drag models into the scene right now, from the Asset Browser. However, CSG brushes work better when they are drawn like architecture drafting:

     
    For "point entities" 3D World Studio lets you position a crosshair, then hit enter to create the current object:

     
    I'm not sure how we should handle brush and model editing in here. Usually when I run into a problem and am not sure how to proceed, I struggle with it a few days, but when I'm done, I am certain the approach I chose was best. Here's another one of those situations. Feel free to discuss and explore ideas with me in the comments below. Here's what the editor looks like at the moment:

     
    --Edit--
     
    Here's something I am playing with. The "Objects" menu contains a lot of brushes and special entities you can create:

     
    This idea actually goes back to the old Quake 3 Arena editor, Radiant (which was otherwise a pretty difficult tool to use!):

     
    You would still be able to drag a model into the scene from the Asset Browser at any time, but the brush and entity creation would be more like 3D World Studio. Of course, we also want the ability to move, rotate, and scale things in the 3D viewport as well! I generally don't like having multiple ways to do a thing, but the editing choices here make good sense. I'll keep playing with ideas and see what I come up with. Feel free to chime in, in the comments below.
  18. Josh
    I realized there are two main ways a plugin is going to be written, either as a Lua script or as a DLL. So I started experimenting with making a JSON file that holds the plugin info and tells the engine where to load it from:
    { "plugin": { "title": "Game Analytics", "description": "Add analytics to your game. Visit www.gameanalytics.com to create your free account.", "author": "© Leadwerks Software. All Rights Reserved.", "url": "https://www.turboengine.com", "library": "GameAnalytics.dll" } } { "plugin": { "title": "Flip Normals", "description": "Reverse faces of model in Model Editor.", "author": "© Leadwerks Software. All Rights Reserved.", "url": "https://www.turboengine.com", "scriptfile": "FlipNormals.lua" } } I like this because the plugin info can be loaded and displayed in the editor without actually loading the plugin.
    I also wanted to try using a JSON file to control script properties. For example, this file "SlidingDoor.json" goes in the same folder as the script and contains all the properties the engine will create when the script is attached to an entity:
    { "script": { "properties": { "enabled": { "label": "Enabled", "type": "boolean", "value": true, "description": "If disabled the door will not move until it is enabled." }, "distance": { "label": "Distance", "type": "float", "value": [1,0,0], "description": "Distance the door should move, in global space." }, "opensound": { "label": "Open Sound", "type": "sound", "value": null, "description": "Sound to play when door opens." }, "closedelay": { "label": "Close Delay", "type": "integer", "value": 2000, "minvalue": 0, "description": "The number of milliseconds a door will stay open before closing again. Set this to 0 to leave open." } } } } I like that it is absolutely explicit, and it allows for more information than the comments allow in Leadwerks 4. There is actually official tools for validating the data. The json data types map very closely to Lua. However, it is more typing than just quickly writing a line of Lua code.
    While we're at it, let's take a look at what a JSON-based scene file format might look like:
    { "scene": { "entities": [ { "type": "Model", "name": "main door", "id": "1c631222-0ec1-11e9-ab14-d663bd873d93", "path": "Models/Doors/door01.gltf", "position": [0,0,0], "euler": [90,0,0], "rotation": [0,0,0,1], "scale": [1,1,1], "matrix": [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]], "mass": 10, "color": [1,1,1,1], "static": false, "scripts": [ { "path": "Scripts/Objects/Doors/SlidingDoor.lua", "properties": { "distance": [1,0,0], "movespeed": 5 } }, { "path": "Scripts/Objects/Effects/Pulse.lua" } ] } ] } }  
  19. Josh
    There's basically two kinds of vegetation, trees and grass.
     
    Grass is plentiful, ubiquitous, dense, but can only be seen at a near distance. Its placement is haphazard and unimportant. It typically does not have any collision. ("Grass" includes any small plants, bushes, rocks, and other debris.)
     
    Trees are fewer in number, larger, and can be seen from a great distance. Placement of groups of trees and individual instances is a little bit more important. Because these tend to be larger objects, they have physics collision.
     
    Now figuring out how to take advantage of modern GPU architecture with both of those types of objects is an interesting problem. I started with geometry shaders, thinking I could use them to create duplicate instances on the fly. This geometry shader will do just that:

    #version 400  
    #define MAX_INSTANCES 81
    #define MAX_VERTICES 243
    #define MAX_ROWS 1
     
    uniform mat4 projectioncameramatrix;
    uniform mat4 camerainversematrix;
     
    layout(triangles) in;
    layout(triangle_strip,max_vertices=MAX_VERTICES) out;
     
    in mat4 ex_entitymatrix[3];
    in vec4 ex_gcolor[3];
    in vec2 ex_texcoords0[3];
    in float ex_selectionstate[3];
    in vec3 ex_VertexCameraPosition[3];
    in vec3 ex_gnormal[3];
    in vec3 ex_tangent[3];
    in vec3 ex_binormal[3];
    in float clipdistance0[3];
     
    out vec3 ex_normal;
    //out vec4 ex_color;
    out vec3 ex_jtangent;
    out vec3 ex_jbinormal;
    out vec3 ex_jjtangent;
     
    void main()
    {
    //mat3 nmat = mat3(camerainversematrix);//[0].xyz,camerainversematrix[1].xyz,camerainversematrix[2].xyz);//39
    //nmat = nmat * mat3(ex_entitymatrix[0][0].xyz,ex_entitymatrix[0][1].xyz,ex_entitymatrix[0][2].xyz);//40
     
    for(int x=0; x<MAX_ROWS; x++)
    {
    for(int y=0; y<MAX_ROWS; y++)
    {
    for(int i=0; i<3; i++)
    {
    mat4 m = ex_entitymatrix;
    m[3][0] += x * 4.0f;
    m[3][2] += y * 4.0f;
    vec4 modelvertexposition = m * gl_in.gl_Position;
    gl_Position = projectioncameramatrix * modelvertexposition;
    ex_normal = ex_gnormal;
    //ex_color = ex_gcolor;
    ex_jjtangent = ex_gnormal;
    ex_jtangent = ex_gnormal;
    ex_jbinormal = ex_gnormal;
    EmitVertex();
    }
    EndPrimitive();
    }
    }
    }
     
    I soon discovered some severe hardware limitations that make geometry shaders unusable for this type of application. There's a 255 limit on the number of vertices that can be emitted, but there's an even harsher limit on the number of varying you can output from the shader. Once you add values for texcoords, normals, binormals, and tangents, geometry shaders actually only allow 16 instances per render...making them inappropriate for this purpose.
     
    What's really needed is an "instance shader" that could control how many times an object is rendered. This would simply discard an entire instance if it isn't in the camera frustum:

    uniform vec4 cameraplane0; uniform vec4 cameraplane1;
    uniform vec4 cameraplane2;
    uniform vec4 cameraplane3;
    uniform vec4 cameraplane4;
    uniform vec4 cameraplane5;
     
    uniform objectradius;
     
    uniform instancematrix[MAX_INSTANCES]
     
    bool PlaneDistanceToSphere(in vec4 plane in vec3 point, in float radius) {}
     
    main ()
    {
    mat4 mat = instancematrix[gl_InstanceID];
    vec3 pos = mat[3].xyz;
    float radius = objectradius * max(max(mat[0].length(),max[1].length),mat[2].length);
    if (PlaneDistanceToSphere(cameraplane0,position,radius) > 0.0) discard;
    }
     
    I realized I could render a number of instances without actually sending their 4x4 matrices to the GPU, and just generate the positions along a grid. This would start with an n X n grid and then add some noise to randomly rotate and scale each instance. The randomized positions would use the object's XZ position on the grid as the input, so I could dynamically generate the same orientation each time, without ever storing the object's actual position in memory. (This is why some Leadwerks 2 maps could be hundreds of megs of data.)
     
    Here is the code in the vertex shader that randomizes object scale and rotation:

    #define ROWSIZE 15 #define SEED 1
    #define randomness 3.0
    #define density 3.0
    #define scalevariation 1
     
    int id = gl_InstanceID;
    int x = id/ROWSIZE;
    int z = id - x * ROWSIZE;
     
    float angle = rand(vec2(SEED-z,SEED+x))*6.2831853;
     
    //Random rotation
    mat4 rotmat = mat4(1.0);
    rotmat[0][0] = sin(angle);
    rotmat[0][2] = cos(angle);
    rotmat[2][0] = -sin(angle+1.570796325);
    rotmat[2][2] = -cos(angle+1.570796325);
    entitymatrix_=rotmat*entitymatrix_;
     
    float scale = rand(vec2(SEED+z,SEED-x));
    float sgn = sign(scale-0.5);
    scale = (abs(scale-0.5));
    scale *= scale;
    scale = (scale *sgn + 0.5);
     
    scale = scale * scalevariation + 1.0-scalevariation/2.0;
    entitymatrix_[0] *= scale;
    entitymatrix_[1] *= scale;
    entitymatrix_[2] *= scale;
    entitymatrix_[3][0] = x*density + rand(vec2(SEED+z,SEED-x))*randomness;
    entitymatrix_[3][2] = z*density + rand(vec2(SEED+x,SEED-z))*randomness;
     
    Here is the result with trees randomly oriented entirely on the GPU:
     

     
    Of course you can have issues like no way to prevent two instances from being too close together, but a random seed, density, and randomness values are adjustable. It would also be possible to calculate a neighbor's position and use that to weight the position of the current instance.
     
    There are still many questions to be answered, but I think this approach is going in the right direction to design a more powerful and lower overhead vegetation rendering system for Leadwerks 3.
  20. Josh
    Our most recent game tournament was a smashing success.  We had fewer entries this time, but they more than made up for it with the great quality of this round of games.  Without further ado I am happy to present the entries...
    Behind Enemy Lines
    Wow!  This game by burgelkat features a variety of missions from blowing up drug manufacturing facilities to sabatoging a plane.  Although the same mechanic is usually used, the action never gets old and you will keep playing just to find out what will happen next.  You may recognize the voice acting from our own Jorn Theunissen (Aggror) on the forum.  You don't want to miss this one!



    Nightmare Prism
    Nirvana is popular right now, and the SNES classic edition was just released.  In case that's not enough 1990's nostalgia for you, here is the excellent Nightmare Prism by AngelWolf.  Clever level design with lots of traps and well-placed enemies will keep you on your toes as you frag your way through three levels of hellish onslaughts.
    The Cemetery
    Third on our list of games is an explicitly Halloween-themed title with tombstones and pumpkins aplenty.  The Cemetery by Rozsoft is a short but suspenseful experience putting you into an old graveyard at night in search of your disappeared friends.  Best played late at night with the lights out!


    Dread Loop
    This title by member "FortifyThisMFker!" brings us back to the 90's shooter theme with a center-mounted gun and an arena of enemies.  After dispatching your foes you can select an upgrade for your weapon, health, or suit, which makes for some interesting choices.  But I've got to be honest, seeing the giblets fly is what really makes this game fun.  Try it out!


    Exit Zed
    In Exit Zed by mdgunn you will explore a scientific facility in search of zombies to high-five...except that the way you like to give high-fives is with your handy dandy fully automatic Bunsen burner tool (patent pending).  The game is obviously unfinished and you might stumble across some doorways leading to nothingness, but the sound effect of your trusty scientific tool alone makes this worth playing.


    Dissension
    Available in Leadwerks Game Launcher, Dissension is another nail-biting SciFi shooter from Garlic Waffle.  Despite the cartoonish graphics, his games really frightening.  This one is sure to keep you on the edge of your seat!


    Sewer Survival
    Garlic Waffle has gone into overtime and brought your TWO free games to play this tournament!  In Sewer Survival you play to make it out of an underground prison.  Expect clever puzzles, loads of enemies, and not a lot of hit points.


  21. Josh
    Along with Leadwerks GUI, Leadwerks 4.4 adds an in-game menu that is available with the default Lua scripted game.  You can use this to allow your users to adjust settings in the game, or provide a more sophisticated way to quit the game than simply pressing the escape key.

    The default window size has been changed to 1280x720 when run from the editor.  Your game will now run in fullscreen mode by default when it is launched outside the editor.
    All of these changes are contained in the Main.lua and Menu.lua scripts, and can all be modified or removed to your heart's content.
    A full build is available now on the beta branch.
  22. Josh
    It's November 1, and this is the longest summer I can remember in a while. Jokes about the seasons aside, last week I had trouble deciding what to do first, so I decided to attack the remaining tasks I was most scared of. This has been my strategy throughout the entire process, and it results in my work becoming progressively easier as we near the finish line.
     
    I finished one big task today that was holding us back. We need a file system watcher for Mac computers to detect changes to the file system. That way when files are created, deleted, modified, or renamed, the editor can detect the change and automatically reconvert and reload assets. This feature allows you to keep Photoshop open and work on a texture, while Leadwerks will display your changes instantly every time you save the file.
     
    I started by asking a few questions on the Mac developer forum on Apple's website. I found one method of doing this with an event stream, but that wasn't recursive. Finally I built some code off the Objective-C example here:
    https://developer.ap...nts/_index.html
     
    Objective-C is frightening (perfect for Halloween!), but after this experience I feel slightly less afraid of it. Once I got the code working, I found that Mac FSEventStreams only give you a folder path; they don't tell you which exact file changed, or whether it was created, deleted, renamed, or modified. Going back to the editor side of things, I added some code that reads the directory structure at startup and stores the file time for each file. Some clever code analyzes a folder when an even occurs, and then is able to emit events based on whether a file was changed, created, deleted, etc.
     
    So what's left to do? Well, here's my exact list:


    Lua Interpreter project for iOS and Android
    Improve flowgraph visual style
    Documentation
    Recursive rendering (like LE2 uses)
    Restructure asset class (I did something stupid here that can be improved)
    Brush rendering batches (to make editor rendering faster)
    Finish skinning in OpenGL ES renderer (copy and paste, mostly)
    Undo system

    And what we finished recently:
    Move project over to VS 2010 (Chris)
    FileSystemWatcher for Mac (today)

    I'm heading down to the hacker lab tonight to talk to the co-founders there. Catch me in Left 4 Dead 2 past about 9 P.M. PST if you want to kill zombies for a round.
  23. Josh
    Leadwerks Game Engine 4.4, scheduled for release soon, features some updated and enhanced visual effects.  In this blog I will talk about some of the adjustments I made.  Having "The Zone" scene that Aggror recreated actually helped a lot to see how shaders could be improved.
    Bloom / Iris Adjustment / HDR
    The bloom and iris adjustment shaders have been updated to give bloom a wider and softer blur.  Iris adjustment is faster and more intense now, which will make the outdoors areas seem very bright when you are indoors, and the indoors areas will look very dark when you are outdoors.


    SSAO
    The SSAO shader has multiple passes added to it, resulting in a much smoother yet crisp result. 



    Vegetation Shaders
    A new vegetation shader called "groundrocks" will make objects align to the terrain.  This lets you easily paint clusters of rocks all across a landscape, resulting in a nice chunky look that breaks up boring heightmap terrain.

    Built-in Fog
    Although several fog shaders have been available in the Workshop for some time, version 4.4 adds a built-in distance fog you can use to make spooky scenes.

    The fog is built into several shaders in order to give correct reflections.  It works great with water.  Notice the reflection is also foggy, and the water itself is affected by fog, giving a nice misty look to the far side of the lake.

    You can try Leadwerks Game Engine 4.4 right now by opting into the beta branch on Steam.
  24. Josh
    Leadwerks is about making games, first and foremost. Therefore, you can expect me to focus on the following in the next year:
    More and better AAA first-party content. We're working out the process right now. It's a lot harder to produce top-quality content, but the results are worth it. I figure once we get the process down it will be easier to go back and produce more.
    Easy to use networking will open up a lot more gameplay possibilities and give us some fun games we can jump in and out of. I want to shoot it out with you guys in a variety of games with different objectives.
    Paid Workshop items are something I am very interested in.

     
    These are the main ideas that I feel will move Leadwerks in the direction we want to go. There will be undoubtedly other features and improvements added, but these are the things that really move us forward. Overall, I want Leadwerks to become more like a moddable game, without losing the simplicity and flexibility we have to make anything.
  25. Josh
    I've never coded much polygonal modeling routines, instead focusing on constructive solid geometry. I wanted to include some tools for modifying surface normals and texture coordinates. I came up with a normal calculation routine that actually uses four different algorithms, depending on the settings specified.
     
    One thing I learned right away is you want to do away with n*n routines. That is, NEVER do this:

    for (i=0; i<surface->CountVertices(); i++) { for (n=0; n<surface->CountVertices(); n++) { //Write some code here } }
    Instead, I used an std::map with a custom compare function. The comparison function below, when used together with an std::map, allows the engine to quickly find a vertex at any position, within a given tolerance:

    bool SurfaceReference::UpdateNormalsCompare(Vec3 v0, Vec3 v1) { if (v0.DistanceToPoint(v1)<UpdateNormalsLinearTolerance) return false; return v0<v1; }
    At first I was experiencing some weird results where some vertices seemed to be ignored:

     
    I realized the problem was that my map, which used Vec3 objects for the key, were not sorting properly. Here was my original Vec3 compare function:

    bool Vec3::operator<(const Vec3 v) { if (x<v.x) return true; if (y<v.y) return true; if (z<v.z) return true; return false; }
    The above function is supposed to result in any set of Vec3 objects being sorted in order. Can you see what's wrong with it? It's supposed to first sort Vec3s by the X component, then the Y, then the Z. Consider the following set of Vec3s:
    A = Vec3(1,2,3)
    B = Vec3(2,4,3)
    C = Vec3(2,1,1)
     
    When sorted, these three Vec3s should be in the following order:
    A,C,B
     
    If you look carefully at the compare function above, it doesn't give consistent results. For example, A would be less than C, but C would also be less than A.
     
    Here's the correct compare function. Notice I added a second logical operation for each element:

    bool Vec3::operator<(const Vec3 v) { if (x<v.x) return true; if (x>v.x) return false; if (y<v.y) return true; if (y>v.y) return false; if (z<v.z) return true; return false; }
    So with that issue sorted out, the resulting code using std::maps is much, MUCH faster, although it can get pretty difficult to visualize. I think I am a hardcore C++ coder now!:

    void SurfaceReference::Optimize(const float& tolerance) { int i,a,b,c,v; Vertex vertex; bool(*fn_pt)(Vertex,Vertex) = OptimizeCompare; std::map<Vertex,std::vector<Vertex>,bool(*)(Vertex,Vertex)> vertexmap (fn_pt); std::map<Vertex,std::vector<Vertex>,bool(*)(Vertex,Vertex)>::iterator it; int vertexcount = 0; std::vector<Vertex> vertexarray; Vec3 normal; OptimizeTolerance = tolerance; //Divide the surface up into clusters and remap polygon indices for (i=0; i<CountIndices(); i++) { v = GetIndiceVertex(i); vertex = Vertex(GetVertexPosition(v),GetVertexNormal(v),GetVertexTexCoords(v,0),GetVertexTexCoords(v,1),GetVertexColor(v)); if (vertexmap.find(vertex)==vertexmap.end()) { vertex.index = vertexcount; vertexcount++; } vertexmap[vertex].push_back(vertex); SetIndiceVertex(i,vertexmap[vertex][0].index); } //Resize vector to number of vertices vertexarray.resize(vertexcount); //Average all vertices within each cluster for (it=vertexmap.begin(); it!=vertexmap.end(); it++) { std::vector<Vertex> vector = (*it).second; //Reset vertex to zero vertex.position = Vec3(0); vertex.normal = Vec3(0); vertex.texcoords[0] = Vec2(0); vertex.texcoords[1] = Vec2(0); vertex.color = Vec4(0); //Get the average vertex for (i=0; i<vector.size(); i++) { vertex.position += vector[i].position; vertex.normal += vector[i].normal; vertex.texcoords[0].x += vector[i].texcoords[0].x; vertex.texcoords[0].y += vector[i].texcoords[0].y; vertex.texcoords[1].x += vector[i].texcoords[1].x; vertex.texcoords[1].y += vector[i].texcoords[1].y; vertex.color += vector[i].color; } vertex.position /= vector.size(); vertex.normal /= vector.size(); vertex.texcoords[0].x /= vector.size(); vertex.texcoords[1].x /= vector.size(); vertex.texcoords[0].y /= vector.size(); vertex.texcoords[1].y /= vector.size(); vertex.color /= vector.size(); //Add to vector vertexarray[vector[0].index] = vertex; } //Clear vertex arrays delete positionarray; delete normalarray; delete texcoordsarray[0]; delete texcoordsarray[1]; delete colorarray; delete binormalarray; delete tangentarray; positionarray = NULL; normalarray = NULL; texcoordsarray[0] = NULL; texcoordsarray[1] = NULL; colorarray = NULL; binormalarray = NULL; tangentarray = NULL; //Add new vertices into surface for (i=0; i<vertexarray.size(); i++) { vertex = vertexarray[i]; AddVertex( vertex.position.x, vertex.position.y, vertex.position.z, vertex.normal.x, vertex.normal.y, vertex.normal.z, vertex.texcoords[0].x, vertex.texcoords[0].y, vertex.texcoords[1].x, vertex.texcoords[1].y, vertex.color.x, vertex.color.y, vertex.color.z, vertex.color.w ); } UpdateTangentsAndBinormals(); }
    Below, you can see what happens when you use the angular threshhold method, with angular tolerance set to zero:

     
    And here it is with a more reasonable tolerance of 30 degrees:

     
    You can calculate texture coordinates for a model using box, plane, cylinder, and sphere texture mapping. You can also do a pure matrix transformation on the texcoords. The editor automatically calculates the bounds of the object and uses those by default, but you can translate, scale, and rotate the texture mapping shape to adjust the coordinates. Box and plane mapping were easy to figure out. Sphere and cylinder mapping were more difficult to visualize. I first cracked cylinder mapping when I realized the x component of the normalized vertex position could be used for the U texture coordinate, and then sphere mapping was just like that for both X/U and Y/V:

     
    Box mapping is good for mechanical stuff and buildings, but bad for organic shapes, as you can see from the visible seam that is created here. Good thing we have four more modes to choose from!:

     
    You also get lots of powerful commands in the surface class. Here's a little taste of the header file:

    virtual void Optimize(const float& tolerance=0.01); virtual void UpdateTexCoords(const int& mode, const Mat4& mat=Mat4(), const float& tilex=1, const float& tiley=1, const int& texcoordset=0); virtual void Transform(const Mat4& mat); virtual void Unweld(); virtual void Facet(); virtual void UpdateNormals(const int& mode, const float& distancetolerance=0.01, const float& angulartolerance=180.0);
    To conclude, here's some other random and funny images I came up with while developing these features. I think they are beautiful in their own flawed way:

     

×
×
  • Create New...