The end of the week is here. The goal this week was to finish the logic system. I would say that is the case as these past few days I’ve only been implementing the behavior of the agent. The system works and can easily be extended with more behaviors and goals for the agent at a later stage.
So far the agent can:
Roam around to find the player, this uses the POM to steer where the agent should look.
Follow the player, as long as the player is visible for the agent, it will follow.
Search for the player, uses the last memory of the player and the POM to steer it’s search.
Look beneath objects, if the player goes somewhere the agent can follow, i.e. hides beneath something, the agent goes as close as possible and bends down to look at the player.
The last behavior isn’t fully functional. For now, the agent goes to the closest position and bends down. But what if the player hides in a tunnel, where only two sides is open and the closest position is next to a tunnel wall? The agent should realize this and try to look through the tunnel at one of it’s open ends. The plan for now is this:
Straight from my position, is it possible to see the target if I bend down?
Yes? The go straight ahead and bend down.
No? Where did the target enter? Go to that position and then bend down.
This will make it seem like the agent is aware of the fact that there’s specific openings to look through.
This past two days I’ve been hard at work on the logic for the agent.
As I mentioned earlier there was a need to redo the pathing of the agent. This was the first thing I finished. After that I threw together a quick goal-driven logic system to implement the easiest action, namely hunting the player. It might sound like a big thing, but when you think about it, it’s all about following a target if I see it. If I can’t see it, some other behavior can take over.
It was quite easy to get functional, and by implementing this basic system I had a better idea of how to design the whole creature system. It consist of three major parts, Sensory Systems, a Working memory and logic. The sensory system feeds the working memory with MemoryFacts about the world. For this project only the vision system is implemented, so raw data about what have been seen is used to create a VisualMemoryFact, it saves away the entity seen, the position it was observed at and the velocity it was traveling at. It’s easy to extend with new types of memory facts for say sounds or smells. When a memory fact is added to the working memory, it is compared to the facts already in the memory to detect if the new fact is just an updated version of an already existing memory. I’ve also added StaticFacts as I call them, they’re used for things the agent should always have knowledge about, such as the POM, the entity it’s hunting for and such.
The logic system then uses these memory facts to act upon. In a perfect world, no queries to new data, say the entities current position, should be made from the logic system. However in reality I have to do it at some occasions as the visual data is gathered on the render thread, so there’s a bit of delay between memory updates.
To get it all into Nebula I use a property to act as the “brain” and glue it all together. It contains the working memory for the agent and takes care of translating raw sensor data into specific memory facts.
This week the goal is to finish the logic system for the agent. I had done some basic groundwork where the agent would use a FSM made up of lua scripts. However, due to some limitations on how Nebula handles the scripts (like that there’s no return value from a script function) I had to redo it in a different way.
As everything has to be done in code now, I also decided to change the whole pattern used as the choice to use a FSM where due to using scripts and the whole system would be easier to design in that way. So now I’m using Goal-driven behavior or Goal Oriented Action Planning (something I researched during the initial phase of the project). The gist of it is to use abstract goals. These goals can consist of smaller subgoals, that in turn can have subgoals. This chain keeps going until a small enough goal is encountered that can be performed as a concrete action, like GoToNextPathPoint or PlayAnimation. I got the basics up today, but with it I realized I now have to redo my path planning, because at the moment it’s pretty much impossible to get the logic to tell the agent to follow a new path due to the path planning being so well contained inside its own entity property.
I fixed the problem with the probability growing beyond 1. As I sat down with a clear head this morning I divided up all parts of the calculation so I could inspect each intermediate result. The error lied with how the probability where divided to each nodes neighbor.
In the initial formula the diffusion constant where divided by a constant number multiplied with the sum of probability gained from neighbors. This constant number where the amount of connections to a node. The nodes in my map has a varying amount of neighbors so I just replaced this constant with the amount of connections this node currently have. What was a problem is that the formula described how to update the probability of a given node so it’s when I replace the constant with the current node connections that the problem occur. That whole part reflect the amount of probability gained form neighbors but the constant describes the probability given to neighbors. So to fix it, instead of getting the sum of a nodes neighbors probability and then multiplying, I let each neighbor do the whole calculation and give me the exact sum this specific node will give to a neighbor.
I then went on and tried to implement a velocity based spreading. It causes the probability to spread more in a given direction such as the last seen direction the target was moving in. I got it working but also here I had problems with the probability growing over time. In the end I just got tired of it so now after the new probability have been calculated it’s renormalized before culling of the visible nodes is performed.
I don’t know if this will come back and haunt me later but things seem to be working as expected for now.
Today I fixed the thing I mentioned in the last post. I elevate the nodes to the agents height so I get all nodes inside the view frustum. It’s basically a cheap projection of the frustum onto the grid. The thing is these elevated points can’t be used for visual confirmation as they’ll check against the wrong parts of the depth buffer or in the case of the nodes close to the agent they’re outside the span of the buffer. So what I did in the end is to gather all nodes inside the “projected” frustum, handle those that can be translated into the depth buffer and for the others I cast a ray from the agents eye to the specific node and check for intersections. This isn’t all that demanding as only a handful of nodes needs to be checked with rays.
I then discovered an interesting bug: the POM loses it’s accuracy quite fast. I discovered that something weren’t right when I tried to implement weighted spreading in the last known velocity of the target. The total amount of probability should always be 1. For some reason I start to gain probability after a few updates and this error is quite significant as a lot of calculations assume the range of probability stays between 0 and 1. I’ve done some debugging but hasn’t been able to determine the problem. It might be floating point precision but that feels a bit unlikely as the error appears after the second or third update. The other possibility is that I’ve implemented the spreading wrong, but I can’t seem to figure out what the problem in that case actually might be.
Quite an eventful day today, but not in a good way. I got some progress in the early morning: I finished up the POM node culling and added confirmation of the target so the agent chases you around. There I found a problem that if the agent gets to close to you the most probable node will outside the view frustum due to it being so close to the ground. So the agent would get stuck in place. I got a solution for this, simply elevate all points to the height of the camera when I do the visibility culling and then use the actual position when it’s time to figure out if a point is beneath something.
Before I could start with that, Nebula decided is was time to assert. It asserted in a really strange place during the render loop. The strange thing is that I didn’t touch any code related to the rendering as I know it. As I tried to fix it I re-exported the game data and then, as I found out later, the skybox decided to stop working and crashing the application. So most of the afternoon was spent to get things to work again. I found out the cause of the crash, the skybox, and was able to fix that with some help. And the assertion disappeared as suddenly as it began so everything works now but I really don’t like it at all. As I don’t know what caused the assertion to fail or what got it to go succeed again I’m a bit anxious I’ll do something that will cause it to fail again.
These past days I’ve been hard at work with how the probabilistic occupancy map will be checked against the synthetic vision. As it turned out, my initial design where unusable.
My first thought where to render the map at different heights. This is so that the agent wont try to look beneath a box as the node at that level will be flagged as disabled as it is inside another object. The node on the next level is then considered the lowest one and the one used for logic.
The important thing to note is the word “render”. This where cumbersome to get working as I wanted to only get some objects, the nodes, to be rendered with a specific view. At first I tried to toggle the visibility of the objects so they would only be visible when the correct view where processed. This didn’t work, and I think I know why now. I was toggling the visibility of the objects but not the meshnodes the rendersystem used.
When this proved fruitless I my second thought where to put all these invisible entities in another stage, but the more I looked into it the more discourage I got as it would involve a third renderpass.
Then it hit me, I just needed to make another shader material that only the vision shader could/would render. With this I finally got something on the screen but to my dismay it was painfully slow even with instanced rendering, something I had hoped would make this a feasible solution.
So I looked back into the research papers and found the test-point method. Basically it translates a world point into NDC space and checks the depth of the point against the depth-buffer to determine if it is visible. This is fast enough to be used in real-time and it’s very easy to add my imaginary map planes to allow the agent to realize if the lowest node can’t be seen.
So all in all I spent two days to confirm that the culling present in the rendering pipeline wasn’t suitable to be used for visibility confirmation against such a dense grid and learned that I should read more carefully.
Happy times! I got the world to render from the agents point of view.
What the player sees, the yellow triangle is the old 2D view cone for the agent.
What the agent sees in full 3D!
It was a bit of a headache to get working. I added a camera onto the agent and created a new view that would use the picking shader to render to an off-screen buffer. Through the debugging webinterface for Nebula applications I could see that the buffer where rendered to correctly but the whole screen kept flickering to black and back. After doing some debugging with Gustav, one of the developers of Nebula Trifid, he finally discovered the cause: The back-buffer where swapped after each view had been rendered. It’s fixed now so I can continue working without the danger of getting a seizure.
During the off-time when I couldn’t work on the agents vision, I finished the groundwork for the behavior state machine so it can now load scripts and change between them.
Today I started implementing the actual vision system for the agent. As I mentioned earlier I will render the scene from the agents point of view and scan over a downsampled buffer to detect what have been seen.
Nebula have an easily extendable render system. There’s some parts needed to get a new add on working as the rendering occurs on another thread but it’s quite straightforward and there’s a lot of finished modules to look at and learn from. I was able to finish the skeleton for the module today so there’s a lot of time left to actually implement its functionality. And one thing I discovered is that there’s a shader available for rendering objects color-coded, so I don’t need to write my own to do that!
Though I didn’t get going with the vision add on until the afternoon as I wanted to get more information about the render system so I had to wait for answers from the people working on the engine. So during the morning I started to implement the logic system. The idea for now is to have a finite state machine where each state is a lua script so it’ll be easy to change and test during development, as scripts can be reloaded without the need to recompile the application. I didn’t fully finish the whole groundwork but I got the most basic components setup so it will be quick to finish when the time comes.
I haven’t done too much today, as it was milestone two today and the whole afternoon was spent listening to all presentations and also doing my own.
I did get some valuable feedback on my project. Not really on what I’ve done but on what I will do. The goal now is to create the real sight sensory system for the agent that will function in fully 3D and not just on the XZ-plane as it currently does. The reason for fully 3D vision for the agent is so it can decide if it sees the target or not and also be able to look beneath objects.
The plan is to use synthetic vision by which one render a crude representation of the scene from the agents point of view with objects color coded. The problem arise in that one need to scan over the rendered scene to be able to tell what been seen. The tip I got where to downsample the rendered image without blending the colors. This will resolve in a much smaller buffer to scan through at the loss of precision of objects spatial locations. But as I’m only interested in what I’ve seen and not where as I can easily obtain that information in other ways I can get away with this. Synthetic vision will also be a great way to showoff the agents inner workings.
To get the agent to look beneath objects I plan on render the POM at several heights so it will become a 3D grid visually but stay in 2D logically. Each node will contain flags on which level it is visible, so a node can be inside an object at one level but still remain visible and active at the other levels.