⚙ Swift Mazes - Diary

 Select entry:

2014-09-23 - Portals on Auto

Now that the connectivity map problems are solved, we can look into auto-visportaling the generated world. Here is an image of a testmap, with two locations (marked yellow and blue). The purple lines represent existing visportals.

What we see on the image above:

The visportal between the locations is added by the prefab connector_ns, the one at the right near the stairs by the prefab exit_stairs_south. The one at the top-left is part of the prefab gate_south, which is a portcullis. There are also a few visportals separating the different floors from each other, added by the pit prefab. However, the horizontal space is poorly laid out.

Sound propagation:

The two dashed lines represent sound sources and sound receivers. We can see that in the A → B case the sound travels a somewhat reasonable path, due the visportal separating the two locations. But the sound path from C → D goes straight through the walls. On position D one would hear the sound coming from the left wall, instead of from the hallway to the north.

Implementation:

The code propagates first the visportal info to neighbouring blocks, so that a block with a visportal from a prefab on the south side will also result in the visportal north flag getting set on the southern block. This ensures that we do not add more than one visportal to one side.

The routine to add a visportal is written direction-agnostic, that is, it operates on the direction forward and looks left and right. It is then called four times with the appropriate paramters to look North-, South-, West- and Eastwards.

Whenever there is a block in the forward direction, AND the two blocks are connected (e.g. there is no solid wall between) AND there is not yet a visportal on this side, AND there is either a left or right block connected to us, then we add a visportal on the forward side. We also flag the visportal flag on the opposite side on the forward block.

The result are visportals on bends, corners and crossings, here marked in dark-green.

The sound propagation now also works better, and due to the culling of closed portals, the map will have better performance.

Note: The two portals immidiately left of the point A are on the floor below, so it only appears as if the code generated to many visportals there.

TODO:

The image above shows that the visportal code is a bit excessive.

For instance, there are two portals very close to each other on the exit stairs. This can be fixed by either including the line visportals=north in the exit_stairs_south definition, or deleting the visportal from the prefab and let the code add it.

In addition, the location separator has its own portal, and there is another portal very close on the south side of the same block. This can be fixed by either not generating portals on connector blocks, or by auto-generating the separator visportal – and just placing it on one side, instead of the center. Right now, two manually created prefabs connector_ns and connector_we exist. Generating these automatically would be preferable.

Likewise, on T-shaped crossings where one or two sides have only one block, the single block might not be needed to be portaled off. Sound propagation will not be much different, and a single block contains usually not enough details and entities to make it its own leaf. As example see the T-shaped crossing near point D.

But such optimizations are for another day.

2014-09-21 - Connecting Worlds

To solve certain problems, like auto-visportaling, we need a connectivity map, which specifies which block can be reached (is connected) from each block.

Before we can compute such a map, an important problem needs to be fixed, though.

Consider the following two locations, which are connected at 0:

 Location A:   Location B:
  ┌──           ──┐
  │ 0           0 │
  └──           ──┘

When the map is build, the following blocks are created:

 ◻◻◼◼

Where ◻ belongs to A and ◼ to B. Location B is then shifted so that its left-most block is in the the same place as the right-most block of A:

 ◻▣◼

This works, but has two important negative side effects:

  1. The middle block consists of two independent blocks, one belonging to A and one to B. Both blocks might include the same prefabs. For worldspawn, there is no harm, as dmap will optimize away the double brushes. But we might end up with two entities stuck into each other. Plus, it wastes resources to have two floors and so on stuck in each other.
  2. The connectivity map has two alternative paths for the same physical space:

 A ←┬→ A ←┬→ B
    └→ B ←┘

This makes it hard to follow and produces problems for things like auto-visportaling.

The same issue (two blocks in the same space) was also caused by alias levels, all the alias level blocks where simply shifted so that they appeared in the same space.

To solve this, block merging was implemented. When locations are fit together, the blocks where they meet are now merged into one block. The block is marked as being a connector block. Prefabs from one block are only included into the other if they weren't already. In addition, alias level blocks are also merged into the level they belong to.

This solves the double worldspawn brushes as well as the double entities problem.

While working on the merging, another problem with complex location layouts surfaced. When you have a chain of locations, merging them in the order of A → B → C is sufficient. But what if we have more complex layouts, like a circle:

 Location A:  Location C:
 ┌──           ┌─┐
 │ 2           2 │
 │ │           │ │
 │ 2           2 │
 │1│           │ │
               4 │
 Location B:   └─┘
 │1│
 │ 4
 └─┘

