Features
Here we discripe our algorithms. We won't show you code, but we will show you how it works for example the swarm algorithm.
Swarm- and fish algorithms
One important part in our reef were the fishes which take a great part in getting the Reef more lively and to create a beautiful visual Experience. But for this part to take effect the fishes needed to have an adequate AI, especially the swarms. Therefore we wrote a few algorithms which differentiate between three cases:
Free Fishes(& Hunter)
This part applies to all fishes that do not belong to any others. It's pretty simple: the only thing that free fishes do is to change their direction in which they move every few seconds. To avoid too hard direction changes, the new direction is calculated based on the old direction. To soften the direction change even more the true old direction influences the new direction partly and to a little part the fish also tends to go to the center, this assures that the distance to the reef doesn't get too high and a soft avoiding of the given borders. In case of a near predator an escape route is calculated instead (as long as the predator is a real threat to the fish type). Our shark is always hungry but instead of a plant he tries to get to the nearest swarm (realized through the leader fish). Too bad he's not fast enough to catch up to it and so is forced to hunt for all eternity (bad for him, nice to see for the user).
Coherent Swarms
The coherent swarms are based on the free fishes. Every swarm has a leader and a list of followers. The leader gets updated like a free fish. His followers are then adjusting themselves according to him and the other swarm fishes. Depending on their status the different influence factors are weighed differently, for example an escaping fish tries far more to get away from danger than to keep his place in the swarm. The main factors for each fish are: direction to the leader, keeping distance to other swarm members and escaping any potential threat, keeping a maximum distance to the closest fish, trying to get close to the swarm center, trying to adapt to the average swarm direction. While every fish acts depending on the swarm, the escape movement is calculated for each fish individually, but they still try to stay close to each other even if they are escaping. This assures a realistic and smooth escape behavior which fits for a swarm.
The behavior of the individual fishes depends on his state, whether he is joining to the swarm, adapting to the next fish, escaping or if he swims alone. This algorithm usually has a quadratic complexity depending on the number of fishes which is not very nice, especially if you want nice and good filled swarms which should have at least 500-700 fishes and so result in many comparisons. To reduce this we implemented a 3D Grid which is generated every time the swarm behavior is updated (which means every frame) and contains all the swarm members in cells. Therefore a fish doesn't need to compare himself to every other fish; instead he gets the information of his relevant neighbors from the grid and only needs to check on those. Because every cell of the grid would need to be checked on changes and, depending on that, updated every time anyway, we don't lose any Calculation power in just building it new every time. The complexity got reduced from n² to n*log(n). With 1000 fishes, for example, we reduced the calculations from 1000000 to 15000, which is a huge win for the performance. This Grid is also used by the following Boid and is useful for every new swarm Type which has a high Number of Fishes.
One other problem in the beginning was to keep the swarm in its shape. This was the result of a too static update system in which we saved the initial relation of each fish to the leader and tried to convert this depending on the movement changes which was not very successful. But now that every fish adjusts itself, mostly ignoring the overall state, this problem got solved automatically. The distance the fishes keep is achieved through trial and error and ended in about 0,75-0,95 fish lengths, even though in reality 1,5-2 fish lengths seems to be common.
Non-Coherent Swarms
This Name can be a bit irritating, because technically most of our swarms are non-coherent, for example earlier in the Project the Boidswarm was referred to as the Non-Coherent Swarm. But now this name is used for what I like to call the "true" Non-Coherent Swarms. Technically the term coherence describes how much Fishes(or other Swarmanimals) adapt their behavior according to the other swarmmembers and the similarity of the movement/behaviour throughout the swarm. While in Boidswarms the Fishes do not swim all straight in the same direction, they still have a good amount of coherence, considering the fact that their overall movement is quite similar and they always adapt themselves depending on the distance to their neighbours. "True" Non-Coherent Swarms have nothing of this Coherence. You could say in this context they're the representation of chaos, in fact they're just a bunch of fishes keeping together. And so we treated them just alike. The algorithm for this Types is not very different to that of free Fishes, it is basically the same with a few adjustments. Core-Component for that is a Method that prevents the Fishes from leaving the Reefarea. We used that and modified it to work with a very small area. A few things had to be adjusted, mostly because the old movement-corrections were not designed for such a small area and therefore to extreme. Also the speed of the Fishes was greatly reduced, because people will recognise much more precise movement, due to the low scaled area. A normal free Fish with the Speed of a non-coherent would look like he's not moving at all. One specific Problem was the high vertical movement, which is not very common for Fishes, especially members in such a swarm. To avoid this the value for the vertical movement gets reduced every frame, once the Fish is close enough to the swarm, so sooner or later it will tend to zero. The swarm centre is determined by the invisible swarm leader which moves like a free fish, but extremely slow, so the swarm moves together a bit, but mostly recognisable over time.
Boid Swarm
The non-coherent swarms are pretty similar to the coherent swarms; the main difference is that they have no leader. Currently they're acting pretty similar to coherent swarms but instead of following a leader they just move around a static center. When finished they shall act based on an extended version of the Boid-Algorithm for which we oriented us upon the work Application of Interactive Genetic Algorithms to Boid Model Based Artificial Fish Schools from Yen-Wei Chen, Kanami Kobayashi, Hitoshi Kawabayashi and Xinyin Huang. The Boid-Algorithm sees every fish as a Boid and calculates a movement vector for each, which is a weighted average of different components. The base components are: keeping a distance to other fishes, gathering together and adjusting the movement. In our concept we want to extend these components by the factors: eating, escaping just like in the work mentioned earlier. Boidswarms exists in Coral Reefs but are relative rare, if there is one, it is most likely to be a swarm of Barracudas which is also the reason we used them as a model for our Boidfishes. Currently the Boidswarm always stays on one Position, which is not really uncommon for this Type of swarm, in reality those Swarm move extremely slow, to human it seems they move nearly nothing at all.
Nemo-Swarms (Clownfish)
These swarms are just non-coherent swarms with even less rules an bound to a Seeanemone, which is there home. The fact that they often swim through the anemone is no problem at all, considering the fact that they do that in reality too and also hide in there home.
Dori-Pair (Paracanthurus/Surgeonfish)
These Fishes always Swim in Pairs, just like in reality. To achieve that they behave basically like a coherent swarm with just 2 Members. Additionally they have no Grid calculated for them, because that's totally unnessecary and would cost more performance than we would get. Also they have a special Distance between them in which they do not adapt them to another, this let them swim together in one direction(normal coherent swarms do not need that because they adapt themselves constantly). Nice Sideeffect: Due to different speed in one Pair these Fishes tend to swim partly circles and sometimes look even like they're dancing.
Rabbit-Swarms (or Foxfaces)
These swarms are based on the coherent too. Their difference is that they do not check their neighbours, because it's possible for them to have none that is close enough to be relevant, due to their low numbers. Also the average movement value influences the fishes movement much less, compared to normal coherent swarms.
Coherent
Update movement of the coherent swarm calculates for every fish the next direction depending on some parameters and its swimstate. Every fish has 4 swimstates, these are Normal,Joining,Adapting,Escaping. In case the fish fits perfect into the swarm it has the swimstate normal. In case he inside of the swarm but too close to the next fish, it need to adapt the distance, so the resulting swimstate for this situation is adapting. In case he is outsite of the swarm he need to join the swarm, so the swimstate for this situation is joining. The last swimstate escaping depends on the distance to the hunter and is dominant to the other states. Based on the swimstate, the directions will will have different weightings, that the resulting direction will match to its current state.
Hunter
Update hunter updates the behavoir of the hunter depending on its state, the hunter waits about 30 seconds after the last hunt before the next hunting time starts. When the hunter is updated, the algorithm calculates at first the distance to the closest hideout. When the distance is calculated, he checks if its hunting time. In case he wants to hunt, the algorithm updates its state to hunting. After that he calculates the distance to the closest swarm based of its centre. When the closest swarm is found, he set his direction the that swarm. Then there is a check if he collides with the closest hideout. In case he does, he adapt his movement an checks if he hits the swarm. If there is no hideout he also check if he hits the targeted swarm. In case he hits it, he will stop hunting, in case he didnt hit the swarm, he will follow it and the algorithm ends. In case there was no hunting time in the beginning, he just checks if he collides with the the closest hideout and dodges it in case he does and the algorithm also ends.
Swarmlogic
This diagramm represents the algorithm for each individual fish in a coherent swarm. The algorithm ignores all fishes except this closest fish and the leader. For the closest fish and predator there is a check which decides if it is too close, or to far away, depending on the situation, the fish adapts. There are also two important positions, the hideout and the weighted swarmcentre. In case the hunter gets to close, the fish tries to escape but also move towards the hideout postion. If it isnt escaping, it also tries to get closer to the swarm centre even if the centre is behind its current position, this directions makes sure, that the swarm stays together. There are two other directions which amends each other, the one is the swarm direction and the other the leaderdirection. If one is missing, the swarm moves rather side by side instead of in a row. All directions including the old direction will be used weighed to calculate the new direction.
Collisions with Mesh
Collides With Mesh is a function, which is used to check if a given position collides with a mesh in the terrain. The algorithm needs the an entity to be able to check if the given position hits something, if there is no entity it will return false. If there is an entity the function checks, if the given position is in its ohn bounding box, if it isnt there must be a wrong parameter and it returns also false. In case if we got an entity and legal parameters the functions casts a ray from the given position to the given direction and checks if it hit something, if it didn'it hit anything it will return false cause there is no object in the current curse. If an object got hit by the raycast, there is another check, if that object is too close to the current position. If it is too close, it will return false. If it is too close, there is a last check needed if the given object is an object where collision is enabled. If it is, it will return true otherwise false.
Worldgrid
The function create worldgrid generates a grid on the XZ-Area, which is used to reduce the amount of comparations too enhance the performance of all allgorithms, which need to compare objects. To generate a grid this function checks at first if it is enabled. In case it isnt, it ends without doing anything. If it is enabled, the function generates at first the gridcells, by given XZ values. After filling all gridcells there is another check if the cells shall be visible. In case they shouldnt, the function ends. If the cells shall be visible, there are some calculations to be done. At first the boxsize need to be calculated. In the next step, the dirctions for every line will be calculated. When both is done, the function generates a plane for every cell. Finally it adds an entity and ends.
Get an area from the Worldgrid
This function is used to get an area around the given position, to reduce the amount of comperations. First of all it creates a resultvector which can be filled in the next steps, it also can remain empty if there are no objects in the given area. Next the function transfrom this given position to the coordinates in the grid. In the next step, the grid iterates threw all x positions for every x position it also will iterate threw the y position. Note: The y positions in the grid are the z positions in the world. If the current grid cell contain objects, all will be pushed into the resultvector. Finally if there are no iterations left, the function will return the resultvector.
Spacemouse
The spacemouse is used to navigate smoothly threw the coralreef. There are two different kind of events the spacemouse will interpret. The first event is the rotation event, if you move it like a joystick. In that case the player/camera will turn left/right or up/down but your wont move. The other event the spacemouse can receive is the motion event. The motion events are interpreted the following way: Push down: Swim downwards Pull up: Swim upwards Left/Right: Swim sidewards Push forward: Swim forward Pull backward: Swim backward After initializing, the spacemouse wont do anything else, but waiting until it receives the next event and interpret it the described way.
HOW TO: Kinect controls
the whole movement can be controlled via the Kinect, we tried to make it as intuitive as possible, however some of them might be a bit tricky in the beginning. Often it's helpful just to do the movements a bit slower until you get them right, but here's the explanation for the different moves:
- Swim:
With this you will just swim forward. It's just an ordinary swim movement, like probably everone has done at least once. Just place your hands together in the centre of your chest. Next push them forward and when there halfway stretched out start to move your hands to the side. - Swim Left and Right:
These will let you slide left or right. Just place stretch out your left or right upper arm diagonally in front of you(the left arm to the left and the right arm to the right). Next hold up the hand of the used arm and move it in a straight line to the opposite direction of your arm. For example if you want to swim left, stretch out your left upper arm as described, hold up the left hand and move the whole arm in a straight line to the right. If it doesn't work try to change the height of your hand a bit. - Swim-Back:
This will let you swim back. Just place your hands similar to the side, like when you want to slide left/right, but this time place both hands beside you(one on each side). When you have placed your hands push them forward, while moving them together, so they will meet up in front of you, with your arms at least a bit stretched out.Just imagine that you want to push yourself away from the water. - Swim Up and Down:
This is possibly the simplest Swimming-Move. For swimming up just stretched both of your arms out in a diagonal up-direction. So your hands should be in front of you, showing up. For swimming down just do the same as before, but this time let your hands show down. The hardest part is actually to find the right height, for that we recommend that you slowly move your hands up and down, while your arms are stretched out, this way you should find the right height pretty fast. - Cameraorientation:
The following movements only control the camera and do not change your position. They all use your torso.
Turning left: Turn your torso to the left.
Turning right: Turn your torso to the right.
Turning down: Lean your torso forward.
Turning up: Lean your torso backwars.
While left and right only check on your shoulders position, down and up also check your hip-center for higher precision (no accidentally movement), that means it's important that your hip stays mostly at one point. However you can move your legs around (as long as they don't cover up other body parts, that could be a problem), to get a better stand and most of the time your hip will automatically stay in a good position.
HOW TO: Use of Oculus (3D)
To use Oculus-Rift with VR CoralReef you need the Oculus Runtime-Package(RTP) 0.5. Once you installed and cofigured it, open your Resolution-settings(just right click on your desktop and choose Resolution). There you should see a new connected Monitor, called Rift. Make sure this Monitor is in Landscapeformat, important is what you see in the top of the window, not what is written under direction, so if needed just change it from Landscape-Format to Vertical-Format and save the settings.
Next move to your x64Release folder and locate the stereo.cfg. Open it(can be opened with the basic windows-editor) and change the stereo mode to OCULUS and save the file. Next make sure fullscreen is deactivated. For that locate the Ogre.cfg in the same folder, open it and make sure the Full Screen section is set to No, then save the file.
Now when you start the programm it should be shown in two pictures in one window. The last thing you need to do is to drag and draw the window on to the rift monitor(just move it out to the side of your screen) and when it's on the Riftmonitor draw to the top of the screen as far as you can, you can't go to high so don't worry. If done right the Windows 7 Window-Snap Function(newer Versions should have this function as well) should put the Window in borderless fullscreen and you can now use the Oculus Rift.
We're sorry that it needs to be this complicated, it's a limitation due to our engine and time, hopefully sometime in the future this will be upgraded and easier to use.
HOW TO: Adding and changing fishes
- First of all you need of course your new fish, that can be modeled in any program you like, you just need an exporter for the Ogre3D engine. The fish should have an Animation called "Swim", the Game won't crash if you have none but obviously it wouldn't look very good. Also you shouldn't forget to add the Fog-Shader, otherwise your fish will not look like it's part of the scene, for that you can just copy and paste the 3 Programs for FogCalculation,FogFactor and FogColor, which can be found in any other material-file from objects in the media-folder (at least everyone that is used). Note: You can also add an "Idle"-Animation (upper and lower case is important). Currently the fish don't go into an Idle-State but maybe someone will add that sooner or later. If the fish has no Idle-Animation, the Swim-Animation is used instead.
- Next you just put all files belonging to your fish in the fish-folder, which is found inside the media-folder.
- Once you have your fish ready you have to add 2 Strings to the default Values in the fish-class. These Values are defined in Fish.h and get their Value assigned in Fish.cpp. You need 2 Strings, one that is called "your_fish_name_in_upper_case"_MESH and one "your_fish_name_in_upper_case"_ANIMATION, obviously "your_fish_name_in_upper_case" is just a placeholder. The Value for the first String must be the filename of your fish.mesh file, including the ".mesh" part. The value of the second string must be the name of your Swim-Animation, usually we just use "Swim" (upper and lower case is important), but for safety every fish has it's own string, so you if someone forgets to use "Swim" or something similar, you just need to correct the string (but for consistency it would be better to re-export the fish with the correct string).
- Your nearly done, your fish can now already be used everywhere in the project, only thing left is to actually use the fish.
If your fish shall be a free fish:
Go to FishManager.cpp and take a look in to the init()-Method. There you will find a switch-case statement. You just need to add a new case with the next free number and the same content like the other cases. Next you replace the to Parameters with the Mesh and Animation String (for example in the default case it's just Fish::FISH_MESH and Fish::FISH_ANIMATION) with your own, don't forget to specify the Namespace by starting with "Fish::", before your Stringname. At last just increase the X X for rand() % X, from randfish, by one(read the comment for additional information).
If your fish shall be a swarm fish:
Go to SwarmManager.cpp and take a look in to the init()-Method and locate the region for your swarm-type (for example coherent). Then replace the to Parameters with the Mesh and Animation String(for example in the default case it's just Fish::FISH_MESH and Fish::FISH_ANIMATION) with your own, don't forget to specify the Namespace by starting with "Fish::", before your Stringname. If you only want to add your new swarm and not to change it, there are several possibilities how you can do that, most simple would probably be to just copy the region and paste it with the replaced string values and add a new value for the number of the swarm/fishes in the swarm (that would go to much into detail for this manual, should be clear when you look at the code).
In both cases you should fit the scaling in the parameters (just try around until it looks good). - (OPTIONAL) You could also write a algorithm for a new swarm type, if you want to do that, just write the algorithm in the Fish class and write a new calc Method in the swarm class which calls everything needed for your new swarm. At last add the new swarm type in swarm.h and a new case in the calcSwarm Method, which calls your calcMySwarmType Method. Also don't forget to add the new type in the init()-Method.
Quick manual
- Create your new fish, with a "Swim"-Animation. Next add the 3 Programms for the Fog-Shader to the material-file for your fish, these can be found in the other material-files. Note: You can also add an "Idle"-Animation(upper and lower case is important). Currently the fish don't go into an Idle-State but maybe someone will add that sooner or later. If the fish has no Idle-Animation, the Swim-Animation is used instead.
- Put all file in the fish-folder, in media.
- Add the default Strings for your fish in Fish.h and Fish.cpp.
- If your fish is a free fish: Navigate to FishManager.cpp and add a case for your fish in the switch-case statement, in the init()-Method. If your fish is a swarm fish: Navigate to SwarmManager.cpp and locate the region for your swarm-type and change the parameters, so your fish is loaded instead. Or if you only want to add and not to change a swarm, copy the region and modify the parameters. Also best would be if you add a new value in the config.txt.
- (Optional): If you want to add a new swarm with new behaviour, write the algorithm in the Fish class and then a wrapper method in the Swarm class which calls everything you need. Next add your new Swarmtype to the Enum in Swarm.h and a case in calcSwarm. At last add a region in the init()-Method from the SwarmManager.