User Manual
Download and Open the Simulation
For this project the following Unreal Engine version is needed: at least 4.14.x. In addition the Runtime Mesh Component plugin must be installed.
Java 8 must be installed on the system. It is important that your Path to mysiccom.jar doesn't contain any spaces. An IDE Microsoft Visual Studio 2015 (VS14) is highly recommended.
This simulation has been developed and tested on the following system:
Processor | RAM | Operating System | GPU | Add. Requirements: |
---|---|---|---|---|
Intel Core i7-6700K @ 4.00 GHz | 64.00GB | Windows 10 Enterprise - 64bit | NVIDIA GeForce GTX 1080 | 1x USB-3.0-Port, HTC Vive |
Simply double click the project file (.uproject) in the folder, wait for Unreal Engine to open and press "Play" in the top bar of the window.
Ingame Controls
Use the Mouse to look around. Left Click to open the menu and select with your mouse the desired button. Left click again to chose or close the menu. Time of Day control: Toggle FastForward T Increase speed P Decrease speed O Movement: Swim Forward: W Swim Backward: S Slide Left: A Slide Right: D Control Camera: Mouse Swim Up: Left Shift Swim Down: Left Control Other: Toggle Flashlight F Toggle HUD Icons I 3D Radial Menu Left Click
To look around you simply move your head while equipped with the HTC Vive. Movement: Swim Forward: Backside Button of Right Controller Swim Backward: Backside Button of Left Controller Control Camera: Use your Head Other: Toggle Flashlight Press Touchpad on Left Controller Menu: 3D Radial Menu Press Touchpad on Right Controller Navigate Move Finger on Pad Choose Click on Pad while on Button to choose Close Click on Pad while in the Center
The (VR) Ingame Menu
For the menu we used icons from other sources. These may be found here.
Complete List of Features
An immersive lifelike coral reef based on the flora and fauna of a Zanzibar reef Created for an Virtual Reality experience Algorithmic birth, growth and decease of corals based on data received from the integrated SICCOM plugin (see paper below) Over 40 selfmade models 4 different kinds of generated corals 3 different modeled corals 2 types of algae Textured and animated 10 different types of animals including fish, predators, crabs, turtles, stingrays, starfish Every model is textured and animated Rocks, wrecks, anchor and many more static objects Generated corals and algae fight for dominance within the reef They also react on parametrical changes such as a change in temperature Swarm algorithms simulate different behaviour of fish and predators (domain-driven design [DDD]) Different states like "Travelling", "Hungry" or "Hunting" They can be found either in swarms, small groups or alone Sharks hunt in packs, fish try to regroup after a predator attacked Fish nibble on corals, making them bleach Feature-rich ingame (VR) menu Simulation Speed / Reset Simulation Change Water Temperature Change Speed of Day & Night Cycle Toggle Fog / View Distance Reset Player Position Volume Show Information on current numbers of simulated algae & corals Change Movement Speed Explore the reef at night with a (VR) flashlight Selfmade features to increase realism Water visualization Godrays Caustics Day & Night Cycle View Distortion Ambient underwater noise Particles Some fish have randomly chosen textures Intuitive movement with VR-Controller or Keyboard Some models use Parallax Occlusion Mapping for a more realistic appearance Triggerable event: Pulling an anchor through the reef, destroying generated corals and algae in its path Fish disappear realistically at night
Project Planning
Working with Scrum
For the master's project "VR CoralReef" we chose the Scrum model for managing the various tasks that approached each one of us. These tasks are stored in a Kanban board which we gradually implement. The whole group is responsible for updating the board. An important ritual are regular meetings where the whole group creates tasks and guesses the estimated amount of work (in points) for them, while each one contributes his or her individual skills and knowledge to the discussion. This ensures, that every member of the group is fully aware of each duty and its complexity.
For Scrum we chose a sprint length of two weeks, in which we complete said estimated and assigned tasks. In addition to the planning meetings regular retroperspectives are held in order to identify issues within the group as early as possible. Each end of a sprint privdes that we present our solutions to those responsible for the masters project and collect feedback (which is a phase also noted in the Kanban board). Subsequently we create a new set of tasks for the next sprint. While doing so, we try to keep the amount of work/point close to the project average so far.
Another fancied ritual for quality assurance is a regular code review where each group member chooses a part of code he or she wants to present and improve.
Development process
In order to visualize the process developer and reviewer undergo, we created an abstract diagram of the development process.
Code Architecture and Diagrams
(free for non commercial use only but not commercial use -MIT License-)SICCOM Plugin
The "SICCOM" Manager from the ZMT delivers raw data on birth, growth and decease of corals and algae, which are also fighting each other for dominance within the reef. The "VR CoralReef" takes this data directly and visualizes it as realistically as possible. Two of our members worked on a plugin for the Unreal Engine 4. Download the PDF below.
Swarm algorithms
We chose to base the algorithms of swarms, predators and lone fish on domain-driven design (DDD) - as far as practicable - in Unreal Engine 4. We could successfully discard the secondary ports, since UE4 already provides everything necessary. All algorithms work basically the same way, though the predators and lone fish own additional states, since they - unlike swarms - show varying behaviour.
The following diagrams describe the process per frame. Function calls are called by name and parameters, content of functions on the other hand only by abstract summaries.
Each frame triggers a tick event with the time that passed. This event selects the Actor class in the primary port in the adapter, which can seen in the diagram to the far left. These forward the events with necessary parameters to the manager class in the administration layer. The task of that manager class is to run the operations inside the domain in the correct order.
Until the implementation of the domain logic one can see, that the adapter and the administration layer are implemented mostly analogue. Inside the domain the functionalities begin to vary more, except for calculations of direction and position of the fish. For swarms these calculations are for social and individual forces and the potential occuring flee direction from predators.
Inside the domain, predators have states which influence the behaviour greatly, even though functionality of these states has been kept as low as possible. The two states are called "Travelling" and "Raiding". Each frame the administration triggers an event, deciding to stay in the current state or changing to the other one. A group of predators acts analogously, since they share states. Either they travel together - or they hunt together. Processing the forces of predators is simpler: since they ignore social forces, they focus on individual ones and hunting direction.
Individual/Lone fish have states inside the domain, which affect each of them individually. Since the states of individual fish differ more (compared to predators), they need states which can manipulate the fish completely. Until this point the individual fish travel through the reef or scrape on corals. Each fish decides individually, which coral it scrapes on. A synchronization is not happening. Once the set time for a state has passed, the fish will move on to the next state.
Ground Animals
Inside the Domain ground animals have a StateMachine, which analyzes - depending on its current state - the surrounding area of the animal for influencing factors. These factors can be objects standing in the way, other ground animals or the player. For each of these cases there are specific states, which alter the direction of movement. In case of a collision, for instance, a new path is calculated. If the player is close, ground animals can become aggressive or hide. Following the viewing direction and rotation of the ground animal will be adjusted depending on the alteration of movement.
States in general
The states of individual fish and predators work the same way. They have states switching into other states as soon as a set individual timer runs out. Until run out, the states receive constantly updated information on their respective movement direction. The moment a state runs out, it receives information which state is set next.
State transitions
There are three kinds of transitioning states:
The first group consists of "ScrapingTravelling", which makes the fish swim around until the "hunger" kicks in, "Scraping", which makes the fish dock and feed off a coral and "ScrapingUndocking", which makes the fish undock slowly until it finds himself in "ScrapingTravelling" again.
The predator logic can be summarized in a second group: "Travelling" makes them swim around in the simulation until several predators unite into the state "Raiding", attacking a swarm of fish until they go back to "Travelling", once "satisfied".
The last group consists of "Travelling" and "Center". These states make fish swim around and rotate them slowly towards the center of the simulation in order to make them not swim out of area.
Swarms.Administration update(Listfishes, float deltaSeconds) for(Fish f : fishes) f.calcSocialForces(fishes) f.calcIndependentForces() f.calcAndUpdateVelocity(deltaSeconds) f.calcAndUpdatePosition(deltaSeconds) Swarms.Domain calcSocialForces(List fishes) this.sumSeperationForces(fishes) this.sumAlignmentForces(fishes) this.sumCohesionForcesForces(fishes) this.normalizeForces() Swarms.Domain sumSeperationForces() Vec3 sumSeperationForce for(Fish f : fishes) if(f.position.distTo(this.position) < range) sumSeperationForce += f.position-this.position Swarms.Domain sumAlignmentForces() Vec3 sumAlignmentForce for(Fish f : fishes) if(f.position.distTo(this.position) < range) sumAlignmentForce += f.direction Swarms.Domain sumSeperationForces() Vec3 sumCohesionForcesForce for(Fish f : fishes) if(f.position.distTo(this.position) < range) sumCohesionForcesForce += this.position-f.position Swarms.Domain calcIndependentForces() this.calcTerrainAlignmentForce() this.calcTerrainRepulsionForce() this.calcSpeedAdaptionForce() this.calcPitchDampingForce() this.calcRandomForce() this.calcAttractionPointForce() this.calcFleeFromPredatorsForce() Swarms.Domain calcAndUpdateVelocity(float deltaSeconds) this.calcCombinedWeightedForce() this.velocity = this.velocity + combinedForce * deltaSeconds / this.mass this.direction = this.velocity.normalize() Swarms.Domain calcAndUpdatePosition(float deltaSeconds) this.position += this.velocity * deltaSeconds
Predators.Administration update(Listpredators, float deltaSeconds) for(Predator p : predators) p.updateState(deltaSeconds) p.updateVelocity(deltaSeconds) p.updatePosition(deltaSeconds) Predators.Domain updateState(float deltaSeconds) this.state.updateTimer(deltaSeconds) if(this.state.timeOver) swapState() Predators.Domain updateVelocity(float deltaSeconds) this.calcPitchForce() this.calcTerrainAligmentForce() this.calcTerrainRepulsionForce() this.calcStateForce(this.state) this.calcDesiredSpeedForce() this.calcCombinedForce() this.velocity += this.combinedForce * deltaSeconds / this.mass Predators.Domain updatePosition(float deltaSeconds) this.position += this.velocity * deltaSeconds
IndividualFishes.Administration update(Listfishes, float deltaSeconds) for(Fish f : fishes) f.updateState(deltaSeconds) f.updateVelocity(deltaSeconds) f.updatePosition(deltaSeconds) IndividualFishes.Domain updateState(float deltaSeconds) this.state.updateTimer(deltaSeconds) if(this.state.timeOver) swapState() IndividualFishes.Domain updateVelocity(float deltaSeconds) this.calcPitchForce() this.calcStateForce(this.state) this.calcDesiredSpeedForce() this.calcCombinedForce() this.velocity = this.velocity + this.combinedForce * deltaSeconds / this.mass IndividualFishes.Domain updatePosition(float deltaSeconds) this.position += this.velocity * deltaSeconds