In this case the code processed the links from A → B and A → C, and when it came to C, it failed to process the link from C → B because B was considered "already processed".

In addition, it is also possible to have a map with two distinct set of locations (aka worlds), that are not connected to each other at all:

 Location A:  Location C:
 ┌─┐           ┌─┐
 │ │           │ │
 │1│           └─┘
 
 Location B:
 │1│
 │ │
 └─┘

While the player might not be able to move between the worlds on his own, there are still valid reasons for such constructs: Maybe there is a teleporter from one world to the other. Or one world is for Easy, and the other for Medium and Hard. So the map generator still needs to deal with these setups.

The location fitting code has been rewritten from scratch, to account for circles, tree and disjunct sets of locations. This will enable us now to build a connectivity map and do exciting things with it :)

2014-09-08 - Prefab inheritance

The prefab definitions now understand includes and inhibits, in a recursive way. This means this actually works:


 [Prefab ceiling_hole]
 offset=0 0 0
 ; this prefab is a bit bigger than a block
 extends=-96 -96 -96 96 96 996
 ; it includes these, unless they are already defined or prohibited
 includes=corner_ne corner_nw corner_se corner_sw floor
 ; if this prefab is used, the "ceiling" prefab will be inhibited (ceiling_hole replaces it)
 inhibits=ceiling

So instead of "baking" the corners and floor into the ceiling_hole prefab, it just includes them. That will make updating prefabs much easier. It also "inhibits" the ceiling prefab, so if you use f.i. the empty prefab together with the ceiling_hole, you will end up with only one copy of the floor and corners, and no ceiling.

2014-09-07 - Complex motions

The script object for binary movers now can also set acceleration and deceleration times for open and close independently, and the time when targets are triggered can be set on begin of open, on end of open or not on open at all. Likewise for close. So you get complex behaviours in one go.

For instance, when the player frobs the pullchain, the following will happen:

This creates a rather nice and fluid motion that makes you believe you really pulled a chain triggering something.

2014-08-14 - Work Resumed

After over a year of no work on Swift Mazes, I've just resumed the development. More information will come later.

2012-09-21 - Rolling in the Deep Fog

The player ambient light already makes the scene more visually interesting. However, another key element in creating mysterious places is fog.

While adding a fog light is easy, the idTech4 engine unfortunately is a bit limited when it comes to mixing foggy and clear sections of a map. The border between fog lights is easily visible, and needs to be "covered up". Also, "fading" the fog is rather difficult, as a normal fog light will always cast the same "strength" of fog, the only parameters you can set are the radius of the light (e.g. how much area is fogged up) and the distance at where everthing is a solid color (this is also the distance at where portals automatically close, which can improve performance).

So adding a fog light to some sections of the map works only in some limited cases, for instance when you want to make a "infinite deep chasm", or when you want to cover the floor of one room. Then the room is hopefully rectangular, or the fog might cross into neighbouring rooms! And don't forget to cover the border of the fog.

And last but not least there is a bug deeply in the engine when it comes to turning off lights - the light color is just set to black and the renderer then knows to "skip" the light. While this works for ordinary lights, it means that fog or blend lights just emit a "darkness", making everything beyond a few meters be covered in absolute black. Nice if that is what you want, but quite useless otherwise.

One workaround for this bug is to teleport the fog light away. Another is to repeatedly destroy the fog light if you no longer need it, and then later recreate it. Alternatively, one can also just shrink the radius to 1 unit - if the fog light is bound to the player, it will be still there, but invisible. Not ideal, but we'll take whatever works. :)

Another point is that while automatically fading the ambient light to another color when the player crosses a location does not work for fog, as a time-based fading makes the fading very obvious to the (still-standing) player. So we need location based fading.

Consider that you want to have a certain fog level, color and strength when you are in one location, and a different fog in the next location. Here is how I got it to work:

This works fairly well in praxis. The only requirement is that there is some sort of "walking space" between the two locations, and that the fog levels aren't too far away from each other. "Covering up" the transition with interesting geometry, or using only locations with slight fog differences next to each other are still good ideas, though. These make sure the transition is less noticable to the player.

The reason for the "long walk" is that we can only store the last crossing point, knowing generally where zone crossings are might be doable, but whether the player actually approaches one is a much harder problem.

So if the player goes forward, the transition will be after the zone border. But if the player then was in a different location and comes back the same way, the transition will again be after the zone border, which in this case is now the other direction. So the place where the fade happens shifts around the zone border, depending from which direction you come. This is why we need some sort of "transition area" where the fading between the fog levels is not that noticable or does not matter.

