This Week In Veloren 50
Happy 50th edition of TWiV! We have a lot of progress and content for this week. We get to take a look at scrolling combat text with @Pfau, and skeletons with @Slipped. @Sharp has been hard at work, and we get to hear about some new updates to erosion and worldsim. We also examine an interesting optimization that the team has come across this week. Finally, the Rust Game Dev Working Group released their monthly newsletter, be sure to check it out!
- AngelOnFira, TWiV Editor
@Acrimon is still working on solidifying auth, it has been tested quite well in the last few weeks. @yusdacra worked on improving frustum culling. This will give performance gains as chunks out of view of the camera were being rendered before, and now they are being culled properly. @Mckol has been working on controller support, and it should be ready soon. @Timo has been working on resolving some issues with how fog moves around erratically.
@Slipped added a lot of new animal types, take a look at their writeup below. You can check out some galleries that @Felixader made showcasing their work on artifact icons, and berry icons. @Shimox also created a gallery show off their work, check it out here. @Silentium and the design team are still working on the skill tree structure.
Thanks to this week's contributors, @Acrimon, @Pfau, @Imbris, @Songtronix, @Shandley, @yusdacra, @payload1, @Qutrin, and @Timo!
Scrolling Combat Text by @Pfau
Hey everyone! Recently, part of the team has been helping me with Scrolling Combat Text (SCT). This is another element that will make Veloren more immersive. The first thing to show off is quite obvious. Single hits get displayed as soon as they happen, and they fade out as they pan upwards. Their colour goes from white to an aggressive orange/red tone depending on how much damage percentage your hits caused at the enemy. You can see this in the image below.
Another way to set up the SCT is to sum up the total damage you did. Again, the higher the percentage of the enemy's health you reduced the more orange the colour becomes. Along with showing this text when you deal damage, it will be shown when you take damage. Below is an example of the summed damage taken by the player.
We also implemented experience and healing text. The XP you gain after the fight will be shown at a random position in the upper half of your screen. The higher the percentage of the gained exp from your currently needed exp to level up the bigger the number. Healing gets displayed like experience; a random position in the lower half of the screen. It's shown as either single or cumulated numbers and doesn't get added up with the damage numbers. This system has only the basic functions and will be expanded later.
Finally, a little unrelated piece of progress. The map becomes slightly translucent when you start moving while having it open. Anyways, see you next time!
Skeletons by @Slipped
Hey! @Slipped here, I've been working on the same MR for quite a bit now so I figured it was a good time to post an update. I've been working on the animation skeletons and setting up the foundations to allow for a ton of new NPCs in the game. Most of the credit goes to @Imbris and @Lawtonfogle who set up skeletal sharing to work for humanoids a few months ago. All I had to do was transfer this to animals, so this writeup isn't technical because they handled all the hard stuff.
The key part of what I've done here is giving a species trait to all the different body setups. For our purposes, we'll call this the skeletons. This means that, unlike before, any time we load in a new mob it will have a set species. Previously, we could load in a wolf and get a wolf, or a pig and get a pig, but that only worked because they were two different skeletons and they only had their own body parts to choose them (i.e., they were randomly choosing a leg from a pool of one leg).
We don't want this long term because creating new skeletons for individual mobs is super over-complicated for what we need. The new set up also moves a lot of the critical animation info like 3D placement and pivot point into config files, which is hugely useful because it lets us to hotload these things, with different placements per species. This will remove the strain off the compilation times and making this MR take a few years less than it would have.
One of the things I've really fleshed out in this MR is exactly how we want our skeletons to look, how many we will need and what makes them all different. One thing that has become clear as I progress is that even more important than how something looks, or how big it is, is how it behaves.
Obviously, we want an animal that has 4 legs to be wired into a skeleton that has 4 legs, but almost as important is behavior. Predator vs prey, speed of movement, style/rigidness of movement are important questions when working out where something fits. Because of this, I've been pretty picky with the models that I'm adding into this MR. It's easy to plug a new mob into a skeleton that mostly fits, but if we can put it into a skeleton that fits it well later, then that's what I'm trying to do. Larger quadrupeds like deer, horses, and buffalo require more than one leg bone to seem alive, so they will come later. Current quadrupeds are all pretty low to the ground, which works with an easier setup.
Current skeletons in my working branch are:
- critter: quadruped, 5 bones. very simple movement, the left and right feet are modeled together because they move together and this streamlines them. Scaled down in animation, for very small mobs.
- quadruped_small: quadruped, 7 bones. Normally prey. Slightly more cartoony feel, lots of cute models.
- quadruped_medium: quadruped, 11 bones. The most complex animal skeleton right now. Generally predators. Usually a bit bigger than quadruped_small, but more importantly allows for flexibility and realism. Splits the head into 4 parts, the torso into 2. Things like movable jaws give us a lot of freedom to animate better attacks. Split torso allows us to simulate things like turning better, or even breathing in idle animations.
- bird_medium: biped, 6 bones. Simple and functional, useful for things like ducks, gives them heads, chests, tails, wings and legs.
- biped_large: simplified in some respects from the humanoid, but made more complex in others. To support something this big we split the "pants" bone into two actual legs, so we'll get some more realistic looking movement. A tricky one. Hopefully I finish this eventually :P
There are others lying dormant (some rumors say there's a dragon skeleton laying around) but those will be in for after 0.5 is released.
Here is an example of a medium quadruped. This is a Tuskram. You can see it looking to its right, and with the bone setup we've built for it we're able to represent a simple head twitch with 4 different angles: the neck at less of an angle than the head, even less for the front torso, and no angle on the rear torso. For now, it's just a cool little detail that's too subtle to notice, but for something like a lunge attack to the side it will be more extreme and the payoff should be good.
This MR brings the total number of animals in the game from 2 to 30, which will be pretty cool. We also add gender to each ingame mob, giving us full customization capability, so the total number of variants possible will be 60, though not every mob will do gender variation.
Iterator Problems with @AngelOnFira and @Sharp
The team came across an interesting bug that is being resolved. In Rust, if you want a sequence of numbers, say (0,1,2,3,4), you could use the iterator notation
0..5, where the first number,
0, is inclusive, and the second number,
5, is exclusive, or not used. This is useful when going through things like arrays, where an index starts at 0 instead of 1.
However, in some cases, you might want the iterator to include the second number, so you would use
0.=5, which would give (0,1,2,3,4,5). The problem is Rust has to do a lot of checking to make sure that this is safe, and so it is incredibly inefficient. Instead,
0..(5 + 1) is much quicker. An even better option is to use
for_each statements to allow the compiler to handle it, but this isn't always possible. @Qutrin has been going through the codebase to resolve this issue wherever possible. For more information on this issue, check out this Reddit post.
The section above was written by myself, @AngelOnFira. After asking #programmers to proofread it, @Sharp gave a much more in-depth summary of the issue. Here is is:
It's worth clarifying that the issue is not exactly with Rust, it's with codegen. Rust uses lazy iterators by default, which means it has to assume next() can be called at any time, not just one after the other. And similarly, to avoid dynamic dispatch, the iterator itself always has just one implementation.
Therefore Rust won't send LLVM code that does the efficient thing (check just once whether the upper bound can overflow, and branch based on that); it has to branch every time you call next. Unfortunately, LLVM usually isn't smart enough to work out that these are equivalent. However, there is another solution that is almost certainly better than replacing ..=X with ..x+1 (which you should only do if you know you won't overflow anyway).
What I just described is part of a broader class of problems caused by Rust using lazy iteration; there are other things that cause similar performance impacts (like chain(), or zip() on arrays). However, if you call .for_each() (instead of using a for loop or calling the iterators directly), Rust knows you are doing internal iteration. So many of the ForEach implementations are specialized for the iterator type to take advantage of the fact that they know the iterator will be run to completion.
These implement all the optimizations you'd expect and LLVM generates much better code for them. I've already seen dramatic speedups in some parts of the codebase that I didn't realize were unoptimized by replacing for loops with for_each, and unlike the other replacement, it is always semantically equivalent if the transformation is possible at all (i.e. you weren't using break; or return; for nonlocal control flow). So I think a good extension of this project would be to go through the codebase and clean up as many for loops as possible.
Erosion Worldgen Updates by @Sharp
Here's a map (128x focus, so it exaggerates small z differences) showing some of the new stuff I've been working on improving. This is in addition to other stuff not on master: hillslope diffusion, sediment transport, debris flow [replacing thermal erosion], soil production, map saving, and significant map rendering improvements.
The main thing is multiple receivers for drainage computation--that is, allowing some of the water to flow downhill in directions other than the very steepest. The drainage per direction is presently automatically weighted across all downhill directions from each chunk by a factor of:
(slope to neighbor in that direction)^(0.5 + 0.6*(avg slope across all neighboring downhill chunks)
Note that doing this is quite computationally expensive. Making it significantly cheaper is nontrivial, to say the least--I even tried SIMD-ing it and haven't had success yet. So I'm working on making it easy to switch between this and the old method of drainage computation.
Currently, this is only performed for erosion purposes, since we can't render it well yet--so in the rendered map, we're still using the old drainage computation for river flows (but the humidity calculation does use it, so it does a much better job of making sure wet areas are wet than the current map does).
The other significant improvement since the last time I shared a map is that we are using a nicer (for our purposes, anyway) algorithm to determine downhill directions for lake chunks. They now try to point directly at the closest pass rather than either always point downhill except the lake bottom (master), or do so except for a single line from the lake bottom to the pass (this branch previously), which produces somewhat nicer underwater erosion.
Combined, these two effects really clamp down on linear artifacts. The particular map shown above has 128x128 chunks (4x larger distance per side than the current map), and doesn't use the plates from before for much (uplift=old altitude^2, kf constant).
Here, I'm also including a second image of the overhead map where the multi-receiver drainage is also used to decide where there's a river. This looks quite nice from overhead, but it renders ugly in game and I haven't yet decided how I want to approach it. I also don't want the overhead map to differ from the world map. So I'm using the old method for the time being. The new method basically gives us wide rivers "for free!", though since we currently deliberately underestimate how much flow is needed to make a river by a large factor it makes the map a bit busy.