-
Pascal Engeler authoredPascal Engeler authored
FocusTerra Realtime Wave Simulator
This repository contains all files that make up the realtime wave simulator exhibit at FocusTerra. More information about the exhibition can be found here.
While the project was originally developed on macOS, it was later ported to Windows (Visual Studio 2019).
Dependencies are OpenGL, ImGui and SDL, and all of them are contained in this repository.
The program is optimized to run on a system containing an Nvidia Geforce RTX 3080, where it achieves roughly 90-95% GPU resource utilization. While CPU and RAM should be of reasonable performance/size, their specs are of secondary importance.
For a general overview of the application logic, one should have a look at the Architecture section. An accompanying code example is shown in Workflow: Sample Main Function.
If one desires to add custom resources (e.g. new predefined structures or a new colour palette), one should head straight to Resources section, where the resource organization and file format conventions are detailed.
For information on how the project can be built, the Building the Project section has you covered with step-by-step instructions.
In depth documentation about all classes and their members can be found in the Classes section. Enums are covered in their own Enums section.
While DearImGui is the basis for the GUI front- and backend, I had to perform some modifications in order to make it work on a touchscreen. Those changes are detailed in the section ImGui Customization.
Finally, shaders and their respective purposes are explained in the section GLSL Shaders.
Table of Contents
- FocusTerra Realtime Wave Simulator
- Table of Contents
- Impressions
- Project Organization
- Resources
- Building the Project
-
Documentation
- Architecture
-
Classes
- A General Note on Pre-/Postconditions
- Drawer (drawer.hpp, drawer.cpp)
- DrawingHandler (drawing_handler.hpp, drawing_handler.cpp)
- EfficientBlock (efficient_block.hpp, efficient_block.cpp)
- EventLogger (event_logger.hpp, event_logger.cpp)
- GuiHandler (gui_handler.hpp, gui_handler.cpp)
- Infrastructure (infrastructure.hpp, infrastructure.cpp)
- InputHandler (input_handler.hpp, input_handler.cpp)
- Message (message.hpp, message.cpp)
- PatternHandler (pattern_handler.hpp, pattern_handler.cpp)
- Pevent (pevent.hpp, pevent.cpp)
- PeventFactory (pevent.hpp, pevent.cpp)
- Shader (shader.hpp, shader.cpp)
- SlimBlockchainHandler (slim_blockchain_handler.hpp, slim_blockchain_handler.cpp)
- TimeoutHandler (timeout_handler.hpp, timeout_handler.cpp)
- Toolbox (toolbox.hpp, toolbox.cpp)
- WaveHandler (wave_handler.hpp, wave_handler.cpp)
- Enums (enums.hpp)
- ImGui Customization
- GLSL Shaders
- Workflow: Sample main function
Impressions
Project Organization
The repo is organized in the following folders:
-
src
(and subfolders) contains all.cpp
-
include
(and subfolders) contains all headers -
shaders
contains all GLSL shaders -
lib
contains libraries -
FocusTerra
contains the Visual Studio project file -
FocusTerra\x64\Release
andFocusTerra\x64\Debug
contain the executables -
build
is left over from the macOS days (contains a currently brokenMakefile
)
Resources
Runtime Resource Folder
Runtime resources are organized in a single folder called ft_top
. It contains the following:
- A folder
bin
that contains the executable andSDL2.dll
- A folder
fonts
that contains the font used by the GUI, namelyCousine-Regular.ttf
- A folder
resource
that contains- The colour palette in
ft_palette.conf
,ft_palette.texture
- The Image Button images in the folder
images
- A folder
textures
that contains the predefined structure textures. There are two versions, one for the FocusTerra resolution (inrocket
), and one for Pascal's resolution (inhome
).
- The colour palette in
- A folder
shaders
that contains the used GLSL shaders.
The program accesses these resources at runtime.
File Formats
This section describes the file formats external files are expected to follow.
GUI Images
Gui Images, such as the button textures, are loaded using stbi
. The exact calls can be found in GuiHandler::load_image_to_texture_
. The important parts are
data = stbi_load(file.c_str(), &width, &height, &nrChannels, STBI_rgb_alpha);
//...
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
Images are expected to be RGBA, i.e. to have an alpha channel. Not being very fluent in image formats, I believe this is the only limitation. To be save, I'll state that images are expected to be loadable with the above calls.
Damping Textures
Damping Textures, such as those used by PatternHandler
and WaveHandler
, are 2D textures and they are composed of two files:
- a
.conf
file that describes several properties of the texture, - a
.texture
file that contains the texture data in RGBA32F format. These two files are expected to be in the same directory. By convention, the name of the texture does never include any file extensions. Thus a texture with namemy_damping_map
will consist of the two filesmy_damping_map.conf
andmy_damping_map.texture
.
A sample .conf
file with comments is shown below.
Its screen dimensions are what the FocusTerra touchscreen supports natively.
Note that the comments are not allowed in real files.
3840 //screen width in pixels
2160 //screen height in pixels
4440 //texture width in texels
3360 //texture height in texels
0 //screen offset left in texels
600 //screen offset right in texels
600 //screen offset bottom in texels
600 //screen offset top in texels
The meaning of the offsets is the following: In this application, we simulate a larger region than what's drawn on the screen. Thus what's drawn on the screen is just a rectangular region within the texture. To describe the placement of this region, we use the number of pixels between the screen region and the texture boundaries in all four directions. That's what these offsets describe.
Note that only if all damping textures agree with the dimensions specified in the application, will the program run correctly. Anything else will give you UB.
A .texture
file contains 4 * texture_width * texture_height
floating point values, each in the range [0,1]; 0 means this channel is fully off, while 1 means the channel is fully on. Each group of four is to be interpreted as the RGBA values of a single texel.
Successive values are delimited with a single space. Each group of four float describes the colour of one texel in the format R G B A. After each 4 * texture_width
values, a newline is expected.
The only channel that matters for now is the red channel. Damping is multiplicative, such that more red means less damping.
For more information on how damping is implemented, study how the damping texture tex_damp
comes into play in the timestepping fragment shader.
Texture uploading is done via the following call:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, texture_width, texture_height, 0, GL_RGBA, GL_FLOAT, data_target.data());
One peculiarity to be aware of is the use of different axis conventions.
Images typically have the origin in the lower left corner with y-axis pointing upwards, while OpenGL has the origin in the top left corner with the y-axis pointing downwards.
This means that the first four floats in the file describe the top left texel, and the first 4 * texture_width
values describe the top most row of texels.
This is only important when using damping textures that are asymmetric with respect to the horizontal.
If it is a problem, consider either flipping the image, or look into calling stbi_set_flip_vertically_on_load(true)
. This can also be taken care of shader side.
Colour Palettes
A colour palette, like that loaded by WaveHandler::load_palette_
and used to draw the waves in a custom colour scheme, is a 1D texture.
Most of the conventions for Damping Textures also apply here. The differences are:
- The
.conf
file only contains one integer, the number of texels in the texture - The
.texture
file still contains four space-denominated floats per texel (RGBA), but there is a newline after each texel.
As things stand now, the lowest wave point maps to the first texel, and the highest wave point maps to the last texel.
Similarly to 2D textures, here we upload the data via
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, palette_.size()/4, 0, GL_RGBA, GL_FLOAT, palette_.data());
Building the Project
Windows
In order to build the project, the following steps must be followed
- Open the project in Visual Studio 2019
- Make sure the following files are excluded from the build (
Rightclick
,Properties
,Excluded from Build
,Yes
:block.hpp
blockchain_handler.hpp
block.cpp
blockchain_handler.cpp
imgui_example_sdl_opengl3.cpp
main.cpp
main_3d.cpp
main_refactored.cpp
- There should be a lot of include-errors reported. To fix this, adjust the paths:
- In the
Solution Explorer
, right click onFocusTerra
and selectProperties
- Navigate to
VCC Directories
and adjust theInclude Directories
. They should point toinclude
include\imgui
-
SDL2-2.0.14\include
(e.g. in WindowsSDL, or vclib (not in project))
- Also adjust the
Library Directories
. They should point toSDL2-2.0.14\lib\x64
. - The linked libraries can be seen in
Linker
,Input
,Additional Dependencies
. They should not require change, and should includeSDL2.lib
,SDL2main.lib
andopengl32.lib
.
- In the
- Open
main_testing.cpp
. This contains themain
function. Adjust it for the target system:- Adjust the screen resolution using the
#defines
at the top. Predefined are the resolutions for the FocusTerra screen and for Pascal's screen. - Adjust the
top_path
to point to the resource folder (i.e.ft_top
). - Adjust the Derived paths, where necessary. Potentially the
tex_path
may need adjustment. - Adjust the
tex_offscreen_*
variables to reflect the used texture dimensions.
- Adjust the screen resolution using the
- Open
input_handler.cpp
.- For a touchscreen, send an event after the
SDL_FINGER*
cases. On non-touch devices, send events after theSDL_MOUSE*
events.
- For a touchscreen, send an event after the
- Open
slim_blockchain_handler.cpp
- Depending on the system, uncomment or comment out the section marked
Uncomment on a touchscreen
.
- Depending on the system, uncomment or comment out the section marked
- Open
drawing_handler.cpp
- Depending on the system, uncomment or comment out the section marked
Uncomment on a touchscreen
.
- Depending on the system, uncomment or comment out the section marked
- Open
gui_handler.cpp
- Depending on the system, change the gui drawcall towards the end in
GuiHandler::update
. - Depending on the system, comment out or uncomment the two sections marked
Uncomment on a touchscreen
.
- Depending on the system, change the gui drawcall towards the end in
- Rightclick on
FocusTerra
, selectBuild
. - When the building is finished, create a
ft_top
folder according to the specifications above, and place the executable in thebin
folder. If there are library issues, also place a copy ofSDL2.dll
in thebin
folder. - Now the application is ready to be run.
macOS
Building on macOS is currently not supported.
Documentation
The following is a documentation for all the code used in the project. As the project is currently at development halt, there are a few known issues that can not be addressed for the time being. They are pointed out in their respective sections.
Architecture
The application is split into different modules, each module handles a subset of the total functionality. The major modules and their responsibilities are
-
WaveHandler
- Handle the wave textures
- Handle integration of the wave equation
- Handle rendering the wave
- Handle certain texture initialisations for other modules (this should be changed)
-
InputHandler
- Interface with SDL to fetch user input
- Translate user input to a usable format (
Pevent
) - Communicate user input to other modules
-
GuiHandler
- Interface with
ImGui
- Draw the GUI
- Communicate GUI input to other modules
- Interface with
-
SlimBlockchainHandler
- Handle the "Spielen" functionality (place, move, remove blocks)
- Draw to wave and damping textures to implement "Spielen"
-
DrawingHandler
- Handle the "Zeichnen" and "Radieren" functionality (draw strokes)
- Draw to wave and damping textures to implement "Zeichnen" and "Radieren"
-
PatternHandler
- Handle all predefined structure placements
- Draw structures to damping textures
-
TimeoutHandler
- Handle the Timeout functionality (if no user input for N seconds, post reset requests for everyone)
-
Toolbox
- Store information needed by several other modules ("global state")
- Implement an event chain where current events are stored
- Implement a mailbox to enable the passing of messages
Much like the name suggests, the Toolbox
is passed around from one module to the next, and each can then access everything stored within it.
Each module can see all information and decide how it reacts to the current combination of state, messages and events (user input).
The logical flow of the application game loop goes as follows:
-
InputHandler
fetches and writes new user input into theToolbox
event chain -
TimeoutHandler
checks if there is any user input, and if timeout occurs posts reset requests to theToolbox
mailbox targeting all other modules -
GuiHandler
checks if user input targets the GUI, updates the global state (e.g. change source frequency, change mouse state to "Zeichnen") and posts appropriate messages for other modules (e.g. place structure) -
WaveHandler
checks if any messages that target it have been posted -
SlimBlockchainHandler
reacts to its messages and user input -
DrawingHandler
reacts to its messages and user input -
PatternHandler
reacts to its messages and user input - Now all state has been updated, all messages posted and handled and damping textures are final
-
WaveHandler
generates the new damping texture and timesteps the wave -
WaveHandler
renders the new wave to the screen -
GuiHandler
renders the GUI to the screen - Renderbuffers are swapped and the frame ends
For an example how this is implemented in practice, see Sample Main Function
Classes
A General Note on Pre-/Postconditions
Sensible preconditions to functions are always implicit.
I.e. it is generally assumed that objects are fully and correctly initialized, except when this can obviously not be the case (e.g. for a function initialize
).
Only "special" preconditions are listed. The same is true for postconditions.
drawer.hpp, drawer.cpp)
Drawer (Description
A Drawer
is an object that can draw a single line of segments to the screen.
It handles single touch of the Zeichnen
functionality.
Note that this class relies on the caller to its methods to take care of the Opengl state (using shaders, binding buffers, etc.).
See DrawingHandler
for more information on how this class is to be used.
The coordinates used are typically OpenGL coordinates, i.e. in the range [-1, 1].
Usage
- Construct object (e.g. when new finger goes down)
-
start_drawing
with initial position (e.g. with finger down position) - Upon new location (e.g. finger motion to new position), setup Opengl state (use shader
draw
, bind FBO, bind VAO, bind VBO, set viewport, bind textures) and calldraw
. Callredraw
with all required FBO/texture combinations. - Destruct when drawing this line is finished (e.g. finger is lifted)
Constructors and Destructors