Here are two images showing the fading when going forward across a few zones, and going back again in the second image. Shown here is only fog density, but fog radius, fog color and player light radius and color are similiarily faded.



You can see the difference on where the fading starts and stops. In praxis the locations are usually much bigger than the fading area, which is 1000 units by default, so this is not really a problem.

And finally here are two screenshots from the same map showing the differences in fog. It might be hard to spot, but the left is more dense and blueish, the right is less dense and neutral grey:

2012-09-10 - Location, location, location!

Now that the first milestone (a working demo map) has been reached, it is time to start refining the process and add the missing features.

One recent addition are locations. Each location is a mini-map in itself, and when the final map is assembled, locations will be matched up on their respective connections. Locations are implemented via locations in TDM, so each location can have its own ambient music, light, and this is then extended so each of these has its own player light and radius. These are all faded from each location to the next, of course.

The nice thing about locations is that they solve two other problems:

Here is an example, shown as the generated map in DR in 3D, as well as a top-down view with the locations marked with colors. If you look closely, you can see the visportals and info_locationseparator entities at the borders.

2012-08-31 - Portals to Another Zone

Adding visportals to a level is both necessary, but also challenging and boring at the same time.

Without visportals, sound does not work properly - it travels straight through solid geometry from point to point, instead of bending correctly around corners and funnel through hallways. See the wiki for an example drawing.

Now adding visportals manually is not something that we want to do, so they must be placed automatically. Doing this in code is hard, however. So for now I have inlined visportals into strategic points into the prefabs. For instance the pit prefab has its hole covered by a visportal. When you place these in a map, all lower floors get automatically separated from the upper floors.

More tricky are bends, corners and gates. It is easy to add a visportal to a solid door, but gates and portcullies cannnot have a visportal in them. Otherwise closing the gate will close the portal, too and this will render a solid black over the gate. The trick here seems to be to add the visportal near the gate, a bit in front of it perhaps.

This means that whereever a gate is, there is a visportal. This is not as optimal, because gates are not nec. at corners or junctions. But it is a lot better than not having visportals at all.

Above: a screenshot showing the working portals.

2012-08-24 - Closing Doors

A first test with linking a lever to a portcullis worked:

However, it would be nice if the gate would close much faster than it opens, simulating the "drawing it up slowly, letting it fall fast". These are small details, but they really enhance the atmosphere. A portcullis that slams into the floor is much more frightening than one that slowly inches lower.

To achieve this I wrote two new script objects swift_door and swift_lever. The first reads the move time for the door, a factor for the speedup (or slow-down) while closing and then creates methods that open and close the gate after setting the correct speed.

Both script objects also allow operating the door or lever only once.

2012-08-17 - Chasing Darkness

If we look at computer games, be it horror-themes like Penumbra, dungeon-crawlers like Legend of Grimrock, or sneaky games like Thief 3, these all have a feature that makes objects in the distance either less visible, or more hazy.

Sometimes, the effect is achieved with a slight blue fog, sometimes things just vanish into the dark.

This simulates the effect of atmosphere (making things shift to the blue and more hazy), or of night vision (which works better in the near).

It also has the side-effect of giving the scene more depth, and especially in the case of using black, to make the player want to explore, to go near things and look closer at them.

The engine of TDM supports various methods for simulating these effects, be it a global fog, ambient lights bound to the player. However, the default coniguration and what almost all FMs use is just using a normal (global) ambient light to lift the shadows. This creates a rather flat look.

RPGista described the phenomen and the differences in this thread.

Because a dungeon crawler is about exploration, the fear of the dark and unknown, I toyed around with implementing a method for this. Here are some screenshots. Because it seemed easiest and causes almost no performance loss, I bound a light to the player and experimented with brightness, radius and light falloff via light texture.

The first image shows a flat, global ambient. The second one shows using a light bound to the player, using ambientInfo as light texture. This creates a rectangular light area, which causes some weird issues when moving - things are dark and suddenly light up when you cross a certain threshold. Better, but not good enough.


The left image shows ambient_biground1, it creates a circular light area and has a much better falloff. However, it still has the wrong falloff, things stay lit for quite some distance and then suddenly go dark. The second shot shows a custom light texture, which has a much more linear falloff. Combined with a radius of about 350 units, this works great.

The colors in the above examples are a bit brighter for the screenshots, but in game you get a small pale blue for things close to you, and everything vanishes to near black in the distance. In reality, night vision shifts the colors and changes the contrast, but simulating all this might require extensive engine changes and be not really worth it. For now, this will suffice and is already much better.