diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000000000000000000000000000000000000..daa3738fe5bd7a0202e5329a2b7f69f217ed8ede
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,70 @@
+BasedOnStyle: WebKit
+IndentWidth: 4
+ColumnLimit: 100
+
+Language: Cpp
+Standard: c++20
+DerivePointerAlignment: false
+
+PointerAlignment: Left
+ReferenceAlignment: Right
+
+SortIncludes: CaseSensitive
+
+AccessModifierOffset: -4
+
+AlignAfterOpenBracket: true
+AlignArrayOfStructures: Left
+AlignConsecutiveAssignments: None
+AlignConsecutiveBitFields: None
+AlignConsecutiveDeclarations: None
+AlignConsecutiveMacros: None
+AlignEscapedNewlines: Left
+AlignOperands: AlignAfterOperator
+AlignTrailingComments: true
+
+BreakBeforeBinaryOperators: NonAssignment
+BreakBeforeBraces: Attach
+BreakBeforeConceptDeclarations: true
+BreakBeforeTernaryOperators: false
+BreakConstructorInitializers: AfterColon
+BreakInheritanceList: AfterColon
+BreakStringLiterals: true
+
+AllowAllArgumentsOnNextLine: false
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortBlocksOnASingleLine: Empty
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortEnumsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: Empty
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLambdasOnASingleLine: Empty
+AllowShortLoopsOnASingleLine: false
+
+AlwaysBreakTemplateDeclarations: Yes
+EmptyLineBeforeAccessModifier: Always
+
+BinPackParameters: true
+ReflowComments: true
+
+ShortNamespaceLines: 0
+NamespaceIndentation: All
+FixNamespaceComments: true
+
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceBeforeSquareBrackets: true
+SpaceInEmptyBlock: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: Leave
+SpacesInConditionalStatement: false
+SpacesInContainerLiterals: true
+SpacesInLineCommentPrefix:
+  Minimum: 1
+  Maximum: -1
+SpacesInParentheses: false
+
+TabWidth: 4
+UseTab: ForContinuationAndIndentation
+
+UseCRLF: false
diff --git a/.gitignore b/.gitignore
index 7ee4ff1903e902c4715c6e2b0c3e784ed5755aaf..8ab30e60ef913a4d49762b7326c878fc130696cc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@
 .editorconfig
 
 # build directories
+bin/
 build/
 cmake-build-debug/
 cmake-build-release/
@@ -18,3 +19,11 @@ cmake-build-release/
 
 # GUI configuration files
 imgui.ini
+
+# Generated source and header files for shaders
+*.hxx
+*.cxx
+
+# Cache for different tasks like source code indexing
+.cache/
+
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
deleted file mode 100644
index 33b70018e368ecc3ad019ea33e57485814eb233a..0000000000000000000000000000000000000000
--- a/.gitlab-ci.yml
+++ /dev/null
@@ -1,89 +0,0 @@
-variables:
-  RUN:
-    value: "all"
-    description: "The tests that should run. Possible values: ubuntu, win, all."
-  GIT_DEPTH: 1
-
-stages:
-  - build
-  - deploy
-
-build_ubuntu_gcc:
-  only:
-    variables:
-      - $RUN =~ /\bubuntu.*/i || $RUN =~ /\ball.*/i
-  stage: build
-  tags: 
-    - ubuntu-gcc-cached
-  variables:
-    GIT_SUBMODULE_STRATEGY: recursive
-  timeout: 10m
-  retry: 1
-  script:
-    - mkdir debug
-    - cd debug
-    - cmake -DCMAKE_BUILD_TYPE=Debug ..
-    - cmake --build .
-  artifacts:
-    name: "Documentation - $CI_PIPELINE_ID"
-    paths:
-      - doc/html
-      - doc/latex
-    expire_in: never
-
-build_win10_msvc:
-  only:
-    variables:
-      - $RUN =~ /\bwin.*/i || $RUN =~ /\ball.*/i
-  stage: build
-  tags: 
-    - win10-msvc-cached
-  variables:
-    GIT_SUBMODULE_STRATEGY: recursive
-  timeout: 10m
-  retry: 0
-  script:
-    - cd 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Tools\'
-    - .\Launch-VsDevShell.ps1
-    - cd $CI_PROJECT_DIR
-    - mkdir debug
-    - cd debug
-    - cmake -DCMAKE_BUILD_TYPE=Debug ..
-    - cmake --build .
-
-deploy_doc_develop:
-  only:
-    variables:
-      - $RUN =~ /\bubuntu.*/i || $RUN =~ /\ball.*/i
-    refs:
-      - develop
-  stage: deploy
-  needs: ["build_ubuntu_gcc"]
-  dependencies: 
-    - build_ubuntu_gcc
-  tags: 
-    - webserver
-  variables:
-    GIT_STRATEGY: none
-  script:
-    - rsync -avh doc/html/ /var/www/html/develop --delete
-    - echo "Check it out at https://vkcv.de/develop"
-
-deploy_doc_branch:
-  only:
-    variables:
-      - $RUN =~ /\bubuntu.*/i || $RUN =~ /\ball.*/i
-  except:
-    refs:
-      - develop
-  stage: deploy
-  needs: ["build_ubuntu_gcc"]
-  dependencies: 
-    - build_ubuntu_gcc
-  tags: 
-    - webserver
-  variables:
-    GIT_STRATEGY: none
-  script:
-    - rsync -avh  doc/html/ /var/www/html/branch/$CI_COMMIT_BRANCH --delete
-    - echo "Check it out at https://vkcv.de/branch/$CI_COMMIT_BRANCH"
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
index e0aaf2d17c340f98ae875f7e0f1238bfe04f7e5d..d3b0a1404e8b1bb1a7b29154759d006adc8615e4 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -14,7 +14,7 @@
 	path = modules/asset_loader/lib/stb
 	url = https://github.com/nothings/stb.git
 [submodule "modules/camera/lib/glm"]
-	path = modules/camera/lib/glm
+	path = modules/geometry/lib/glm
 	url = https://github.com/g-truc/glm.git
 [submodule "modules/shader_compiler/lib/glslang"]
 	path = modules/shader_compiler/lib/glslang
@@ -22,3 +22,27 @@
 [submodule "modules/gui/lib/imgui"]
 	path = modules/gui/lib/imgui
 	url = https://github.com/ocornut/imgui.git
+[submodule "modules/upscaling/lib/FidelityFX-FSR"]
+	path = modules/upscaling/lib/FidelityFX-FSR
+	url = https://github.com/GPUOpen-Effects/FidelityFX-FSR.git
+[submodule "lib/Vulkan-Headers"]
+	path = lib/Vulkan-Headers
+	url = https://github.com/KhronosGroup/Vulkan-Headers.git
+[submodule "lib/Vulkan-Hpp"]
+	path = lib/Vulkan-Hpp
+	url = https://github.com/KhronosGroup/Vulkan-Hpp
+[submodule "modules/upscaling/lib/NVIDIAImageScaling"]
+	path = modules/upscaling/lib/NVIDIAImageScaling
+	url = https://github.com/NVIDIAGameWorks/NVIDIAImageScaling.git
+[submodule "lib/VulkanMemoryAllocator-Hpp"]
+	path = lib/VulkanMemoryAllocator-Hpp
+	url = https://github.com/YaaZ/VulkanMemoryAllocator-Hpp.git
+[submodule "modules/algorithm/lib/FidelityFX-SPD"]
+	path = modules/algorithm/lib/FidelityFX-SPD
+	url = https://github.com/GPUOpen-Effects/FidelityFX-SPD.git
+[submodule "modules/upscaling/lib/FidelityFX-FSR2"]
+	path = modules/upscaling/lib/FidelityFX-FSR2
+	url = https://github.com/TheJackiMonster/FidelityFX-FSR2.git
+[submodule "lib/VulkanMemoryAllocator"]
+	path = lib/VulkanMemoryAllocator
+	url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000000000000000000000000000000000000..540b00747eee74a94be3c987d1ac4c8595738a09
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,20 @@
+Alexander Gauggel
+Artur Wasmut
+Josch Morgenstern
+Katharina Krämer
+Lars Hoerttrich
+Leonie Franken
+Mara Vogt
+Mark O. Mints
+Sebastian Gaida
+Simeon Hermann
+Susanne Dötsch
+Tobias Frisch
+Trevor Hollmann
+Vanessa Karolek
+
+
+
+
+
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000000000000000000000000000000000000..3e2cfbfed372f4d7aa182b6129c34f2bc6abccb6
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,43 @@
+# Changelog
+
+## [0.1.0](https://gitlab.uni-koblenz.de/vulkan2021/vkcv-framework/tree/0.1.0) (2021-12-07)
+
+** Platform support:**
+
+ - Linux support (GCC and CLang)
+ - MacOS support (Apple CLang)
+ - Windows support (MSVC and MinGW-GCC experimentally)
+
+** New modules:**
+
+ - [Asset-Loader](modules/asset_loader/README.md): A VkCV module to load basic assets like models, materials and images
+ - [Camera](modules/asset_loader/README.md): A VkCV module to manage cameras and their handle view and projection matrices
+ - [GUI](modules/gui/README.md): A VkCV module to integrate GUI rendering to your application as additional pass
+ - [Material](modules/material/README.md): A VkCV module to abstract typical kinds of materials for rendering
+ - [Meshlet](modules/meshlet/README.md): A VkCV module to divide vertex data of a mesh into meshlets
+ - [Scene](modules/scene/README.md): A VkCV module to load and manage a scene, simplify its rendering and potentially optimize it
+ - [Shader-Compiler](modules/shader_compiler/README.md): A VkCV module to compile shaders at runtime
+ - [Upscaling](modules/upscaling/README.md): A VkCV module to upscale images in realtime
+
+** New features:**
+
+ - Resizable windows
+ - Multiple windows and multiple swapchains (window management)
+ - Dynamically requesting Vulkan features and extensions
+ - Shader reflection and runtime shader compilation (various shader stages)
+ - Realtime ray tracing
+ - Mesh shaders
+ - Indirect dispatch
+ - Compute pipelines and compute shaders
+ - Multiple queues and graphic pipelines
+ - Bindless textures
+ - ImGUI support
+ - Mipmapping
+ - Logging
+ - Command buffer synchronization
+ - Doxygen source code documentation
+ - Buffer, sampler and image management
+ - Camera management with gamepad support
+ - Input event synchronization
+ - Resource management with handles
+
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2ae078a428a8e5e640ed8dc7bcc2f4e58e159c6b..15f953b3b3b46e7e6f86526d90a53a5d4d40d3bc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,8 +1,41 @@
 cmake_minimum_required(VERSION 3.16)
 project(vkcv)
 
+# cmake options
+option(BUILD_MODULES "Enables building VkCV as shared libraries" ON)
+option(BUILD_PROJECTS "Enables building the VkCV projects" ON)
+option(BUILD_CLANG_FORMAT "Enables formatting the source code" OFF)
+option(BUILD_DOXYGEN_DOCS "Enables building the VkCV doxygen documentation" OFF)
+option(BUILD_SHARED "Enables building VkCV as shared libraries" OFF)
+option(BUILD_VMA_VULKAN_VERSION "Enforce a specific Vulkan version for VMA" OFF)
+option(BUILD_VALIDATION_FORCED "Enforce validation layers being built-in" OFF)
+
+# uncomment the following line if cmake will refuse to build projects
+#set(BUILD_PROJECTS ON)
+
+if ((WIN32) AND (NOT BUILD_VMA_VULKAN_VERSION))
+	set(BUILD_VMA_VULKAN_VERSION "1.3.0")
+endif()
+
+if (BUILD_PROJECTS)
+	set(BUILD_MODULES ${BUILD_PROJECTS})
+endif()
+
+message(STATUS "Options:")
+message(" - BUILD_MODULES: ${BUILD_MODULES}")
+message(" - BUILD_PROJECTS: ${BUILD_PROJECTS}")
+message(" - BUILD_CLANG_FORMAT: ${BUILD_CLANG_FORMAT}")
+message(" - BUILD_DOXYGEN_DOCS: ${BUILD_DOXYGEN_DOCS}")
+message(" - BUILD_SHARED: ${BUILD_SHARED}")
+
+if (BUILD_SHARED)
+	set(vkcv_build_attribute SHARED)
+else()
+	set(vkcv_build_attribute STATIC)
+endif()
+
 # settings c++ standard for the framework
-set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD 20)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
 # checking build type and setting up a variable
@@ -11,11 +44,18 @@ if (CMAKE_BUILD_TYPE)
 	set(vkcv_build_${_vkcv_build_type} 1)
 endif()
 
-message("-- Language: [ C++ " ${CMAKE_CXX_STANDARD} " ]")
-message("-- Compiler: [ " ${CMAKE_CXX_COMPILER_ID} " " ${CMAKE_CXX_COMPILER_VERSION} " ]")
+if (EXISTS "/usr/bin/mold")
+	set(CMAKE_LINKER "/usr/bin/mold")
+endif()
+
+message(STATUS "Language: [ C++ " ${CMAKE_CXX_STANDARD} " ]")
+message(STATUS "Compiler: [ " ${CMAKE_CXX_COMPILER_ID} " " ${CMAKE_CXX_COMPILER_VERSION} " ]")
+message(STATUS "Linker: [ " ${CMAKE_LINKER} " ]")
 
 if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.0.0"))
 	message(FATAL_ERROR "Upgrade your compiler! GCC 9.0+ is required!")
+elseif(MINGW)
+	message(WARNING "MinGW is impressively unstable! So beware it may not compile or crash during runtime!")
 endif()
 
 # setting up different paths
@@ -33,38 +73,61 @@ set(vkcv_flags ${CMAKE_CXX_FLAGS})
 # enabling warnings in the debug build
 if (vkcv_build_debug)
 	if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
-		set(vkcv_flags ${vkcv_flags} " -Weverything")
+		#set(vkcv_flags ${vkcv_flags} " -Weverything")
+		set(vkcv_flags ${vkcv_flags} " -Wextra -Wall -Wno-unused-parameter")
 	elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
-		set(vkcv_flags ${vkcv_flags} " -Wextra -Wall -pedantic")
+		set(vkcv_flags ${vkcv_flags} " -Wextra -Wall -pedantic -Wno-unused-parameter")
 	else()
 		set(vkcv_flags ${vkcv_flags} " -W4")
 	endif()
+	
+	list(APPEND vkcv_definitions VULKAN_VALIDATION_LAYERS)
+elseif (BUILD_VALIDATION_FORCED)
+	list(APPEND vkcv_definitions VULKAN_VALIDATION_LAYERS)
+endif()
+
+if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "11.0.0"))
+	list(APPEND vkcv_definitions __NO_SEMAPHORES__)
 endif()
 
 # configure everything to use the required dependencies
 include(${vkcv_config}/Libraries.cmake)
 
+# set macro to enable vulkan debug labels
+list(APPEND vkcv_definitions VULKAN_DEBUG_LABELS)
+
 # set the compile definitions aka preprocessor variables
 add_compile_definitions(${vkcv_definitions})
 
-# add modules as targets
-add_subdirectory(modules)
+# check if the framework is used from a parent scope
+get_directory_property(vkcv_parent_scope PARENT_DIRECTORY)
+
+if (BUILD_MODULES)
+	message(STATUS "Modules: ON")
+	
+	# add modules as targets
+	add_subdirectory(modules)
+else()
+	message(STATUS "Modules: OFF")
+endif()
 
 # add source files for compilation
 include(${vkcv_config}/Sources.cmake)
 
-message("-- Libraries: [ ${vkcv_libraries} ]")
-message("-- Flags: [ ${vkcv_flags} ]")
+message(STATUS "Framework:")
+message(" - Includes: [ ${vkcv_includes} ]")
+message(" - Libraries: [ ${vkcv_libraries} ]")
+message(" - Flags: [ ${vkcv_flags} ]")
 
 # set the compiler flags for the framework
 set(CMAKE_CXX_FLAGS ${vkcv_flags})
 
-# create VkCV framework as static library using all source files
-add_library(vkcv STATIC ${vkcv_sources})
+# create VkCV framework as library using all source files
+add_library(vkcv ${vkcv_build_attribute} ${vkcv_sources})
 
 if(MSVC)
   #enable multicore compilation on visual studio
-  target_compile_options(vkcv PRIVATE "/MP" "/openmp")
+  target_compile_options(vkcv PRIVATE "/MP" "/openmp" "/Zc:offsetof-")
 
   #set source groups to create proper filters in visual studio
   source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${vkcv_sources})
@@ -72,20 +135,40 @@ endif()
 
 # add include directories from dependencies as system includes
 target_include_directories(vkcv SYSTEM BEFORE PRIVATE ${vkcv_includes})
-message(STATUS ${vkcv_includes})
 
 # add the own include directory for public headers
 target_include_directories(vkcv BEFORE PUBLIC ${vkcv_include})
-message(STATUS ${vkcv_include})
 
 # link the framework using all required libraries
 target_link_libraries(vkcv ${vkcv_libraries})
-message(STATUS ${vkcv_libraries})
 
-# add sub-projects/examples as targets
-add_subdirectory(projects)
+if (BUILD_PROJECTS)
+	message(STATUS "Projects: ON")
+	
+	# add sub-projects/examples as targets
+	add_subdirectory(projects)
+else()
+	message(STATUS "Projects: OFF")
+endif()
 
-if (NOT WIN32)
+if (BUILD_DOXYGEN_DOCS)
+	message(STATUS "Doxygen: ON")
+	
 	# add doxygen as target if installed
 	include(${vkcv_config}/ext/Doxygen.cmake)
+else()
+	message(STATUS "Doxygen: OFF")
+endif()
+
+if (vkcv_parent_scope)
+	list(APPEND vkcv_includes ${vkcv_include})
+	list(APPEND vkcv_libraries vkcv)
+	
+	if (BUILD_MODULES)
+		list(APPEND vkcv_includes ${vkcv_modules_includes})
+		list(APPEND vkcv_libraries ${vkcv_modules_libraries})
+	endif()
+	
+	set(vkcv_includes ${vkcv_includes} PARENT_SCOPE)
+	set(vkcv_libraries ${vkcv_libraries} PARENT_SCOPE)
 endif()
\ No newline at end of file
diff --git a/Doxyfile b/Doxyfile
index a657ede81c43b3cae1fc5c17f071aa7dc65add04..a7c5c76bb52fe4a0cd4b2f982abff642af011f8f 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -1,4 +1,4 @@
-# Doxyfile 1.9.1
+# Doxyfile 1.9.3
 
 # This file describes the settings to be used by the documentation system
 # doxygen (www.doxygen.org) for a project.
@@ -38,7 +38,7 @@ PROJECT_NAME           = "VkCV Framework"
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 0.0.1
+PROJECT_NUMBER         = 0.1.0
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
@@ -93,14 +93,6 @@ ALLOW_UNICODE_NAMES    = NO
 
 OUTPUT_LANGUAGE        = English
 
-# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all
-# documentation generated by doxygen is written. Doxygen will use this
-# information to generate all generated output in the proper direction.
-# Possible values are: None, LTR, RTL and Context.
-# The default value is: None.
-
-OUTPUT_TEXT_DIRECTION  = None
-
 # If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
 # descriptions after the members that are listed in the file and class
 # documentation (similar to Javadoc). Set to NO to disable this.
@@ -258,16 +250,16 @@ TAB_SIZE               = 4
 # the documentation. An alias has the form:
 # name=value
 # For example adding
-# "sideeffect=@par Side Effects:\n"
+# "sideeffect=@par Side Effects:^^"
 # will allow you to put the command \sideeffect (or @sideeffect) in the
 # documentation, which will result in a user-defined paragraph with heading
-# "Side Effects:". You can put \n's in the value part of an alias to insert
-# newlines (in the resulting output). You can put ^^ in the value part of an
-# alias to insert a newline as if a physical newline was in the original file.
-# When you need a literal { or } or , in the value part of an alias you have to
-# escape them by means of a backslash (\), this can lead to conflicts with the
-# commands \{ and \} for these it is advised to use the version @{ and @} or use
-# a double escape (\\{ and \\})
+# "Side Effects:". Note that you cannot put \n's in the value part of an alias
+# to insert newlines (in the resulting output). You can put ^^ in the value part
+# of an alias to insert a newline as if a physical newline was in the original
+# file. When you need a literal { or } or , in the value part of an alias you
+# have to escape them by means of a backslash (\), this can lead to conflicts
+# with the commands \{ and \} for these it is advised to use the version @{ and
+# @} or use a double escape (\\{ and \\})
 
 ALIASES                =
 
@@ -312,8 +304,8 @@ OPTIMIZE_OUTPUT_SLICE  = NO
 # extension. Doxygen has a built-in mapping, but you can override or extend it
 # using this tag. The format is ext=language, where ext is a file extension, and
 # language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
-# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL,
-# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
+# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice,
+# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
 # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
 # tries to guess whether the code is fixed or free formatted code, this is the
 # default for Fortran type files). For instance to make doxygen treat .inc files
@@ -365,13 +357,13 @@ AUTOLINK_SUPPORT       = YES
 # diagrams that involve STL classes more complete and accurate.
 # The default value is: NO.
 
-BUILTIN_STL_SUPPORT    = NO
+BUILTIN_STL_SUPPORT    = YES
 
 # If you use Microsoft's C++/CLI language, you should set this option to YES to
 # enable parsing support.
 # The default value is: NO.
 
-CPP_CLI_SUPPORT        = NO
+CPP_CLI_SUPPORT        = YES
 
 # Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
 # https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
@@ -404,7 +396,7 @@ DISTRIBUTE_GROUP_DOC   = NO
 # is disabled and one has to add nested compounds explicitly via \ingroup.
 # The default value is: NO.
 
-GROUP_NESTED_COMPOUNDS = NO
+GROUP_NESTED_COMPOUNDS = YES
 
 # Set the SUBGROUPING tag to YES to allow class member groups of the same type
 # (for instance a group of public functions) to be put as a subgroup of that
@@ -434,7 +426,7 @@ INLINE_GROUPED_CLASSES = NO
 # Man pages) or section (for LaTeX and RTF).
 # The default value is: NO.
 
-INLINE_SIMPLE_STRUCTS  = NO
+INLINE_SIMPLE_STRUCTS  = YES
 
 # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
 # enum is documented as struct, union, or enum with the name of the typedef. So
@@ -466,12 +458,12 @@ LOOKUP_CACHE_SIZE      = 0
 # than 0 to get more control over the balance between CPU load and processing
 # speed. At this moment only the input processing can be done using multiple
 # threads. Since this is still an experimental feature the default is set to 1,
-# which efficively disables parallel processing. Please report any issues you
+# which effectively disables parallel processing. Please report any issues you
 # encounter. Generating dot graphs in parallel is controlled by the
 # DOT_NUM_THREADS setting.
 # Minimum value: 0, maximum value: 32, default value: 1.
 
-NUM_PROC_THREADS       = 1
+NUM_PROC_THREADS       = 0
 
 #---------------------------------------------------------------------------
 # Build related configuration options
@@ -491,13 +483,13 @@ EXTRACT_ALL            = NO
 # be included in the documentation.
 # The default value is: NO.
 
-EXTRACT_PRIVATE        = YES
+EXTRACT_PRIVATE        = NO
 
 # If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
 # methods of a class will be included in the documentation.
 # The default value is: NO.
 
-EXTRACT_PRIV_VIRTUAL   = YES
+EXTRACT_PRIV_VIRTUAL   = NO
 
 # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
 # scope will be included in the documentation.
@@ -517,7 +509,7 @@ EXTRACT_STATIC         = YES
 # for Java sources.
 # The default value is: YES.
 
-EXTRACT_LOCAL_CLASSES  = YES
+EXTRACT_LOCAL_CLASSES  = NO
 
 # This flag is only useful for Objective-C code. If set to YES, local methods,
 # which are defined in the implementation section but not in the interface are
@@ -564,7 +556,7 @@ HIDE_UNDOC_CLASSES     = NO
 # documentation.
 # The default value is: NO.
 
-HIDE_FRIEND_COMPOUNDS  = NO
+HIDE_FRIEND_COMPOUNDS  = YES
 
 # If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
 # documentation blocks found inside the body of a function. If set to NO, these
@@ -610,6 +602,12 @@ HIDE_SCOPE_NAMES       = NO
 
 HIDE_COMPOUND_REFERENCE= NO
 
+# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class
+# will show which file needs to be included to use the class.
+# The default value is: YES.
+
+SHOW_HEADERFILE        = YES
+
 # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
 # the files that are included by a file in the documentation of that file.
 # The default value is: YES.
@@ -667,7 +665,7 @@ SORT_MEMBERS_CTORS_1ST = NO
 # appear in their defined order.
 # The default value is: NO.
 
-SORT_GROUP_NAMES       = NO
+SORT_GROUP_NAMES       = YES
 
 # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
 # fully-qualified names, including namespaces. If set to NO, the class list will
@@ -677,7 +675,7 @@ SORT_GROUP_NAMES       = NO
 # list.
 # The default value is: NO.
 
-SORT_BY_SCOPE_NAME     = NO
+SORT_BY_SCOPE_NAME     = YES
 
 # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
 # type resolution of all parameters of a function it will reject a match between
@@ -743,7 +741,7 @@ SHOW_USED_FILES        = YES
 # (if specified).
 # The default value is: YES.
 
-SHOW_FILES             = YES
+SHOW_FILES             = NO
 
 # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
 # page. This will remove the Namespaces entry from the Quick Index and from the
@@ -767,7 +765,8 @@ FILE_VERSION_FILTER    =
 # output files in an output format independent way. To create the layout file
 # that represents doxygen's defaults, run doxygen with the -l option. You can
 # optionally specify a file name after the option, if omitted DoxygenLayout.xml
-# will be used as the name of the layout file.
+# will be used as the name of the layout file. See also section "Changing the
+# layout of pages" for information.
 #
 # Note that if you run doxygen from a directory containing a file called
 # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
@@ -813,21 +812,29 @@ WARNINGS               = YES
 WARN_IF_UNDOCUMENTED   = YES
 
 # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
-# potential errors in the documentation, such as not documenting some parameters
-# in a documented function, or documenting parameters that don't exist or using
-# markup commands wrongly.
+# potential errors in the documentation, such as documenting some parameters in
+# a documented function twice, or documenting parameters that don't exist or
+# using markup commands wrongly.
 # The default value is: YES.
 
 WARN_IF_DOC_ERROR      = YES
 
+# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete
+# function parameter documentation. If set to NO, doxygen will accept that some
+# parameters have no documentation without warning.
+# The default value is: YES.
+
+WARN_IF_INCOMPLETE_DOC = YES
+
 # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
 # are documented, but have no documentation for their parameters or return
-# value. If set to NO, doxygen will only warn about wrong or incomplete
-# parameter documentation, but not about the absence of documentation. If
-# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
+# value. If set to NO, doxygen will only warn about wrong parameter
+# documentation, but not about the absence of documentation. If EXTRACT_ALL is
+# set to YES then this flag will automatically be disabled. See also
+# WARN_IF_INCOMPLETE_DOC
 # The default value is: NO.
 
-WARN_NO_PARAMDOC       = NO
+WARN_NO_PARAMDOC       = YES
 
 # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
 # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
@@ -850,7 +857,10 @@ WARN_FORMAT            = "$file:$line: $text"
 
 # The WARN_LOGFILE tag can be used to specify a file to which warning and error
 # messages should be written. If left blank the output is written to standard
-# error (stderr).
+# error (stderr). In case the file specified cannot be opened for writing the
+# warning and error messages are written to standard error. When as file - is
+# specified the warning and error messages are written to standard output
+# (stdout).
 
 WARN_LOGFILE           =
 
@@ -864,8 +874,7 @@ WARN_LOGFILE           =
 # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
 # Note: If this tag is empty the current directory is searched.
 
-INPUT                  = src \
-                         include \
+INPUT                  = include \
                          modules
 
 # This tag can be used to specify the character encoding of the source files
@@ -890,10 +899,10 @@ INPUT_ENCODING         = UTF-8
 #
 # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
 # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
-# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
-# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
-# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl,
-# *.ucf, *.qsf and *.ice.
+# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,
+# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C
+# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
+# *.vhdl, *.ucf, *.qsf and *.ice.
 
 FILE_PATTERNS          = *.c \
                          *.cc \
@@ -976,7 +985,7 @@ EXCLUDE_PATTERNS       = */lib/*
 # (namespaces, classes, functions, etc.) that should be excluded from the
 # output. The symbol name can be a fully qualified name, a word, or if the
 # wildcard * is used, a substring. Examples: ANamespace, AClass,
-# AClass::ANamespace, ANamespace::*Test
+# ANamespace::AClass, ANamespace::*Test
 #
 # Note that the wildcards are matched against the file with absolute path, so to
 # exclude all test directories use the pattern */test/*
@@ -1076,7 +1085,7 @@ USE_MDFILE_AS_MAINPAGE =
 # also VERBATIM_HEADERS is set to NO.
 # The default value is: NO.
 
-SOURCE_BROWSER         = YES
+SOURCE_BROWSER         = NO
 
 # Setting the INLINE_SOURCES tag to YES will include the body of functions,
 # classes and enums directly into the documentation.
@@ -1095,13 +1104,13 @@ STRIP_CODE_COMMENTS    = YES
 # entity all documented functions referencing it will be listed.
 # The default value is: NO.
 
-REFERENCED_BY_RELATION = YES
+REFERENCED_BY_RELATION = NO
 
 # If the REFERENCES_RELATION tag is set to YES then for each documented function
 # all documented entities called/used by that function will be listed.
 # The default value is: NO.
 
-REFERENCES_RELATION    = YES
+REFERENCES_RELATION    = NO
 
 # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
 # to YES then the hyperlinks from functions in REFERENCES_RELATION and
@@ -1261,17 +1270,17 @@ HTML_EXTRA_FILES       =
 
 # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
 # will adjust the colors in the style sheet and background images according to
-# this color. Hue is specified as an angle on a colorwheel, see
+# this color. Hue is specified as an angle on a color-wheel, see
 # https://en.wikipedia.org/wiki/Hue for more information. For instance the value
 # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
 # purple, and 360 is red again.
 # Minimum value: 0, maximum value: 359, default value: 220.
 # This tag requires that the tag GENERATE_HTML is set to YES.
 
-HTML_COLORSTYLE_HUE    = 220
+HTML_COLORSTYLE_HUE    = 0
 
 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
-# in the HTML output. For a value of 0 the output will use grayscales only. A
+# in the HTML output. For a value of 0 the output will use gray-scales only. A
 # value of 255 will produce the most vivid colors.
 # Minimum value: 0, maximum value: 255, default value: 100.
 # This tag requires that the tag GENERATE_HTML is set to YES.
@@ -1287,7 +1296,7 @@ HTML_COLORSTYLE_SAT    = 100
 # Minimum value: 40, maximum value: 240, default value: 80.
 # This tag requires that the tag GENERATE_HTML is set to YES.
 
-HTML_COLORSTYLE_GAMMA  = 80
+HTML_COLORSTYLE_GAMMA  = 220
 
 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
 # page will contain the date and time when the page was generated. Setting this
@@ -1353,6 +1362,13 @@ GENERATE_DOCSET        = NO
 
 DOCSET_FEEDNAME        = "Doxygen generated docs"
 
+# This tag determines the URL of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDURL         =
+
 # This tag specifies a string that should uniquely identify the documentation
 # set bundle. This should be a reverse domain-name style string, e.g.
 # com.mycompany.MyDocSet. Doxygen will append .docset to the name.
@@ -1378,8 +1394,12 @@ DOCSET_PUBLISHER_NAME  = Publisher
 # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
 # additional HTML index files: index.hhp, index.hhc, and index.hhk. The
 # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
-# (see:
-# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows.
+# on Windows. In the beginning of 2021 Microsoft took the original page, with
+# a.o. the download links, offline the HTML help workshop was already many years
+# in maintenance mode). You can download the HTML help workshop from the web
+# archives at Installation executable (see:
+# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo
+# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe).
 #
 # The HTML Help Workshop contains a compiler that can convert all HTML output
 # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
@@ -1538,16 +1558,28 @@ DISABLE_INDEX          = NO
 # to work a browser that supports JavaScript, DHTML, CSS and frames is required
 # (i.e. any modern browser). Windows users are probably better off using the
 # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
-# further fine-tune the look of the index. As an example, the default style
-# sheet generated by doxygen has an example that shows how to put an image at
-# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
-# the same information as the tab index, you could consider setting
-# DISABLE_INDEX to YES when enabling this option.
+# further fine tune the look of the index (see "Fine-tuning the output"). As an
+# example, the default style sheet generated by doxygen has an example that
+# shows how to put an image at the root of the tree instead of the PROJECT_NAME.
+# Since the tree basically has the same information as the tab index, you could
+# consider setting DISABLE_INDEX to YES when enabling this option.
 # The default value is: NO.
 # This tag requires that the tag GENERATE_HTML is set to YES.
 
 GENERATE_TREEVIEW      = YES
 
+# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the
+# FULL_SIDEBAR option determines if the side bar is limited to only the treeview
+# area (value NO) or if it should extend to the full height of the window (value
+# YES). Setting this to YES gives a layout similar to
+# https://docs.readthedocs.io with more room for contents, but less room for the
+# project logo, title, and description. If either GENERATE_TREEVIEW or
+# DISABLE_INDEX is set to NO, this option has no effect.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FULL_SIDEBAR           = NO
+
 # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
 # doxygen will group on one line in the generated HTML documentation.
 #
@@ -1572,6 +1604,13 @@ TREEVIEW_WIDTH         = 250
 
 EXT_LINKS_IN_WINDOW    = NO
 
+# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email
+# addresses.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+OBFUSCATE_EMAILS       = YES
+
 # If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
 # tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
 # https://inkscape.org) to generate formulas as SVG images instead of PNGs for
@@ -1620,11 +1659,29 @@ FORMULA_MACROFILE      =
 
 USE_MATHJAX            = NO
 
+# With MATHJAX_VERSION it is possible to specify the MathJax version to be used.
+# Note that the different versions of MathJax have different requirements with
+# regards to the different settings, so it is possible that also other MathJax
+# settings have to be changed when switching between the different MathJax
+# versions.
+# Possible values are: MathJax_2 and MathJax_3.
+# The default value is: MathJax_2.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_VERSION        = MathJax_2
+
 # When MathJax is enabled you can set the default output format to be used for
-# the MathJax output. See the MathJax site (see:
-# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details.
+# the MathJax output. For more details about the output format see MathJax
+# version 2 (see:
+# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
+# (see:
+# http://docs.mathjax.org/en/latest/web/components/output.html).
 # Possible values are: HTML-CSS (which is slower, but has the best
-# compatibility), NativeMML (i.e. MathML) and SVG.
+# compatibility. This is the name for Mathjax version 2, for MathJax version 3
+# this will be translated into chtml), NativeMML (i.e. MathML. Only supported
+# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This
+# is the name for Mathjax version 3, for MathJax version 2 this will be
+# translated into HTML-CSS) and SVG.
 # The default value is: HTML-CSS.
 # This tag requires that the tag USE_MATHJAX is set to YES.
 
@@ -1637,15 +1694,21 @@ MATHJAX_FORMAT         = HTML-CSS
 # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
 # Content Delivery Network so you can quickly see the result without installing
 # MathJax. However, it is strongly recommended to install a local copy of
-# MathJax from https://www.mathjax.org before deployment.
-# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2.
+# MathJax from https://www.mathjax.org before deployment. The default value is:
+# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2
+# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3
 # This tag requires that the tag USE_MATHJAX is set to YES.
 
 MATHJAX_RELPATH        = https://cdn.jsdelivr.net/npm/mathjax@2
 
 # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
 # extension names that should be enabled during MathJax rendering. For example
+# for MathJax version 2 (see
+# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
 # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# For example for MathJax version 3 (see
+# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
+# MATHJAX_EXTENSIONS = ams
 # This tag requires that the tag USE_MATHJAX is set to YES.
 
 MATHJAX_EXTENSIONS     =
@@ -1825,29 +1888,31 @@ PAPER_TYPE             = a4
 
 EXTRA_PACKAGES         =
 
-# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
-# generated LaTeX document. The header should contain everything until the first
-# chapter. If it is left blank doxygen will generate a standard header. See
-# section "Doxygen usage" for information on how to let doxygen write the
-# default header to a separate file.
+# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for
+# the generated LaTeX document. The header should contain everything until the
+# first chapter. If it is left blank doxygen will generate a standard header. It
+# is highly recommended to start with a default header using
+# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty
+# and then modify the file new_header.tex. See also section "Doxygen usage" for
+# information on how to generate the default header that doxygen normally uses.
 #
-# Note: Only use a user-defined header if you know what you are doing! The
-# following commands have a special meaning inside the header: $title,
-# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
-# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
-# string, for the replacement values of the other commands the user is referred
-# to HTML_HEADER.
+# Note: Only use a user-defined header if you know what you are doing!
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. The following
+# commands have a special meaning inside the header (and footer): For a
+# description of the possible markers and block names see the documentation.
 # This tag requires that the tag GENERATE_LATEX is set to YES.
 
 LATEX_HEADER           =
 
-# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
-# generated LaTeX document. The footer should contain everything after the last
-# chapter. If it is left blank doxygen will generate a standard footer. See
+# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for
+# the generated LaTeX document. The footer should contain everything after the
+# last chapter. If it is left blank doxygen will generate a standard footer. See
 # LATEX_HEADER for more information on how to generate a default footer and what
-# special commands can be used inside the footer.
-#
-# Note: Only use a user-defined footer if you know what you are doing!
+# special commands can be used inside the footer. See also section "Doxygen
+# usage" for information on how to generate the default footer that doxygen
+# normally uses. Note: Only use a user-defined footer if you know what you are
+# doing!
 # This tag requires that the tag GENERATE_LATEX is set to YES.
 
 LATEX_FOOTER           =
@@ -1892,8 +1957,7 @@ USE_PDFLATEX           = YES
 
 # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
 # command to the generated LaTeX files. This will instruct LaTeX to keep running
-# if errors occur, instead of asking the user for help. This option is also used
-# when generating formulas in HTML.
+# if errors occur, instead of asking the user for help.
 # The default value is: NO.
 # This tag requires that the tag GENERATE_LATEX is set to YES.
 
@@ -1906,16 +1970,6 @@ LATEX_BATCHMODE        = NO
 
 LATEX_HIDE_INDICES     = NO
 
-# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
-# code with syntax highlighting in the LaTeX output.
-#
-# Note that which sources are shown also depends on other settings such as
-# SOURCE_BROWSER.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_SOURCE_CODE      = NO
-
 # The LATEX_BIB_STYLE tag can be used to specify the style to use for the
 # bibliography, e.g. plainnat, or ieeetr. See
 # https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
@@ -1996,16 +2050,6 @@ RTF_STYLESHEET_FILE    =
 
 RTF_EXTENSIONS_FILE    =
 
-# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
-# with syntax highlighting in the RTF output.
-#
-# Note that which sources are shown also depends on other settings such as
-# SOURCE_BROWSER.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_RTF is set to YES.
-
-RTF_SOURCE_CODE        = NO
-
 #---------------------------------------------------------------------------
 # Configuration options related to the man page output
 #---------------------------------------------------------------------------
@@ -2102,15 +2146,6 @@ GENERATE_DOCBOOK       = NO
 
 DOCBOOK_OUTPUT         = docbook
 
-# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
-# program listings (including syntax highlighting and cross-referencing
-# information) to the DOCBOOK output. Note that enabling this will significantly
-# increase the size of the DOCBOOK output.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
-
-DOCBOOK_PROGRAMLISTING = NO
-
 #---------------------------------------------------------------------------
 # Configuration options for the AutoGen Definitions output
 #---------------------------------------------------------------------------
@@ -2293,15 +2328,6 @@ EXTERNAL_PAGES         = YES
 # Configuration options related to the dot tool
 #---------------------------------------------------------------------------
 
-# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
-# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
-# NO turns the diagrams off. Note that this option also works with HAVE_DOT
-# disabled, but it is recommended to install and use dot, since it yields more
-# powerful graphs.
-# The default value is: YES.
-
-CLASS_DIAGRAMS         = NO
-
 # You can include diagrams made with dia in doxygen documentation. Doxygen will
 # then run dia to produce the diagram and insert it in the documentation. The
 # DIA_PATH tag allows you to specify the directory where the dia binary resides.
@@ -2322,7 +2348,7 @@ HIDE_UNDOC_RELATIONS   = YES
 # set to NO
 # The default value is: NO.
 
-HAVE_DOT               = YES
+HAVE_DOT               = NO
 
 # The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
 # to run in parallel. When set to 0 doxygen will base this on the number of
@@ -2358,11 +2384,14 @@ DOT_FONTSIZE           = 10
 
 DOT_FONTPATH           =
 
-# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
-# each documented class showing the direct and indirect inheritance relations.
-# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a
+# graph for each documented class showing the direct and indirect inheritance
+# relations. In case HAVE_DOT is set as well dot will be used to draw the graph,
+# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set
+# to TEXT the direct and indirect inheritance relations will be shown as texts /
+# links.
+# Possible values are: NO, YES, TEXT and GRAPH.
 # The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
 
 CLASS_GRAPH            = YES
 
@@ -2461,7 +2490,7 @@ INCLUDED_BY_GRAPH      = YES
 # The default value is: NO.
 # This tag requires that the tag HAVE_DOT is set to YES.
 
-CALL_GRAPH             = YES
+CALL_GRAPH             = NO
 
 # If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
 # dependency graph for every global function or class method.
@@ -2473,7 +2502,7 @@ CALL_GRAPH             = YES
 # The default value is: NO.
 # This tag requires that the tag HAVE_DOT is set to YES.
 
-CALLER_GRAPH           = YES
+CALLER_GRAPH           = NO
 
 # If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
 # hierarchy of all classes instead of a textual one.
@@ -2491,6 +2520,13 @@ GRAPHICAL_HIERARCHY    = YES
 
 DIRECTORY_GRAPH        = YES
 
+# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels
+# of child directories generated in directory dependency graphs by dot.
+# Minimum value: 1, maximum value: 25, default value: 1.
+# This tag requires that the tag DIRECTORY_GRAPH is set to YES.
+
+DIR_GRAPH_MAX_DEPTH    = 1
+
 # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
 # generated by dot. For an explanation of the image formats see the section
 # output formats in the documentation of the dot tool (Graphviz (see:
@@ -2544,10 +2580,10 @@ MSCFILE_DIRS           =
 DIAFILE_DIRS           =
 
 # When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
-# path where java can find the plantuml.jar file. If left blank, it is assumed
-# PlantUML is not used or called during a preprocessing step. Doxygen will
-# generate a warning when it encounters a \startuml command in this case and
-# will not generate output for the diagram.
+# path where java can find the plantuml.jar file or to the filename of jar file
+# to be used. If left blank, it is assumed PlantUML is not used or called during
+# a preprocessing step. Doxygen will generate a warning when it encounters a
+# \startuml command in this case and will not generate output for the diagram.
 
 PLANTUML_JAR_PATH      =
 
@@ -2609,6 +2645,8 @@ DOT_MULTI_TARGETS      = NO
 # If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
 # explaining the meaning of the various boxes and arrows in the dot generated
 # graphs.
+# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal
+# graphical representation for inheritance and collaboration diagrams is used.
 # The default value is: YES.
 # This tag requires that the tag HAVE_DOT is set to YES.
 
@@ -2617,8 +2655,8 @@ GENERATE_LEGEND        = YES
 # If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate
 # files that are used to generate the various graphs.
 #
-# Note: This setting is not only used for dot files but also for msc and
-# plantuml temporary files.
+# Note: This setting is not only used for dot files but also for msc temporary
+# files.
 # The default value is: YES.
 
 DOT_CLEANUP            = YES
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..c1f3c5685b29d78b26dcc8c9e454b74c33f73ba5
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Universität Koblenz
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index 29c45f2660cb29d8ded6cdac14d1bf2db6b2ffc3..db60c73d3bfb25aa47d8c9808e0f2a07989724c2 100644
--- a/README.md
+++ b/README.md
@@ -5,23 +5,84 @@
 
 ## Repository
 
-Git LFS is used for bigger resource files like meshes and textures. So you need to install Git LFS and use `git lfs install` after cloning.
+Git LFS is used for bigger resource files like meshes and textures. So you need to install Git LFS 
+and use `git lfs install` after cloning.
 
 More information about Git LFS [here](https://git-lfs.github.com/).
 
 ## Build
 
- [![pipeline status](https://gitlab.uni-koblenz.de/vulkan2021/vkcv-framework/badges/develop/pipeline.svg)](https://gitlab.uni-koblenz.de/vulkan2021/vkcv-framework/-/commits/develop)
+Git submodules are used for libraries. To download the submodules either clone using 
+`git clone --recurse-submodules` or after `git clone` use `git submodule init` and 
+`git submodule update`.
 
-Git submodules are used for libraries. 
-To download the submodules either clone using `git clone --recurse-submodules` or after `git clone` use `git submodule init` and `git submodule update`.
+Detailed build process:
+ - [How to build on Windows](doc/BUILD_WINDOWS.md)
+ - [How to build on macOS](doc/BUILD_MACOS.md)
+ - [How to build on Linux](doc/BUILD_LINUX.md)
 
-## Documentation
+### Dependencies (required):
+
+Most dependencies are used via submodules but for example Vulkan needs to be installed correctly 
+depending on your platform. So please setup your environment properly.
+
+| Name of dependency                                                                | Used as submodule |
+|-----------------------------------------------------------------------------------|---|
+| [Vulkan](https://www.vulkan.org/)                                                 | ❌ |
+| [Vulkan-Headers](https://github.com/KhronosGroup/Vulkan-Headers)                  | ✅ |
+| [Vulkan-Hpp](https://github.com/KhronosGroup/Vulkan-Hpp)                          | ✅ |
+| [GLFW](https://www.glfw.org/)                                                     | ✅ |
+| [SPIRV-CROSS](https://github.com/KhronosGroup/SPIRV-Cross)                        | ✅ |
+| [VulkanMemoryAllocator-Hpp](https://github.com/malte-v/VulkanMemoryAllocator-Hpp) | ✅ |
+
+### Modules (optional):
+
+The following modules are provided in this repository and they build automatically together with 
+the framework if used. You can configure/adjust the build using CMake if necessary.
+
+ - [Algorithm](modules/algorithm/README.md)
+ - [Asset-Loader](modules/asset_loader/README.md)
+ - [Camera](modules/asset_loader/README.md)
+ - [GUI](modules/gui/README.md)
+ - [Effects](modules/effects/README.md)
+ - [Geometry](modules/geometry/README.md)
+ - [Material](modules/material/README.md)
+ - [Meshlet](modules/meshlet/README.md)
+ - [Scene](modules/scene/README.md)
+ - [Shader-Compiler](modules/shader_compiler/README.md)
+ - [Upscaling](modules/upscaling/README.md)
 
-The documentation for the develop-branch can be found here:  
-https://vkcv.de/develop/  
+### Projects (optional):
+
+The following projects are provided in this repository and can be build with their own CMake 
+targets:
+
+ - [bindless_textures](projects/bindless_textures/README.md)
+ - [fire_works](projects/fire_works/README.md)
+ - [first_mesh](projects/first_mesh/README.md)
+ - [first_scene](projects/first_scene/README.md)
+ - [first_triangle](projects/first_triangle/README.md)
+ - [head_demo](projects/head_demo/README.md)
+ - [indirect_dispatch](projects/indirect_dispatch/README.md)
+ - [indirect_draw](projects/indirect_draw/README.md)
+ - [mesh_shader](projects/mesh_shader/README.md)
+ - [mpm](projects/mpm/README.md)
+ - [particle_simulation](projects/particle_simulation/README.md)
+ - [path_tracer](projects/path_tracer/README.md)
+ - [ray_tracer](projects/ray_tracer/README.md)
+ - [rtx_ambient_occlusion](projects/rtx_ambient_occlusion/README.md)
+ - [sph](projects/sph/README.md)
+ - [voxelization](projects/voxelization/README.md)
+
+## Development
+
+See this guide to setup your IDE for most forward development.
+ - [How to setup your IDE](doc/SETUP_IDE.md)
+
+## Documentation
 
-The documentation concerning the respective merge request is listed here:  
-https://vkcv.de/branch/  
+A pre-built documentation can be found here:  
+https://userpages.uni-koblenz.de/~vkcv/doc/
 
-It is automatically generated and uploaded using the CI pipeline.
+But it is recommended to build the documentation with Doxygen locally to get the most recent 
+changes. There is also an optional CMake target to build the documentation via Doxygen.
diff --git a/config/Libraries.cmake b/config/Libraries.cmake
index ec014f84c820abf4988b070d5b733be08c377319..f78ca1f5df4ef40683b7948473dbc6a506f67cd1 100644
--- a/config/Libraries.cmake
+++ b/config/Libraries.cmake
@@ -3,13 +3,24 @@ set(vkcv_config_lib ${vkcv_config}/lib)
 set(vkcv_lib_path ${PROJECT_SOURCE_DIR}/${vkcv_lib})
 
 if(NOT WIN32)
-	set(vkcv_libraries  stdc++fs)
+	if (((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.1.0")) OR
+		((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.0.0")))
+		set(vkcv_libraries stdc++fs)
+	endif()
+	
+	if (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
+		list(APPEND vkcv_flags -Xpreprocessor)
+	else()
+		# optimization for loading times
+		list(APPEND vkcv_flags -fopenmp)
+	endif()
 	
-	# optimization for loading times
 	list(APPEND vkcv_flags -pthread)
-	list(APPEND vkcv_flags -fopenmp)
 endif()
 
+# add custom functions to check for git submodules
+include(${vkcv_config_ext}/Git.cmake)
+
 list(APPEND vkcv_definitions _USE_MATH_DEFINES)
 
 # some formatted printing
@@ -19,6 +30,7 @@ set(vkcv_config_msg " - Library: ")
 include(${vkcv_config_lib}/GLFW.cmake)    # glfw-x11 / glfw-wayland					# libglfw3-dev
 include(${vkcv_config_lib}/Vulkan.cmake)  # vulkan-intel / vulkan-radeon / nvidia	# libvulkan-dev
 include(${vkcv_config_lib}/SPIRV_Cross.cmake)  # SPIRV-Cross	                    # libspirv_cross_c_shared
+include(${vkcv_config_lib}/VulkanMemoryAllocator.cmake) # VulkanMemoryAllocator
 
 # cleanup of compiler flags
 if (vkcv_flags)
@@ -33,6 +45,9 @@ endif ()
 # fix dependencies for different Linux distros (looking at you Ubuntu)
 include(${vkcv_config_ext}/CheckLibraries.cmake)
 
+# add custom function to include a file like a shader as string
+include(${vkcv_config_ext}/IncludeShader.cmake)
+
 # cleanup of compiler definitions aka preprocessor variables
 if (vkcv_definitions)
     list(REMOVE_DUPLICATES vkcv_definitions)
diff --git a/config/Sources.cmake b/config/Sources.cmake
index 4397e4978eb022d267571d185a1f122d053a5ea1..6cf9c6f4663c4307ee6c9350941cbe6e2f2b5a2c 100644
--- a/config/Sources.cmake
+++ b/config/Sources.cmake
@@ -1,11 +1,23 @@
 
 # adding all source files and header files of the framework:
 set(vkcv_sources
+		${vkcv_include}/vkcv/Features.hpp
+		${vkcv_source}/vkcv/Features.cpp
+		
+		${vkcv_include}/vkcv/FeatureManager.hpp
+		${vkcv_source}/vkcv/FeatureManager.cpp
+		
 		${vkcv_include}/vkcv/Context.hpp
 		${vkcv_source}/vkcv/Context.cpp
 
 		${vkcv_include}/vkcv/Core.hpp
 		${vkcv_source}/vkcv/Core.cpp
+		
+		${vkcv_include}/vkcv/File.hpp
+		${vkcv_source}/vkcv/File.cpp
+		
+		${vkcv_include}/vkcv/Pass.hpp
+		${vkcv_source}/vkcv/Pass.cpp
 
 		${vkcv_include}/vkcv/PassConfig.hpp
 		${vkcv_source}/vkcv/PassConfig.cpp
@@ -15,14 +27,23 @@ set(vkcv_sources
 
 		${vkcv_include}/vkcv/Handles.hpp
 		${vkcv_source}/vkcv/Handles.cpp
+		
+		${vkcv_source}/vkcv/HandleManager.hpp
 
 		${vkcv_include}/vkcv/Window.hpp
 		${vkcv_source}/vkcv/Window.cpp
-
+		
+		${vkcv_include}/vkcv/BufferTypes.hpp
 		${vkcv_include}/vkcv/Buffer.hpp
 		
-		${vkcv_include}/vkcv/BufferManager.hpp
+		${vkcv_include}/vkcv/PushConstants.hpp
+		${vkcv_source}/vkcv/PushConstants.cpp
+		
+		${vkcv_source}/vkcv/BufferManager.hpp
 		${vkcv_source}/vkcv/BufferManager.cpp
+		
+		${vkcv_include}/vkcv/ImageConfig.hpp
+		${vkcv_source}/vkcv/ImageConfig.cpp
 
 		${vkcv_include}/vkcv/Image.hpp
 		${vkcv_source}/vkcv/Image.cpp
@@ -30,54 +51,100 @@ set(vkcv_sources
 		${vkcv_source}/vkcv/ImageManager.hpp
 		${vkcv_source}/vkcv/ImageManager.cpp
 		
+		${vkcv_include}/vkcv/PipelineConfig.hpp
+		${vkcv_source}/vkcv/PipelineConfig.cpp
+		
 		${vkcv_include}/vkcv/Logger.hpp
-
-		${vkcv_include}/vkcv/Swapchain.hpp
-		${vkcv_source}/vkcv/Swapchain.cpp
 		
 		${vkcv_include}/vkcv/ShaderStage.hpp
 		
 		${vkcv_include}/vkcv/ShaderProgram.hpp
 		${vkcv_source}/vkcv/ShaderProgram.cpp
 
-		${vkcv_include}/vkcv/PipelineConfig.hpp
+		${vkcv_include}/vkcv/GraphicsPipelineConfig.hpp
+		${vkcv_source}/vkcv/GraphicsPipelineConfig.cpp
+		
+		${vkcv_include}/vkcv/ComputePipelineConfig.hpp
 
-		${vkcv_source}/vkcv/PipelineManager.hpp
-		${vkcv_source}/vkcv/PipelineManager.cpp
-        
-        ${vkcv_include}/vkcv/CommandResources.hpp
-        ${vkcv_source}/vkcv/CommandResources.cpp
-        
-        ${vkcv_include}/vkcv/SyncResources.hpp
-        ${vkcv_source}/vkcv/SyncResources.cpp
+		${vkcv_source}/vkcv/ComputePipelineManager.hpp
+		${vkcv_source}/vkcv/ComputePipelineManager.cpp
+
+		${vkcv_source}/vkcv/GraphicsPipelineManager.hpp
+		${vkcv_source}/vkcv/GraphicsPipelineManager.cpp
         
         ${vkcv_include}/vkcv/QueueManager.hpp
         ${vkcv_source}/vkcv/QueueManager.cpp
 
-        ${vkcv_source}/vkcv/ImageLayoutTransitions.hpp
-        ${vkcv_source}/vkcv/ImageLayoutTransitions.cpp
-
 		${vkcv_include}/vkcv/VertexLayout.hpp
 		${vkcv_source}/vkcv/VertexLayout.cpp
+		
+		${vkcv_include}/vkcv/DispatchSize.hpp
+		${vkcv_source}/vkcv/DispatchSize.cpp
 
 		${vkcv_include}/vkcv/Event.hpp
+		
+		${vkcv_include}/vkcv/TypeGuard.hpp
+		${vkcv_source}/vkcv/TypeGuard.cpp
+		
+		${vkcv_include}/vkcv/DescriptorTypes.hpp
 
-		${vkcv_source}/vkcv/DescriptorManager.hpp
-		${vkcv_source}/vkcv/DescriptorManager.cpp
-
-		${vkcv_include}/vkcv/DescriptorConfig.hpp
-		${vkcv_source}/vkcv/DescriptorConfig.cpp
+		${vkcv_include}/vkcv/DescriptorBinding.hpp
+		${vkcv_source}/vkcv/DescriptorBinding.cpp
+		
+		${vkcv_include}/vkcv/DescriptorWrites.hpp
+		${vkcv_source}/vkcv/DescriptorWrites.cpp
+		
+		${vkcv_source}/vkcv/DescriptorSetLayoutManager.hpp
+		${vkcv_source}/vkcv/DescriptorSetLayoutManager.cpp
+		
+		${vkcv_source}/vkcv/DescriptorSetManager.hpp
+		${vkcv_source}/vkcv/DescriptorSetManager.cpp
 		
 		${vkcv_source}/vkcv/SamplerManager.hpp
 		${vkcv_source}/vkcv/SamplerManager.cpp
+
+		${vkcv_source}/vkcv/WindowManager.hpp
+		${vkcv_source}/vkcv/WindowManager.cpp
+
+		${vkcv_source}/vkcv/SwapchainManager.hpp
+		${vkcv_source}/vkcv/SwapchainManager.cpp
         
-        ${vkcv_include}/vkcv/DescriptorWrites.hpp
-        
-        ${vkcv_include}/vkcv/DrawcallRecording.hpp
-        ${vkcv_source}/vkcv/DrawcallRecording.cpp
-        
-        ${vkcv_include}/vkcv/CommandStreamManager.hpp
+        ${vkcv_source}/vkcv/CommandStreamManager.hpp
         ${vkcv_source}/vkcv/CommandStreamManager.cpp
         
-        ${vkcv_include}/vkcv/CommandRecordingFunctionTypes.hpp
+        ${vkcv_include}/vkcv/EventFunctionTypes.hpp
+        
+        ${vkcv_include}/vkcv/Multisampling.hpp
+        ${vkcv_source}/vkcv/Multisampling.cpp
+		
+		${vkcv_include}/vkcv/Downsampler.hpp
+		${vkcv_source}/vkcv/Downsampler.cpp
+		
+		${vkcv_include}/vkcv/BlitDownsampler.hpp
+		${vkcv_source}/vkcv/BlitDownsampler.cpp
+		
+		${vkcv_include}/vkcv/SamplerTypes.hpp
+		
+		${vkcv_include}/vkcv/Sampler.hpp
+		${vkcv_source}/vkcv/Sampler.cpp
+		
+		${vkcv_include}/vkcv/DescriptorSetUsage.hpp
+		${vkcv_source}/vkcv/DescriptorSetUsage.cpp
+		
+		${vkcv_include}/vkcv/Drawcall.hpp
+		${vkcv_source}/vkcv/Drawcall.cpp
+		
+		${vkcv_include}/vkcv/VertexData.hpp
+		${vkcv_source}/vkcv/VertexData.cpp
+		
+		${vkcv_include}/vkcv/Result.hpp
 )
+
+if (BUILD_CLANG_FORMAT)
+	message(STATUS "Clang-Format: ON")
+	
+	# add clang-format as target if installed
+	include(${vkcv_config}/ext/ClangFormat.cmake)
+else()
+	message(STATUS "Clang-Format: OFF")
+endif()
diff --git a/config/ext/ClangFormat.cmake b/config/ext/ClangFormat.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..723fd7a9f52d19d2d1c3b0279eee243fa11afe91
--- /dev/null
+++ b/config/ext/ClangFormat.cmake
@@ -0,0 +1,11 @@
+
+if (EXISTS "/usr/bin/clang-format")
+	# note the option ALL which allows to format the source together with the application
+	add_custom_target( clang_format ALL
+			COMMAND /usr/bin/clang-format -style=file --sort-includes -i ${vkcv_sources}
+			WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+			COMMENT "Formatting code with Clang-Format"
+			VERBATIM )
+else ()
+	message(WARNING "Doxygen need to be installed to generate the doxygen documentation")
+endif ()
diff --git a/config/ext/Doxygen.cmake b/config/ext/Doxygen.cmake
index b711ac4b24777642fc3202be8909ac405b84d92b..c5f6d7f88cbdb9ef7c119c4b5dd6ee7de1199bc9 100644
--- a/config/ext/Doxygen.cmake
+++ b/config/ext/Doxygen.cmake
@@ -1,6 +1,6 @@
 
 # check if Doxygen is installed
-find_package(Doxygen)
+find_package(Doxygen QUIET)
 
 if (DOXYGEN_FOUND)
 	# note the option ALL which allows to build the docs together with the application
diff --git a/config/ext/Git.cmake b/config/ext/Git.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..a943ce02ab3a170a67fae7394fa6f616239ab5b4
--- /dev/null
+++ b/config/ext/Git.cmake
@@ -0,0 +1,15 @@
+
+function(use_git_submodule submodule_path submodule_status)
+    file(GLOB path_glob "${submodule_path}/*")
+    list(LENGTH path_glob glob_len)
+
+    if(glob_len GREATER 0)
+        set(${submodule_status} TRUE PARENT_SCOPE)
+        return()
+    endif()
+
+    get_filename_component(submodule_name ${submodule_path} NAME)
+
+    message(WARNING "${submodule_name} is required..! Update the submodules!")
+    set(${submodule_status} FALSE PARENT_SCOPE)
+endfunction()
diff --git a/config/ext/IncludeShader.cmake b/config/ext/IncludeShader.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..e67a8716fb32a953c93a3c6624f0d459a025e950
--- /dev/null
+++ b/config/ext/IncludeShader.cmake
@@ -0,0 +1,75 @@
+
+function(include_shader shader include_dir source_dir)
+	if (NOT EXISTS ${shader})
+		message(WARNING "Shader file does not exist: ${shader}")
+	else()
+		get_filename_component(filename ${shader} NAME)
+		file(SIZE ${shader} filesize)
+		
+		set(include_target_file ${include_dir}/${filename}.hxx)
+		set(source_target_file ${source_dir}/${filename}.cxx)
+		
+		if ((EXISTS ${source_target_file}) AND (EXISTS ${include_target_file}))
+			file(TIMESTAMP ${shader} shader_timestamp "%Y-%m-%dT%H:%M:%S")
+			file(TIMESTAMP ${source_target_file} source_timestamp "%Y-%m-%dT%H:%M:%S")
+			
+			string(COMPARE GREATER ${shader_timestamp} ${source_timestamp} shader_update)
+		else()
+			set(shader_update true)
+		endif()
+		
+		if (shader_update)
+			string(TOUPPER ${filename} varname)
+			string(REPLACE "." "_" varname ${varname})
+			
+			set(shader_header "#pragma once\n")
+			string(APPEND shader_header "// This file is auto-generated via cmake, so don't touch it!\n")
+			string(APPEND shader_header "extern unsigned char ${varname} [${filesize}]\;\n")
+			string(APPEND shader_header "extern unsigned int ${varname}_LEN\;\n")
+			string(APPEND shader_header "const std::string ${varname}_SHADER (reinterpret_cast<const char*>(${varname}), ${varname}_LEN)\;")
+			
+			file(WRITE ${include_target_file} ${shader_header})
+			
+			find_program(xxd_program "xxd")
+			
+			if (EXISTS ${xxd_program})
+				get_filename_component(shader_directory ${shader} DIRECTORY)
+				
+				add_custom_command(
+						OUTPUT ${source_target_file}
+						WORKING_DIRECTORY "${shader_directory}"
+						COMMAND xxd -i -C "${filename}" "${source_target_file}"
+						COMMENT "Processing shader into source files: ${shader}"
+				)
+			else()
+				set(shader_source "// This file is auto-generated via cmake, so don't touch it!\n")
+				string(APPEND shader_source "unsigned char ${varname}[] = {")
+				
+				math(EXPR max_fileoffset "${filesize} - 1" OUTPUT_FORMAT DECIMAL)
+				
+				message(STATUS "Processing shader into source files: ${shader}")
+				
+				foreach(fileoffset RANGE ${max_fileoffset})
+					file(READ ${shader} shader_source_byte OFFSET ${fileoffset} LIMIT 1 HEX)
+					
+					math(EXPR offset_modulo "${fileoffset} % 12" OUTPUT_FORMAT DECIMAL)
+					
+					if (${offset_modulo} EQUAL 0)
+						string(APPEND shader_source "\n  ")
+					endif()
+					
+					if (${fileoffset} LESS ${max_fileoffset})
+						string(APPEND shader_source "0x${shader_source_byte}, ")
+					else()
+						string(APPEND shader_source "0x${shader_source_byte}\n")
+					endif()
+				endforeach()
+				
+				string(APPEND shader_source "}\;\n")
+				string(APPEND shader_source "unsigned int ${varname}_LEN = ${filesize}\;")
+				
+				file(WRITE ${source_target_file} ${shader_source})
+			endif()
+		endif()
+	endif()
+endfunction()
diff --git a/config/ext/Project.cmake b/config/ext/Project.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..6b27862bc5eab401c3d98b061fdfca7f9d1bce3b
--- /dev/null
+++ b/config/ext/Project.cmake
@@ -0,0 +1,19 @@
+
+# the first argument should be the project's target
+macro(add_project)
+    # this should fix the execution path to load local files from the project
+    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
+    
+    # this will create an executable for the project
+    add_executable(${ARGN})
+    
+    # this should fix the execution path to load local files from the project (for MSVC)
+    if(MSVC)
+        set_target_properties(${ARGV0} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+        set_target_properties(${ARGV0} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+
+        # in addition to setting the output directory, the working directory has to be set
+        # by default visual studio sets the working directory to the build directory, when using the debugger
+        set_target_properties(${ARGV0} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+    endif()
+endmacro()
diff --git a/config/lib/GLFW.cmake b/config/lib/GLFW.cmake
index 1b68d8aa97ba59158a7bd805ab2470f554f705aa..977eabc8c3916da694420eb8b069e058b94c15f2 100644
--- a/config/lib/GLFW.cmake
+++ b/config/lib/GLFW.cmake
@@ -6,13 +6,14 @@ if (glfw3_FOUND)
 
     message(${vkcv_config_msg} " GLFW    -   " ${glfw3_VERSION})
 else()
-    if (EXISTS "${vkcv_lib_path}/glfw")
+    use_git_submodule("${vkcv_lib_path}/glfw" glfw_status)
+
+    if (${glfw_status})
         add_subdirectory(${vkcv_lib}/glfw)
 
         list(APPEND vkcv_libraries glfw)
+        list(APPEND vkcv_includes ${vkcv_lib_path}/glfw/include)
 
         message(${vkcv_config_msg} " GLFW    -   " ${glfw3_VERSION})
-    else()
-        message(WARNING "GLFW is required..! Update the submodules!")
     endif ()
 endif ()
diff --git a/config/lib/SPIRV_Cross.cmake b/config/lib/SPIRV_Cross.cmake
index 2e705d7d5a006e3851d14d22a57fd667c61c79f5..c6304606a14a30f8de7dba1fe2e479ed63743e1e 100644
--- a/config/lib/SPIRV_Cross.cmake
+++ b/config/lib/SPIRV_Cross.cmake
@@ -1,3 +1,4 @@
+
 find_package(spirv_cross_c_shared QUIET)
 
 if (spirv-cross_FOUND)
@@ -5,7 +6,9 @@ if (spirv-cross_FOUND)
 
     message(${vkcv_config_msg} " SPIRV Cross    - " ${SPIRV_CROSS_VERSION})
 else()
-    if (EXISTS "${vkcv_lib_path}/SPIRV-Cross")
+    use_git_submodule("${vkcv_lib_path}/SPIRV-Cross" spirv_cross_status)
+
+    if (${spirv_cross_status})
         set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS OFF CACHE INTERNAL "")
         set(SPIRV_CROSS_SHARED OFF CACHE INTERNAL "")
         set(SPIRV_CROSS_STATIC ON CACHE INTERNAL "")
@@ -25,9 +28,8 @@ else()
         add_subdirectory(${vkcv_lib}/SPIRV-Cross)
 
         list(APPEND vkcv_libraries spirv-cross-cpp)
+        list(APPEND vkcv_includes ${vkcv_lib_path}/SPIV-Cross/include)
 
         message(${vkcv_config_msg} " SPIRV Cross    - " ${SPIRV_CROSS_VERSION})
-    else()
-        message(WARNING "SPIRV-Cross is required..! Update the submodules!")
     endif ()
-endif ()
\ No newline at end of file
+endif ()
diff --git a/config/lib/Vulkan.cmake b/config/lib/Vulkan.cmake
index e8fe4aee3a949fa7bf9906558f8a0c558eb47cf2..11e254a67349488b48718bd48ac69ead641fb167 100644
--- a/config/lib/Vulkan.cmake
+++ b/config/lib/Vulkan.cmake
@@ -2,8 +2,19 @@
 find_package(Vulkan REQUIRED)
 
 if (Vulkan_FOUND)
-    list(APPEND vkcv_includes ${Vulkan_INCLUDE_DIR})
+    use_git_submodule("${vkcv_lib_path}/Vulkan-Headers" vulkan_headers_status)
+    
+    if (${vulkan_headers_status})
+        list(APPEND vkcv_includes ${vkcv_lib_path}/Vulkan-Headers/include)
+    endif()
+
+    use_git_submodule("${vkcv_lib_path}/Vulkan-Hpp" vulkan_hpp_status)
+
+    if (${vulkan_hpp_status})
+        list(APPEND vkcv_includes ${vkcv_lib_path}/Vulkan-Hpp)
+    endif()
+    
     list(APPEND vkcv_libraries ${Vulkan_LIBRARIES})
 
-    message(${vkcv_config_msg} " Vulkan  -   ")
+    message(${vkcv_config_msg} " Vulkan  -   " ${Vulkan_VERSION})
 endif ()
diff --git a/config/lib/VulkanMemoryAllocator.cmake b/config/lib/VulkanMemoryAllocator.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..fa69b0fd7825c6511592123162f4b5570890e1da
--- /dev/null
+++ b/config/lib/VulkanMemoryAllocator.cmake
@@ -0,0 +1,70 @@
+
+find_package(Vulkan QUIET)
+
+if (Vulkan_VERSION)
+	set(BUILD_VMA_VULKAN_VERSION ${Vulkan_VERSION})
+endif()
+
+set(VMA_VULKAN_VERSION OFF CACHE INTERNAL "")
+
+if (BUILD_VMA_VULKAN_VERSION)
+	string(REGEX REPLACE "(\\.[0-9]+)+$" "" VMA_VULKAN_MAJOR_VERSION ${BUILD_VMA_VULKAN_VERSION})
+	string(REGEX REPLACE "^[0-9]+\\.([0-9]+)(\\.[0-9]+)?" "\\1" VMA_VULKAN_MINOR_VERSION ${BUILD_VMA_VULKAN_VERSION})
+	string(REGEX REPLACE "^([0-9]+\\.)+" "" VMA_VULKAN_PATCH_VERSION ${BUILD_VMA_VULKAN_VERSION})
+	
+	math(EXPR VMA_VULKAN_VERSION "1000000 * ${VMA_VULKAN_MAJOR_VERSION} + 1000 * ${VMA_VULKAN_MINOR_VERSION} + ${VMA_VULKAN_PATCH_VERSION}" OUTPUT_FORMAT DECIMAL)
+endif()
+
+use_git_submodule("${vkcv_lib_path}/VulkanMemoryAllocator" vma_status)
+
+if (${vma_status})
+	if (EXISTS "${vkcv_lib_path}/VulkanMemoryAllocator/include")
+		set(VMA_H_PATH "${vkcv_lib_path}/VulkanMemoryAllocator/include" CACHE INTERNAL "")
+	else()
+		set(VMA_H_PATH "${vkcv_lib_path}/VulkanMemoryAllocator" CACHE INTERNAL "")
+	endif()
+
+	list(APPEND vkcv_includes ${VMA_H_PATH})
+endif()
+
+use_git_submodule("${vkcv_lib_path}/VulkanMemoryAllocator-Hpp" vma_hpp_status)
+
+if (${vma_hpp_status})
+	if (EXISTS "${vkcv_lib_path}/VulkanMemoryAllocator-Hpp/include")
+		set(VMA_HPP_PATH "${vkcv_lib_path}/VulkanMemoryAllocator-Hpp/include" CACHE INTERNAL "")
+	else()
+		set(VMA_HPP_PATH "${vkcv_lib_path}/VulkanMemoryAllocator-Hpp" CACHE INTERNAL "")
+	endif()
+	
+	set(VMA_VULKAN_H_PATH "${vkcv_lib_path}/Vulkan-Headers/include" CACHE INTERNAL "")
+	set(VMA_VULKAN_HPP_PATH "${vkcv_lib_path}/Vulkan-Hpp" CACHE INTERNAL "")
+	
+	set(VMA_RECORDING_ENABLED OFF CACHE INTERNAL "")
+	set(VMA_USE_STL_CONTAINERS OFF CACHE INTERNAL "")
+	
+	string(COMPARE EQUAL "${vkcv_build_attribute}" "SHARED" VMA_USE_DYNAMIC_LINKING)
+	
+	if (${VMA_USE_DYNAMIC_LINKING})
+		set(VMA_STATIC_VULKAN_FUNCTIONS OFF CACHE INTERNAL "")
+		set(VMA_DYNAMIC_VULKAN_FUNCTIONS ON CACHE INTERNAL "")
+	else()
+		set(VMA_STATIC_VULKAN_FUNCTIONS ON CACHE INTERNAL "")
+		set(VMA_DYNAMIC_VULKAN_FUNCTIONS OFF CACHE INTERNAL "")
+	endif()
+	
+	set(VMA_DEBUG_ALWAYS_DEDICATED_MEMORY OFF CACHE INTERNAL "")
+	set(VMA_DEBUG_INITIALIZE_ALLOCATIONS OFF CACHE INTERNAL "")
+	set(VMA_DEBUG_GLOBAL_MUTEX OFF CACHE INTERNAL "")
+	set(VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT OFF CACHE INTERNAL "")
+	
+	add_subdirectory(${vkcv_config_lib}/vma)
+	
+	list(APPEND vkcv_libraries VulkanMemoryAllocator)
+	list(APPEND vkcv_includes ${VMA_HPP_PATH})
+	
+	if (VMA_VULKAN_VERSION)
+		list(APPEND vkcv_definitions "VMA_VULKAN_VERSION=${VMA_VULKAN_VERSION}")
+	endif()
+	
+	message(${vkcv_config_msg} " VMA     -   " ${BUILD_VMA_VULKAN_VERSION})
+endif ()
diff --git a/config/lib/vma/CMakeLists.txt b/config/lib/vma/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..42126320aae984dd9040f302ba3d81cb1a90ac4c
--- /dev/null
+++ b/config/lib/vma/CMakeLists.txt
@@ -0,0 +1,73 @@
+cmake_minimum_required(VERSION 3.9)
+
+project(VulkanMemoryAllocator)
+
+# settings c++ standard for the vma
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+find_package(Vulkan REQUIRED)
+
+option(VMA_VULKAN_VERSION "Enforced Vulkan version" OFF)
+option(VMA_H_PATH "Location of C headers" "")
+option(VMA_HPP_PATH "Location of C++ headers" "")
+
+option(VMA_VULKAN_H_PATH "Location of Vulkan C headers" "")
+option(VMA_VULKAN_HPP_PATH "Location of Vulkan C++ headers" "")
+
+message(STATUS "VMA_BUILD_SAMPLE = ${VMA_BUILD_SAMPLE}")
+message(STATUS "VMA_BUILD_SAMPLE_SHADERS = ${VMA_BUILD_SAMPLE_SHADERS}")
+message(STATUS "VMA_BUILD_REPLAY = ${VMA_BUILD_REPLAY}")
+
+option(VMA_RECORDING_ENABLED "Enable VMA memory recording for debugging" OFF)
+option(VMA_USE_STL_CONTAINERS "Use C++ STL containers instead of VMA's containers" OFF)
+option(VMA_STATIC_VULKAN_FUNCTIONS "Link statically with Vulkan API" OFF)
+option(VMA_DYNAMIC_VULKAN_FUNCTIONS "Fetch pointers to Vulkan functions internally (no static linking)" ON)
+option(VMA_DEBUG_ALWAYS_DEDICATED_MEMORY "Every allocation will have its own memory block" OFF)
+option(VMA_DEBUG_INITIALIZE_ALLOCATIONS "Automatically fill new allocations and destroyed allocations with some bit pattern" OFF)
+option(VMA_DEBUG_GLOBAL_MUTEX "Enable single mutex protecting all entry calls to the library" OFF)
+option(VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT "Never exceed VkPhysicalDeviceLimits::maxMemoryAllocationCount and return error" OFF)
+
+message(STATUS "VMA_RECORDING_ENABLED = ${VMA_RECORDING_ENABLED}")
+message(STATUS "VMA_USE_STL_CONTAINERS = ${VMA_USE_STL_CONTAINERS}")
+message(STATUS "VMA_DYNAMIC_VULKAN_FUNCTIONS = ${VMA_DYNAMIC_VULKAN_FUNCTIONS}")
+message(STATUS "VMA_DEBUG_ALWAYS_DEDICATED_MEMORY = ${VMA_DEBUG_ALWAYS_DEDICATED_MEMORY}")
+message(STATUS "VMA_DEBUG_INITIALIZE_ALLOCATIONS = ${VMA_DEBUG_INITIALIZE_ALLOCATIONS}")
+message(STATUS "VMA_DEBUG_GLOBAL_MUTEX = ${VMA_DEBUG_GLOBAL_MUTEX}")
+message(STATUS "VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT = ${VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT}")
+
+if (${VMA_STATIC_VULKAN_FUNCTIONS})
+	add_library(VulkanMemoryAllocator STATIC vma.cpp)
+else()
+	add_library(VulkanMemoryAllocator SHARED vma.cpp)
+endif()
+
+set_target_properties(
+		VulkanMemoryAllocator PROPERTIES
+		
+		CXX_EXTENSIONS OFF
+		CXX_STANDARD ${CMAKE_CXX_STANDARD}
+		CXX_STANDARD_REQUIRED ON
+)
+
+target_include_directories(VulkanMemoryAllocator SYSTEM BEFORE PRIVATE ${VMA_VULKAN_H_PATH} ${VMA_VULKAN_HPP_PATH} ${VMA_H_PATH})
+target_include_directories(VulkanMemoryAllocator PUBLIC ${VMA_HPP_PATH})
+
+# Only link to Vulkan if static linking is used
+if (NOT ${VMA_DYNAMIC_VULKAN_FUNCTIONS})
+	target_link_libraries(VulkanMemoryAllocator PUBLIC Vulkan::Vulkan)
+endif()
+
+target_compile_definitions(
+		VulkanMemoryAllocator
+		
+		PUBLIC
+		VMA_USE_STL_CONTAINERS=$<BOOL:${VMA_USE_STL_CONTAINERS}>
+		VMA_DYNAMIC_VULKAN_FUNCTIONS=$<BOOL:${VMA_DYNAMIC_VULKAN_FUNCTIONS}>
+		VMA_DEBUG_ALWAYS_DEDICATED_MEMORY=$<BOOL:${VMA_DEBUG_ALWAYS_DEDICATED_MEMORY}>
+		VMA_DEBUG_INITIALIZE_ALLOCATIONS=$<BOOL:${VMA_DEBUG_INITIALIZE_ALLOCATIONS}>
+		VMA_DEBUG_GLOBAL_MUTEX=$<BOOL:${VMA_DEBUG_GLOBAL_MUTEX}>
+		VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT=$<BOOL:${VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT}>
+		VMA_RECORDING_ENABLED=$<BOOL:${VMA_RECORDING_ENABLED}>
+		VMA_VULKAN_VERSION=${VMA_VULKAN_VERSION}
+)
diff --git a/config/lib/vma/vma.cpp b/config/lib/vma/vma.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1fc40eb10123b1c89f6c348f4c2e898df21b43b0
--- /dev/null
+++ b/config/lib/vma/vma.cpp
@@ -0,0 +1,60 @@
+
+#ifndef NDEBUG
+#ifndef _DEBUG
+#define _DEBUG
+#endif
+#endif
+
+#ifndef _MSVC_LANG
+#ifdef __MINGW32__
+#include <stdint.h>
+#include <stdlib.h>
+
+class VmaMutex {
+public:
+	VmaMutex() : m_locked(false) {}
+	
+	void Lock() {
+		while (m_locked);
+		m_locked = true;
+	}
+	
+	void Unlock() {
+		m_locked = false;
+	}
+private:
+	bool m_locked;
+};
+
+#define VMA_MUTEX VmaMutex
+
+template <typename T>
+T* custom_overestimate_malloc(size_t size) {
+	return new T[size + (sizeof(T) - 1) / sizeof(T)];
+}
+
+void* custom_aligned_malloc(size_t alignment, size_t size) {
+	if (alignment > 4) {
+		return custom_overestimate_malloc<uint64_t>(size);
+	} else
+	if (alignment > 2) {
+		return custom_overestimate_malloc<uint32_t>(size);
+	} else
+	if (alignment > 1) {
+		return custom_overestimate_malloc<uint16_t>(size);
+	} else {
+		return custom_overestimate_malloc<uint8_t>(size);
+	}
+}
+
+void custom_free(void *ptr) {
+	delete[] reinterpret_cast<uint8_t*>(ptr);
+}
+
+#define VMA_SYSTEM_ALIGNED_MALLOC(size, alignment) (custom_aligned_malloc(alignment, size))
+#define VMA_SYSTEM_FREE(ptr) (custom_free(ptr))
+#endif
+#endif
+
+#define VMA_IMPLEMENTATION
+#include "vk_mem_alloc.hpp"
diff --git a/doc/BUILD_LINUX.md b/doc/BUILD_LINUX.md
new file mode 100644
index 0000000000000000000000000000000000000000..149679e24a05fbeb8ce65346b7df9de0392c41c2
--- /dev/null
+++ b/doc/BUILD_LINUX.md
@@ -0,0 +1,51 @@
+# How to build on Linux
+
+## How to build on Ubuntu
+
+1. Install the required and useful packages to develop:
+```
+sudo apt install cmake make gcc g++ libvulkan-dev vulkan-validationlayers-dev vulkan-tools
+```
+2. Install the Vulkan drivers for your system:
+```
+# For most GPUs (except Nvidia):
+sudo apt install mesa-vulkan-drivers
+```
+3. Clone the repository and run the following commands:
+```
+mkdir debug
+cd debug
+cmake -DCMAKE_C_COMPILER="/usr/bin/gcc" -DCMAKE_CXX_COMPILER="/usr/bin/g++" -DCMAKE_BUILD_TYPE=Debug ..
+cmake --build .
+```
+
+## How to build on Archlinux by the way
+
+1. Install required and useful tools to develop:
+```
+sudo pacman -S cmake make gcc vulkan-icd-loader vulkan-headers vulkan-validation-layers vulkan-tools
+```
+2. Install the matching Vulkan drivers for your system:
+```
+# For discrete Nvidia GPUs:
+sudo pacman -S nvidia
+
+# For integrated or discrete (AMD) Radeon GPUs:
+sudo pacman -S vulkan-radeon
+
+# For integrated or discrete Intel GPUs:
+sudo pacman -S vulkan-intel
+```
+3. Clone the repository and run the following commands:
+```
+mkdir debug
+cd debug
+cmake -DCMAKE_C_COMPILER="/usr/bin/gcc" -DCMAKE_CXX_COMPILER="/usr/bin/g++" -DCMAKE_BUILD_TYPE=Debug ..
+cmake --build .
+```
+
+## How to build with Clang on Linux
+
+Just follow the steps of "How to build on YOUR DISTRO" but replace `gcc` with `clang` and `g++` with `clang++`.
+This should work actually but maybe the names of the packages are a little different. So look at your official 
+repository for the packages and their names if it doesn't work.
diff --git a/doc/BUILD_MACOS.md b/doc/BUILD_MACOS.md
new file mode 100644
index 0000000000000000000000000000000000000000..c0b20bd1c59030492cea66d6b562b6204e2025db
--- /dev/null
+++ b/doc/BUILD_MACOS.md
@@ -0,0 +1,19 @@
+# How to build on macOS
+
+## How to build with Clang on macOS
+
+1. Install Homebrew
+2. Install required tools to compile:
+```
+brew install cmake llvm libomp
+```
+3. Download and install latest [vulkansdk-macos-*.dmg](https://vulkan.lunarg.com/doc/sdk/1.2.189.0/mac/getting_started.html) to ~/VulkanSDK
+4. Clone the repository and run the following commands:
+```
+mkdir debug
+cd debug
+export LDFLAGS="-L/usr/local/opt/llvm/lib"
+export CPPFLAGS="-I/usr/local/opt/llvm/include"
+cmake -DCMAKE_C_COMPILER="/usr/local/opt/llvm/bin/clang" -DCMAKE_CXX_COMPILER="/usr/local/opt/llvm/bin/clang++" -DCMAKE_BUILD_TYPE=Debug ..
+cmake --build .
+```
\ No newline at end of file
diff --git a/doc/BUILD_WINDOWS.md b/doc/BUILD_WINDOWS.md
new file mode 100644
index 0000000000000000000000000000000000000000..729c202abac9273a821c7d21ac2ad1536a887045
--- /dev/null
+++ b/doc/BUILD_WINDOWS.md
@@ -0,0 +1,35 @@
+# How to build on Windows
+
+## How to build with MSVC on Windows
+
+1. Download and install [VulkanSDK](https://vulkan.lunarg.com/sdk/home#windows)
+2. Download and install [CMake](https://cmake.org/install/)
+3. Download and install [VisualStudio](https://visualstudio.microsoft.com/vs/features/cplusplus/) for MSVC
+4. Create and configure a project with the framework and build it
+
+## How to build with GCC on Windows
+
+1. Install [MSYS2](https://www.msys2.org/)
+2. Run "MSYS2 MSYS" from Start menu
+3. Enter the following commands into MSYS2 console: (this will update and install required packages)
+```
+pacman -Syu
+pacman -Su
+pacman -S --needed base-devel mingw-w64-x86_64-toolchain
+pacman -S mingw-w64-x86_64-vulkan-devel
+pacman -S cmake
+```
+4. Add to Path:
+```
+C:\msys64\usr\bin
+C:\msys64\usr\local\bin
+C:\msys64\mingw64\bin
+C:\msys64\mingw32\bin
+```
+5. Clone the repository and run the following commands:
+```
+mkdir debug
+cd debug
+cmake --no-warn-unused-cli -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_C_COMPILER:FILEPATH=C:\msys64\mingw64\bin\x86_64-w64-mingw32-gcc.exe -DCMAKE_CXX_COMPILER:FILEPATH=C:\msys64\mingw64\bin\x86_64-w64-mingw32-g++.exe .. -G "Unix Makefiles"
+cmake --build .
+```
\ No newline at end of file
diff --git a/doc/SETUP_IDE.md b/doc/SETUP_IDE.md
new file mode 100644
index 0000000000000000000000000000000000000000..93f0878c11ae0194b1522bd3eb087d557dc0ad05
--- /dev/null
+++ b/doc/SETUP_IDE.md
@@ -0,0 +1,35 @@
+# How to setup your IDE
+
+## VSCodium
+
+1. Install [VSCodium](https://github.com/VSCodium/vscodium)
+2. Run `scripts/setup-codium.sh` in your local repository
+3. Run VSCodium and open your local repository as project
+
+### VSCode
+
+If you should prefer to use the official binaries from Microsoft including their own telemetry, you can follow these steps:
+
+1. Download and install [VSCode](https://code.visualstudio.com/Download)
+2. Run `scripts/setup-codium.sh` in your local repository or in case of a Windows host run following line from PowerShell:
+```
+Get-Content resources\extensions.txt | ForEach-Object {code --install-extension $_}
+```
+3. Run VSCode and open your local repository as project
+
+## Eclipse CDT
+
+1. Download and install [Eclipse CDT](https://www.eclipse.org/cdt/downloads.php)
+2. Run Eclipse CDT and open your local repository as project
+
+## CLion
+
+1. Download and install [CLion](https://www.jetbrains.com/clion/download/)
+2. Run CLion and open your local repository as project
+
+## Visual Studio
+
+1. Download and install [Visual Studio](https://visualstudio.microsoft.com/downloads/)
+2. Make sure you have installed the requirements for [CMake projects](https://docs.microsoft.com/en-us/cpp/build/cmake-projects-in-visual-studio?view=msvc-170).
+3. Run Visual Studio and open your local repository as project
+
diff --git a/include/vkcv/BlitDownsampler.hpp b/include/vkcv/BlitDownsampler.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d5b7d1008bafa7e7d636e4d498fcaba997ed0da4
--- /dev/null
+++ b/include/vkcv/BlitDownsampler.hpp
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "Downsampler.hpp"
+
+namespace vkcv {
+
+	class ImageManager;
+
+	/**
+	 * @brief A class to handle downsampling via blit from a graphics queue.
+	 */
+	class BlitDownsampler : public Downsampler {
+		friend class Core;
+
+	private:
+		/**
+		 * Reference to the image manager.
+		 */
+		ImageManager &m_imageManager;
+
+		/**
+		 * @brief Constructor to create a blit downsampler instance.
+		 *
+		 * @param[in,out] core Reference to a Core instance
+		 * @param[in,out] imageManager Reference to an image manager
+		 */
+		BlitDownsampler(Core &core, ImageManager &imageManager);
+
+	public:
+		/**
+		 * @brief Record the commands of the downsampling instance to
+		 * generate all mip levels of an input image via a
+		 * command stream.
+		 *
+		 * @param[in] cmdStream Command stream handle
+		 * @param[in] image Image handle
+		 */
+		void recordDownsampling(const CommandStreamHandle &cmdStream,
+								const ImageHandle &image) override;
+	};
+
+} // namespace vkcv
diff --git a/include/vkcv/Buffer.hpp b/include/vkcv/Buffer.hpp
index ae935ba9501d4d7776cad7e3ba190a2dd02e5e38..37bab6b8b1156e036f4eb17681843812e49165f5 100644
--- a/include/vkcv/Buffer.hpp
+++ b/include/vkcv/Buffer.hpp
@@ -1,84 +1,172 @@
 #pragma once
 /**
- * @authors Lars Hoerttrich, Tobias Frisch
+ * @authors Tobias Frisch, Lars Hoerttrich, Alexander Gauggel
  * @file vkcv/Buffer.hpp
- * @brief template buffer class, template for type security, implemented here because template classes can't be written in .cpp
+ * @brief Template buffer class for type security with buffers.
  */
-#include "Handles.hpp"
-#include "BufferManager.hpp"
 
 #include <vector>
 
+#include "BufferTypes.hpp"
+#include "Core.hpp"
+#include "Handles.hpp"
+
 namespace vkcv {
 
-	template<typename T>
+	/**
+	 * @brief Template class for buffer handling and filling data.
+	 *
+	 * @tparam T Buffer content type
+	 */
+	template <typename T>
 	class Buffer {
-		friend class Core;
 	public:
-		// explicit destruction of default constructor
-		Buffer<T>() = delete;
-		
-		[[nodiscard]]
-		const BufferHandle& getHandle() const {
+		Buffer() : m_core(nullptr), m_handle() {}
+
+		Buffer(Core* core, const BufferHandle &handle) : m_core(core), m_handle(handle) {}
+
+		Buffer(const Buffer &other) = default;
+		Buffer(Buffer &&other) noexcept = default;
+
+		~Buffer() = default;
+
+		Buffer &operator=(const Buffer &other) = default;
+		Buffer &operator=(Buffer &&other) noexcept = default;
+
+		/**
+		 * @brief Returns the buffers handle.
+		 *
+		 * @return The #BufferHandle to be used with the #Core
+		 */
+		[[nodiscard]] const BufferHandle &getHandle() const {
 			return m_handle;
 		}
-		
-		[[nodiscard]]
-		BufferType getType() const {
-			return m_type;
+
+		/**
+		 * @brief Returns the type of the buffer.
+		 *
+		 * @return The #BufferType of the #Buffer
+		 */
+		[[nodiscard]] BufferType getType() const {
+			return m_core->getBufferType(m_handle);
 		};
-		
-		[[nodiscard]]
-		size_t getCount() const {
-			return m_count;
+
+		[[nodiscard]] BufferMemoryType getMemoryType() const {
+			return m_core->getBufferMemoryType(m_handle);
 		}
-		
-		[[nodiscard]]
-		size_t getSize() const {
-			return m_count * sizeof(T);
+
+		/**
+		 * @brief Returns the count of elements in the buffer.
+		 *
+		 * @return The number of objects of type T the #Buffer holds
+		 */
+		[[nodiscard]] size_t getCount() const {
+			return m_core->getBufferSize(m_handle) / sizeof(T);
 		}
 
-        [[nodiscard]]
-        const vk::Buffer getVulkanHandle() const {
-            return m_manager->getBuffer(m_handle);
-        }
-		
+		/**
+		 * @brief Returns the size of the buffer in bytes.
+		 *
+		 * @return The size of the #Buffer in bytes
+		 */
+		[[nodiscard]] size_t getSize() const {
+			return m_core->getBufferSize(m_handle);
+		}
+
+		/**
+		 * @brief Returns the vulkan buffer handle of the buffer.
+		 *
+		 * @return The vulkan handle of the #Buffer to be used for manual vulkan commands
+		 */
+		[[nodiscard]] vk::Buffer getVulkanHandle() const {
+			return m_core->getBuffer(m_handle);
+		}
+
+		/**
+		 * @brief Fills the #Buffer with data of type T.
+		 *
+		 * @param[in] data Pointer to the array of object type T
+		 * @param[in] count The number of objects to copy from the data array
+		 * @param[in] offset The offset into the #Buffer where the data is copied into
+		 */
 		void fill(const T* data, size_t count = 0, size_t offset = 0) {
-			 m_manager->fillBuffer(m_handle, data, count * sizeof(T), offset * sizeof(T));
+			m_core->fillBuffer(m_handle, data, count * sizeof(T), offset * sizeof(T));
 		}
-		
-		void fill(const std::vector<T>& vector, size_t offset = 0) {
-			fill( static_cast<const T*>(vector.data()), static_cast<size_t>(vector.size()), offset);
+
+		/**
+		 * @brief Fills the #Buffer with data from a vector of type T.
+		 *
+		 * @param vector Vector of type T to be copied into the #Buffer
+		 * @param offset The offset into the #Buffer where the data is copied into
+		 */
+		void fill(const std::vector<T> &vector, size_t offset = 0) {
+			fill(static_cast<const T*>(vector.data()), static_cast<size_t>(vector.size()), offset);
 		}
 		
-		[[nodiscard]]
-		T* map(size_t offset = 0, size_t count = 0) {
-			return reinterpret_cast<T*>(m_manager->mapBuffer(m_handle, offset * sizeof(T), count * sizeof(T)));
+		/**
+		 * @brief Fills the #Buffer with data from an array of type T
+		 * and size N.
+		 *
+		 * @tparam N Size of the array to be copied into the #Buffer
+		 * @param array Array of type T to be copied into the #Buffer
+		 * @param offset The offset into the #Buffer where the data is copied into
+		 */
+		template<size_t N>
+		void fill(const std::array<T, N> &array, size_t offset = 0) {
+			fill(static_cast<const T*>(array.data()), N, offset);
+		}
+
+		/**
+		 * @brief Reads the #Buffer directly into a data pointer of type T.
+		 *
+		 * @param[in] data Pointer to the array of object type T
+		 * @param[in] count The number of objects to copy from the buffer
+		 * @param[in] offset The offset into the #Buffer where the data is copied from
+		 */
+		void read(T* data, size_t count = 0, size_t offset = 0) {
+			m_core->readBuffer(m_handle, data, count * sizeof(T), offset * sizeof(T));
+		}
+
+		/**
+		 * @brief Reads the #Buffer directly to a vector of type T.
+		 *
+		 * @param vector Vector of type T to be copied into from the #Buffer
+		 * @param offset The offset into the #Buffer where the data is copied from
+		 */
+		void read(std::vector<T> &vector, size_t offset = 0) {
+			read(static_cast<T*>(vector.data()), static_cast<size_t>(vector.size()), offset);
 		}
 
+		/**
+		 * @brief Maps memory to the #Buffer and returns it.
+		 *
+		 * @param[in] offset Offset of mapping in objects of type T
+		 * @param[in] count Count of objects of type T that are mapped
+		 * @return Pointer to mapped memory as type T
+		 */
+		[[nodiscard]] T* map(size_t offset = 0, size_t count = 0) {
+			return reinterpret_cast<T*>(
+				m_core->mapBuffer(m_handle, offset * sizeof(T), count * sizeof(T)));
+		}
+
+		/**
+		 * @brief Unmaps the #Buffer, invalidates the pointer obtained by map().
+		 */
 		void unmap() {
-			m_manager->unmapBuffer(m_handle);
+			m_core->unmapBuffer(m_handle);
 		}
 
 	private:
-		BufferManager* const m_manager;
-		const BufferHandle m_handle;
-		const BufferType m_type;
-		const size_t m_count;
-		const BufferMemoryType m_memoryType;
-		
-		Buffer<T>(BufferManager* manager, BufferHandle handle, BufferType type, size_t count, BufferMemoryType memoryType) :
-				m_manager(manager),
-				m_handle(handle),
-				m_type(type),
-				m_count(count),
-				m_memoryType(memoryType)
-		{}
-		
-		[[nodiscard]]
-		static Buffer<T> create(BufferManager* manager, BufferType type, size_t count, BufferMemoryType memoryType) {
-			return Buffer<T>(manager, manager->createBuffer(type, count * sizeof(T), memoryType), type, count, memoryType);
-		}
-		
+		Core* m_core;
+		BufferHandle m_handle;
 	};
-}
+
+	template <typename T>
+	Buffer<T> buffer(Core &core, BufferType type, size_t count,
+					 BufferMemoryType memoryType = BufferMemoryType::DEVICE_LOCAL,
+					 bool readable = false) {
+		return Buffer<T>(&core,
+						 core.createBuffer(type, typeGuard<T>(), count, memoryType, readable));
+	}
+
+} // namespace vkcv
diff --git a/include/vkcv/BufferManager.hpp b/include/vkcv/BufferManager.hpp
deleted file mode 100644
index 9eb80d70862a79a01593e6fe4c3aabe98d253ac8..0000000000000000000000000000000000000000
--- a/include/vkcv/BufferManager.hpp
+++ /dev/null
@@ -1,140 +0,0 @@
-#pragma once
-
-#include <vector>
-#include <vulkan/vulkan.hpp>
-
-#include "Handles.hpp"
-
-namespace vkcv
-{
-	enum class BufferType {
-		INDEX,
-		VERTEX,
-		UNIFORM,
-		STORAGE,
-		STAGING
-	};
-	
-	enum class BufferMemoryType {
-		DEVICE_LOCAL,
-		HOST_VISIBLE
-	};
-	
-	class Core;
-	
-	class BufferManager
-	{
-		friend class Core;
-	private:
-		
-		struct Buffer
-		{
-			vk::Buffer m_handle;
-			vk::DeviceMemory m_memory;
-			size_t m_size = 0;
-			void* m_mapped = nullptr;
-			bool m_mappable = false;
-		};
-		
-		Core* m_core;
-		std::vector<Buffer> m_buffers;
-		BufferHandle m_stagingBuffer;
-		
-		BufferManager() noexcept;
-		
-		void init();
-		
-		/**
-		 * Destroys and deallocates buffer represented by a given
-		 * buffer handle id.
-		 *
-		 * @param id Buffer handle id
-		 */
-		void destroyBufferById(uint64_t id);
-		
-	public:
-		~BufferManager() noexcept;
-		
-		BufferManager(BufferManager&& other) = delete;
-		BufferManager(const BufferManager& other) = delete;
-		
-		BufferManager& operator=(BufferManager&& other) = delete;
-		BufferManager& operator=(const BufferManager& other) = delete;
-		
-		/**
-		 * Creates and allocates a new buffer and returns its
-		 * unique buffer handle.
-		 *
-		 * @param type Type of buffer
-		 * @param size Size of buffer in bytes
-		 * @param memoryType Type of buffers memory
-		 * @return New buffer handle
-		 */
-		BufferHandle createBuffer(BufferType type, size_t size, BufferMemoryType memoryType);
-		
-		/**
-		 * Returns the Vulkan buffer handle of a buffer
-		 * represented by a given buffer handle.
-		 *
-		 * @param handle Buffer handle
-		 * @return Vulkan buffer handle
-		 */
-		[[nodiscard]]
-		vk::Buffer getBuffer(const BufferHandle& handle) const;
-		
-		/**
-		 * Returns the size of a buffer represented
-		 * by a given buffer handle.
-		 *
-		 * @param handle Buffer handle
-		 * @return Size of the buffer
-		 */
-		[[nodiscard]]
-		size_t getBufferSize(const BufferHandle& handle) const;
-		
-		/**
-		 * Returns the Vulkan device memory handle of a buffer
-		 * represented by a given buffer handle id.
-		 *
-		 * @param handle Buffer handle
-		 * @return Vulkan device memory handle
-		 */
-		[[nodiscard]]
-		vk::DeviceMemory getDeviceMemory(const BufferHandle& handle) const;
-		
-		/**
-		 * Fills a buffer represented by a given buffer
-		 * handle with custom data.
-		 *
-		 * @param handle Buffer handle
-		 * @param data Pointer to data
-		 * @param size Size of data in bytes
-		 * @param offset Offset to fill in data in bytes
-		 */
-		void fillBuffer(const BufferHandle& handle, const void* data, size_t size, size_t offset);
-		
-		/**
-		 * Maps memory to a buffer represented by a given
-		 * buffer handle and returns it.
-		 *
-		 * @param handle Buffer handle
-		 * @param offset Offset of mapping in bytes
-		 * @param size Size of mapping in bytes
-		 * @return Pointer to mapped memory
-		 */
-		void* mapBuffer(const BufferHandle& handle, size_t offset, size_t size);
-		
-		/**
-		 * Unmaps memory from a buffer represented by a given
-		 * buffer handle.
-		 *
-		 * @param handle Buffer handle
-		 */
-		void unmapBuffer(const BufferHandle& handle);
-		
-		void recordBufferMemoryBarrier(
-			const BufferHandle& handle,
-			vk::CommandBuffer cmdBuffer);
-	};
-	
-}
diff --git a/include/vkcv/BufferTypes.hpp b/include/vkcv/BufferTypes.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..85bc0f2a1b7af5d1351bb6aae396e052743572dd
--- /dev/null
+++ b/include/vkcv/BufferTypes.hpp
@@ -0,0 +1,34 @@
+#pragma once
+/**
+ * @authors Tobias Frisch
+ * @file vkcv/BufferTypes.hpp
+ * @brief Enum classes to specify attributes of buffers.
+ */
+
+namespace vkcv {
+
+	/**
+	 * @brief Enum class to specify types of buffers.
+	 */
+	enum class BufferType {
+		INDEX,
+		VERTEX,
+		UNIFORM,
+		STORAGE,
+		STAGING,
+		INDIRECT,
+
+		UNKNOWN
+	};
+
+	/**
+	 * @brief Enum class to specify types of buffer memory.
+	 */
+	enum class BufferMemoryType {
+		DEVICE_LOCAL,
+		HOST_VISIBLE,
+
+		UNKNOWN
+	};
+
+} // namespace vkcv
diff --git a/include/vkcv/CommandRecordingFunctionTypes.hpp b/include/vkcv/CommandRecordingFunctionTypes.hpp
deleted file mode 100644
index c236fb2c717afd2a3bafc4b3a22708cdac942ffe..0000000000000000000000000000000000000000
--- a/include/vkcv/CommandRecordingFunctionTypes.hpp
+++ /dev/null
@@ -1,8 +0,0 @@
-#pragma once
-#include "vkcv/Event.hpp"
-#include <vulkan/vulkan.hpp>
-
-namespace vkcv {
-	typedef typename event_function<const vk::CommandBuffer&>::type RecordCommandFunction;
-	typedef typename event_function<>::type FinishCommandFunction;
-}
\ No newline at end of file
diff --git a/include/vkcv/CommandResources.hpp b/include/vkcv/CommandResources.hpp
deleted file mode 100644
index ffdd6d0315549c7522623f535856bbaffc8e5c6e..0000000000000000000000000000000000000000
--- a/include/vkcv/CommandResources.hpp
+++ /dev/null
@@ -1,25 +0,0 @@
-#pragma once
-#include <vulkan/vulkan.hpp>
-#include <unordered_set>
-#include "QueueManager.hpp"
-
-namespace vkcv {
-	struct CommandResources {
-		std::vector<vk::CommandPool> cmdPoolPerQueueFamily;
-	};
-
-	std::unordered_set<int> generateQueueFamilyIndexSet(const QueueManager& queueManager);
-	CommandResources		createCommandResources(const vk::Device& device, const std::unordered_set<int> &familyIndexSet);
-	void					destroyCommandResources(const vk::Device& device, const CommandResources& resources);
-	vk::CommandBuffer		allocateCommandBuffer(const vk::Device& device, const vk::CommandPool cmdPool);
-	vk::CommandPool			chooseCmdPool(const Queue &queue, const CommandResources &cmdResources);
-	Queue					getQueueForSubmit(const QueueType type, const QueueManager &queueManager);
-	void					beginCommandBuffer(const vk::CommandBuffer cmdBuffer, const vk::CommandBufferUsageFlags flags);
-
-	void submitCommandBufferToQueue(
-		const vk::Queue						queue,
-		const vk::CommandBuffer				cmdBuffer,
-		const vk::Fence						fence,
-		const std::vector<vk::Semaphore>&	waitSemaphores,
-		const std::vector<vk::Semaphore>&	signalSemaphores);
-}
\ No newline at end of file
diff --git a/include/vkcv/CommandStreamManager.hpp b/include/vkcv/CommandStreamManager.hpp
deleted file mode 100644
index 4af2127ccf6271f1076e3dde05304b8f9c556139..0000000000000000000000000000000000000000
--- a/include/vkcv/CommandStreamManager.hpp
+++ /dev/null
@@ -1,55 +0,0 @@
-#pragma once
-#include <vulkan/vulkan.hpp>
-#include <vector>
-#include "vkcv/Event.hpp"
-#include "vkcv/Handles.hpp"
-#include "vkcv/CommandRecordingFunctionTypes.hpp"
-
-namespace vkcv {
-	
-	class Core;
-
-	class CommandStreamManager
-	{
-		friend class Core;
-	private:
-		struct CommandStream {
-			inline CommandStream(vk::CommandBuffer cmdBuffer, vk::Queue queue, vk::CommandPool cmdPool) 
-				: cmdBuffer(cmdBuffer), cmdPool(cmdPool), queue(queue) {};
-			vk::CommandBuffer                   cmdBuffer;
-			vk::CommandPool                     cmdPool;
-			vk::Queue                           queue;
-			std::vector<FinishCommandFunction>  callbacks;
-		};
-
-		Core* m_core;
-		std::vector<CommandStream> m_commandStreams;
-
-		CommandStreamManager() noexcept;
-
-		void init(Core* core);
-
-	public:
-		~CommandStreamManager() noexcept;
-
-		CommandStreamManager(CommandStreamManager&& other) = delete;
-		CommandStreamManager(const CommandStreamManager& other) = delete;
-
-		CommandStreamManager& operator=(CommandStreamManager&& other) = delete;
-		CommandStreamManager& operator=(const CommandStreamManager& other) = delete;
-
-		CommandStreamHandle createCommandStream(
-			const vk::Queue queue,
-			vk::CommandPool cmdPool);
-
-		void recordCommandsToStream(const CommandStreamHandle handle, const RecordCommandFunction record);
-		void addFinishCallbackToStream(const CommandStreamHandle handle, const FinishCommandFunction finish);
-		void submitCommandStreamSynchronous(
-			const CommandStreamHandle   handle,
-			std::vector<vk::Semaphore>  &waitSemaphores,
-			std::vector<vk::Semaphore>  &signalSemaphores);
-
-		vk::CommandBuffer getStreamCommandBuffer(const CommandStreamHandle handle);
-	};
-
-}
\ No newline at end of file
diff --git a/include/vkcv/ComputePipelineConfig.hpp b/include/vkcv/ComputePipelineConfig.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..8a030ba9c1a66c1b7ffcb74e283ce95e06d93243
--- /dev/null
+++ b/include/vkcv/ComputePipelineConfig.hpp
@@ -0,0 +1,19 @@
+#pragma once
+/**
+ * @authors Mark Mints, Tobias Frisch
+ * @file vkcv/ComputePipelineConfig.hpp
+ * @brief Compute pipeline config struct to hand over required information to pipeline creation.
+ */
+
+#include "PipelineConfig.hpp"
+
+namespace vkcv {
+
+	/**
+	 * @brief Class to configure a compute pipeline before its creation.
+	 */
+	class ComputePipelineConfig : public PipelineConfig {
+		using PipelineConfig::PipelineConfig;
+	};
+
+} // namespace vkcv
\ No newline at end of file
diff --git a/include/vkcv/Context.hpp b/include/vkcv/Context.hpp
index 1c01a6134ba1642b3a130a7a4d3d299cc3f7b875..9146c59102b9e7e2cc51132f92e59274168c806e 100644
--- a/include/vkcv/Context.hpp
+++ b/include/vkcv/Context.hpp
@@ -1,57 +1,121 @@
 #pragma once
+/**
+ * @authors Tobias Frisch, Artur Wasmut, Sebastian Gaida, Alexander Gauggel
+ * @file vkcv/Context.hpp
+ * @brief Class to handle the instance, device, allocator and features of the current context.
+ */
 
+#include <string>
+#include <vk_mem_alloc.hpp>
 #include <vulkan/vulkan.hpp>
 
+#include "Features.hpp"
+#include "Handles.hpp"
 #include "QueueManager.hpp"
 
-namespace vkcv
-{
-    class Context
-    {
-        friend class Core;
-    public:
-        // explicit destruction of default constructor
-        Context() = delete;
-        // is never called directly
-        ~Context() noexcept;
-
-        Context(const Context &other) = delete; // copy-ctor
-        Context(Context &&other) noexcept; // move-ctor
-
-        Context & operator=(const Context &other) = delete; // copy assignment
-        Context & operator=(Context &&other) noexcept; // move assignment
-
-        [[nodiscard]]
-        const vk::Instance &getInstance() const;
-        
-        [[nodiscard]]
-        const vk::PhysicalDevice &getPhysicalDevice() const;
-        
-        [[nodiscard]]
-        const vk::Device &getDevice() const;
-        
-        [[nodiscard]]
-        const QueueManager& getQueueManager() const;
-        
-        static Context create(const char *applicationName,
-							  uint32_t applicationVersion,
-							  std::vector<vk::QueueFlagBits> queueFlags,
-							  std::vector<const char *> instanceExtensions,
-							  std::vector<const char *> deviceExtensions);
-
-    private:
-        /**
-         * Constructor of #Context requires an @p instance, a @p physicalDevice and a @p device.
-         *
-         * @param instance Vulkan-Instance
-         * @param physicalDevice Vulkan-PhysicalDevice
-         * @param device Vulkan-Device
-         */
-        Context(vk::Instance instance, vk::PhysicalDevice physicalDevice, vk::Device device, QueueManager&& queueManager) noexcept;
-        
-        vk::Instance        m_Instance;
-        vk::PhysicalDevice  m_PhysicalDevice;
-        vk::Device          m_Device;
-		QueueManager		m_QueueManager;
-    };
-}
+namespace vkcv {
+
+	/**
+	 * @brief Class to manage core resources for vulkan callbacks.
+	 *
+	 * The class to manage the vulkan resources as an instance,
+	 * a device, a physical device and a memory allocator. Additionally
+	 * instances of this class will hold the feature manager and the
+	 * queue manager.
+	 */
+	class Context {
+		friend class Core;
+
+	public:
+		// explicit destruction of default constructor
+		Context() = delete;
+		// is never called directly
+		~Context() noexcept;
+
+		Context(const Context &other) = delete; // copy-ctor
+		Context(Context &&other) noexcept;      // move-ctor
+
+		Context &operator=(const Context &other) = delete; // copy assignment
+		Context &operator=(Context &&other) noexcept;      // move assignment
+
+		/**
+		 * @brief Returns the vulkan instance of the context.
+		 *
+		 * @return Vulkan instance
+		 */
+		[[nodiscard]] const vk::Instance &getInstance() const;
+
+		/**
+		 * @brief Returns the vulkan physical device of the context.
+		 *
+		 * @return Vulkan physical device
+		 */
+		[[nodiscard]] const vk::PhysicalDevice &getPhysicalDevice() const;
+
+		/**
+		 * @brief Returns the vulkan device of the context.
+		 *
+		 * @return Vulkan device
+		 */
+		[[nodiscard]] const vk::Device &getDevice() const;
+
+		/**
+		 * @brief Returns the feature manager of the context.
+		 *
+		 * @return Feature manager
+		 */
+		[[nodiscard]] const FeatureManager &getFeatureManager() const;
+
+		/**
+		 * @brief Returns the queue manager of the context.
+		 *
+		 * @return Queue manager
+		 */
+		[[nodiscard]] const QueueManager &getQueueManager() const;
+
+		/**
+		 * @brief Returns the VMA allocator of the context.
+		 *
+		 * @return VMA allocator
+		 */
+		[[nodiscard]] const vma::Allocator &getAllocator() const;
+
+		/**
+		 * @brief Creates a context for a given application with
+		 * a specific name, version, queue requirements, features and
+		 * required instance extensions.
+		 *
+		 * @param applicationName Application name
+		 * @param applicationVersion  Application version
+		 * @param queueFlags Queue flags
+		 * @param features Features
+		 * @param instanceExtensions Instance extensions
+		 * @return New context
+		 */
+		static Context create(const std::string &applicationName, uint32_t applicationVersion,
+							  const std::vector<vk::QueueFlagBits> &queueFlags,
+							  const Features &features,
+							  const std::vector<const char*> &instanceExtensions = {});
+
+	private:
+		/**
+		 * @brief Constructor of #Context requires an @p instance,
+		 * a @p physicalDevice and a @p device.
+		 *
+		 * @param instance Vulkan-Instance
+		 * @param physicalDevice Vulkan-PhysicalDevice
+		 * @param device Vulkan-Device
+		 */
+		Context(vk::Instance instance, vk::PhysicalDevice physicalDevice, vk::Device device,
+				FeatureManager &&featureManager, QueueManager &&queueManager,
+				vma::Allocator &&allocator) noexcept;
+
+		vk::Instance m_Instance;
+		vk::PhysicalDevice m_PhysicalDevice;
+		vk::Device m_Device;
+		FeatureManager m_FeatureManager;
+		QueueManager m_QueueManager;
+		vma::Allocator m_Allocator;
+	};
+
+} // namespace vkcv
diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp
index c7512346c9137b77c365e807b679b3950925f535..1cec2a7149ab17de76f390944e27941c317cd6e6 100644
--- a/include/vkcv/Core.hpp
+++ b/include/vkcv/Core.hpp
@@ -1,292 +1,944 @@
 #pragma once
 /**
- * @file src/vkcv/Core.hpp
- * @brief Handling of global states regarding dependencies
+ * @authors Alexander Gauggel, Tobias Frisch, Sebastian Gaida, Artur Wasmut, Lars Hoerttrich,
+ *          Mara Vogt, Mark Mints, Simeon Hermann, Alex Laptop, Katharina Krämer, Vanessa Karolek
+ * @file vkcv/Core.hpp
+ * @brief Handling of global states regarding dependencies.
  */
 
 #include <memory>
+#include <string>
 #include <vulkan/vulkan.hpp>
 
-#include "vkcv/Context.hpp"
-#include "vkcv/Swapchain.hpp"
-#include "vkcv/Window.hpp"
-#include "vkcv/PassConfig.hpp"
-#include "vkcv/Handles.hpp"
-#include "vkcv/Buffer.hpp"
-#include "vkcv/Image.hpp"
-#include "vkcv/PipelineConfig.hpp"
-#include "CommandResources.hpp"
-#include "SyncResources.hpp"
-#include "Result.hpp"
-#include "vkcv/DescriptorConfig.hpp"
-#include "Sampler.hpp"
+#include "BlitDownsampler.hpp"
+#include "BufferTypes.hpp"
+#include "ComputePipelineConfig.hpp"
+#include "Context.hpp"
 #include "DescriptorWrites.hpp"
+#include "DispatchSize.hpp"
+#include "Drawcall.hpp"
 #include "Event.hpp"
-#include "DrawcallRecording.hpp"
-#include "CommandRecordingFunctionTypes.hpp"
-
-namespace vkcv
-{
-
-    // forward declarations
-    class PassManager;
-    class PipelineManager;
-    class DescriptorManager;
-    class BufferManager;
-    class SamplerManager;
-    class ImageManager;
+#include "EventFunctionTypes.hpp"
+#include "GraphicsPipelineConfig.hpp"
+#include "Handles.hpp"
+#include "ImageConfig.hpp"
+#include "PassConfig.hpp"
+#include "PushConstants.hpp"
+#include "Result.hpp"
+#include "SamplerTypes.hpp"
+#include "Window.hpp"
+
+#define VKCV_FRAMEWORK_NAME "VkCV"
+#define VKCV_FRAMEWORK_VERSION (VK_MAKE_VERSION(0, 1, 0))
+
+namespace vkcv {
+
+	// forward declarations
+	class PassManager;
+	class GraphicsPipelineManager;
+	class ComputePipelineManager;
+	class DescriptorSetLayoutManager;
+	class DescriptorSetManager;
+	class BufferManager;
+	class SamplerManager;
+	class ImageManager;
 	class CommandStreamManager;
+	class WindowManager;
+	class SwapchainManager;
+
+	/**
+	 * @brief Class to handle the core functionality of the framework.
+	 *
+	 * The class handles the core functionality of the framework with most
+	 * calls addressing resource management via more simplified abstraction.
+	 */
+	class Core final {
+	private:
+		/**
+		 * Constructor of #Core requires an @p context.
+		 *
+		 * @param context encapsulates various Vulkan objects
+		 */
+		explicit Core(Context &&context) noexcept;
 
-	struct SubmitInfo {
-		QueueType queueType;
-		std::vector<vk::Semaphore> waitSemaphores;
-		std::vector<vk::Semaphore> signalSemaphores;
-	};
+		// explicit destruction of default constructor
+		Core() = delete;
+
+		Result acquireSwapchainImage(const SwapchainHandle &swapchainHandle);
+
+		Context m_Context;
+
+		std::unique_ptr<PassManager> m_PassManager;
+		std::unique_ptr<GraphicsPipelineManager> m_GraphicsPipelineManager;
+		std::unique_ptr<ComputePipelineManager> m_ComputePipelineManager;
+		std::unique_ptr<DescriptorSetLayoutManager> m_DescriptorSetLayoutManager;
+		std::unique_ptr<DescriptorSetManager> m_DescriptorSetManager;
+		std::unique_ptr<BufferManager> m_BufferManager;
+		std::unique_ptr<SamplerManager> m_SamplerManager;
+		std::unique_ptr<ImageManager> m_ImageManager;
+		std::unique_ptr<CommandStreamManager> m_CommandStreamManager;
+		std::unique_ptr<WindowManager> m_WindowManager;
+		std::unique_ptr<SwapchainManager> m_SwapchainManager;
+
+		std::vector<vk::CommandPool> m_CommandPools;
+		vk::Semaphore m_RenderFinished;
+		vk::Semaphore m_SwapchainImageAcquired;
+		uint32_t m_currentSwapchainImageIndex;
+
+		std::unique_ptr<Downsampler> m_downsampler;
+
+		/**
+		 * Sets up swapchain images
+		 * @param handle Handle of swapchain
+		 */
+		void setSwapchainImages(SwapchainHandle handle);
+
+	public:
+		/**
+		 * Destructor of #Core destroys the Vulkan objects contained in the core's context.
+		 */
+		~Core() noexcept;
+
+		/**
+		 * Copy-constructor of #Core is deleted!
+		 *
+		 * @param other Other instance of #Context
+		 */
+		Core(const Core &other) = delete;
+
+		/**
+		 * Move-constructor of #Core uses default behavior!
+		 *
+		 * @param other Other instance of #Context
+		 */
+		Core(Core &&other) = delete; // move-ctor
+
+		/**
+		 * Copy assignment operator of #Core is deleted!
+		 *
+		 * @param other Other instance of Context
+		 * @return Reference to itself
+		 */
+		Core &operator=(const Core &other) = delete;
+
+		/**
+		 * Move assignment operator of #Core uses default behavior!
+		 *
+		 * @param other Other instance of Context
+		 * @return Reference to itself
+		 */
+		Core &operator=(Core &&other) = delete;
+
+		/**
+		 * Returns the context of a Core instance.
+		 *
+		 * @return Current Context
+		 */
+		[[nodiscard]] const Context &getContext() const;
+
+		/**
+		 * Creates a #Core with given @p applicationName and @p applicationVersion for your
+		 * application.
+		 *
+		 * It is also possible to require a specific amount of queues, ask for specific queue-flags
+		 * or extensions. This function will take care of the required arguments as best as
+		 * possible.
+		 *
+		 * To pass a valid version for your application, you should use #VK_MAKE_VERSION().
+		 *
+		 * @param[in] applicationName Name of the application
+		 * @param[in] applicationVersion Version of the application
+		 * @param[in] queueFlags (optional) Requested flags of queues
+		 * @param[in] instanceExtensions (optional) Requested instance extensions
+		 * @param[in] deviceExtensions (optional) Requested device extensions
+		 * @return New instance of #Context
+		 */
+		static Core create(const std::string &applicationName, uint32_t applicationVersion,
+						   const std::vector<vk::QueueFlagBits> &queueFlags = {},
+						   const Features &features = {},
+						   const std::vector<const char*> &instanceExtensions = {});
+
+		/**
+		 * Creates a basic vulkan graphics pipeline using @p config from the pipeline config class
+		 * and returns it using the @p handle. Fixed Functions for pipeline are set with standard
+		 * values.
+		 *
+		 * @param config a pipeline config object from the pipeline config class
+		 * @param handle a handle to return the created vulkan handle
+		 * @return True if pipeline creation was successful, False if not
+		 */
+		[[nodiscard]] GraphicsPipelineHandle
+		createGraphicsPipeline(const GraphicsPipelineConfig &config);
+
+		/**
+		 * Creates a basic vulkan compute pipeline using @p shader program and returns it using the
+		 * @p handle. Fixed Functions for pipeline are set with standard values.
+		 *
+		 * @param config Contains the compiles compute shader and the corresponding descriptor set
+		 * layout
+		 * @return True if pipeline creation was successful, False if not
+		 */
+		[[nodiscard]] ComputePipelineHandle
+		createComputePipeline(const ComputePipelineConfig &config);
+
+		/**
+		 * Creates a basic vulkan render pass using @p config from the render pass config class and
+		 * returns it. Fixed Functions for pipeline are set with standard values.
+		 *
+		 * @param[in] config a render pass config object from the render pass config class
+		 * @return A handle to represent the created pass
+		 */
+		[[nodiscard]] PassHandle createPass(const PassConfig &config);
+
+		/**
+		 * Returns the used configuration for a created render pass which is
+		 * represented by the given handle.
+		 *
+		 * @param[in] pass Pass handle
+		 * @return Pass configuration
+		 */
+		[[nodiscard]] const PassConfig &getPassConfiguration(const PassHandle &pass);
+
+		/**
+		 * @brief Creates a buffer with given parameters and returns its handle.
+		 *
+		 * @param[in] type Type of buffer created
+		 * @param[in] typeGuard Type guard for the buffer
+		 * @param[in] count Count of elements of its guarded type
+		 * @param[in] memoryType Type of buffers memory
+		 * @param[in] readable Flag whether the buffer supports reading from it
+		 * @return A handle to represent the created buffer
+		 */
+		BufferHandle createBuffer(BufferType type, const TypeGuard &typeGuard, size_t count,
+								  BufferMemoryType memoryType = BufferMemoryType::DEVICE_LOCAL,
+								  bool readable = false);
+
+		/**
+		 * @brief Creates a buffer with given parameters and returns its handle.
+		 *
+		 * @param[in] type Type of buffer created
+		 * @param[in] size Size of the buffer
+		 * @param[in] memoryType Type of buffers memory
+		 * @param[in] readable Flag whether the buffer supports reading from it
+		 * @return A handle to represent the created buffer
+		 */
+		BufferHandle createBuffer(BufferType type, size_t size,
+								  BufferMemoryType memoryType = BufferMemoryType::DEVICE_LOCAL,
+								  bool readable = false);
+
+		/**
+		 * @brief Returns the vulkan buffer of a given buffer handle.
+		 *
+		 * @param[in] buffer Buffer handle
+		 * @return Vulkan buffer
+		 */
+		vk::Buffer getBuffer(const BufferHandle &buffer) const;
+
+		/**
+		 * @brief Returns the buffer type of a buffer represented
+		 * by a given buffer handle.
+		 *
+		 * @param[in] buffer Buffer handle
+		 * @return Buffer type
+		 */
+		[[nodiscard]] BufferType getBufferType(const BufferHandle &buffer) const;
+
+		/**
+		 * @brief Returns the buffer memory type of a buffer
+		 * represented by a given buffer handle.
+		 *
+		 * @param[in] buffer Buffer handle
+		 * @return Buffer memory type
+		 */
+		[[nodiscard]] BufferMemoryType getBufferMemoryType(const BufferHandle &buffer) const;
+
+		/**
+		 * @brief Returns the size of a buffer represented
+		 * by a given buffer handle.
+		 *
+		 * @param[in] buffer Buffer handle
+		 * @return Size of the buffer
+		 */
+		[[nodiscard]] size_t getBufferSize(const BufferHandle &buffer) const;
+
+		/**
+		 * @brief Fills a buffer represented by a given buffer
+		 * handle with custom data.
+		 *
+		 * @param[in] buffer Buffer handle
+		 * @param[in] data Pointer to data
+		 * @param[in] size Size of data in bytes
+		 * @param[in] offset Offset to fill in data in bytes
+		 */
+		void fillBuffer(const BufferHandle &buffer, const void* data, size_t size, size_t offset);
+
+		/**
+		 * @brief Reads from a buffer represented by a given
+		 * buffer handle to some data pointer.
+		 *
+		 * @param[in] buffer Buffer handle
+		 * @param[in] data Pointer to data
+		 * @param[in] size Size of data to read in bytes
+		 * @param[in] offset Offset to read from buffer in bytes
+		 */
+		void readBuffer(const BufferHandle &buffer, void* data, size_t size, size_t offset);
+
+		/**
+		 * @brief Maps memory to a buffer represented by a given
+		 * buffer handle and returns it.
+		 *
+		 * @param[in] buffer Buffer handle
+		 * @param[in] offset Offset of mapping in bytes
+		 * @param[in] size Size of mapping in bytes
+		 * @return Pointer to mapped memory
+		 */
+		void* mapBuffer(const BufferHandle &buffer, size_t offset, size_t size);
+
+		/**
+		 * @brief Unmaps memory from a buffer represented by a given
+		 * buffer handle.
+		 *
+		 * @param[in] buffer Buffer handle
+		 */
+		void unmapBuffer(const BufferHandle &buffer);
+
+		/**
+		 * Creates a Sampler with given attributes.
+		 *
+		 * @param[in] magFilter Magnifying filter
+		 * @param[in] minFilter Minimizing filter
+		 * @param[in] mipmapMode Mipmapping filter
+		 * @param[in] addressMode Address mode
+		 * @param[in] mipLodBias Mip level of detail bias
+		 * @return Sampler handle
+		 */
+		[[nodiscard]] SamplerHandle
+		createSampler(SamplerFilterType magFilter, SamplerFilterType minFilter,
+					  SamplerMipmapMode mipmapMode, SamplerAddressMode addressMode,
+					  float mipLodBias = 0.0f,
+					  SamplerBorderColor borderColor = SamplerBorderColor::INT_ZERO_OPAQUE);
+
+		/**
+		 * Creates an #Image with a given format, configuration
+		 * and whether a mipchain should be created.
+		 *
+		 * @param[in] format Image format
+		 * @param[in] config Image configuration
+		 * @param[in] createMipChain Flag to create a mip chain
+		 * @return Image handle
+		 */
+		[[nodiscard]] ImageHandle createImage(vk::Format format,
+											  const ImageConfig& config,
+											  bool createMipChain = false);
+
+		/**
+		 * @brief Fills the image with given data of a specified size
+		 * in bytes.
+		 *
+		 * @param[in] image Image handle
+		 * @param[in] data Image data pointer
+		 * @param[in] size Size of data
+		 * @param[in] firstLayer First image layer
+		 * @param[in] layerCount Image layer count
+		 */
+		void fillImage(const ImageHandle &image,
+					   const void* data,
+					   size_t size,
+					   uint32_t firstLayer,
+					   uint32_t layerCount);
+
+		/**
+		 * @brief Switches the images layout synchronously if possible.
+		 *
+		 * @param[in] image Image handle
+		 * @param[in] layout New image layout
+		 */
+		void switchImageLayout(const ImageHandle &image, vk::ImageLayout layout);
+
+		/**
+		 * @brief Returns the default blit-downsampler.
+		 *
+		 * @return Blit-downsampler
+		 */
+		[[nodiscard]] Downsampler &getDownsampler();
+
+		/**
+		 * Creates a new window and returns it's handle
+		 * @param[in] applicationName Window title
+		 * @param[in] windowWidth Window width
+		 * @param[in] windowHeight Window height
+		 * @param[in] resizeable resizeability bool
+		 * @return windowHandle
+		 */
+		[[nodiscard]] WindowHandle createWindow(const std::string &applicationName,
+												uint32_t windowWidth, uint32_t windowHeight,
+												bool resizeable);
+
+		/**
+		 * Getter for window reference
+		 * @param[in] handle of the window
+		 * @return the window
+		 */
+		[[nodiscard]] Window &getWindow(const WindowHandle &handle);
+
+		/**
+		 * @brief Returns the image format for the current surface
+		 * of the swapchain.
+		 *
+		 * @param[in] handle Swapchain handle
+		 * @return Swapchain image format
+		 */
+		[[nodiscard]] vk::Format getSwapchainFormat(const SwapchainHandle &swapchain) const;
+
+		/**
+		 * @brief Returns the amount of images for the swapchain.
+		 *
+		 * @param[in] handle Swapchain handle
+		 * @return Number of images
+		 */
+		[[nodiscard]] uint32_t getSwapchainImageCount(const SwapchainHandle &swapchain) const;
+
+		/**
+		 * @brief Returns the extent from the current surface of
+		 * the swapchain.
+		 *
+		 * @param[in] handle Swapchain handle
+		 * @return Extent of the swapchains surface
+		 */
+		[[nodiscard]] vk::Extent2D getSwapchainExtent(const SwapchainHandle &swapchain) const;
+
+		/**
+		 * @brief Returns the image width.
+		 *
+		 * @param[in] image Image handle
+		 * @return imageWidth
+		 */
+		[[nodiscard]] uint32_t getImageWidth(const ImageHandle &image);
+
+		/**
+		 * @brief Returns the image height.
+		 *
+		 * @param[in] image Image handle
+		 * @return imageHeight
+		 */
+		[[nodiscard]] uint32_t getImageHeight(const ImageHandle &image);
+
+		/**
+		 * @brief Returns the image depth.
+		 *
+		 * @param[in] image Image handle
+		 * @return imageDepth
+		 */
+		[[nodiscard]] uint32_t getImageDepth(const ImageHandle &image);
+
+		/**
+		 * @brief Returns the image format of the image.
+		 *
+		 * @param[in] image Image handle
+		 * @return imageFormat
+		 */
+		[[nodiscard]] vk::Format getImageFormat(const ImageHandle &image);
+
+		/**
+		 * @brief Returns whether the image supports storage or not.
+		 *
+		 * @param[in] image Image handle
+		 * @return True, if the image supports storage, otherwise false.
+		 */
+		[[nodiscard]] bool isImageSupportingStorage(const ImageHandle &image);
+
+		/**
+		 * @brief Returns the images amount of mip levels.
+		 *
+		 * @param[in] image Image handle
+		 * @return Amount of mip levels
+		 */
+		[[nodiscard]] uint32_t getImageMipLevels(const ImageHandle &image);
+
+		/**
+		 * @brief Returns the images amount of array layers.
+		 *
+		 * @param[in] image Image handle
+		 * @return Amount of array layers
+		 */
+		[[nodiscard]] uint32_t getImageArrayLayers(const ImageHandle &image);
+
+		/**
+		 * @brief Creates a descriptor set layout handle by a set of descriptor bindings.
+		 *
+		 * @param[in] bindings Descriptor bindings
+		 * @return Descriptor set layout handle
+		 */
+		[[nodiscard]] DescriptorSetLayoutHandle
+		createDescriptorSetLayout(const DescriptorBindings &bindings);
+
+		/**
+		 * @brief Creates a new descriptor set
+		 *
+		 * @param[in] layout Handle to the layout that the descriptor set will use
+		 * @return Handle that represents the descriptor set
+		 */
+		[[nodiscard]] DescriptorSetHandle
+		createDescriptorSet(const DescriptorSetLayoutHandle &layout);
 
-    class Core final
-    {
-    private:
-
-        /**
-         * Constructor of #Core requires an @p context.
-         *
-         * @param context encapsulates various Vulkan objects
-         */
-        Core(Context &&context, Window &window, const Swapchain& swapChain,  std::vector<vk::ImageView> imageViews,
-			const CommandResources& commandResources, const SyncResources& syncResources) noexcept;
-        // explicit destruction of default constructor
-        Core() = delete;
-
-		Result acquireSwapchainImage();
-
-        Context m_Context;
-
-        Swapchain                       m_swapchain;
-        Window&                   		m_window;
-
-        std::unique_ptr<PassManager>            m_PassManager;
-        std::unique_ptr<PipelineManager>        m_PipelineManager;
-        std::unique_ptr<DescriptorManager>      m_DescriptorManager;
-        std::unique_ptr<BufferManager>          m_BufferManager;
-        std::unique_ptr<SamplerManager>         m_SamplerManager;
-        std::unique_ptr<ImageManager>           m_ImageManager;
-        std::unique_ptr<CommandStreamManager>   m_CommandStreamManager;
-
-		CommandResources    m_CommandResources;
-		SyncResources       m_SyncResources;
-		uint32_t            m_currentSwapchainImageIndex;
-
-		event_handle<int,int> e_resizeHandle;
-
-        static std::vector<vk::ImageView> createSwapchainImageViews( Context &context, Swapchain& swapChain);
-
-    public:
-        /**
-         * Destructor of #Core destroys the Vulkan objects contained in the core's context.
-         */
-        ~Core() noexcept;
-
-        /**
-         * Copy-constructor of #Core is deleted!
-         *
-         * @param other Other instance of #Context
-         */
-        Core(const Core& other) = delete;
-
-        /**
-         * Move-constructor of #Core uses default behavior!
-         *
-         * @param other Other instance of #Context
-         */
-        Core(Core &&other) = delete; // move-ctor
-
-        /**
-         * Copy assignment operator of #Core is deleted!
-         *
-         * @param other Other instance of #Context
-         * @return Reference to itself
-         */
-        Core & operator=(const Core &other) = delete;
-
-        /**
-         * Move assignment operator of #Core uses default behavior!
-         *
-         * @param other Other instance of #Context
-         * @return Reference to itself
-         */
-        Core & operator=(Core &&other) = delete;
-
-        [[nodiscard]]
-        const Context &getContext() const;
-        
-        [[nodiscard]]
-        const Swapchain& getSwapchain() const;
-
-        /**
-             * Creates a #Core with given @p applicationName and @p applicationVersion for your application.
-             *
-             * It is also possible to require a specific amount of queues, ask for specific queue-flags or
-             * extensions. This function will take care of the required arguments as best as possible.
-             *
-             * To pass a valid version for your application, you should use #VK_MAKE_VERSION().
-             *
-             * @param[in] applicationName Name of the application
-             * @param[in] applicationVersion Version of the application
-             * @param[in] queueFlags (optional) Requested flags of queues
-             * @param[in] instanceExtensions (optional) Requested instance extensions
-             * @param[in] deviceExtensions (optional) Requested device extensions
-             * @return New instance of #Context
-             */
-        static Core create(Window &window,
-                           const char *applicationName,
-                           uint32_t applicationVersion,
-                           std::vector<vk::QueueFlagBits> queueFlags    = {},
-                           std::vector<const char*> instanceExtensions  = {},
-                           std::vector<const char*> deviceExtensions    = {});
-
-        /**
-         * Creates a basic vulkan graphics pipeline using @p config from the pipeline config class and returns it using the @p handle.
-         * Fixed Functions for pipeline are set with standard values.
-         *
-         * @param config a pipeline config object from the pipeline config class
-         * @param handle a handle to return the created vulkan handle
-         * @return True if pipeline creation was successful, False if not
-         */
-        [[nodiscard]]
-        PipelineHandle createGraphicsPipeline(const PipelineConfig &config);
-
-        /**
-         * Creates a basic vulkan compute pipeline using @p shader program and returns it using the @p handle.
-         * Fixed Functions for pipeline are set with standard values.
-         *
-         * @param shader program that hold the compiles compute shader
-         * @param handle a handle to return the created vulkan handle
-         * @return True if pipeline creation was successful, False if not
-         */
-        [[nodiscard]]
-        PipelineHandle createComputePipeline(
-            const ShaderProgram &config, 
-            const std::vector<vk::DescriptorSetLayout> &descriptorSetLayouts);
-
-        /**
-         * Creates a basic vulkan render pass using @p config from the render pass config class and returns it using the @p handle.
-         * Fixed Functions for pipeline are set with standard values.
-         *
-         * @param config a render pass config object from the render pass config class
-         * @param handle a handle to return the created vulkan handle
-         * @return True if render pass creation was successful, False if not
-         */
-        [[nodiscard]]
-        PassHandle createPass(const PassConfig &config);
-
-        /**
-            * Creates a #Buffer with data-type T and @p bufferType
-            * @param type Type of Buffer created
-            * @param count Count of elements of type T
-            * @param memoryType Type of Buffers memory
-            * return Buffer-Object
-            */
-        template<typename T>
-        Buffer<T> createBuffer(vkcv::BufferType type, size_t count, BufferMemoryType memoryType = BufferMemoryType::DEVICE_LOCAL) {
-        	return Buffer<T>::create(m_BufferManager.get(), type, count, memoryType);
-        }
-        
-        /**
-         * Creates a Sampler with given attributes.
-         *
-         * @param magFilter Magnifying filter
-         * @param minFilter Minimizing filter
-         * @param mipmapMode Mipmapping filter
-         * @param addressMode Address mode
-         * @return Sampler handle
-         */
-        [[nodiscard]]
-        SamplerHandle createSampler(SamplerFilterType magFilter, SamplerFilterType minFilter,
-									SamplerMipmapMode mipmapMode, SamplerAddressMode addressMode);
-
-        /**
-         * Creates an #Image with a given format, width, height and depth.
-         *
-         * @param format Image format
-         * @param width Image width
-         * @param height Image height
-         * @param depth Image depth
-         * @return Image-Object
-         */
-        [[nodiscard]]
-        Image createImage(
-			vk::Format  format,
-			uint32_t    width,
-			uint32_t    height,
-			uint32_t    depth = 1,
-			bool        createMipChain = false,
-			bool        supportStorage = false,
-			bool        supportColorAttachment = false);
-
-        /** TODO:
-         *   @param setDescriptions
-         *   @return
-         */
-        [[nodiscard]]
-        DescriptorSetHandle createDescriptorSet(const std::vector<DescriptorBinding> &bindings);
-		void writeDescriptorSet(DescriptorSetHandle handle, const DescriptorWrites& writes);
-
-		DescriptorSet getDescriptorSet(const DescriptorSetHandle handle) const;
-
-		/**
-		 * @brief start recording command buffers and increment frame index
-		*/
-		bool beginFrame(uint32_t& width, uint32_t& height);
-
-		void recordDrawcallsToCmdStream(
-            const CommandStreamHandle       cmdStreamHandle,
-			const PassHandle                renderpassHandle, 
-			const PipelineHandle            pipelineHandle,
-			const PushConstantData          &pushConstantData,
-			const std::vector<DrawcallInfo> &drawcalls,
-			const std::vector<ImageHandle>  &renderTargets);
-
-		void recordComputeDispatchToCmdStream(
-			CommandStreamHandle cmdStream,
-			PipelineHandle computePipeline,
-			const uint32_t dispatchCount[3],
+		/**
+		 * @brief Writes resources bindings to a descriptor set
+		 *
+		 * @param handle Handle of the descriptor set
+		 * @param writes Struct containing the resource bindings to be written
+		 * must be compatible with the descriptor set's layout
+		 */
+		void writeDescriptorSet(DescriptorSetHandle handle, const DescriptorWrites &writes);
+
+		/**
+		 * @brief Start recording command buffers and increment frame index
+		 */
+		bool beginFrame(uint32_t &width, uint32_t &height, const WindowHandle &windowHandle);
+
+		/**
+		 * @brief Records drawcalls to a command stream
+		 *
+		 * @param cmdStreamHandle Handle of the command stream that the drawcalls are recorded into
+		 * @param pipelineHandle Handle of the pipeline that is used for the drawcalls
+		 * @param pushConstants Push constants that are used for the drawcalls, ignored if constant
+		 * size is set to 0
+		 * @param drawcalls Information about each drawcall, consisting of mesh handle, descriptor
+		 * set bindings and instance count
+		 * @param renderTargets Image handles that are used as render targets
+		 * @param windowHandle Window handle that is used to retrieve the corresponding swapchain
+		 */
+		void recordDrawcallsToCmdStream(const CommandStreamHandle &cmdStreamHandle,
+										const GraphicsPipelineHandle &pipelineHandle,
+										const PushConstants &pushConstants,
+										const std::vector<InstanceDrawcall> &drawcalls,
+										const std::vector<ImageHandle> &renderTargets,
+										const WindowHandle &windowHandle);
+
+		/**
+		 * @brief Records indirect drawcalls to a command stream
+		 *
+		 * @param cmdStreamHandle Handle of the command stream that the drawcalls are recorded into
+		 * @param pipelineHandle Handle of the pipeline that is used for the drawcalls
+		 * @param pushConstantData Push constants that are used for the drawcalls, ignored if
+		 * constant size is set to 0
+		 * @param drawcalls Information about each drawcall, consisting of mesh handle, descriptor
+		 * set bindings and draw count
+		 * @param renderTargets Image handles that are used as render targets
+		 * @param windowHandle Window handle that is used to retrieve the corresponding swapchain
+		 */
+		void recordIndirectDrawcallsToCmdStream(const CommandStreamHandle cmdStreamHandle,
+												const GraphicsPipelineHandle &pipelineHandle,
+												const PushConstants &pushConstantData,
+												const std::vector<IndirectDrawcall> &drawcalls,
+												const std::vector<ImageHandle> &renderTargets,
+												const WindowHandle &windowHandle);
+
+		/**
+		 * @brief Records mesh shader drawcalls to a command stream
+		 *
+		 * @param cmdStreamHandle Handle of the command stream that the drawcalls are recorded into
+		 * @param pipelineHandle Handle of the pipeline that is used for the drawcalls
+		 * @param pushConstantData Push constants that are used for the drawcalls, ignored if
+		 * constant size is set to 0
+		 * @param drawcalls Information about each drawcall, consisting of descriptor set bindings
+		 * and task shader task count
+		 * @param renderTargets Image handles that are used as render targets
+		 * @param windowHandle Window handle that is used to retrieve the corresponding swapchain
+		 */
+		void recordMeshShaderDrawcalls(const CommandStreamHandle &cmdStreamHandle,
+									   const GraphicsPipelineHandle &pipelineHandle,
+									   const PushConstants &pushConstantData,
+									   const std::vector<TaskDrawcall> &drawcalls,
+									   const std::vector<ImageHandle> &renderTargets,
+									   const WindowHandle &windowHandle);
+
+		/**
+		 * Records the rtx ray generation to the @p cmdStreamHandle.
+		 * Currently only supports @p closestHit, @p rayGen and @c miss shaderstages @c.
+		 *
+		 * @param cmdStreamHandle The command stream handle which receives relevant commands for
+		 * drawing.
+		 * @param rtxPipeline The raytracing pipeline from the RTXModule.
+		 * @param rtxPipelineLayout The raytracing pipeline layout from the RTXModule.
+		 * @param rgenRegion The shader binding table region for ray generation shaders.
+		 * @param rmissRegion The shader binding table region for ray miss shaders.
+		 * @param rchitRegion The shader binding table region for ray closest hit shaders.
+		 * @param rcallRegion The shader binding table region for callable shaders.
+		 * @param descriptorSetUsages The descriptor set usages.
+		 * @param pushConstants The push constants.
+		 * @param windowHandle The window handle defining in which window to render.
+		 */
+		void recordRayGenerationToCmdStream(
+			CommandStreamHandle cmdStreamHandle, vk::Pipeline rtxPipeline,
+			vk::PipelineLayout rtxPipelineLayout, vk::StridedDeviceAddressRegionKHR rgenRegion,
+			vk::StridedDeviceAddressRegionKHR rmissRegion,
+			vk::StridedDeviceAddressRegionKHR rchitRegion,
+			vk::StridedDeviceAddressRegionKHR rcallRegion,
 			const std::vector<DescriptorSetUsage> &descriptorSetUsages,
-			const PushConstantData& pushConstantData);
+			const PushConstants &pushConstants, const WindowHandle &windowHandle);
 
 		/**
-		 * @brief end recording and present image
-		*/
-		void endFrame();
+		 * @brief Record a compute shader dispatch into a command stream
+		 *
+		 * @param cmdStream Handle of the command stream that the dispatch is recorded into
+		 * @param computePipeline Handle of the pipeline that is used for the dispatch
+		 * @param dispatchSize How many work groups are dispatched
+		 * @param descriptorSetUsages Descriptor set usages of the dispatch
+		 * @param pushConstants Push constant data for the dispatch
+		 */
+		void
+		recordComputeDispatchToCmdStream(const CommandStreamHandle &cmdStream,
+										 const ComputePipelineHandle &computePipeline,
+										 const DispatchSize &dispatchSize,
+										 const std::vector<DescriptorSetUsage> &descriptorSetUsages,
+										 const PushConstants &pushConstants);
 
 		/**
-		 * Submit a command buffer to any queue of selected type. The recording can be customized by a
-		 * custom record-command-function. If the command submission has finished, an optional finish-function
-		 * will be called.
+		 * @brief Record the start of a debug label into a command stream.
+		 * Debug labels are displayed in GPU debuggers, such as RenderDoc
 		 *
-		 * @param submitInfo Submit information
-		 * @param record Record-command-function
-		 * @param finish Finish-command-function or nullptr
+		 * @param cmdStream Handle of the command stream that the label start is recorded into
+		 * @param label Label name, which is displayed in a debugger
+		 * @param color Display color for the label in a debugger
+		 */
+		void recordBeginDebugLabel(const CommandStreamHandle &cmdStream, const std::string &label,
+								   const std::array<float, 4> &color);
+
+		/**
+		 * @brief Record the end of a debug label into a command stream
+		 * @param cmdStream Handle of the command stream that the label end is recorded into
 		 */
-		void recordAndSubmitCommandsImmediate(
-			const SubmitInfo            &submitInfo, 
-			const RecordCommandFunction &record, 
-			const FinishCommandFunction &finish);
+		void recordEndDebugLabel(const CommandStreamHandle &cmdStream);
 
+		/**
+		 * @brief Record an indirect compute shader dispatch into a command stream
+		 *
+		 * @param cmdStream Handle of the command stream that the indirect dispatch is recorded into
+		 * @param computePipeline Handle of the pipeline that is used for the indirect dispatch
+		 * @param buffer GPU Buffer from which the dispatch counts are read
+		 * @param bufferArgOffset Offset into the GPU Buffer from where the dispatch counts are read
+		 * @param descriptorSetUsages Descriptor set bindings of the indirect dispatch
+		 * @param pushConstants Push constant data for the indirect dispatch
+		 */
+		void recordComputeIndirectDispatchToCmdStream(
+			const CommandStreamHandle cmdStream, const ComputePipelineHandle computePipeline,
+			const vkcv::BufferHandle buffer, const size_t bufferArgOffset,
+			const std::vector<DescriptorSetUsage> &descriptorSetUsages,
+			const PushConstants &pushConstants);
+
+		/**
+		 * @brief End recording and present image
+		 */
+		void endFrame(const WindowHandle &windowHandle);
+
+		/**
+		 * @brief Create a new command stream
+		 *
+		 * @param queueType The type of queue to which the command stream will be submitted to
+		 * @return Handle which represents the command stream
+		 */
 		CommandStreamHandle createCommandStream(QueueType queueType);
 
-		void recordCommandsToStream(
-			const CommandStreamHandle   cmdStreamHandle,
-			const RecordCommandFunction &record,
-			const FinishCommandFunction &finish);
-
-		void submitCommandStream(const CommandStreamHandle handle);
-		void prepareSwapchainImageForPresent(const CommandStreamHandle handle);
-		void prepareImageForSampling(const CommandStreamHandle cmdStream, const ImageHandle image);
-		void prepareImageForStorage(const CommandStreamHandle cmdStream, const ImageHandle image);
-		void recordImageMemoryBarrier(const CommandStreamHandle cmdStream, const ImageHandle image);
-		void recordBufferMemoryBarrier(const CommandStreamHandle cmdStream, const BufferHandle buffer);
-		
-		vk::ImageView getSwapchainImageView() const;
-		
-    };
-}
+		/**
+		 * @brief Record commands to a command stream by providing a function
+		 *
+		 * @param cmdStreamHandle Handle of the command stream to record to
+		 * @param record Recording function
+		 * @param finish Finish function, called after execution of commands is finished
+		 */
+		void recordCommandsToStream(const CommandStreamHandle &stream,
+									const RecordCommandFunction &record,
+									const FinishCommandFunction &finish);
+
+		/**
+		 * @brief Submit command stream to GPU for actual execution
+		 *
+		 * @param[in] handle Command stream to submit
+		 * @param[in] signalRendering Flag to specify if the command stream finishes rendering
+		 */
+		void submitCommandStream(const CommandStreamHandle &stream, bool signalRendering = true);
+
+		/**
+		 * @brief Prepare swapchain image for presentation to screen.
+		 * Handles internal state such as image format, also acts as a memory barrier
+		 *
+		 * @param handle Handle of the command stream to record the preparation commands to
+		 */
+		void prepareSwapchainImageForPresent(const CommandStreamHandle &handle);
+
+		/**
+		 * @brief Prepare image for use as a sampled image.
+		 * Handles internal state such as image format, also acts as a memory barrier
+		 *
+		 * @param[in] cmdStream Handle of the command stream to record the preparation commands to
+		 * @param[in] image Handle of the image to prepare
+		 * @param[in] mipLevelCount Count of mip levels to prepare
+		 * @param[in] mipLevelOffset Offset to start preparing mip levels
+		 */
+		void prepareImageForSampling(const CommandStreamHandle &cmdStream, const ImageHandle &image,
+									 uint32_t mipLevelCount = 0, uint32_t mipLevelOffset = 0);
+
+		/**
+		 * @brief Prepare image for use as a storage image.
+		 * Handles internal state such as image format, also acts as a memory barrier
+		 *
+		 * @param[in] cmdStream Handle of the command stream to record the preparation commands to
+		 * @param[in] image Handle of the image to prepare
+		 * @param[in] mipLevelCount Count of mip levels to prepare
+		 * @param[in] mipLevelOffset Offset to start preparing mip levels
+		 */
+		void prepareImageForStorage(const CommandStreamHandle &cmdStream, const ImageHandle &image,
+									uint32_t mipLevelCount = 0, uint32_t mipLevelOffset = 0);
+
+		/**
+		 * @brief Manual trigger to record commands to prepare an image for use as an attachment
+		 *
+		 * normally layout transitions for attachments are handled by the core
+		 * however for manual vulkan use, e.g. ImGui integration, this function is exposed
+		 * this is also why the command buffer is passed directly, instead of the command stream
+		 * handle
+		 *
+		 * @param cmdBuffer The vulkan command buffer to record to
+		 * @param image Handle of the image to prepare
+		 */
+		void prepareImageForAttachmentManually(const vk::CommandBuffer &cmdBuffer,
+											   const ImageHandle &image);
+
+		/**
+		 * @brief Indicate an external change of an image's layout
+		 *
+		 * if manual vulkan work, e.g. ImGui integration, changes an image layout this function must
+		 * be used to update the internal image state
+		 *
+		 * @param image Handle of the image whose layout was changed
+		 * @param layout The current layout of the image
+		 */
+		void updateImageLayoutManual(const vkcv::ImageHandle &image, const vk::ImageLayout layout);
+
+		/**
+		 * @brief Records a memory barrier to synchronize subsequent accesses to the image's data
+		 *
+		 * @param cmdStream Handle of the command stream to record the barrier to
+		 * @param image Handle of the image the barrier belongs to
+		 */
+		void recordImageMemoryBarrier(const CommandStreamHandle &cmdStream,
+									  const ImageHandle &image);
+
+		/**
+		 * @brief Records a buffer barrier to synchronize subsequent accesses to the buffer's data
+		 *
+		 * @param cmdStream Handle of the command stream to record the barrier to
+		 * @param buffer Handle of the buffer the barrier belongs to
+		 */
+		void recordBufferMemoryBarrier(const CommandStreamHandle &cmdStream,
+									   const BufferHandle &buffer);
+
+		/**
+		 * @brief Resolve a source MSAA image into a destination image for further use
+		 *
+		 * @param cmdStream Handle of the command stream to record the resolve to
+		 * @param src The MSAA image that is resolved
+		 * @param dst The target non-MSAA image that is resolved into
+		 */
+		void resolveMSAAImage(const CommandStreamHandle &cmdStream, const ImageHandle &src,
+							  const ImageHandle &dst);
+
+		/**
+		 * @return Vulkan image view of the current swapchain image
+		 */
+		[[nodiscard]] vk::ImageView getSwapchainImageView() const;
+
+		/**
+		 * @brief Records a generic memory barrier to a command stream
+		 *
+		 * @param cmdStream Handle of the command stream the barrier is recorded to
+		 */
+		void recordMemoryBarrier(const CommandStreamHandle &cmdStream);
+
+		/**
+		 * @brief Record a blit (bit block image transfer) of a source image into a destination
+		 * image, mip 0 is used for both
+		 *
+		 * @param cmdStream Handle of the command stream the blit operation is recorded into
+		 * @param src The source image that is read from
+		 * @param dst The destination image that is written into
+		 * @param filterType The type of interpolation that is used
+		 */
+		void recordBlitImage(const CommandStreamHandle &cmdStream, const ImageHandle &src,
+							 const ImageHandle &dst, SamplerFilterType filterType);
+
+		/**
+		 * @brief Sets a debug label to a buffer handle.
+		 *
+		 * @param[in,out] handle Buffer handle
+		 * @param[in] label Debug label
+		 */
+		void setDebugLabel(const BufferHandle &handle, const std::string &label);
+
+		/**
+		 * @brief Sets a debug label to a pass handle.
+		 *
+		 * @param[in,out] handle Pass handle
+		 * @param[in] label Debug label
+		 */
+		void setDebugLabel(const PassHandle &handle, const std::string &label);
+
+		/**
+		 * @brief Sets a debug label to a graphics pipeline handle.
+		 *
+		 * @param[in,out] handle Graphics pipeline handle
+		 * @param[in] label Debug label
+		 */
+		void setDebugLabel(const GraphicsPipelineHandle &handle, const std::string &label);
+
+		/**
+		 * @brief Sets a debug label to a compute pipeline handle.
+		 *
+		 * @param[in,out] handle Compute pipeline handle
+		 * @param[in] label Debug label
+		 */
+		void setDebugLabel(const ComputePipelineHandle &handle, const std::string &label);
+
+		/**
+		 * @brief Sets a debug label to a descriptor set handle.
+		 *
+		 * @param[in,out] handle Descriptor set handle
+		 * @param[in] label Debug label
+		 */
+		void setDebugLabel(const DescriptorSetHandle &handle, const std::string &label);
+
+		/**
+		 * @brief Sets a debug label to a sampler handle.
+		 *
+		 * @param[in,out] handle Sampler handle
+		 * @param[in] label Debug label
+		 */
+		void setDebugLabel(const SamplerHandle &handle, const std::string &label);
+
+		/**
+		 * @brief Sets a debug label to an image handle.
+		 *
+		 * @param[in,out] handle Image handle
+		 * @param[in] label Debug label
+		 */
+		void setDebugLabel(const ImageHandle &handle, const std::string &label);
+
+		/**
+		 * @brief Sets a debug label to a command stream handle.
+		 *
+		 * @param[in,out] handle Command stream handle
+		 * @param[in] label Debug label
+		 */
+		void setDebugLabel(const CommandStreamHandle &handle, const std::string &label);
+
+		/**
+		 * @brief Runs the application in the current until all windows get closed.
+		 *
+		 * The frame callback will be called for each window every single frame.
+		 *
+		 * @param[in] frame Frame callback
+		 */
+		void run(const WindowFrameFunction &frame);
+
+		/**
+		 * @brief Return the underlying vulkan handle for a render pass
+		 * by its given pass handle.
+		 *
+		 * @param[in] handle Pass handle
+		 * @return Vulkan render pass
+		 */
+		[[nodiscard]] vk::RenderPass getVulkanRenderPass(const PassHandle &handle) const;
+
+		/**
+		 * @brief Return the underlying vulkan handle for a pipeline
+		 * by its given graphics pipeline handle.
+		 *
+		 * @param[in] handle Graphics pipeline handle
+		 * @return Vulkan pipeline
+		 */
+		[[nodiscard]] vk::Pipeline getVulkanPipeline(const GraphicsPipelineHandle &handle) const;
+
+		/**
+		 * @brief Return the underlying vulkan handle for a pipeline
+		 * by its given compute pipeline handle.
+		 *
+		 * @param[in] handle Compute pipeline handle
+		 * @return Vulkan pipeline
+		 */
+		[[nodiscard]] vk::Pipeline getVulkanPipeline(const ComputePipelineHandle &handle) const;
+
+		/**
+		 * @brief Return the underlying vulkan handle for a descriptor set layout
+		 * by its given descriptor set layout handle.
+		 *
+		 * @param[in] handle Descriptor set layout handle
+		 * @return Vulkan descriptor set layout
+		 */
+		[[nodiscard]] vk::DescriptorSetLayout
+		getVulkanDescriptorSetLayout(const DescriptorSetLayoutHandle &handle) const;
+
+		/**
+		 * @brief Return the underlying vulkan handle for a descriptor set
+		 * by its given descriptor set handle.
+		 *
+		 * @param[in] handle Descriptor set handle
+		 * @return Vulkan descriptor set
+		 */
+		[[nodiscard]] vk::DescriptorSet
+		getVulkanDescriptorSet(const DescriptorSetHandle &handle) const;
+
+		/**
+		 * @brief Return the underlying vulkan handle for a buffer
+		 * by its given buffer handle.
+		 *
+		 * @param[in] handle Buffer handle
+		 * @return Vulkan buffer
+		 */
+		[[nodiscard]] vk::Buffer getVulkanBuffer(const BufferHandle &handle) const;
+
+		/**
+		 * @brief Return the underlying vulkan handle for a sampler
+		 * by its given sampler handle.
+		 *
+		 * @param[in] handle Sampler handle
+		 * @return Vulkan sampler
+		 */
+		[[nodiscard]] vk::Sampler getVulkanSampler(const SamplerHandle &handle) const;
+
+		/**
+		 * @brief Return the underlying vulkan handle for a image
+		 * by its given image handle.
+		 *
+		 * @param[in] handle Image handle
+		 * @return Vulkan image
+		 */
+		[[nodiscard]] vk::Image getVulkanImage(const ImageHandle &handle) const;
+
+		/**
+		 * @brief Return the underlying vulkan handle for a image view
+		 * by its given image handle.
+		 *
+		 * @param[in] handle Image handle
+		 * @return Vulkan image view
+		 */
+		[[nodiscard]] vk::ImageView getVulkanImageView(const ImageHandle &handle) const;
+
+		/**
+		 * @brief Return the underlying vulkan handle for a device memory
+		 * by its given buffer handle.
+		 *
+		 * @param[in] handle Buffer handle
+		 * @return Vulkan device memory
+		 */
+		[[nodiscard]] vk::DeviceMemory getVulkanDeviceMemory(const BufferHandle &handle) const;
+
+		/**
+		 * @brief Return the underlying vulkan handle for a device memory
+		 * by its given image handle.
+		 *
+		 * @param[in] handle Image handle
+		 * @return Vulkan device memory
+		 */
+		[[nodiscard]] vk::DeviceMemory getVulkanDeviceMemory(const ImageHandle &handle) const;
+	};
+} // namespace vkcv
diff --git a/include/vkcv/DescriptorBinding.hpp b/include/vkcv/DescriptorBinding.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c97d1dfebc6d4415c04f51a761ddcdbc34ba6d68
--- /dev/null
+++ b/include/vkcv/DescriptorBinding.hpp
@@ -0,0 +1,31 @@
+#pragma once
+/**
+ * @authors Artur Wasmut, Tobias Frisch, Simeon Hermann, Alexander Gauggel, Vanessa Karolek
+ * @file vkcv/DescriptorConfig.hpp
+ * @brief Structures to handle descriptor bindings.
+ */
+
+#include <unordered_map>
+
+#include "DescriptorTypes.hpp"
+#include "ShaderStage.hpp"
+
+namespace vkcv {
+
+	/**
+	 * @brief Structure to store details from a descriptor binding.
+	 */
+	struct DescriptorBinding {
+		uint32_t bindingID;
+		DescriptorType descriptorType;
+		uint32_t descriptorCount;
+		ShaderStages shaderStages;
+		bool variableCount;
+		bool partialBinding;
+
+		bool operator==(const DescriptorBinding &other) const;
+	};
+
+	typedef std::unordered_map<uint32_t, DescriptorBinding> DescriptorBindings;
+
+} // namespace vkcv
diff --git a/include/vkcv/DescriptorConfig.hpp b/include/vkcv/DescriptorConfig.hpp
deleted file mode 100644
index c6d0dfd1bc60988afb8b6a9326a8d50d8a4ea32e..0000000000000000000000000000000000000000
--- a/include/vkcv/DescriptorConfig.hpp
+++ /dev/null
@@ -1,49 +0,0 @@
-#pragma once
-
-#include <vulkan/vulkan.hpp>
-
-#include "vkcv/Handles.hpp"
-#include "vkcv/ShaderStage.hpp"
-
-namespace vkcv
-{
-    struct DescriptorSet
-    {
-        vk::DescriptorSet       vulkanHandle;
-        vk::DescriptorSetLayout layout;
-    };
-
-    /*
-    * All the types of descriptors (resources) that can be retrieved by the shaders
-    */
-    enum class DescriptorType
-    {
-        UNIFORM_BUFFER,
-        STORAGE_BUFFER,
-        SAMPLER,
-        IMAGE_SAMPLED,
-		IMAGE_STORAGE
-    };    
-    
-    /*
-    * One binding for a descriptor set
-    * @param[in] a unique binding ID
-    * @param[in] a descriptor type
-    * @param[in] the number of descriptors of this type (arrays of the same type possible)
-    * @param[in] the shader stage where the descriptor is supposed to be retrieved
-    */
-    struct DescriptorBinding
-    {
-        DescriptorBinding(
-            uint32_t bindingID,
-            DescriptorType descriptorType,
-            uint32_t descriptorCount,
-            ShaderStage shaderStage
-        ) noexcept;
-        
-        uint32_t bindingID;
-        DescriptorType descriptorType;
-        uint32_t descriptorCount;
-        ShaderStage shaderStage;
-    };
-}
diff --git a/include/vkcv/DescriptorSetUsage.hpp b/include/vkcv/DescriptorSetUsage.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..098d1846c35b3724fb2540ebb41a1b6cd61d62fc
--- /dev/null
+++ b/include/vkcv/DescriptorSetUsage.hpp
@@ -0,0 +1,26 @@
+#pragma once
+/**
+ * @authors Tobias Frisch
+ * @file vkcv/DescriptorUsage.hpp
+ * @brief Structures to handle descriptor usages.
+ */
+
+#include <vector>
+
+#include "Handles.hpp"
+
+namespace vkcv {
+
+	/**
+	 * @brief Structure to configure a descriptor set usage.
+	 */
+	struct DescriptorSetUsage {
+		uint32_t location;
+		DescriptorSetHandle descriptorSet;
+		std::vector<uint32_t> dynamicOffsets;
+	};
+
+	DescriptorSetUsage useDescriptorSet(uint32_t location, const DescriptorSetHandle &descriptorSet,
+										const std::vector<uint32_t> &dynamicOffsets = {});
+
+} // namespace vkcv
diff --git a/include/vkcv/DescriptorTypes.hpp b/include/vkcv/DescriptorTypes.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..75035b2071554e8b2fe7c8309b5b8269c0c0ca72
--- /dev/null
+++ b/include/vkcv/DescriptorTypes.hpp
@@ -0,0 +1,56 @@
+#pragma once
+/**
+ * @authors Artur Wasmut, Tobias Frisch, Simeon Hermann, Alexander Gauggel, Vanessa Karolek
+ * @file vkcv/DescriptorConfig.hpp
+ * @brief Enum classes to handle descriptor types.
+ */
+
+#include <vulkan/vulkan.hpp>
+
+namespace vkcv {
+
+	/**
+	 * @brief Enum class to specify the type of a descriptor set binding.
+	 */
+	enum class DescriptorType {
+		UNIFORM_BUFFER,
+		STORAGE_BUFFER,
+		SAMPLER,
+		IMAGE_SAMPLED,
+		IMAGE_STORAGE,
+		UNIFORM_BUFFER_DYNAMIC,
+		STORAGE_BUFFER_DYNAMIC,
+		ACCELERATION_STRUCTURE_KHR
+	};
+
+	/**
+	 * @brief Converts the descriptor type from the frameworks enumeration
+	 * to the Vulkan type specifier.
+	 *
+	 * @param[in] type Descriptor type
+	 * @return Vulkan descriptor type
+	 */
+	constexpr vk::DescriptorType getVkDescriptorType(DescriptorType type) noexcept {
+		switch (type) {
+		case DescriptorType::UNIFORM_BUFFER:
+			return vk::DescriptorType::eUniformBuffer;
+		case DescriptorType::UNIFORM_BUFFER_DYNAMIC:
+			return vk::DescriptorType::eUniformBufferDynamic;
+		case DescriptorType::STORAGE_BUFFER:
+			return vk::DescriptorType::eStorageBuffer;
+		case DescriptorType::STORAGE_BUFFER_DYNAMIC:
+			return vk::DescriptorType::eStorageBufferDynamic;
+		case DescriptorType::SAMPLER:
+			return vk::DescriptorType::eSampler;
+		case DescriptorType::IMAGE_SAMPLED:
+			return vk::DescriptorType::eSampledImage;
+		case DescriptorType::IMAGE_STORAGE:
+			return vk::DescriptorType::eStorageImage;
+		case DescriptorType::ACCELERATION_STRUCTURE_KHR:
+			return vk::DescriptorType::eAccelerationStructureKHR;
+		default:
+			return vk::DescriptorType::eMutableVALVE;
+		}
+	}
+
+} // namespace vkcv
diff --git a/include/vkcv/DescriptorWrites.hpp b/include/vkcv/DescriptorWrites.hpp
index f28a6c91e189b13413ffefec0f05e5a0a358ee26..caff4cef865c2cfc909e0aa41974a18e5d18a9b3 100644
--- a/include/vkcv/DescriptorWrites.hpp
+++ b/include/vkcv/DescriptorWrites.hpp
@@ -1,48 +1,207 @@
 #pragma once
-#include "Handles.hpp"
+/**
+ * @authors Artur Wasmut, Tobias Frisch, Alexander Gauggel, Vanessa Karolek
+ * @file vkcv/DescriptorWrites.hpp
+ * @brief Structures to handle descriptor writes.
+ */
+
 #include <vector>
+#include <vulkan/vulkan.hpp>
+
+#include "Handles.hpp"
 
 namespace vkcv {
+
+	/**
+	 * @brief Structure to store details writing a sampled image to a descriptor set.
+	 */
 	struct SampledImageDescriptorWrite {
-		inline SampledImageDescriptorWrite(uint32_t binding, ImageHandle image, uint32_t mipLevel = 0, bool useGeneralLayout = false)
-		    : binding(binding), image(image), mipLevel(mipLevel), useGeneralLayout(useGeneralLayout) {};
-		uint32_t	binding;
-		ImageHandle	image;
-		uint32_t    mipLevel;
-		bool        useGeneralLayout;
+		uint32_t binding;
+		ImageHandle image;
+		uint32_t mipLevel;
+		bool useGeneralLayout;
+		uint32_t arrayIndex;
+		uint32_t mipCount;
+		bool arrayView;
 	};
 
+	/**
+	 * @brief Structure to store details writing a storage image to a descriptor set.
+	 */
 	struct StorageImageDescriptorWrite {
-		inline StorageImageDescriptorWrite(uint32_t binding, ImageHandle image, uint32_t mipLevel = 0) 
-			: binding(binding), image(image), mipLevel(mipLevel) {};
-		uint32_t	binding;
-		ImageHandle	image;
-		uint32_t	mipLevel;
+		uint32_t binding;
+		ImageHandle image;
+		uint32_t mipLevel;
+		uint32_t mipCount;
+		bool arrayView;
 	};
 
-	struct UniformBufferDescriptorWrite {
-		inline UniformBufferDescriptorWrite(uint32_t binding, BufferHandle buffer) : binding(binding), buffer(buffer) {};
-		uint32_t		binding;
-		BufferHandle	buffer;
+	/**
+	 * @brief Structure to store details writing a buffer to a descriptor set.
+	 */
+	struct BufferDescriptorWrite {
+		uint32_t binding;
+		BufferHandle buffer;
+		bool dynamic;
+		uint32_t offset;
+		uint32_t size;
 	};
 
-	struct StorageBufferDescriptorWrite {
-		inline StorageBufferDescriptorWrite(uint32_t binding, BufferHandle buffer) : binding(binding), buffer(buffer) {};
-		uint32_t		binding;
-		BufferHandle	buffer;
+	/**
+	 * @brief Structure to store details writing a sampler to a descriptor set.
+	 */
+	struct SamplerDescriptorWrite {
+		uint32_t binding;
+		SamplerHandle sampler;
 	};
 
-	struct SamplerDescriptorWrite {
-		inline SamplerDescriptorWrite(uint32_t binding, SamplerHandle sampler) : binding(binding), sampler(sampler) {};
-		uint32_t		binding;
-		SamplerHandle	sampler;
+	/**
+	 * @brief Structure to store details writing an acceleration structure to
+	 * a descriptor set.
+	 */
+	struct AccelerationDescriptorWrite {
+		uint32_t binding;
+		std::vector<vk::AccelerationStructureKHR> structures;
 	};
 
-	struct DescriptorWrites {
-		std::vector<SampledImageDescriptorWrite>		sampledImageWrites;
-		std::vector<StorageImageDescriptorWrite>		storageImageWrites;
-		std::vector<UniformBufferDescriptorWrite>	    uniformBufferWrites;
-		std::vector<StorageBufferDescriptorWrite>	    storageBufferWrites;
-		std::vector<SamplerDescriptorWrite>			    samplerWrites;
+	/**
+	 * @brief Class to store details about writing to
+	 * a descriptor set and its bindings.
+	 */
+	class DescriptorWrites {
+	private:
+		std::vector<SampledImageDescriptorWrite> m_sampledImageWrites;
+		std::vector<StorageImageDescriptorWrite> m_storageImageWrites;
+		std::vector<BufferDescriptorWrite> m_uniformBufferWrites;
+		std::vector<BufferDescriptorWrite> m_storageBufferWrites;
+		std::vector<SamplerDescriptorWrite> m_samplerWrites;
+		std::vector<AccelerationDescriptorWrite> m_accelerationWrites;
+
+	public:
+		/**
+		 * @brief Adds an entry to write an image to a given binding
+		 * of a descriptor set to sample from it using specific details.
+		 *
+		 * @param[in] binding Binding index
+		 * @param[in] image Image handle
+		 * @param[in] mipLevel Mip level index
+		 * @param[in] useGeneralLayout Flag to use a general layout
+		 * @param[in] arrayIndex Image array index
+		 * @param[in] mipCount Mip level count
+		 * @return Instance of descriptor writes
+		 */
+		DescriptorWrites &writeSampledImage(uint32_t binding, const ImageHandle& image,
+											uint32_t mipLevel = 0, bool useGeneralLayout = false,
+											uint32_t arrayIndex = 0, uint32_t mipCount = 1,
+											bool arrayView = false);
+
+		/**
+		 * @brief Adds an entry to write an image to a given binding
+		 * of a descriptor set to store into it using specific details.
+		 *
+		 * @param[in] binding Binding index
+		 * @param[in,out] image Image handle
+		 * @param[in] mipLevel Mip level index
+		 * @param[in] mipCount Mip level count
+		 * @return Instance of descriptor writes
+		 */
+		DescriptorWrites &writeStorageImage(uint32_t binding, const ImageHandle& image,
+											uint32_t mipLevel = 0, uint32_t mipCount = 1,
+											bool arrayView = false);
+
+		/**
+		 * @brief Adds an entry to write a buffer to a given binding
+		 * of a descriptor set as uniform buffer using specific details.
+		 *
+		 * @param[in] binding Binding index
+		 * @param[in] buffer Buffer handle
+		 * @param[in] dynamic Flag to use dynamic access
+		 * @param[in] offset Offset for buffer access range
+		 * @param[in] size Size of the buffer access range
+		 * @return Instance of descriptor writes
+		 */
+		DescriptorWrites &writeUniformBuffer(uint32_t binding, const BufferHandle& buffer,
+											 bool dynamic = false, uint32_t offset = 0,
+											 uint32_t size = 0);
+
+		/**
+		 * @brief Adds an entry to write a buffer to a given binding
+		 * of a descriptor set as storage buffer using specific details.
+		 *
+		 * @param[in] binding Binding index
+		 * @param[in] buffer Buffer handle
+		 * @param[in,out] dynamic Flag to use dynamic access
+		 * @param[in] offset Offset for buffer access range
+		 * @param[in] size Size of the buffer access range
+		 * @return Instance of descriptor writes
+		 */
+		DescriptorWrites &writeStorageBuffer(uint32_t binding, const BufferHandle& buffer,
+											 bool dynamic = false, uint32_t offset = 0,
+											 uint32_t size = 0);
+
+		/**
+		 * @brief Adds an entry to write a sampler to a given binding
+		 * of a descriptor set.
+		 *
+		 * @param[in] binding Binding index
+		 * @param[in] sampler Sampler handle
+		 * @return Instance of descriptor writes
+		 */
+		DescriptorWrites &writeSampler(uint32_t binding, const SamplerHandle& sampler);
+
+		/**
+		 * @brief Adds an entry for acceleration to a given binding
+		 * of a descriptor set.
+		 *
+		 * @param[in] binding Binding index
+		 * @param[in] structures Acceleration structures
+		 * @return Instance of descriptor writes
+		 */
+		DescriptorWrites &
+		writeAcceleration(uint32_t binding,
+						  const std::vector<vk::AccelerationStructureKHR> &structures);
+
+		/**
+		 * @brief Returns the list of stored write entries for sampled images.
+		 *
+		 * @return Sampled image write details
+		 */
+		[[nodiscard]] const std::vector<SampledImageDescriptorWrite> &getSampledImageWrites() const;
+
+		/**
+		 * @brief Returns the list of stored write entries for storage images.
+		 *
+		 * @return Storage image write details
+		 */
+		[[nodiscard]] const std::vector<StorageImageDescriptorWrite> &getStorageImageWrites() const;
+
+		/**
+		 * @brief Returns the list of stored write entries for uniform buffers.
+		 *
+		 * @return Uniform buffers write details
+		 */
+		[[nodiscard]] const std::vector<BufferDescriptorWrite> &getUniformBufferWrites() const;
+
+		/**
+		 * @brief Returns the list of stored write entries for storage buffers.
+		 *
+		 * @return Storage buffers write details
+		 */
+		[[nodiscard]] const std::vector<BufferDescriptorWrite> &getStorageBufferWrites() const;
+
+		/**
+		 * @brief Returns the list of stored write entries for samplers.
+		 *
+		 * @return Samplers write details
+		 */
+		[[nodiscard]] const std::vector<SamplerDescriptorWrite> &getSamplerWrites() const;
+
+		/**
+		 * @brief Returns the list of stored write entries for accelerations.
+		 *
+		 * @return Accelerations write details
+		 */
+		[[nodiscard]] const std::vector<AccelerationDescriptorWrite> &getAccelerationWrites() const;
 	};
-}
\ No newline at end of file
+
+} // namespace vkcv
\ No newline at end of file
diff --git a/include/vkcv/DispatchSize.hpp b/include/vkcv/DispatchSize.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7f8a27feef9262968cb62bc451c8a0169d0ca9e2
--- /dev/null
+++ b/include/vkcv/DispatchSize.hpp
@@ -0,0 +1,109 @@
+#pragma once
+/**
+ * @authors Tobias Frisch
+ * @file vkcv/DispatchSize.hpp
+ * @brief Class to handle dispatch sizes.
+ */
+
+#include <array>
+#include <vulkan/vulkan.hpp>
+
+namespace vkcv {
+
+	/**
+	 * @brief Class representing a dispatch size to invoke a compute pipeline with.
+	 */
+	class DispatchSize final {
+	private:
+		std::array<uint32_t, 3> m_Dispatch;
+
+	public:
+		/**
+		 * Implicit constructor to convert an unsigned integer to a
+		 * one dimensional dispatch size.
+		 *
+		 * @param[in] count Count of invocations
+		 */
+		DispatchSize(uint32_t count);
+
+		/**
+		 * Explicit constructor to create a dispatch size with two or
+		 * three dimensions setting each value specifically.
+		 *
+		 * @param[in] dimensionX Size of X dimension
+		 * @param[in] dimentionY Size of Y dimension
+		 * @param[in] dimensionZ Size of Z dimension (optional)
+		 */
+		DispatchSize(uint32_t dimensionX, uint32_t dimentionY, uint32_t dimensionZ = 1);
+
+		DispatchSize(const DispatchSize &other) = default;
+		DispatchSize(DispatchSize &&other) = default;
+
+		~DispatchSize() = default;
+
+		DispatchSize &operator=(const DispatchSize &other) = default;
+		DispatchSize &operator=(DispatchSize &&other) = default;
+
+		/**
+		 * Returns the raw data of the dispatch size as readonly unsigned
+		 * integer pointer.
+		 *
+		 * @return Pointer to data
+		 */
+		[[nodiscard]] const uint32_t* data() const;
+
+		/**
+		 * Returns the size value of the dispatch size by a given index.
+		 *
+		 * @param[in] index Size index
+		 * @return Size value by index
+		 */
+		[[nodiscard]] uint32_t operator[](size_t index) const;
+
+		/**
+		 * Returns the value for the X dimension of the dispatch size.
+		 *
+		 * @return Size of X dimension
+		 */
+		[[nodiscard]] uint32_t x() const;
+
+		/**
+		 * Returns the value for the Y dimension of the dispatch size.
+		 *
+		 * @return Size of Y dimension
+		 */
+		[[nodiscard]] uint32_t y() const;
+
+		/**
+		 * Returns the value for the Z dimension of the dispatch size.
+		 *
+		 * @return Size of Z dimension
+		 */
+		[[nodiscard]] uint32_t z() const;
+
+		/**
+		 * Checks whether the dispatch size is valid for compute shader
+		 * invocations and returns the result as boolean value.
+		 *
+		 * @return True if the dispatch size is valid, otherwise false.
+		 */
+		bool check() const;
+	};
+
+	/**
+	 * Returns the proper dispatch size by dividing a global amount of invocations
+	 * as three dimensional dispatch size into invocations of a fixed group size
+	 * for the used work groups of the compute shader.
+	 *
+	 * This function will generate over fitted results to make sure all global
+	 * invocations get computed. So make sure the used compute shader handles those
+	 * additional invocations out of bounds from the original global invocations.
+	 *
+	 * @param[in] globalInvocations Size of planned global invocations
+	 * @param[in] groupSize Size of work group in compute stage
+	 * @return Dispatch size respecting the actual work group size
+	 */
+	[[nodiscard]] DispatchSize dispatchInvocations(DispatchSize globalInvocations,
+												   DispatchSize groupSize);
+
+} // namespace vkcv
diff --git a/include/vkcv/Downsampler.hpp b/include/vkcv/Downsampler.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..82416fd2c957f58de8e7ba9092dfc3d9d677e56a
--- /dev/null
+++ b/include/vkcv/Downsampler.hpp
@@ -0,0 +1,40 @@
+#pragma once
+
+#include "Handles.hpp"
+
+namespace vkcv {
+
+	class Core;
+
+	/**
+	 * @brief An abstract class to handle downsampling of images for mip generation.
+	 */
+	class Downsampler {
+	protected:
+		/**
+		 * Reference to the current Core instance.
+		 */
+		Core &m_core;
+
+	public:
+		/**
+		 * @brief Constructor to create a downsampler instance.
+		 *
+		 * @param[in,out] core Reference to a Core instance
+		 */
+		explicit Downsampler(Core &core);
+
+		virtual ~Downsampler() = default;
+
+		/**
+		 * @brief Record the commands of the given downsampler instance to
+		 * scale the image down on its own mip levels.
+		 *
+		 * @param[in] cmdStream Command stream handle
+		 * @param[in] image Image handle
+		 */
+		virtual void recordDownsampling(const CommandStreamHandle &cmdStream,
+										const ImageHandle &image) = 0;
+	};
+
+} // namespace vkcv
\ No newline at end of file
diff --git a/include/vkcv/Drawcall.hpp b/include/vkcv/Drawcall.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..08e436ae52d44c3514b504ae2d573d7c447fab80
--- /dev/null
+++ b/include/vkcv/Drawcall.hpp
@@ -0,0 +1,89 @@
+#pragma once
+/**
+ * @authors Tobias Frisch
+ * @file vkcv/Drawcall.hpp
+ * @brief Classes to define different drawcalls.
+ */
+
+#include <vector>
+
+#include "DescriptorSetUsage.hpp"
+#include "Handles.hpp"
+#include "VertexData.hpp"
+
+namespace vkcv {
+
+	/**
+	 * @brief Base class to store details for a general drawcall.
+	 */
+	class Drawcall {
+	private:
+		std::vector<DescriptorSetUsage> m_usages;
+
+	public:
+		Drawcall() = default;
+
+		Drawcall(const Drawcall &other) = default;
+		Drawcall(Drawcall &&other) noexcept = default;
+
+		~Drawcall() = default;
+
+		Drawcall &operator=(const Drawcall &other) = default;
+		Drawcall &operator=(Drawcall &&other) noexcept = default;
+
+		[[nodiscard]] const std::vector<DescriptorSetUsage> &getDescriptorSetUsages() const;
+
+		void useDescriptorSet(uint32_t location, const DescriptorSetHandle &descriptorSet,
+							  const std::vector<uint32_t> &dynamicOffsets = {});
+	};
+
+	/**
+	 * @brief Class to store details for an instance drawcall.
+	 */
+	class InstanceDrawcall : public Drawcall {
+	private:
+		VertexData m_vertexData;
+		uint32_t m_instanceCount;
+
+	public:
+		explicit InstanceDrawcall(const VertexData &vertexData, uint32_t instanceCount = 1);
+
+		[[nodiscard]] const VertexData &getVertexData() const;
+
+		[[nodiscard]] uint32_t getInstanceCount() const;
+	};
+
+	/**
+	 * @brief Class to store details for an indirect drawcall.
+	 */
+	class IndirectDrawcall : public Drawcall {
+	private:
+		BufferHandle m_indirectDrawBuffer;
+		VertexData m_vertexData;
+		uint32_t m_drawCount;
+
+	public:
+		explicit IndirectDrawcall(const BufferHandle &indirectDrawBuffer,
+								  const VertexData &vertexData, uint32_t drawCount = 1);
+
+		[[nodiscard]] BufferHandle getIndirectDrawBuffer() const;
+
+		[[nodiscard]] const VertexData &getVertexData() const;
+
+		[[nodiscard]] uint32_t getDrawCount() const;
+	};
+
+	/**
+	 * @brief Class to store details for a task drawcall.
+	 */
+	class TaskDrawcall : public Drawcall {
+	private:
+		uint32_t m_taskCount;
+
+	public:
+		explicit TaskDrawcall(uint32_t taskCount = 1);
+
+		[[nodiscard]] uint32_t getTaskCount() const;
+	};
+
+} // namespace vkcv
diff --git a/include/vkcv/DrawcallRecording.hpp b/include/vkcv/DrawcallRecording.hpp
deleted file mode 100644
index 0929ad038fb95ec1573e7c76e5ce13adb84ab760..0000000000000000000000000000000000000000
--- a/include/vkcv/DrawcallRecording.hpp
+++ /dev/null
@@ -1,54 +0,0 @@
-#pragma once
-#include <vulkan/vulkan.hpp>
-#include <vkcv/Handles.hpp>
-#include <vkcv/DescriptorConfig.hpp>
-
-namespace vkcv {
-    struct VertexBufferBinding {
-        inline VertexBufferBinding(vk::DeviceSize offset, vk::Buffer buffer) noexcept
-            : offset(offset), buffer(buffer) {}
-
-        vk::DeviceSize  offset;
-        vk::Buffer      buffer;
-    };
-
-    struct DescriptorSetUsage {
-        inline DescriptorSetUsage(uint32_t setLocation, vk::DescriptorSet vulkanHandle) noexcept
-            : setLocation(setLocation), vulkanHandle(vulkanHandle) {}
-
-        const uint32_t          setLocation;
-        const vk::DescriptorSet vulkanHandle;
-    };
-
-    struct Mesh {
-        inline Mesh(std::vector<VertexBufferBinding> vertexBufferBindings, vk::Buffer indexBuffer, size_t indexCount) noexcept
-            : vertexBufferBindings(vertexBufferBindings), indexBuffer(indexBuffer), indexCount(indexCount){}
-
-        std::vector<VertexBufferBinding>    vertexBufferBindings;
-        vk::Buffer                          indexBuffer;
-        size_t                              indexCount;
-    };
-
-    struct PushConstantData {
-        inline PushConstantData(void* data, size_t sizePerDrawcall) : data(data), sizePerDrawcall(sizePerDrawcall) {}
-
-        void* data;
-        size_t  sizePerDrawcall;
-    };
-
-    struct DrawcallInfo {
-        inline DrawcallInfo(const Mesh& mesh, const std::vector<DescriptorSetUsage>& descriptorSets)
-            : mesh(mesh), descriptorSets(descriptorSets) {}
-
-        Mesh                            mesh;
-        std::vector<DescriptorSetUsage> descriptorSets;
-    };
-
-    void recordDrawcall(
-        const DrawcallInfo      &drawcall,
-        vk::CommandBuffer       cmdBuffer,
-        vk::PipelineLayout      pipelineLayout,
-        const PushConstantData  &pushConstantData,
-        const size_t            drawcallIndex);
-
-}
\ No newline at end of file
diff --git a/include/vkcv/Event.hpp b/include/vkcv/Event.hpp
index da5cbc72fbb3eee3a71a35c1da6fe32dff06b057..3bba94b20afb56dd185751e8cafc787076822c2a 100644
--- a/include/vkcv/Event.hpp
+++ b/include/vkcv/Event.hpp
@@ -1,104 +1,163 @@
 #pragma once
+/**
+ * @authors Tobias Frisch, Sebastian Gaida, Josch Morgenstern, Katharina Krämer
+ * @file vkcv/Event.hpp
+ * @brief Template event struct to synchronize callbacks.
+ */
 
 #include <functional>
+
+#ifndef __MINGW32__
+#ifdef __NO_SEMAPHORES__
 #include <mutex>
+#else
+#include <semaphore>
+#endif
+#endif
+
+#include <vector>
 
 namespace vkcv {
-	
-	template<typename... T>
+
+	/**
+	 * @brief Template for a function handle to an event
+	 *
+	 * @tparam T Event parameter type list
+	 */
+	template <typename... T>
 	struct event_handle {
 		uint32_t id;
 	};
 
-    template<typename... T>
-    struct event_function {
-        typedef std::function<void(T...)> type;
-	
+	/**
+	 * @brief Template for an event function
+	 *
+	 * @tparam T Event parameter type list
+	 */
+	template <typename... T>
+	struct event_function {
+		typedef std::function<void(T...)> type;
+
 		event_handle<T...> handle;
-        type callback;
-    };
-
-    /**
-     * template for event handling
-     * @tparam T parameter list
-     */
-    template<typename... T>
-    struct event {
-    private:
-        std::vector< event_function<T...> > m_functions;
-        uint32_t m_id_counter;
-		std::mutex m_mutex;
+		type callback;
+	};
+
+	/**
+	 * @brief Template for event handling
+	 *
+	 * @tparam T Event parameter type list
+	 */
+	template <typename... T>
+	struct event {
+	private:
+		std::vector< event_function<T...> > m_functions;
+		uint32_t m_id_counter;
 
-    public:
+#ifndef __MINGW32__
+#ifdef __NO_SEMAPHORES__
+		std::mutex m_mutex;
+#else
+		std::binary_semaphore m_semaphore;
+#endif
+#endif
 
-        /**
-         * calls all function handles with the given arguments
-         * @param arguments of the given function
-         */
-        void operator()(T... arguments) {
+	public:
+		/**
+		 * @brief Calls all function handles with the given arguments.
+		 *
+		 * @param[in,out] arguments Arguments of the given event
+		 */
+		void operator()(T... arguments) {
 			lock();
 
-            for (auto &function : this->m_functions) {
+			for (auto &function : this->m_functions) {
 				function.callback(arguments...);
 			}
-            
-            unlock();
-        }
-
-        /**
-         * adds a function handle to the event to be called
-         * @param callback of the function
-         * @return handle of the function
-         */
+
+			unlock();
+		}
+
+		/**
+		 * @brief Adds a function handle to the event to be called.
+		 *
+		 * @param[in] callback Event callback
+		 * @return Handle of the function
+		 */
 		event_handle<T...> add(typename event_function<T...>::type callback) {
 			event_function<T...> function;
 			function.handle = { m_id_counter++ };
 			function.callback = callback;
-            this->m_functions.push_back(function);
-            return function.handle;
-        }
-
-        /**
-         * removes a function handle of the event
-         * @param handle of the function
-         */
-        void remove(event_handle<T...> handle) {
-            this->m_functions.erase(
-					std::remove_if(this->m_functions.begin(), this->m_functions.end(), [&handle](auto function){
-						return (handle.id == function.handle.id);
-					}),
-                    this->m_functions.end()
-            );
-        }
-        
-        /**
-         * locks the event so its function handles won't be called
-         */
-        void lock() {
+			this->m_functions.push_back(function);
+			return function.handle;
+		}
+
+		/**
+		 * @brief Removes a function handle of the event.
+		 *
+		 * @param handle Handle of the function
+		 */
+		void remove(event_handle<T...> handle) {
+			this->m_functions.erase(std::remove_if(this->m_functions.begin(),
+												   this->m_functions.end(),
+												   [&handle](auto function) {
+													   return (handle.id == function.handle.id);
+												   }),
+									this->m_functions.end());
+		}
+
+		/**
+		 * @brief Locks the event so its function handles won't
+		 * be called until unlocked.
+		 */
+		void lock() {
+#ifndef __MINGW32__
+#ifdef __NO_SEMAPHORES__
 			m_mutex.lock();
-        }
-	
+#else
+			m_semaphore.acquire();
+#endif
+#endif
+		}
+
 		/**
-		* unlocks the event so its function handles can be called after locking
-		*/
-        void unlock() {
+		 * @brief Unlocks the event so its function handles can
+		 * be called after locking.
+		 */
+		void unlock() {
+#ifndef __MINGW32__
+#ifdef __NO_SEMAPHORES__
 			m_mutex.unlock();
-        }
+#else
+			m_semaphore.release();
+#endif
+#endif
+		}
 
-        explicit event(bool locked = false) {
-        	if (locked) {
-        		lock();
-        	}
-        }
+		explicit event(bool locked = false)
+#ifndef __MINGW32__
+#ifndef __NO_SEMAPHORES__
+			:
+			m_semaphore(locked ? 1 : 0)
+#endif
+#endif
+		{
+#ifndef __MINGW32__
+#ifdef __NO_SEMAPHORES__
+			if (locked)
+				m_mutex.lock();
+#endif
+#endif
+		}
 
-        event(const event &other) = delete;
+		event(const event &other) = delete;
 
-        event(event &&other) = delete;
+		event(event &&other) = delete;
 
-        ~event() = default;
+		~event() = default;
 
-        event &operator=(const event &other) = delete;
+		event &operator=(const event &other) = delete;
+
+		event &operator=(event &&other) = delete;
+	};
 
-        event &operator=(event &&other) = delete;
-    };
-}
+} // namespace vkcv
diff --git a/include/vkcv/EventFunctionTypes.hpp b/include/vkcv/EventFunctionTypes.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..12871cbce3b6d06c1849cdf3a90956a91dad724d
--- /dev/null
+++ b/include/vkcv/EventFunctionTypes.hpp
@@ -0,0 +1,31 @@
+#pragma once
+/**
+ * @authors Alexander Gauggel, Tobias Frisch
+ * @file vkcv/CommandRecordingFunctionTypes.hpp
+ * @brief Abstract function types to handle command recording for example.
+ */
+
+#include <vulkan/vulkan.hpp>
+
+#include "Event.hpp"
+#include "Handles.hpp"
+
+namespace vkcv {
+
+	/**
+	 * @brief Function to be called for recording a command buffer.
+	 */
+	typedef typename event_function<const vk::CommandBuffer &>::type RecordCommandFunction;
+
+	/**
+	 * @brief Function to be called after finishing a given process.
+	 */
+	typedef typename event_function<>::type FinishCommandFunction;
+
+	/**
+	 * @brief Function to be called each frame for every open window.
+	 */
+	typedef typename event_function<const WindowHandle &, double, double, uint32_t, uint32_t>::type
+		WindowFrameFunction;
+
+} // namespace vkcv
\ No newline at end of file
diff --git a/include/vkcv/FeatureManager.hpp b/include/vkcv/FeatureManager.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..fa9f77befd4e36f6beef1a10cc95392463a0266e
--- /dev/null
+++ b/include/vkcv/FeatureManager.hpp
@@ -0,0 +1,531 @@
+#pragma once
+/**
+ * @authors Tobias Frisch, Artur Wasmut, Vanessa Karolek, Sebastian Gaida
+ * @file vkcv/FeatureManager.hpp
+ * @brief Class to manage feature support and extension usage.
+ */
+
+#include <functional>
+#include <unordered_set>
+#include <vector>
+#include <vulkan/vulkan.hpp>
+
+#include "Logger.hpp"
+
+namespace vkcv {
+
+	/**
+	 * @brief Class to manage extension and feature requirements, support and usage.
+	 */
+	class FeatureManager {
+	private:
+		/**
+		 * Physical device to check feature support against.
+		 */
+		vk::PhysicalDevice &m_physicalDevice;
+
+		/**
+		 * List of supported extensions.
+		 */
+		std::vector<const char*> m_supportedExtensions;
+
+		/**
+		 * List of activated extensions for usage.
+		 */
+		std::vector<const char*> m_activeExtensions;
+
+		/**
+		 * Feature structure chain to request activated features.
+		 */
+		vk::PhysicalDeviceFeatures2 m_featuresBase;
+
+		/**
+		 * List of base structures allocated to request extension specific features.
+		 */
+		std::vector<vk::BaseOutStructure*> m_featuresExtensions;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceFeatures.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceFeatures &features,
+										bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDevice16BitStorageFeatures.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDevice16BitStorageFeatures &features,
+										bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDevice8BitStorageFeatures.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDevice8BitStorageFeatures &features,
+										bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceBufferDeviceAddressFeatures.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool
+		checkSupport(const vk::PhysicalDeviceBufferDeviceAddressFeatures &features,
+					 bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceDescriptorIndexingFeatures.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool
+		checkSupport(const vk::PhysicalDeviceDescriptorIndexingFeatures &features,
+					 bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceHostQueryResetFeatures.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceHostQueryResetFeatures &features,
+										bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceImagelessFramebufferFeatures.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool
+		checkSupport(const vk::PhysicalDeviceImagelessFramebufferFeatures &features,
+					 bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceMultiviewFeatures.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceMultiviewFeatures &features,
+										bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceProtectedMemoryFeatures.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceProtectedMemoryFeatures &features,
+										bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceSamplerYcbcrConversionFeatures.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool
+		checkSupport(const vk::PhysicalDeviceSamplerYcbcrConversionFeatures &features,
+					 bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceScalarBlockLayoutFeatures.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceScalarBlockLayoutFeatures &features,
+										bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceSeparateDepthStencilLayoutsFeatures.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool
+		checkSupport(const vk::PhysicalDeviceSeparateDepthStencilLayoutsFeatures &features,
+					 bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceShaderAtomicInt64Features.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceShaderAtomicInt64Features &features,
+										bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceShaderFloat16Int8Features.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceShaderFloat16Int8Features &features,
+										bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceShaderSubgroupExtendedTypesFeatures.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool
+		checkSupport(const vk::PhysicalDeviceShaderSubgroupExtendedTypesFeatures &features,
+					 bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceTimelineSemaphoreFeatures.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceTimelineSemaphoreFeatures &features,
+										bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceUniformBufferStandardLayoutFeatures.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool
+		checkSupport(const vk::PhysicalDeviceUniformBufferStandardLayoutFeatures &features,
+					 bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceVariablePointersFeatures.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceVariablePointersFeatures &features,
+										bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceVulkanMemoryModelFeatures.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceVulkanMemoryModelFeatures &features,
+										bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceMeshShaderFeaturesNV.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceMeshShaderFeaturesNV &features,
+										bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceShaderAtomicFloatFeaturesEXT.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool
+		checkSupport(const vk::PhysicalDeviceShaderAtomicFloatFeaturesEXT &features,
+					 bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceShaderAtomicFloat2FeaturesEXT.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool
+		checkSupport(const vk::PhysicalDeviceShaderAtomicFloat2FeaturesEXT &features,
+					 bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceVulkan12Features.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceVulkan12Features &features,
+										bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceVulkan11Features.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceVulkan11Features &features,
+										bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceAccelerationStructureFeaturesKHR.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool
+		checkSupport(const vk::PhysicalDeviceAccelerationStructureFeaturesKHR &features,
+					 bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceRayTracingPipelineFeaturesKHR.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool
+		checkSupport(const vk::PhysicalDeviceRayTracingPipelineFeaturesKHR &features,
+					 bool required) const;
+
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceVulkan13Features.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceVulkan13Features &features,
+										bool required) const;
+		
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceCoherentMemoryFeaturesAMD.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceCoherentMemoryFeaturesAMD &features,
+										bool required) const;
+		
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceSubgroupSizeControlFeatures.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceSubgroupSizeControlFeatures &features,
+										bool required) const;
+		
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceIndexTypeUint8FeaturesEXT.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceIndexTypeUint8FeaturesEXT &features,
+										bool required) const;
+		
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceShaderTerminateInvocationFeatures.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceShaderTerminateInvocationFeatures &features,
+										bool required) const;
+
+		/**
+		 * @brief Searches for a base structure of a given structure type.
+		 *
+		 * @param[in] type Structure type
+		 * @return Pointer to first matching base structure or nullptr
+		 */
+		[[nodiscard]] vk::BaseOutStructure* findFeatureStructure(vk::StructureType type) const;
+
+	public:
+		/**
+		 * @brief Constructor of a feature manager with a given physical device.
+		 *
+		 * @param[in,out] physicalDevice Physical device
+		 */
+		explicit FeatureManager(vk::PhysicalDevice &physicalDevice);
+
+		FeatureManager(const FeatureManager &other) = delete;
+
+		/**
+		 * @brief Move-constructor of a feature manager.
+		 *
+		 * @param[in,out] other Other feature manager instance
+		 */
+		FeatureManager(FeatureManager &&other) noexcept;
+
+		/**
+		 * @brief Destructor of a feature manager.
+		 */
+		~FeatureManager();
+
+		FeatureManager &operator=(const FeatureManager &other) = delete;
+
+		/**
+		 * @brief Move-operator of a feature manager.
+		 *
+		 * @param[in,out] other Other feature manager instance
+		 * @return Reference to the feature manager itself
+		 */
+		FeatureManager &operator=(FeatureManager &&other) noexcept;
+
+		/**
+		 * @brief Check if a specific extension is supported by the managers physical device.
+		 *
+		 * @param[in] extension Extension identifier string
+		 * @return @p True, if the @p extension is supported, else @p false
+		 */
+		[[nodiscard]] bool isExtensionSupported(const std::string &extension) const;
+
+		/**
+		 * @brief Activate a specific extension if supported by the managers physical device.
+		 *
+		 * @param[in] extension Extension identifier string
+		 * @param[in] required True, if the @p extension is required, else false
+		 * @return @p True, if the @p extension could be activated, else @p false
+		 */
+		bool useExtension(const std::string &extension, bool required = true);
+
+		/**
+		 * @brief Check if a specific extension is activated by the manager.
+		 *
+		 * @param[in] extension Extension identifier string
+		 * @return @p True, if the @p extension is activated, else @p false
+		 */
+		[[nodiscard]] bool isExtensionActive(const std::string &extension) const;
+
+		/**
+		 * @brief Return list of activated extensions for usage.
+		 *
+		 * @return List of activated extensions
+		 */
+		[[nodiscard]] const std::vector<const char*> &getActiveExtensions() const;
+
+		/**
+		 * @brief Request specific features for optional or required usage ( only core Vulkan 1.0 ).
+		 *
+		 * @param[in] featureFunction Function or lambda to request specific features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the requested features could be activated, else @p false
+		 */
+		bool useFeatures(const std::function<void(vk::PhysicalDeviceFeatures &)> &featureFunction,
+						 bool required = true);
+
+		/**
+		 * @brief Request specific features for optional or required usage.
+		 *
+		 * @tparam T Template parameter to use specific base structure types
+		 * @param[in] featureFunction Function or lambda to request specific features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the requested features could be activated, else @p false
+		 * @see checkSupport()
+		 */
+		template <typename T>
+		bool useFeatures(const std::function<void(T &)> &featureFunction, bool required = true) {
+			T features;
+			T* features_ptr = reinterpret_cast<T*>(findFeatureStructure(features.sType));
+
+			if (features_ptr) {
+				features = *features_ptr;
+			}
+
+			featureFunction(features);
+
+			if (!checkSupport(features, required)) {
+				if (required) {
+					vkcv_log_throw_error("Required feature is not supported!");
+				}
+
+				return false;
+			}
+
+			if (features_ptr) {
+				*features_ptr = features;
+				return true;
+			}
+
+			features_ptr = new T(features);
+
+			if (m_featuresExtensions.empty()) {
+				m_featuresBase.setPNext(features_ptr);
+			} else {
+				m_featuresExtensions.back()->setPNext(
+					reinterpret_cast<vk::BaseOutStructure*>(features_ptr));
+			}
+
+			m_featuresExtensions.push_back(reinterpret_cast<vk::BaseOutStructure*>(features_ptr));
+
+			return true;
+		}
+
+		/**
+		 * @brief Return feature structure chain to request all activated features.
+		 *
+		 * @return Head of feature structure chain
+		 */
+		[[nodiscard]] const vk::PhysicalDeviceFeatures2 &getFeatures() const;
+
+		/**
+		 * @brief Checks all activated features for a specific feature and returns its state.
+		 *
+		 * @tparam T Template parameter to use specific base structure types
+		 * @param[in] type Vulkan structure type identifier
+		 * @param[in] featureTestFunction Function to test feature structure with for requested
+		 * attributes
+		 * @return True, if the requested attributes are available, else false
+		 */
+		template <typename T>
+		bool checkFeatures(vk::StructureType type,
+						   const std::function<bool(const T &)> &featureTestFunction) const {
+			const auto* base = reinterpret_cast<const vk::BaseInStructure*>(&getFeatures());
+
+			while (base) {
+				if ((base->sType == type)
+					&& (featureTestFunction(*reinterpret_cast<const T*>(base)))) {
+					return true;
+				}
+
+				base = base->pNext;
+			}
+
+			return false;
+		}
+	};
+
+} // namespace vkcv
diff --git a/include/vkcv/Features.hpp b/include/vkcv/Features.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..8b39de4f8f1183664f163d8add7f135102dab631
--- /dev/null
+++ b/include/vkcv/Features.hpp
@@ -0,0 +1,203 @@
+#pragma once
+/**
+ * @authors Tobias Frisch
+ * @file vkcv/Features.hpp
+ * @brief Class to manage feature requests.
+ */
+
+#include <functional>
+#include <initializer_list>
+#include <vector>
+
+#include "FeatureManager.hpp"
+
+namespace vkcv {
+
+	/**
+	 * Abstract function type to request a feature via the feature manager.
+	 */
+	typedef std::function<bool(FeatureManager &)> Feature;
+
+	/**
+	 * @brief Class to manage a list of feature requests at once.
+	 */
+	class Features {
+	private:
+		/**
+		 * List of feature requests.
+		 */
+		std::vector<Feature> m_features;
+
+	public:
+		/**
+		 * @brief Constructor of a features instance.
+		 */
+		Features() = default;
+
+		/**
+		 * @brief Constructor of a features instance with a given list of extension identifier
+		 * strings.
+		 *
+		 * @param[in] list List of extension identifier strings
+		 */
+		Features(const std::initializer_list<std::string> &list);
+
+		/**
+		 * @brief Copy-constructor of a features instance.
+		 *
+		 * @param[in] other Other features instance
+		 */
+		Features(const Features &other) = default;
+
+		/**
+		 * @brief Move-constructor of a features instance.
+		 *
+		 * @param[in,out] other Other features instance
+		 */
+		Features(Features &&other) = default;
+
+		/**
+		 * @brief Destructor of a features instance.
+		 */
+		~Features() = default;
+
+		/**
+		 * @brief Copy-operator of a features instance.
+		 *
+		 * @param[in] other Other features instance
+		 * @return Reference to the features instance itself
+		 */
+		Features &operator=(const Features &other) = default;
+
+		/**
+		 * @brief Move-operator of a features instance.
+		 *
+		 * @param[in,out] other Other features instance
+		 * @return Reference to the features instance itself
+		 */
+		Features &operator=(Features &&other) = default;
+
+		/**
+		 * @brief Request a specific extension as required.
+		 *
+		 * @param[in] extension Extension identifier string
+		 */
+		void requireExtension(const std::string &extension);
+
+		/**
+		 * @brief Request a specific extension and some of its features as required ( only core
+		 * Vulkan 1.0 ).
+		 *
+		 * @param[in] extension Extension identifier string
+		 * @param[in] featureFunction
+		 */
+		void requireExtensionFeature(
+			const std::string &extension,
+			const std::function<void(vk::PhysicalDeviceFeatures &)> &featureFunction);
+
+		/**
+		 * @brief Request a specific extension and some of its features as required.
+		 *
+		 * @tparam T Template parameter to use specific base structure types
+		 * @param[in] extension Extension identifier string
+		 * @param[in] featureFunction Function or lambda to request specific features
+		 */
+		template <typename T>
+		void requireExtensionFeature(const std::string &extension,
+									 const std::function<void(T &)> &featureFunction) {
+			m_features.emplace_back([extension, featureFunction](FeatureManager &featureManager) {
+				if (featureManager.useExtension(extension, true)) {
+					return featureManager.template useFeatures<T>(featureFunction, true);
+				} else {
+					return false;
+				}
+			});
+		}
+
+		/**
+		 * @brief Request a specific set of features as required ( only core Vulkan 1.0 ).
+		 *
+		 * @param[in] featureFunction Function or lambda to request specific features
+		 */
+		void
+		requireFeature(const std::function<void(vk::PhysicalDeviceFeatures &)> &featureFunction);
+
+		/**
+		 * @brief Request a specific set of features as required.
+		 *
+		 * @tparam T Template parameter to use specific base structure types
+		 * @param[in] featureFunction Function or lambda to request specific features
+		 */
+		template <typename T>
+		void requireFeature(const std::function<void(T &)> &featureFunction) {
+			m_features.emplace_back([featureFunction](FeatureManager &featureManager) {
+				return featureManager.template useFeatures<T>(featureFunction, true);
+			});
+		}
+
+		/**
+		 * @brief Request a specific extension as optional.
+		 *
+		 * @param[in] extension Extension identifier string
+		 */
+		void tryExtension(const std::string &extension);
+
+		/**
+		 * @brief Request a specific extension and some of its features as optional ( only core
+		 * Vulkan 1.0 ).
+		 *
+		 * @param[in] extension Extension identifier string
+		 * @param[in] featureFunction Function or lambda to request specific features
+		 */
+		void tryExtensionFeature(
+			const std::string &extension,
+			const std::function<void(vk::PhysicalDeviceFeatures &)> &featureFunction);
+
+		/**
+		 * @brief Request a specific extension and some of its features as optional.
+		 *
+		 * @tparam T Template parameter to use specific base structure types
+		 * @param[in] extension Extension identifier string
+		 * @param[in] featureFunction Function or lambda to request specific features
+		 */
+		template <typename T>
+		void tryExtensionFeature(const std::string &extension,
+								 const std::function<void(T &)> &featureFunction) {
+			m_features.emplace_back([extension, featureFunction](FeatureManager &featureManager) {
+				if (featureManager.useExtension(extension, false)) {
+					return featureManager.template useFeatures<T>(featureFunction, false);
+				} else {
+					return false;
+				}
+			});
+		}
+
+		/**
+		 * @brief Request a specific set of features as optional ( only core Vulkan 1.0 ).
+		 *
+		 * @param[in] featureFunction Function or lambda to request specific features
+		 */
+		void tryFeature(const std::function<void(vk::PhysicalDeviceFeatures &)> &featureFunction);
+
+		/**
+		 * @brief Request a specific set of features as optional.
+		 *
+		 * @tparam T Template parameter to use specific base structure types
+		 * @param[in] featureFunction Function or lambda to request specific features
+		 */
+		template <typename T>
+		void tryFeature(const std::function<void(T &)> &featureFunction) {
+			m_features.emplace_back([featureFunction](FeatureManager &featureManager) {
+				return featureManager.template useFeatures<T>(featureFunction, false);
+			});
+		}
+
+		/**
+		 * @brief Return list of feature requests.
+		 *
+		 * @return List of feature requests
+		 */
+		[[nodiscard]] const std::vector<Feature> &getList() const;
+	};
+
+} // namespace vkcv
diff --git a/include/vkcv/File.hpp b/include/vkcv/File.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..51491b1b04a2a658f410e4872f7661237ca913b2
--- /dev/null
+++ b/include/vkcv/File.hpp
@@ -0,0 +1,26 @@
+#pragma once
+/**
+ * @authors Tobias Frisch
+ * @file vkcv/File.hpp
+ * @brief Functions to handle generating temporary file paths.
+ */
+
+#include <filesystem>
+
+namespace vkcv {
+
+	/**
+	 * @brief Generate a new temporary file path and return it.
+	 *
+	 * @return A unique path for a temporary file
+	 */
+	std::filesystem::path generateTemporaryFilePath();
+
+	/**
+	 * @brief Generate a new temporary directory path and return it.
+	 *
+	 * @return A unique path for a temporary directory
+	 */
+	std::filesystem::path generateTemporaryDirectoryPath();
+
+} // namespace vkcv
diff --git a/include/vkcv/GraphicsPipelineConfig.hpp b/include/vkcv/GraphicsPipelineConfig.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..80084cb1408a71ebd2abe12ec12d6a395e9b6d49
--- /dev/null
+++ b/include/vkcv/GraphicsPipelineConfig.hpp
@@ -0,0 +1,144 @@
+#pragma once
+/**
+ * @authors Mara Vogt, Mark Mints, Tobias Frisch
+ * @file vkcv/GraphicsPipelineConfig.hpp
+ * @brief Graphics pipeline config struct to hand over required information to pipeline creation.
+ */
+
+#include <cstdint>
+#include <vector>
+
+#include "Multisampling.hpp"
+#include "PipelineConfig.hpp"
+#include "VertexLayout.hpp"
+
+namespace vkcv {
+
+	/**
+	 * @brief Enum class to specify types of primitive topology.
+	 */
+	enum class PrimitiveTopology {
+		PointList,
+		LineList,
+		TriangleList,
+		PatchList
+	};
+
+	/**
+	 * @brief Enum class to specify modes of culling.
+	 */
+	enum class CullMode {
+		None,
+		Front,
+		Back,
+		Both
+	};
+
+	/**
+	 * @brief Enum class to specify depth-test modes.
+	 */
+	enum class DepthTest {
+		None,
+		Less,
+		LessEqual,
+		Greater,
+		GreatherEqual,
+		Equal
+	};
+
+	// add more as needed
+	// alternatively we could expose the blend factors directly
+	/**
+	 * @brief Enum class to specify blending modes.
+	 */
+	enum class BlendMode {
+		None,
+		Additive
+	};
+
+	/**
+	 * @brief Class to configure a graphics pipeline before its creation.
+	 */
+	class GraphicsPipelineConfig : public PipelineConfig {
+	private:
+		PassHandle m_PassHandle;
+		VertexLayout m_VertexLayout;
+
+		uint32_t m_Width;
+		uint32_t m_Height;
+
+		bool m_UseConservativeRasterization = false;
+		PrimitiveTopology m_PrimitiveTopology = PrimitiveTopology::TriangleList;
+		BlendMode m_blendMode = BlendMode::None;
+		bool m_EnableDepthClamping = false;
+		CullMode m_Culling = CullMode::None;
+		DepthTest m_DepthTest = DepthTest::LessEqual;
+		bool m_DepthWrite = true;
+		bool m_AlphaToCoverage = false;
+		uint32_t m_TessellationControlPoints = 0;
+
+	public:
+		GraphicsPipelineConfig();
+
+		GraphicsPipelineConfig(const ShaderProgram &program, const PassHandle &pass,
+							   const VertexLayout &vertexLayout,
+							   const std::vector<DescriptorSetLayoutHandle> &layouts);
+
+		GraphicsPipelineConfig(const GraphicsPipelineConfig &other) = default;
+		GraphicsPipelineConfig(GraphicsPipelineConfig &&other) = default;
+
+		~GraphicsPipelineConfig() = default;
+
+		GraphicsPipelineConfig &operator=(const GraphicsPipelineConfig &other) = default;
+		GraphicsPipelineConfig &operator=(GraphicsPipelineConfig &&other) = default;
+
+		[[nodiscard]] const PassHandle &getPass() const;
+
+		[[nodiscard]] const VertexLayout &getVertexLayout() const;
+
+		[[nodiscard]] uint32_t getWidth() const;
+
+		[[nodiscard]] uint32_t getHeight() const;
+
+		void setResolution(uint32_t width, uint32_t height);
+
+		[[nodiscard]] bool isViewportDynamic() const;
+
+		[[nodiscard]] bool isUsingConservativeRasterization() const;
+
+		void setUsingConservativeRasterization(bool conservativeRasterization);
+
+		[[nodiscard]] PrimitiveTopology getPrimitiveTopology() const;
+
+		void setPrimitiveTopology(PrimitiveTopology primitiveTopology);
+
+		[[nodiscard]] BlendMode getBlendMode() const;
+
+		void setBlendMode(BlendMode blendMode);
+
+		[[nodiscard]] bool isDepthClampingEnabled() const;
+
+		void setDepthClampingEnabled(bool depthClamping);
+
+		[[nodiscard]] CullMode getCulling() const;
+
+		void setCulling(CullMode cullMode);
+
+		[[nodiscard]] DepthTest getDepthTest() const;
+
+		void setDepthTest(DepthTest depthTest);
+
+		[[nodiscard]] bool isWritingDepth() const;
+
+		void setWritingDepth(bool writingDepth);
+
+		[[nodiscard]] bool isWritingAlphaToCoverage() const;
+
+		void setWritingAlphaToCoverage(bool alphaToCoverage);
+
+		[[nodiscard]] uint32_t getTesselationControlPoints() const;
+
+		void setTesselationControlPoints(uint32_t tessellationControlPoints);
+	};
+
+} // namespace vkcv
\ No newline at end of file
diff --git a/include/vkcv/Handles.hpp b/include/vkcv/Handles.hpp
index ea05bd212dd9314957e4771070bedb3963b1b2dc..3e92ee1557bac4be5b03f8b74cab1319c501d707 100644
--- a/include/vkcv/Handles.hpp
+++ b/include/vkcv/Handles.hpp
@@ -1,7 +1,7 @@
 #pragma once
 /**
- * @authors Artur Wasmut
- * @file src/vkcv/Handles.cpp
+ * @authors Tobias Frisch, Alexander Gauggel, Artur Wasmut, Sebastian Gaida, Mark Mints
+ * @file vkcv/Handles.hpp
  * @brief Central header file for all possible handles that the framework will hand out.
  */
 
@@ -9,103 +9,214 @@
 
 #include "Event.hpp"
 
-namespace vkcv
-{
-	
+namespace vkcv {
+
+	/**
+	 * @brief Function to be called when a handles resources can be destroyed.
+	 */
 	typedef typename event_function<uint64_t>::type HandleDestroyFunction;
-	
+
+	/**
+	 * @brief Class for general memory management via handles.
+	 */
 	class Handle {
-		friend std::ostream& operator << (std::ostream& out, const Handle& handle);
-		
+		friend std::ostream &operator<<(std::ostream &out, const Handle &handle);
+
 	private:
 		uint64_t m_id;
 		uint64_t* m_rc;
-		
+
 		HandleDestroyFunction m_destroy;
-	
+
 	protected:
+		/**
+		 * @brief Constructor of an invalid handle
+		 */
 		Handle();
-		
-		explicit Handle(uint64_t id, const HandleDestroyFunction& destroy = nullptr);
-		
+
+		/**
+		 * @brief Constructor of a valid handle with an
+		 * unique id and an optional destroy callback.
+		 *
+		 * @param[in] id Unique handle id
+		 * @param[in] destroy Destroy callback (optional)
+		 */
+		explicit Handle(uint64_t id, const HandleDestroyFunction &destroy = nullptr);
+
 		/**
-		 * Returns the actual handle id of a handle.
+		 * @brief Returns the actual handle id of a handle.
 		 *
 		 * @return Handle id
 		 */
-		[[nodiscard]]
-		uint64_t getId() const;
-		
+		[[nodiscard]] uint64_t getId() const;
+
 		/**
-		 * Returns the reference counter of a handle
+		 * @brief Returns the reference counter of a handle
 		 *
 		 * @return Reference counter
 		 */
-		[[nodiscard]]
-		uint64_t getRC() const;
-	
+		[[nodiscard]] uint64_t getRC() const;
+
 	public:
 		virtual ~Handle();
-		
-		Handle(const Handle& other);
-		Handle(Handle&& other) noexcept;
-		
-		Handle& operator=(const Handle& other);
-		Handle& operator=(Handle&& other) noexcept;
-		
+
+		Handle(const Handle &other);
+		Handle(Handle &&other) noexcept;
+
+		Handle &operator=(const Handle &other);
+		Handle &operator=(Handle &&other) noexcept;
+
+		/**
+		 * @brief Returns whether a handle is valid to use.
+		 *
+		 * @return True, if the handle is valid, else false.
+		 */
 		explicit operator bool() const;
+
+		/**
+		 * @brief Returns whether a handle is invalid to use.
+		 *
+		 * @return True, if the handle is invalid, else false.
+		 */
 		bool operator!() const;
-		
 	};
-	
-	std::ostream& operator << (std::ostream& out, const Handle& handle);
-	
-    // Handle returned for any buffer created with the core/context objects
-    class BufferHandle : public Handle {
-    	friend class BufferManager;
+
+	/**
+	 * @brief Stream operator to print a handle into an output
+	 * stream.
+	 *
+	 * @param[out] out Output stream
+	 * @param[in] handle
+	 * @return Output stream after printing
+	 */
+	std::ostream &operator<<(std::ostream &out, const Handle &handle);
+
+	/**
+	 * @brief Handle class for buffers.
+	 */
+	class BufferHandle : public Handle {
+		friend class BufferManager;
+
 	private:
 		using Handle::Handle;
-    };
-	
+	};
+
+	/**
+	 * @brief Handle class for render passes.
+	 */
 	class PassHandle : public Handle {
 		friend class PassManager;
+
 	private:
 		using Handle::Handle;
 	};
-	
-	class PipelineHandle : public Handle {
-		friend class PipelineManager;
+
+	/**
+	 * @brief Handle class for graphics pipelines.
+	 */
+	class GraphicsPipelineHandle : public Handle {
+		friend class GraphicsPipelineManager;
+
 	private:
 		using Handle::Handle;
 	};
-	
+
+	/**
+	 * @brief Handle class for compute pipelines.
+	 */
+	class ComputePipelineHandle : public Handle {
+		friend class ComputePipelineManager;
+
+	private:
+		using Handle::Handle;
+	};
+
+	/**
+	 * @brief Handle class for descriptor set layouts.
+	 */
+	class DescriptorSetLayoutHandle : public Handle {
+		friend class DescriptorSetLayoutManager;
+
+	private:
+		using Handle::Handle;
+	};
+
+	/**
+	 * @brief Handle class for descriptor sets.
+	 */
 	class DescriptorSetHandle : public Handle {
-		friend class DescriptorManager;
+		friend class DescriptorSetManager;
+
 	private:
 		using Handle::Handle;
 	};
-	
+
+	/**
+	 * @brief Handle class for samplers.
+	 */
 	class SamplerHandle : public Handle {
 		friend class SamplerManager;
+
 	private:
 		using Handle::Handle;
 	};
 
+	/**
+	 * @brief Handle class for images.
+	 */
 	class ImageHandle : public Handle {
 		friend class ImageManager;
+
+	private:
 		using Handle::Handle;
+
 	public:
-		[[nodiscard]]
-		bool isSwapchainImage() const;
-		
-		static ImageHandle createSwapchainImageHandle(const HandleDestroyFunction& destroy = nullptr);
-		
+		/**
+		 * @brief Returns whether the handle represents an swapchain image.
+		 *
+		 * @return True, if the handle represents a swapchain image, else false.
+		 */
+		[[nodiscard]] bool isSwapchainImage() const;
+
+		/**
+		 * @brief Creates a valid image handle to represent a swapchain image
+		 * using an optional destroy callback.
+		 *
+		 * @param[in] destroy Destroy callback (optional)
+		 * @return New swapchain image handle
+		 */
+		static ImageHandle
+		createSwapchainImageHandle(const HandleDestroyFunction &destroy = nullptr);
+	};
+
+	/**
+	 * @brief Handle class for windows.
+	 */
+	class WindowHandle : public Handle {
+		friend class WindowManager;
+
+	private:
+		using Handle::Handle;
+	};
+
+	/**
+	 * @brief Handle class for swapchains.
+	 */
+	class SwapchainHandle : public Handle {
+		friend class SwapchainManager;
+
+	private:
+		using Handle::Handle;
+	};
+
+	/**
+	 * @brief Handle class for command streams.
+	 */
+	class CommandStreamHandle : public Handle {
+		friend class CommandStreamManager;
+
+	private:
+		using Handle::Handle;
 	};
 
-    class CommandStreamHandle : public Handle {
-        friend class CommandStreamManager;
-    private:
-        using Handle::Handle;
-    };
-	
-}
+} // namespace vkcv
diff --git a/include/vkcv/Image.hpp b/include/vkcv/Image.hpp
index eac96975886a92e0043bbb97cbe64f76943efa7e..c9c91db25fb4398439d609247d71ba6be3524464 100644
--- a/include/vkcv/Image.hpp
+++ b/include/vkcv/Image.hpp
@@ -1,64 +1,152 @@
 #pragma once
 /**
- * @authors Lars Hoerttrich
- * @file vkcv/Buffer.hpp
- * @brief class for image handles
+ * @authors Alexander Gauggel, Tobias Frisch, Lars Hoerttrich, Artur Wasmut
+ * @file vkcv/Image.hpp
+ * @brief Class for image handling.
  */
-#include "vulkan/vulkan.hpp"
 
+#include <vulkan/vulkan.hpp>
+
+#include "BufferTypes.hpp"
+#include "Core.hpp"
 #include "Handles.hpp"
+#include "ImageConfig.hpp"
+#include "Multisampling.hpp"
 
 namespace vkcv {
 
-    // forward declares
-    class ImageManager;
+	class Downsampler;
 
+	/**
+	 * @brief Returns whether an image format is usable as depth buffer.
+	 *
+	 * @param format Vulkan image format
+	 * @return True, if the format is valid to use as depth buffer,
+	 * otherwise false.
+	 */
 	bool isDepthFormat(const vk::Format format);
 
+	/**
+	 * @brief Returns whether an image format is usable as stencil buffer.
+	 *
+	 * @param format Vulkan image format
+	 * @return True, if the format is valid to use as stencil buffer,
+	 * otherwise false.
+	 */
+	bool isStencilFormat(const vk::Format format);
+
+	/**
+	 * @brief Class for image handling and filling data.
+	 */
 	class Image {
 		friend class Core;
+
 	public:
-		[[nodiscard]]
-		vk::Format getFormat() const;
-		
-		[[nodiscard]]
-		uint32_t getWidth() const;
-		
-		[[nodiscard]]
-		uint32_t getHeight() const;
-		
-		[[nodiscard]]
-		uint32_t getDepth() const;
+		Image() : m_core(nullptr), m_handle() {};
+
+		Image(Core* core, const ImageHandle &handle) : m_core(core), m_handle(handle) {}
+
+		Image(const Image &other) = default;
+		Image(Image &&other) = default;
+
+		~Image() = default;
+
+		Image &operator=(const Image &other) = default;
+		Image &operator=(Image &&other) = default;
+
+		/**
+		 * @brief Returns the format of the image.
+		 *
+		 * @return Vulkan image format
+		 */
+		[[nodiscard]] vk::Format getFormat() const;
 
-		[[nodiscard]]
-		vkcv::ImageHandle getHandle() const;
+		/**
+		 * @brief Returns the width of the image.
+		 *
+		 * @return Width of the image
+		 */
+		[[nodiscard]] uint32_t getWidth() const;
 
-		[[nodiscard]]
-		uint32_t getMipCount() const;
+		/**
+		 * @brief Returns the height of the image.
+		 *
+		 * @return Height of the image
+		 */
+		[[nodiscard]] uint32_t getHeight() const;
 
+		/**
+		 * @brief Returns the depth of the image.
+		 *
+		 * @return Depth of the image
+		 */
+		[[nodiscard]] uint32_t getDepth() const;
+
+		/**
+		 * @brief Returns the image handle of the image.
+		 *
+		 * @return Handle of the image
+		 */
+		[[nodiscard]] const vkcv::ImageHandle &getHandle() const;
+
+		/**
+		 * @brief Returns the amount of mip levels of the image.
+		 *
+		 * @return Number of mip levels
+		 */
+		[[nodiscard]] uint32_t getMipLevels() const;
+
+		/**
+		 * @brief Switches the image layout,
+		 * returns after operation is finished.
+		 *
+		 * @param[in] newLayout Layout that image is switched to
+		 */
 		void switchLayout(vk::ImageLayout newLayout);
-		
-		void fill(void* data, size_t size = SIZE_MAX);
-		void generateMipChainImmediate();
-		void recordMipChainGeneration(const vkcv::CommandStreamHandle& cmdStream);
-	private:
-	    // TODO: const qualifier removed, very hacky!!!
-	    //  Else you cannot recreate an image. Pls fix.
-		ImageManager*       m_manager;
-		ImageHandle   m_handle;
 
-		Image(ImageManager* manager, const ImageHandle& handle);
-		
-		static Image create(
-			ImageManager* manager,
-			vk::Format format,
-			uint32_t width,
-			uint32_t height,
-			uint32_t depth,
-			uint32_t mipCount,
-			bool supportStorage,
-			bool supportColorAttachment);
+		/**
+		 * @brief Fills the image with data of a given size in bytes.
+		 *
+		 * @param[in] data Pointer to the source data
+		 * @param[in] size Lower limit of the data size to copy in bytes,
+		 * the actual number of copied bytes is min(size, imageDataSize)
+		 */
+		void fill(const void* data, size_t size = SIZE_MAX);
 		
+		/**
+		 * @brief Fills a specific image layer with data of a given
+		 * size in bytes.
+		 *
+		 * @param[in] layer Image layer destination
+		 * @param[in] data Pointer to the source data
+		 * @param[in] size Lower limit of the data size to copy in bytes,
+		 * the actual number of copied bytes is min(size, imageDataSize)
+		 */
+		void fillLayer(uint32_t layer, const void* data, size_t size = SIZE_MAX);
+
+		/**
+		 * @brief Records mip chain generation to command stream,
+		 * mip level zero is used as source
+		 *
+		 * @param[out] cmdStream Command stream that the commands are recorded into
+		 * @param[in,out] downsampler Downsampler to generate mip levels with
+		 */
+		void recordMipChainGeneration(const vkcv::CommandStreamHandle &cmdStream,
+									  Downsampler &downsampler);
+
+	private:
+		Core* m_core;
+		ImageHandle m_handle;
 	};
+
+	Image image(Core &core, vk::Format format, uint32_t width, uint32_t height, uint32_t depth = 1,
+				bool createMipChain = false, bool supportStorage = false,
+				bool supportColorAttachment = false,
+				Multisampling multisampling = Multisampling::None);
 	
-}
+	Image image(Core &core,
+				vk::Format format,
+				const ImageConfig &config,
+				bool createMipChain = false);
+
+} // namespace vkcv
diff --git a/include/vkcv/ImageConfig.hpp b/include/vkcv/ImageConfig.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..5f495f39871335b602a4b8a087968c4085dc2518
--- /dev/null
+++ b/include/vkcv/ImageConfig.hpp
@@ -0,0 +1,163 @@
+#pragma once
+/**
+ * @authors Tobias Frisch
+ * @file vkcv/ImageConfig.hpp
+ * @brief Structure for image configuration.
+ */
+
+#include "Multisampling.hpp"
+
+namespace vkcv {
+	
+	/**
+	 * @brief Structure to configure image before its creation
+	 */
+	struct ImageConfig {
+	private:
+		uint32_t m_width;
+		uint32_t m_height;
+		uint32_t m_depth;
+		
+		bool m_supportStorage;
+		bool m_supportColorAttachment;
+		bool m_cubeMapImage;
+		
+		Multisampling m_msaa;
+		
+	public:
+		/**
+		 * Constructor of the image configuration by
+		 * a given resolution.
+		 *
+		 * @param[in] width Image width
+		 * @param[in] height Image height
+		 * @param[in] depth Image depth
+		 */
+		ImageConfig(uint32_t width,
+					uint32_t height,
+					uint32_t depth = 1);
+	
+		ImageConfig(const ImageConfig &other) = default;
+		ImageConfig(ImageConfig&& other) = default;
+
+		~ImageConfig() = default;
+
+		ImageConfig& operator=(const ImageConfig &other) = default;
+		ImageConfig& operator=(ImageConfig&& other) = default;
+		
+		/**
+		 * Return the configured width of the image.
+		 *
+		 * @return Image width
+		 */
+		[[nodiscard]]
+		uint32_t getWidth() const;
+		
+		/**
+		 * Set configured width of the image.
+		 *
+		 * @param[in] width Image width
+		 */
+		void setWidth(uint32_t width);
+		
+		/**
+		 * Return the configured height of the image.
+		 *
+		 * @return Image height
+		 */
+		[[nodiscard]]
+		uint32_t getHeight() const;
+		
+		/**
+		 * Set configured height of the image.
+		 *
+		 * @param[in] height Image height
+		 */
+		void setHeight(uint32_t height);
+		
+		/**
+		 * Return the configured depth of the image.
+		 *
+		 * @return Image depth
+		 */
+		[[nodiscard]]
+		uint32_t getDepth() const;
+		
+		/**
+		 * Set configured depth of the image.
+		 *
+		 * @param[in] depth Image depth
+		 */
+		void setDepth(uint32_t depth);
+		
+		/**
+		 * Return whether the image is configured to
+		 * support storage operations.
+		 *
+		 * @return True, if it supports storage, otherwise false
+		 */
+		[[nodiscard]]
+		bool isSupportingStorage() const;
+		
+		/**
+		 * Set whether the image is configured to
+		 * support storage operations.
+		 *
+		 * @param[in] supportStorage Support storage
+		 */
+		void setSupportingStorage(bool supportStorage);
+		
+		/**
+		 * Return whether the image is configured to
+		 * support being used as color attachment.
+		 *
+		 * @return True, if it supports color attachment, otherwise false
+		 */
+		[[nodiscard]]
+		bool isSupportingColorAttachment() const;
+		
+		/**
+		 * Set whether the image is configured to
+		 * support being used as color attachment.
+		 *
+		 * @param[in] supportColorAttachment Support color attachment
+		 */
+		void setSupportingColorAttachment(bool supportColorAttachment);
+		
+		/**
+		 * Return whether the image is configured to
+		 * be a cube map.
+		 *
+		 * @return True, if the image is a cube map, otherwise false
+		 */
+		[[nodiscard]]
+		bool isCubeMapImage() const;
+		
+		/**
+		 * Set whether the image is configured to
+		 * be a cube map.
+		 *
+		 * @param[in] cubeMapImage Is cube map image
+		 */
+		void setCubeMapImage(bool cubeMapImage);
+		
+		/**
+		 * Return type of multisampling the image
+		 * is configured to use.
+		 *
+		 * @return Multisampling
+		 */
+		[[nodiscard]]
+		Multisampling getMultisampling() const;
+		
+		/**
+		 * Set the multisampling of the image
+		 * configuration.
+		 *
+		 * @param[in] msaa Multisampling
+		 */
+		void setMultisampling(Multisampling msaa);
+		
+	};
+	
+}
diff --git a/include/vkcv/Logger.hpp b/include/vkcv/Logger.hpp
index d484711f642506926b1281a830fb2c9caf8240a2..8a366b265bfb029ccc0f379b8e193834c54343df 100644
--- a/include/vkcv/Logger.hpp
+++ b/include/vkcv/Logger.hpp
@@ -1,37 +1,63 @@
 #pragma once
+/**
+ * @authors Tobias Frisch
+ * @file vkcv/Logger.hpp
+ * @brief Logging macro function to print line of code specific information.
+ */
 
-#include <stdio.h>
+#include <cstdio>
+#include <exception>
 
 namespace vkcv {
-	
+
+	/**
+	 * @brief Enum class to specify the level of logging.
+	 */
 	enum class LogLevel {
+		RAW_INFO,
 		INFO,
 		WARNING,
 		ERROR
 	};
-	
+
+	/**
+	 * @brief Return the fitting output stream to print messages
+	 * of a given level of logging.
+	 *
+	 * @param[in] level Level of logging
+	 * @return Output stream (stdout or stderr)
+	 */
 	constexpr auto getLogOutput(LogLevel level) {
 		switch (level) {
-			case LogLevel::INFO:
-				return stdout;
-			default:
-				return stderr;
+		case LogLevel::RAW_INFO:
+		case LogLevel::INFO:
+			return stdout;
+		default:
+			return stderr;
 		}
 	}
-	
+
+	/**
+	 * @brief Returns the fitting identifier for messages of
+	 * a given level of logging.
+	 *
+	 * @param[in] level Level of logging
+	 * @return Identifier of the given level of logging
+	 */
 	constexpr const char* getLogName(LogLevel level) {
 		switch (level) {
-			case LogLevel::INFO:
-				return "INFO";
-			case LogLevel::WARNING:
-				return "WARNING";
-			case LogLevel::ERROR:
-				return "ERROR";
-			default:
-				return "UNKNOWN";
+		case LogLevel::RAW_INFO:
+		case LogLevel::INFO:
+			return "INFO";
+		case LogLevel::WARNING:
+			return "WARNING";
+		case LogLevel::ERROR:
+			return "ERROR";
+		default:
+			return "UNKNOWN";
 		}
 	}
-	
+
 #ifndef NDEBUG
 #ifndef VKCV_DEBUG_MESSAGE_LEN
 #define VKCV_DEBUG_MESSAGE_LEN 1024
@@ -41,28 +67,69 @@ namespace vkcv {
 #define __PRETTY_FUNCTION__ __FUNCSIG__
 #endif
 
-#define vkcv_log(level, ...) {      \
-  char output_message [             \
-    VKCV_DEBUG_MESSAGE_LEN          \
-  ];                                \
-  snprintf(                         \
-    output_message,                 \
-    VKCV_DEBUG_MESSAGE_LEN,         \
-    __VA_ARGS__                     \
-  );                                \
-  fprintf(                          \
-    getLogOutput(level),            \
-    "[%s]: %s [%s, line %d: %s]\n", \
-  	vkcv::getLogName(level),        \
-    output_message,                 \
-    __FILE__,                       \
-    __LINE__,                       \
-    __PRETTY_FUNCTION__             \
-  );                                \
-}
+/**
+ * @brief Macro-function to log formatting messages with
+ * a specific level of logging.
+ *
+ * @param[in] level Level of logging
+ */
+#define vkcv_log(level, ...)                                                         \
+	{                                                                                \
+		char output_message [VKCV_DEBUG_MESSAGE_LEN];                                \
+		snprintf(output_message, VKCV_DEBUG_MESSAGE_LEN, __VA_ARGS__);               \
+		auto output = getLogOutput(level);                                           \
+		if (level != vkcv::LogLevel::RAW_INFO) {                                     \
+			fprintf(output, "[%s]: %s [%s, line %d: %s]\n", vkcv::getLogName(level), \
+					output_message, __FILE__, __LINE__, __PRETTY_FUNCTION__);        \
+		} else {                                                                     \
+			fprintf(output, "[%s]: %s\n", vkcv::getLogName(level), output_message);  \
+		}                                                                            \
+		fflush(output);                                                              \
+	}
 
 #else
-#define vkcv_log(level, ...) {}
+/**
+ * @brief Macro-function to log formatting messages with
+ * a specific level of logging.
+ *
+ * @param[in] level Level of logging
+ */
+#define vkcv_log(level, ...) \
+	{}
 #endif
 
-}
+/**
+ * @brief Macro-function to log the message of any error
+ * or an exception.
+ *
+ * @param[in] error Error or exception
+ */
+#define vkcv_log_error(error) \
+	{ vkcv_log(LogLevel::ERROR, "%s", (error).what()); }
+
+/**
+ * @brief Macro-function to throw and log any error or
+ * an exception.
+ *
+ * @param[in] error Error or exception
+ */
+#define vkcv_log_throw(error)               \
+	{                                       \
+		try {                               \
+			throw error;                    \
+		} catch (const std::exception &e) { \
+			vkcv_log_error(e);              \
+			throw;                          \
+		}                                   \
+	}
+
+/**
+ * @brief Macro-function to throw and log an error
+ * with its custom message.
+ *
+ * @param[in] message Error message
+ */
+#define vkcv_log_throw_error(message) \
+	{ vkcv_log_throw(std::runtime_error(message)); }
+
+} // namespace vkcv
diff --git a/include/vkcv/Multisampling.hpp b/include/vkcv/Multisampling.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..5b285d191875d4e530a6d694241961bf6b19f781
--- /dev/null
+++ b/include/vkcv/Multisampling.hpp
@@ -0,0 +1,37 @@
+#pragma once
+/**
+ * @authors Alexander Gauggel, Tobias Frisch
+ * @file vkcv/ImageConfig.hpp
+ * @brief File to provide functions supporting the use of multisampling.
+ */
+
+#include <vulkan/vulkan.hpp>
+
+namespace vkcv {
+
+	enum class Multisampling {
+		None,
+		MSAA2X,
+		MSAA4X,
+		MSAA8X
+	};
+
+	/**
+	 * @brief Returns the sample count flag bits of a given
+	 * multi-sample anti-aliasing mode.
+	 *
+	 * @param[in] msaa MSAA mode
+	 * @return Sample count flag bits
+	 */
+	vk::SampleCountFlagBits msaaToSampleCountFlagBits(Multisampling msaa);
+
+	/**
+	 * @brief Returns the amount of samples of a given
+	 * multi-sample anti-aliasing mode.
+	 *
+	 * @param msaa MSAA mode
+	 * @return Number of samples
+	 */
+	uint32_t msaaToSampleCount(Multisampling msaa);
+
+} // namespace vkcv
diff --git a/include/vkcv/Pass.hpp b/include/vkcv/Pass.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c5299d391c0c3760441a15740a68719d6e480bd4
--- /dev/null
+++ b/include/vkcv/Pass.hpp
@@ -0,0 +1,24 @@
+#pragma once
+/**
+ * @authors Tobias Frisch
+ * @file vkcv/Pass.hpp
+ * @brief Support functions for basic pass creation.
+ */
+
+#include "Core.hpp"
+#include "Handles.hpp"
+#include "PassConfig.hpp"
+
+namespace vkcv {
+
+	PassHandle passFormats(Core &core, const std::vector<vk::Format> &formats, bool clear = true,
+						   Multisampling multisampling = Multisampling::None);
+
+	PassHandle passFormat(Core &core, vk::Format format, bool clear = true,
+						  Multisampling multisampling = Multisampling::None);
+
+	PassHandle passSwapchain(Core &core, const SwapchainHandle &swapchain,
+							 const std::vector<vk::Format> &formats, bool clear = true,
+							 Multisampling multisampling = Multisampling::None);
+
+} // namespace vkcv
diff --git a/include/vkcv/PassConfig.hpp b/include/vkcv/PassConfig.hpp
index 8f3b516d4b4451c513366fbd8469908bccde6a5f..efe741d07fd731a2c71fd6bf706cda7e39d43a50 100644
--- a/include/vkcv/PassConfig.hpp
+++ b/include/vkcv/PassConfig.hpp
@@ -1,51 +1,94 @@
 #pragma once
+/**
+ * @authors Alexander Gauggel, Artur Wasmut, Tobias Frisch
+ * @file vkcv/PassConfig.hpp
+ * @brief Enums and structures to handle render pass configuration.
+ */
 
 #include <vector>
 #include <vulkan/vulkan.hpp>
 
-namespace vkcv
-{
-    enum class AttachmentLayout
-    {
-        UNDEFINED,
-        GENERAL,
-
-        COLOR_ATTACHMENT,
-        SHADER_READ_ONLY,
-
-        DEPTH_STENCIL_ATTACHMENT,
-        DEPTH_STENCIL_READ_ONLY,
-
-        TRANSFER_SRC,
-        TRANSFER_DST,
-
-        PRESENTATION
-    };
-
-    enum class AttachmentOperation
-    {
-        LOAD,
-        CLEAR,
-        STORE,
-        DONT_CARE
-    };
-
-    struct AttachmentDescription
-    {
-        AttachmentDescription(
-            AttachmentOperation store_op,
-            AttachmentOperation load_op,
-            vk::Format format) noexcept;
-
-        AttachmentOperation store_operation;
-        AttachmentOperation load_operation;
-
-        vk::Format format;
-    };
-
-    struct PassConfig
-    {
-        explicit PassConfig(std::vector<AttachmentDescription> attachments) noexcept;
-        std::vector<AttachmentDescription> attachments{};
-    };
-}
\ No newline at end of file
+#include "Multisampling.hpp"
+
+namespace vkcv {
+
+	/**
+	 * @brief Enum class to specify types of attachment operations.
+	 */
+	enum class AttachmentOperation {
+		LOAD,
+		CLEAR,
+		STORE,
+		DONT_CARE
+	};
+
+	/**
+	 * @brief Class to store details about an attachment of a pass.
+	 */
+	class AttachmentDescription {
+	private:
+		vk::Format m_format;
+
+		AttachmentOperation m_load_op;
+		AttachmentOperation m_store_op;
+
+		vk::ClearValue m_clear_value;
+
+	public:
+		AttachmentDescription(vk::Format format, AttachmentOperation load,
+							  AttachmentOperation store);
+
+		AttachmentDescription(vk::Format format, AttachmentOperation load,
+							  AttachmentOperation store, const vk::ClearValue &clear);
+
+		AttachmentDescription(const AttachmentDescription &other) = default;
+		AttachmentDescription(AttachmentDescription &&other) = default;
+
+		~AttachmentDescription() = default;
+
+		AttachmentDescription &operator=(const AttachmentDescription &other) = default;
+		AttachmentDescription &operator=(AttachmentDescription &&other) = default;
+
+		[[nodiscard]] vk::Format getFormat() const;
+
+		[[nodiscard]] AttachmentOperation getLoadOperation() const;
+
+		[[nodiscard]] AttachmentOperation getStoreOperation() const;
+
+		void setClearValue(const vk::ClearValue &clear);
+
+		[[nodiscard]] const vk::ClearValue &getClearValue() const;
+	};
+
+	using AttachmentDescriptions = std::vector<AttachmentDescription>;
+
+	/**
+	 * @brief Class to configure a pass for usage.
+	 */
+	class PassConfig {
+	private:
+		AttachmentDescriptions m_attachments;
+		Multisampling m_multisampling;
+
+	public:
+		PassConfig();
+
+		explicit PassConfig(const AttachmentDescriptions &attachments,
+							Multisampling multisampling = Multisampling::None);
+
+		PassConfig(const PassConfig &other) = default;
+		PassConfig(PassConfig &&other) = default;
+
+		~PassConfig() = default;
+
+		PassConfig &operator=(const PassConfig &other) = default;
+		PassConfig &operator=(PassConfig &&other) = default;
+
+		[[nodiscard]] const AttachmentDescriptions &getAttachments() const;
+
+		void setMultisampling(Multisampling multisampling);
+
+		[[nodiscard]] Multisampling getMultisampling() const;
+	};
+
+} // namespace vkcv
\ No newline at end of file
diff --git a/include/vkcv/PipelineConfig.hpp b/include/vkcv/PipelineConfig.hpp
index 1e00c5209118469a7dc6ff1eb1e3c98a3c6903a7..575f17057ed1f0a8588fcf141a11a0e539aca178 100644
--- a/include/vkcv/PipelineConfig.hpp
+++ b/include/vkcv/PipelineConfig.hpp
@@ -1,30 +1,48 @@
 #pragma once
 /**
- * @authors Mara Vogt, Mark Mints
- * @file src/vkcv/Pipeline.hpp
- * @brief Pipeline class to handle shader stages
+ * @authors Tobias Frisch
+ * @file vkcv/PipelineConfig.hpp
+ * @brief Pipeline config class to hand over required information to pipeline creation
  */
 
 #include <vector>
-#include <cstdint>
+
 #include "Handles.hpp"
 #include "ShaderProgram.hpp"
-#include "VertexLayout.hpp"
 
 namespace vkcv {
 
-    enum class PrimitiveTopology{PointList, LineList, TriangleList };
-
-    struct PipelineConfig {
-        ShaderProgram                         m_ShaderProgram;
-        uint32_t                              m_Width;
-		uint32_t                              m_Height;
-        PassHandle                            m_PassHandle;
-        VertexLayout                          m_VertexLayout;
-        std::vector<vk::DescriptorSetLayout>  m_DescriptorLayouts;
-        bool                                  m_UseDynamicViewport;
-        bool                                  m_UseConservativeRasterization = false;
-        PrimitiveTopology                     m_PrimitiveTopology = PrimitiveTopology::TriangleList;
-    };
-
-}
\ No newline at end of file
+	/**
+	 * @brief Class to configure a general pipeline before its creation.
+	 */
+	class PipelineConfig {
+	private:
+		ShaderProgram m_ShaderProgram;
+		std::vector<DescriptorSetLayoutHandle> m_DescriptorSetLayouts;
+
+	public:
+		PipelineConfig();
+
+		PipelineConfig(const ShaderProgram &program,
+					   const std::vector<DescriptorSetLayoutHandle> &layouts);
+
+		PipelineConfig(const PipelineConfig &other) = default;
+		PipelineConfig(PipelineConfig &&other) = default;
+
+		~PipelineConfig() = default;
+
+		PipelineConfig &operator=(const PipelineConfig &other) = default;
+		PipelineConfig &operator=(PipelineConfig &&other) = default;
+
+		void setShaderProgram(const ShaderProgram &program);
+
+		[[nodiscard]] const ShaderProgram &getShaderProgram() const;
+
+		void addDescriptorSetLayout(const DescriptorSetLayoutHandle &layout);
+
+		void addDescriptorSetLayouts(const std::vector<DescriptorSetLayoutHandle> &layouts);
+
+		[[nodiscard]] const std::vector<DescriptorSetLayoutHandle> &getDescriptorSetLayouts() const;
+	};
+
+} // namespace vkcv
diff --git a/include/vkcv/PushConstants.hpp b/include/vkcv/PushConstants.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4c577acbe09cd6b19d82213526bc4b7d498a6ede
--- /dev/null
+++ b/include/vkcv/PushConstants.hpp
@@ -0,0 +1,157 @@
+#pragma once
+/**
+ * @authors Tobias Frisch, Alexander Gauggel
+ * @file vkcv/PushConstants.hpp
+ * @brief Class to manage push constants for pipeline recording.
+ */
+
+#include <vector>
+#include <vulkan/vulkan.hpp>
+
+#include "Logger.hpp"
+#include "TypeGuard.hpp"
+
+namespace vkcv {
+
+	/**
+	 * @brief Class to handle push constants data per drawcall.
+	 */
+	class PushConstants final {
+	private:
+		TypeGuard m_typeGuard;
+		std::vector<uint8_t> m_data;
+
+	public:
+		explicit PushConstants(size_t sizePerDrawcall);
+		explicit PushConstants(const TypeGuard &guard);
+
+		PushConstants(const PushConstants &other) = default;
+		PushConstants(PushConstants &&other) = default;
+
+		~PushConstants() = default;
+
+		PushConstants &operator=(const PushConstants &other) = default;
+		PushConstants &operator=(PushConstants &&other) = default;
+
+		/**
+		 * @brief Returns the size of the data that is bound
+		 * per drawcall in bytes.
+		 *
+		 * @return Size of data per drawcall
+		 */
+		[[nodiscard]] size_t getSizePerDrawcall() const;
+
+		/**
+		 * @brief Returns the size of total data stored for
+		 * push constants in bytes
+		 *
+		 * @return Total size of data
+		 */
+		[[nodiscard]] size_t getFullSize() const;
+
+		/**
+		 * @brief Returns the number of drawcalls that data
+		 * is stored for.
+		 *
+		 * @return Number of drawcalls
+		 */
+		[[nodiscard]] size_t getDrawcallCount() const;
+
+		/**
+		 * @brief Clears the data for all drawcalls currently.
+		 * stored.
+		 */
+		void clear();
+
+		/**
+		 * @brief Appends data for a single drawcall to the
+		 * storage with a given type.
+		 *
+		 * @tparam T Type of data (must match the size per drawcall)
+		 * @param[in] value Data to append
+		 * @return True, if operation was successfull, otherwise false
+		 */
+		template <typename T = uint8_t>
+		bool appendDrawcall(const T &value) {
+			if (!m_typeGuard.template check<T>()) {
+				return false;
+			}
+
+			const size_t offset = m_data.size();
+			m_data.resize(offset + sizeof(value));
+			std::memcpy(m_data.data() + offset, &value, sizeof(value));
+			return true;
+		}
+
+		/**
+		 * @brief Returns the data of the drawcall by a given index
+		 * as reference.
+		 *
+		 * @tparam T Type of data
+		 * @param[in] index Index of the drawcall
+		 * @return Drawcall data
+		 */
+		template <typename T = uint8_t>
+		T &getDrawcall(size_t index) {
+			const size_t offset = (index * getSizePerDrawcall());
+			return *reinterpret_cast<T*>(m_data.data() + offset);
+		}
+
+		/**
+		 * @brief Returns the data of the drawcall by a given index
+		 * as const reference.
+		 *
+		 * @tparam T Type of data
+		 * @param[in] index Index of the drawcall
+		 * @return Drawcall data
+		 */
+		template <typename T = uint8_t>
+		const T &getDrawcall(size_t index) const {
+			const size_t offset = (index * getSizePerDrawcall());
+			return *reinterpret_cast<const T*>(m_data.data() + offset);
+		}
+
+		/**
+		 * @brief Returns the data of the drawcall by a given index
+		 * as a pointer.
+		 *
+		 * @param[in] index Index of the drawcall
+		 * @return Drawcall data
+		 */
+		[[nodiscard]] const void* getDrawcallData(size_t index) const;
+
+		/**
+		 * @brief Returns the pointer to the entire drawcall data which
+		 * might be nullptr if the data is empty.
+		 *
+		 * @return Pointer to the data
+		 */
+		[[nodiscard]] const void* getData() const;
+	};
+
+	template <typename T>
+	PushConstants pushConstants() {
+		return PushConstants(typeGuard<T>());
+	}
+
+	template <typename T>
+	PushConstants pushConstants(const T &value) {
+		auto pc = pushConstants<T>();
+		pc.template appendDrawcall<T>(value);
+		return pc;
+	}
+
+	template <typename T>
+	PushConstants pushConstants(const std::vector<T> &values) {
+		auto pc = pushConstants<T>();
+
+		for (const T &value : values) {
+			if (!(pc.template appendDrawcall<T>(value))) {
+				break;
+			}
+		}
+
+		return pc;
+	}
+
+} // namespace vkcv
diff --git a/include/vkcv/QueueManager.hpp b/include/vkcv/QueueManager.hpp
index ac043b2d351014ea79fcae0d0fc439bb64a87b72..5bdf64dd2ac10f9cbfb4a03d58a3ed27d827a755 100644
--- a/include/vkcv/QueueManager.hpp
+++ b/include/vkcv/QueueManager.hpp
@@ -1,51 +1,102 @@
 #pragma once
+/**
+ * @authors Sebastian Gaida, Tobias Frisch, Alexander Gauggel
+ * @file vkcv/QueueManager.hpp
+ * @brief Types to manage queues of a device.
+ */
+
 #include <vulkan/vulkan.hpp>
 
 namespace vkcv {
 
-	enum class QueueType { Compute, Transfer, Graphics, Present };
+	/**
+	 * @brief Enum class to represent types of queues.
+	 */
+	enum class QueueType {
+		Compute,
+		Transfer,
+		Graphics,
+		Present
+	};
 
+	/**
+	 * @brief Structure to represent a queue and its details.
+	 */
 	struct Queue {
 		int familyIndex;
 		int queueIndex;
-		
+
 		vk::Queue handle;
 	};
-	
+
+	/**
+	 * @brief Class to manage queues of a device.
+	 */
 	class QueueManager {
 	public:
+		/**
+		 * @brief Creates a queue manager with the given pairs of queues.
+		 *
+		 * @param[in,out] device Vulkan device that holds the queues
+		 * @param[in] queuePairsGraphics Graphic queue pairs of queueFamily and queueIndex
+		 * @param[in] queuePairsCompute Compute queue pairs of queueFamily and queueIndex
+		 * @param[in] queuePairsTransfer Transfer queue pairs of queueFamily and queueIndex
+		 * @return New queue manager with the specified queue pairs
+		 */
 		static QueueManager create(vk::Device device,
-                            std::vector<std::pair<int, int>> &queuePairsGraphics,
-                            std::vector<std::pair<int, int>> &queuePairsCompute,
-                            std::vector<std::pair<int, int>> &queuePairsTransfer);
-
-        [[nodiscard]]
-        const Queue &getPresentQueue() const;
-		
-		[[nodiscard]]
-		const std::vector<Queue> &getGraphicsQueues() const;
-		
-		[[nodiscard]]
-        const std::vector<Queue> &getComputeQueues() const;
-		
-		[[nodiscard]]
-        const std::vector<Queue> &getTransferQueues() const;
-
-        static void queueCreateInfosQueueHandles(vk::PhysicalDevice &physicalDevice,
-                std::vector<float> &queuePriorities,
-                std::vector<vk::QueueFlagBits> &queueFlags,
-                std::vector<vk::DeviceQueueCreateInfo> &queueCreateInfos,
-                std::vector<std::pair<int, int>> &queuePairsGraphics,
-                std::vector<std::pair<int, int>> &queuePairsCompute,
-                std::vector<std::pair<int, int>> &queuePairsTransfer);
-
-    private:
-        std::vector<Queue> m_graphicsQueues;
-        std::vector<Queue> m_computeQueues;
-        std::vector<Queue> m_transferQueues;
-		
+								   const std::vector<std::pair<int, int>> &queuePairsGraphics,
+								   const std::vector<std::pair<int, int>> &queuePairsCompute,
+								   const std::vector<std::pair<int, int>> &queuePairsTransfer);
+
+		/**
+		 * @brief Returns the default queue with present support.
+		 * Recommended to use the present queue in the swapchain.
+		 *
+		 * @return Default present queue
+		 */
+		[[nodiscard]] const Queue &getPresentQueue() const;
+
+		/**
+		 * @brief Returns all queues with the graphics flag.
+		 *
+		 * @return Vector of graphics queues
+		 */
+		[[nodiscard]] const std::vector<Queue> &getGraphicsQueues() const;
+
+		/**
+		 * @brief Returns all queues with the compute flag.
+		 *
+		 * @return Vector of compute queues
+		 */
+		[[nodiscard]] const std::vector<Queue> &getComputeQueues() const;
+
+		/**
+		 * @brief Returns all queues with the transfer flag.
+		 *
+		 * @return Vector of transfer queues
+		 */
+		[[nodiscard]] const std::vector<Queue> &getTransferQueues() const;
+
+		/**
+		 * @brief Checks for presenting support of a given surface
+		 * in the queues and returns the queue family index of the
+		 * supporting queue.
+		 *
+		 * @param[in] physicalDevice Vulkan physical device
+		 * @param[in] surface Surface
+		 * @return Queue family index of the supporting present queue
+		 */
+		static uint32_t checkSurfaceSupport(const vk::PhysicalDevice &physicalDevice,
+											const vk::SurfaceKHR &surface);
+
+	private:
+		std::vector<Queue> m_graphicsQueues;
+		std::vector<Queue> m_computeQueues;
+		std::vector<Queue> m_transferQueues;
+
 		size_t m_presentIndex;
 
-        QueueManager(std::vector<Queue>&& graphicsQueues, std::vector<Queue>&& computeQueues, std::vector<Queue>&& transferQueues, size_t presentIndex);
+		QueueManager(std::vector<Queue> &&graphicsQueues, std::vector<Queue> &&computeQueues,
+					 std::vector<Queue> &&transferQueues, size_t presentIndex);
 	};
-}
+} // namespace vkcv
diff --git a/include/vkcv/Result.hpp b/include/vkcv/Result.hpp
index b78e444de9040f2982122d9242f584c7f9c340cf..7a11031913cd4c0604f4023f5e8c5d9be6abba2f 100644
--- a/include/vkcv/Result.hpp
+++ b/include/vkcv/Result.hpp
@@ -1,12 +1,18 @@
 #pragma once
+/**
+ * @authors Tobias Frisch
+ * @file vkcv/Result.hpp
+ * @brief Enum to represent result values of function which can fail.
+ */
 
 namespace vkcv {
-	
+
+	/**
+	 * @brief Enum class to specify the result of a function call.
+	 */
 	enum class Result {
-		
 		SUCCESS = 0,
 		ERROR = 1
-		
 	};
-	
-}
+
+} // namespace vkcv
diff --git a/include/vkcv/Sampler.hpp b/include/vkcv/Sampler.hpp
index 007ed5ae4737275ddacbc5881a2c4c202b8806a4..c4dc00e6e6178edeb8e056ac21838a268d60a66c 100644
--- a/include/vkcv/Sampler.hpp
+++ b/include/vkcv/Sampler.hpp
@@ -1,22 +1,18 @@
 #pragma once
+/**
+ * @authors Tobias Frisch
+ * @file vkcv/Sampler.hpp
+ * @brief Support functions for basic sampler creation.
+ */
+
+#include "Core.hpp"
+#include "Handles.hpp"
+#include "SamplerTypes.hpp"
 
 namespace vkcv {
 
-	enum class SamplerFilterType {
-		NEAREST = 1,
-		LINEAR = 2
-	};
-	
-	enum class SamplerMipmapMode {
-		NEAREST = 1,
-		LINEAR = 2
-	};
-	
-	enum class SamplerAddressMode {
-		REPEAT = 1,
-		MIRRORED_REPEAT = 2,
-		CLAMP_TO_EDGE = 3,
-		MIRROR_CLAMP_TO_EDGE = 4
-	};
+	[[nodiscard]] SamplerHandle samplerLinear(Core &core, bool clampToEdge = false);
+
+	[[nodiscard]] SamplerHandle samplerNearest(Core &core, bool clampToEdge = false);
 
-}
+} // namespace vkcv
diff --git a/include/vkcv/SamplerTypes.hpp b/include/vkcv/SamplerTypes.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..2c64da27acb6c5a2b689ef56e242303917f2f03a
--- /dev/null
+++ b/include/vkcv/SamplerTypes.hpp
@@ -0,0 +1,51 @@
+#pragma once
+/**
+ * @authors Tobias Frisch
+ * @file vkcv/SamplerTypes.hpp
+ * @brief Enums for different sampler attributes.
+ */
+
+namespace vkcv {
+
+	/**
+	 * @brief Enum class to specify a samplers type to filter during access.
+	 */
+	enum class SamplerFilterType {
+		NEAREST = 1,
+		LINEAR = 2
+	};
+
+	/**
+	 * @brief Enum class to specify a samplers mode to access mipmaps.
+	 */
+	enum class SamplerMipmapMode {
+		NEAREST = 1,
+		LINEAR = 2
+	};
+
+	/**
+	 * @brief Enum class to specify a samplers mode to access via address space.
+	 */
+	enum class SamplerAddressMode {
+		REPEAT = 1,
+		MIRRORED_REPEAT = 2,
+		CLAMP_TO_EDGE = 3,
+		MIRROR_CLAMP_TO_EDGE = 4,
+		CLAMP_TO_BORDER = 5
+	};
+
+	/**
+	 * @brief Enum class to specify a samplers color beyond a textures border.
+	 */
+	enum class SamplerBorderColor {
+		INT_ZERO_OPAQUE = 1,
+		INT_ZERO_TRANSPARENT = 2,
+
+		FLOAT_ZERO_OPAQUE = 3,
+		FLOAT_ZERO_TRANSPARENT = 4,
+
+		INT_ONE_OPAQUE = 5,
+		FLOAT_ONE_OPAQUE = 6
+	};
+
+} // namespace vkcv
diff --git a/include/vkcv/ShaderProgram.hpp b/include/vkcv/ShaderProgram.hpp
index 78b1f02169fe630427b9f66150e32078d42b7b3f..11156fdb5111da53df53ba7cbe80fa130d45b060 100644
--- a/include/vkcv/ShaderProgram.hpp
+++ b/include/vkcv/ShaderProgram.hpp
@@ -1,71 +1,105 @@
 #pragma once
 /**
- * @authors Simeon Hermann, Leonie Franken
- * @file src/vkcv/ShaderProgram.hpp
- * @brief ShaderProgram class to handle and prepare the shader stages for a graphics pipeline
+ * @authors Artur Wasmut, Leonie Franken, Tobias Frisch, Simeon Hermann, Alexander Gauggel, Mark
+ * Mints
+ * @file vkcv/ShaderProgram.hpp
+ * @brief ShaderProgram class to handle and prepare the shader stages for a graphics pipeline.
  */
 
-#include <unordered_map>
-#include <fstream>
-#include <iostream>
 #include <algorithm>
 #include <filesystem>
-#include <vulkan/vulkan.hpp>
+#include <fstream>
+#include <iostream>
 #include <spirv_cross.hpp>
-#include "VertexLayout.hpp"
+#include <unordered_map>
+#include <vulkan/vulkan.hpp>
+
+#include "DescriptorBinding.hpp"
 #include "ShaderStage.hpp"
-#include "DescriptorConfig.hpp"
+#include "VertexLayout.hpp"
 
 namespace vkcv {
 
-    struct Shader
-    {
-        std::vector<char> shaderCode;
-        ShaderStage shaderStage;
-    };
+	/**
+	 * @brief Class to manage and reflect shaders as a program.
+	 */
+	class ShaderProgram {
+	public:
+		ShaderProgram() noexcept;   // ctor
+		~ShaderProgram() = default; // dtor
 
-	class ShaderProgram
-	{
-    public:
-        ShaderProgram() noexcept; // ctor
-        ~ShaderProgram() = default; // dtor
+		/**
+		 * @brief Adds a shader into the shader program.
+		 * The shader is only added if the shader program does not contain
+		 * the particular shader stage already.
+		 * Contains:
+		 * (1) reading the SPIR-V file,
+		 * (2) creating a shader module,
+		 * (3) creating a shader stage,
+		 * (4) adding to the shader stage list,
+		 * (5) destroying of the shader module
+		 *
+		 * @param[in] stage The stage of the shader
+		 * @param[in] path Path to the SPIR-V shader file
+		 */
+		bool addShader(ShaderStage stage, const std::filesystem::path &path);
 
-        /**
-        * Adds a shader into the shader program.
-        * The shader is only added if the shader program does not contain the particular shader stage already.
-        * Contains: (1) reading of the code, (2) creation of a shader module, (3) creation of a shader stage, (4) adding to the shader stage list, (5) destroying of the shader module
-        * @param[in] flag that signals the respective shaderStage (e.g. VK_SHADER_STAGE_VERTEX_BIT)
-        * @param[in] relative path to the shader code (e.g. "../../../../../shaders/vert.spv")
-        */
-        bool addShader(ShaderStage shaderStage, const std::filesystem::path &shaderPath);
+		/**
+		 * @brief Returns the shader binary of a specified stage from the program.
+		 * Needed for the transfer to the pipeline.
+		 *
+		 * @param[in] stage The stage of the shader
+		 * @return Shader code binary of the given stage
+		 */
+		const std::vector<uint32_t> &getShaderBinary(ShaderStage stage) const;
 
-        /**
-        * Returns the shader program's shader of the specified shader.
-        * Needed for the transfer to the pipeline.
-        * @return Shader object consisting of buffer with shader code and shader stage enum
-        */
-        const Shader &getShader(ShaderStage shaderStage) const;
+		/**
+		 * @brief Returns whether a shader exists in the program for a
+		 * specified shader stage.
+		 *
+		 * @param[in] stage The stage of the shader
+		 * @return True, if a shader exists for the stage, else false
+		 */
+		bool existsShader(ShaderStage stage) const;
 
-        bool existsShader(ShaderStage shaderStage) const;
+		/**
+		 * @brief Returns the vertex attachments for the program and its
+		 * shader stages.
+		 *
+		 * @return Vertex attachments
+		 */
+		const std::vector<VertexAttachment> &getVertexAttachments() const;
 
-        const std::vector<VertexAttachment> &getVertexAttachments() const;
-		size_t getPushConstantSize() const;
+		/**
+		 * @brief Returns the size of the programs push constants.
+		 *
+		 * @return Size of push constants
+		 */
+		size_t getPushConstantsSize() const;
 
-        const std::vector<std::vector<DescriptorBinding>>& getReflectedDescriptors() const;
+		/**
+		 * @brief Returns the reflected descriptor set bindings mapped via
+		 * their descriptor set id.
+		 *
+		 * @return Reflected descriptor set bindings
+		 */
+		const std::unordered_map<uint32_t, DescriptorBindings> &getReflectedDescriptors() const;
 
 	private:
-	    /**
-	     * Called after successfully adding a shader to the program.
-	     * Fills vertex input attachments and descriptor sets (if present).
-	     * @param shaderStage the stage to reflect data from
-	     */
-        void reflectShader(ShaderStage shaderStage);
+		/**
+		 * @brief Called after successfully adding a shader to the program.
+		 * Fills vertex input attachments and descriptor sets (if present).
+		 *
+		 * @param[in] shaderStage the stage to reflect data from
+		 */
+		void reflectShader(ShaderStage shaderStage);
 
-        std::unordered_map<ShaderStage, Shader> m_Shaders;
+		std::unordered_map<ShaderStage, std::vector<uint32_t> > m_Shaders;
 
-        // contains all vertex input attachments used in the vertex buffer
-        std::vector<VertexAttachment> m_VertexAttachments;
-        std::vector<std::vector<DescriptorBinding>> m_DescriptorSets;
-		size_t m_pushConstantSize = 0;
+		// contains all vertex input attachments used in the vertex buffer
+		VertexAttachments m_VertexAttachments;
+		std::unordered_map<uint32_t, DescriptorBindings> m_DescriptorSets;
+		size_t m_pushConstantsSize = 0;
 	};
-}
+
+} // namespace vkcv
diff --git a/include/vkcv/ShaderStage.hpp b/include/vkcv/ShaderStage.hpp
index dca395bdba82a2f1cb38bb0a25196cfd3dab8019..2b6ab23ca623bb837d3e4fd3c5b22ca3b187fd43 100644
--- a/include/vkcv/ShaderStage.hpp
+++ b/include/vkcv/ShaderStage.hpp
@@ -1,15 +1,88 @@
 #pragma once
+/**
+ * @authors Artur Wasmut, Simeon Hermann, Tobias Frisch, Vanessa Karolek, Alexander Gauggel, Lars
+ * Hoerttrich
+ * @file vkcv/ShaderStage.hpp
+ * @brief Enum and struct to operate with multiple shader stages.
+ */
+
+#include <vulkan/vulkan.hpp>
 
 namespace vkcv {
-	
-	enum class ShaderStage
-	{
-		VERTEX,
-		TESS_CONTROL,
-		TESS_EVAL,
-		GEOMETRY,
-		FRAGMENT,
-		COMPUTE
+
+	/**
+	 * @brief Enum class to specify the stage of a shader.
+	 */
+	enum class ShaderStage : VkShaderStageFlags {
+		VERTEX = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eVertex),
+		TESS_CONTROL =
+			static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eTessellationControl),
+		TESS_EVAL =
+			static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eTessellationEvaluation),
+		GEOMETRY = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eGeometry),
+		FRAGMENT = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eFragment),
+		COMPUTE = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eCompute),
+		TASK = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eTaskNV),
+		MESH = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eMeshNV),
+		RAY_GEN = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eRaygenKHR),
+		RAY_ANY_HIT = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eAnyHitKHR),
+		RAY_CLOSEST_HIT = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eClosestHitKHR),
+		RAY_MISS = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eMissKHR),
+		RAY_INTERSECTION =
+			static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eIntersectionKHR),
+		RAY_CALLABLE = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eCallableKHR)
+	};
+
+	using ShaderStages = vk::Flags<ShaderStage>;
+
+} // namespace vkcv
+
+/**
+ * @cond VULKAN_TYPES
+ */
+namespace vk {
+
+	template <>
+	struct [[maybe_unused]] FlagTraits<vkcv::ShaderStage> {
+		enum : VkFlags {
+			allFlags [[maybe_unused]] =
+				(VkFlags(vkcv::ShaderStage::VERTEX) | VkFlags(vkcv::ShaderStage::TESS_CONTROL)
+				 | VkFlags(vkcv::ShaderStage::TESS_EVAL) | VkFlags(vkcv::ShaderStage::GEOMETRY)
+				 | VkFlags(vkcv::ShaderStage::FRAGMENT) | VkFlags(vkcv::ShaderStage::COMPUTE)
+				 | VkFlags(vkcv::ShaderStage::TASK) | VkFlags(vkcv::ShaderStage::MESH)
+				 | VkFlags(vkcv::ShaderStage::RAY_GEN) | VkFlags(vkcv::ShaderStage::RAY_ANY_HIT)
+				 | VkFlags(vkcv::ShaderStage::RAY_CLOSEST_HIT)
+				 | VkFlags(vkcv::ShaderStage::RAY_MISS)
+				 | VkFlags(vkcv::ShaderStage::RAY_INTERSECTION)
+				 | VkFlags(vkcv::ShaderStage::RAY_CALLABLE))
+		};
 	};
 
-}
+} // namespace vk
+/**
+ * @endcond
+ */
+
+namespace vkcv {
+
+	constexpr vk::ShaderStageFlags getShaderStageFlags(ShaderStages shaderStages) noexcept {
+		return vk::ShaderStageFlags(static_cast<VkShaderStageFlags>(shaderStages));
+	}
+
+	constexpr ShaderStages operator|(ShaderStage stage0, ShaderStage stage1) noexcept {
+		return ShaderStages(stage0) | stage1;
+	}
+
+	constexpr ShaderStages operator&(ShaderStage stage0, ShaderStage stage1) noexcept {
+		return ShaderStages(stage0) & stage1;
+	}
+
+	constexpr ShaderStages operator^(ShaderStage stage0, ShaderStage stage1) noexcept {
+		return ShaderStages(stage0) ^ stage1;
+	}
+
+	constexpr ShaderStages operator~(ShaderStage stage) noexcept {
+		return ~(ShaderStages(stage));
+	}
+
+} // namespace vkcv
diff --git a/include/vkcv/Swapchain.hpp b/include/vkcv/Swapchain.hpp
deleted file mode 100644
index b75fc5a87156ea56061e41b4b0974928c83ffa28..0000000000000000000000000000000000000000
--- a/include/vkcv/Swapchain.hpp
+++ /dev/null
@@ -1,122 +0,0 @@
-#pragma once
-#include "vulkan/vulkan.hpp"
-#include "Context.hpp"
-#include "vkcv/Window.hpp"
-
-#include <atomic>
-
-namespace vkcv
-{
-    class Swapchain final {
-    private:
-    	friend class Core;
-
-        struct Surface
-        {
-            vk::SurfaceKHR handle;
-            std::vector<vk::SurfaceFormatKHR> formats;
-            vk::SurfaceCapabilitiesKHR capabilities;
-            std::vector<vk::PresentModeKHR> presentModes;
-        };
-        
-        Surface m_Surface;
-
-        vk::SwapchainKHR m_Swapchain;
-        vk::Format m_Format;
-        vk::ColorSpaceKHR m_ColorSpace;
-        vk::PresentModeKHR m_PresentMode;
-		uint32_t m_ImageCount;
-	
-		vk::Extent2D m_Extent;
-	
-		std::atomic<bool> m_RecreationRequired;
-
-        /**
-         * Constructor of a SwapChain object
-         * glfw is not initialized in this class because ist must be sure that there exists a context first
-         * glfw is already initialized by the window class
-         * @param surface used by the swapchain
-         * @param swapchain to show images in the window
-         * @param format
-         */
-         // TODO:
-        Swapchain(const Surface &surface,
-                  vk::SwapchainKHR swapchain,
-                  vk::Format format,
-                  vk::ColorSpaceKHR colorSpace,
-                  vk::PresentModeKHR presentMode,
-                  uint32_t imageCount,
-				  vk::Extent2D extent) noexcept;
-	
-		/**
-		 * TODO
-		 *
-		 * @return
-		 */
-		bool shouldUpdateSwapchain() const;
-	
-		/**
-		 * TODO
-		 *
-		 * context
-		 * window
-		 */
-		void updateSwapchain(const Context &context, const Window &window);
-	
-		/**
-		 *
-		 */
-		void signalSwapchainRecreation();
-
-    public:
-    	Swapchain(const Swapchain& other);
-
-        /**
-         * @return The swapchain linked with the #SwapChain class
-         * @note The reference to our Swapchain variable is needed for the recreation step
-         */
-        [[nodiscard]]
-        const vk::SwapchainKHR& getSwapchain() const;
-
-        /**
-         * gets the current surface object
-         * @return current surface
-         */
-        [[nodiscard]]
-        vk::SurfaceKHR getSurface() const;
-
-        /**
-         * gets the chosen swapchain format
-         * @return gets the chosen swapchain format
-         */
-        [[nodiscard]]
-        vk::Format getFormat() const;
-
-        /**
-         * creates a swap chain object out of the given window and the given context
-         * @param window a wrapper that represents a glfw window
-         * @param context of the application
-         * @return returns an object of swapChain
-         */
-        static Swapchain create(const Window &window, const Context &context);
-
-        /**
-         * Destructor of SwapChain
-         */
-        virtual ~Swapchain();
-
-		/**
-		 * @return number of images in swapchain
-		*/
-		uint32_t getImageCount() const;
-	
-        /**
-         * TODO
-         *
-         * @return
-         */
-        [[nodiscard]]
-		const vk::Extent2D& getExtent() const;
-    
-    };
-}
diff --git a/include/vkcv/SyncResources.hpp b/include/vkcv/SyncResources.hpp
deleted file mode 100644
index c41019cc46ee1375a83323a6ecc877ecc1c1727a..0000000000000000000000000000000000000000
--- a/include/vkcv/SyncResources.hpp
+++ /dev/null
@@ -1,15 +0,0 @@
-#pragma once
-#include <vulkan/vulkan.hpp>
-
-namespace vkcv {
-	struct SyncResources {
-		vk::Semaphore	renderFinished;
-		vk::Semaphore	swapchainImageAcquired;
-		vk::Fence		presentFinished;
-	};
-
-	SyncResources	createSyncResources(const vk::Device &device);
-	void			destroySyncResources(const vk::Device &device, const SyncResources &resources);
-	vk::Fence		createFence(const vk::Device &device);
-	void			waitForFence(const vk::Device& device, const vk::Fence fence);
-}
\ No newline at end of file
diff --git a/include/vkcv/TypeGuard.hpp b/include/vkcv/TypeGuard.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a5312b161d4ded7b061d351608e269b04f20807a
--- /dev/null
+++ b/include/vkcv/TypeGuard.hpp
@@ -0,0 +1,149 @@
+#pragma once
+/**
+ * @authors Tobias Frisch
+ * @file vkcv/TypeGuard.hpp
+ * @brief Support type safety for classes in debug compilation.
+ */
+
+#include <stdlib.h>
+#include <typeinfo>
+
+namespace vkcv {
+
+	/**
+	 * @brief Class bringing type safety during runtime to other classes.
+	 */
+	class TypeGuard {
+	private:
+#ifndef NDEBUG
+		const char* m_typeName;
+		size_t m_typeHash;
+
+		[[nodiscard]] bool checkType(const char* name, size_t hash, size_t size) const;
+#endif
+		size_t m_typeSize;
+
+		[[nodiscard]] bool checkTypeSize(size_t size) const;
+
+	public:
+		/**
+		 * Explicit constructor to create a type guard by a specific
+		 * size only. The guard will not verify an individual type but
+		 * whether its size matches the requirement.
+		 *
+		 * @param[in] size Size of type
+		 */
+		explicit TypeGuard(size_t size = 0);
+
+		/**
+		 * Explicit constructor to create a type guard by a types
+		 * ID information and its size. The guard will verify a type
+		 * by all available information in debug mode.
+		 *
+		 * @param[in] info ID information of type
+		 * @param[in] size Size of type
+		 */
+		TypeGuard(const std::type_info &info, size_t size);
+
+		TypeGuard(const TypeGuard &other) = default;
+		TypeGuard(TypeGuard &&other) noexcept = default;
+
+		~TypeGuard() = default;
+
+		TypeGuard &operator=(const TypeGuard &other) = default;
+		TypeGuard &operator=(TypeGuard &&other) noexcept = default;
+
+		/**
+		 * Operator to compare two type guards and returns
+		 * whether their stored type information and size
+		 * match as boolean value.
+		 *
+		 * @param[in] other Other type guard
+		 * @return True if the details match, otherwise false.
+		 */
+		bool operator==(const TypeGuard &other) const;
+
+		/**
+		 * Operator to compare two type guards and returns
+		 * whether their stored type information and size
+		 * do not match as boolean value.
+		 *
+		 * @param[in] other Other type guard
+		 * @return True if the details differ, otherwise false.
+		 */
+		bool operator!=(const TypeGuard &other) const;
+
+		/**
+		 * Checks whether a type from a template parameter
+		 * matches with the used type by the given guard.
+		 *
+		 * @tparam T Type to check against
+		 * @return True if both types match, otherwise false.
+		 */
+		template <typename T>
+		[[nodiscard]] bool check() const {
+#ifndef NDEBUG
+			return checkType(typeid(T).name(), typeid(T).hash_code(), sizeof(T));
+#else
+			return checkTypeSize(sizeof(T));
+#endif
+		}
+
+		/**
+		 * Returns the size of this guards type in bytes.
+		 *
+		 * @return Size of type
+		 */
+		[[nodiscard]] size_t typeSize() const;
+	};
+
+	/**
+	 * Creates a new type guard with a given type specified
+	 * as template parameter.
+	 *
+	 * @tparam T Type
+	 * @return New type guard
+	 */
+	template <typename T>
+	TypeGuard typeGuard() {
+		static TypeGuard guard(typeid(T), sizeof(T));
+		return guard;
+	}
+
+	/**
+	 * Creates a new type guard with a given type specified
+	 * as template parameter by the passed parameter.
+	 *
+	 * @tparam T Type
+	 * @return New type guard
+	 */
+	template <typename T>
+	TypeGuard typeGuard(T) {
+		return typeGuard<T>();
+	}
+
+	/**
+	 * Creates a new type guard with a given type specified
+	 * as template parameter by the passed parameter.
+	 *
+	 * @tparam T Type
+	 * @return New type guard
+	 */
+	template <typename T>
+	TypeGuard typeGuard(const T &) {
+		return typeGuard<T>();
+	}
+
+	/**
+	 * Creates a new type guard with a given type specified
+	 * as template parameter by the passed parameter.
+	 *
+	 * @tparam T Type
+	 * @return New type guard
+	 */
+	template <typename T>
+	TypeGuard typeGuard(T &&) {
+		return typeGuard<T>();
+	}
+
+} // namespace vkcv
diff --git a/include/vkcv/VertexData.hpp b/include/vkcv/VertexData.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..845b9fba9a646d702377c297112029944a254f17
--- /dev/null
+++ b/include/vkcv/VertexData.hpp
@@ -0,0 +1,109 @@
+#pragma once
+/**
+ * @authors Sebastian Gaida, Alexander Gauggel, Artur Wasmut, Tobias Frisch
+ * @file vkcv/VertexData.hpp
+ * @brief Types to configure vertex data for drawcalls.
+ */
+
+#include <vector>
+
+#include "Handles.hpp"
+
+namespace vkcv {
+
+	/**
+	 * @brief Structure to store details about a vertex buffer binding.
+	 */
+	struct VertexBufferBinding {
+		BufferHandle buffer;
+		size_t offset;
+	};
+
+	VertexBufferBinding vertexBufferBinding(const BufferHandle &buffer, size_t offset = 0);
+
+	typedef std::vector<VertexBufferBinding> VertexBufferBindings;
+
+	/**
+	 * @brief Enum class to specify the size of indexes.
+	 */
+	enum class IndexBitCount {
+		Bit8,
+		Bit16,
+		Bit32
+	};
+
+	/**
+	 * @brief Class to store the details of vertex data for rendering
+	 */
+	class VertexData {
+	private:
+		VertexBufferBindings m_bindings;
+		BufferHandle m_indices;
+		IndexBitCount m_indexBitCount;
+		size_t m_count;
+
+	public:
+		/**
+		 * @brief Constructor of vertex data by providing an optional vector
+		 * of vertex buffer bindings.
+		 *
+		 * @param[in] bindings Vertex buffer bindings (optional)
+		 */
+		explicit VertexData(const VertexBufferBindings &bindings = {});
+
+		VertexData(const VertexData &other) = default;
+		VertexData(VertexData &&other) noexcept = default;
+
+		~VertexData() = default;
+
+		VertexData &operator=(const VertexData &other) = default;
+		VertexData &operator=(VertexData &&other) noexcept = default;
+
+		/**
+		 * @brief Return the used vertex buffer bindings of the vertex data.
+		 *
+		 * @return Vertex buffer bindings
+		 */
+		[[nodiscard]] const VertexBufferBindings &getVertexBufferBindings() const;
+
+		/**
+		 * @brief Set the optional index buffer and its used index bit count.
+		 *
+		 * @param[in] indices Index buffer handle
+		 * @param[in] indexBitCount Index bit count
+		 */
+		void setIndexBuffer(const BufferHandle &indices,
+							IndexBitCount indexBitCount = IndexBitCount::Bit16);
+
+		/**
+		 * @brief Return the handle from the used index buffer of the vertex
+		 * data.
+		 *
+		 * @return Index buffer handle
+		 */
+		[[nodiscard]] const BufferHandle &getIndexBuffer() const;
+
+		/**
+		 * @brief Return the index bit count of the indices used in the
+		 * vertex data.
+		 *
+		 * @return Index bit count
+		 */
+		[[nodiscard]] IndexBitCount getIndexBitCount() const;
+
+		/**
+		 * @brief Set the count of elements to use by the vertex data.
+		 *
+		 * @param count Count of vertex elements
+		 */
+		void setCount(size_t count);
+
+		/**
+		 * @brief Return the count of elements in use by the vertex data.
+		 *
+		 * @return Count of vertex elements
+		 */
+		[[nodiscard]] size_t getCount() const;
+	};
+
+} // namespace vkcv
diff --git a/include/vkcv/VertexLayout.hpp b/include/vkcv/VertexLayout.hpp
index 0600b99a24a327605e89b2e8ec304c20dbf7ad2e..2c6cfb486348dea7f255c4fbdc26a27b3bfdda56 100644
--- a/include/vkcv/VertexLayout.hpp
+++ b/include/vkcv/VertexLayout.hpp
@@ -1,66 +1,108 @@
 #pragma once
+/**
+ * @authors Alexander Gauggel, Artur Wasmut, Mara Vogt, Susanne Dötsch,
+ *          Trevor Hollmann, Leonie Franken, Simeon Hermann, Tobias Frisch
+ * @file vkcv/VertexLayout.hpp
+ * @brief Structures to handle vertex layout, bindings and attachments.
+ */
 
-#include <vector>
 #include <iostream>
 #include <string>
+#include <vector>
+
+namespace vkcv {
 
-namespace vkcv{
-    enum class VertexAttachmentFormat{
-        FLOAT,
-        FLOAT2,
-        FLOAT3,
-        FLOAT4,
-        INT,
-        INT2,
-        INT3,
-        INT4
-    };
+	/**
+	 * @brief Enum class to specify the format of vertex attributes.
+	 */
+	enum class VertexAttachmentFormat {
+		FLOAT,
+		FLOAT2,
+		FLOAT3,
+		FLOAT4,
+		INT,
+		INT2,
+		INT3,
+		INT4
+	};
 
+	/**
+	 * @brief Returns the size in bytes of a vertex with a
+	 * given vertex format.
+	 *
+	 * @param[in] format Vertex format
+	 * @return Size in bytes
+	 */
 	uint32_t getFormatSize(VertexAttachmentFormat format);
 
-    struct VertexAttachment{
-        friend struct VertexBinding;
-        /**
-         * Describes an individual vertex input attribute/attachment.
-         * @param inputLocation its location in the vertex shader.
-         * @param name the name referred to in the shader.
-         * @param format the format (and therefore, the size) this attachment is in.
-         * The offset is calculated when a collection of attachments forms a binding, hence the friend declaration.
-         */
-        VertexAttachment(uint32_t inputLocation, const std::string &name, VertexAttachmentFormat format) noexcept;
-        VertexAttachment() = delete;
+	/**
+	 * @brief Structure to store the details of a vertex input attachment.
+	 *
+	 * Describes an individual vertex input attribute/attachment. The offset
+	 * is calculated when a collection of attachments forms a binding.
+	 */
+	struct VertexAttachment {
+		uint32_t inputLocation;
+		std::string name;
+		VertexAttachmentFormat format;
+		uint32_t offset;
+	};
+
+	typedef std::vector<VertexAttachment> VertexAttachments;
+
+	/**
+	 * @brief Structure to store the details of a vertex buffer binding.
+	 *
+	 * Describes all vertex input attachments _one_ buffer contains to create
+	 * a vertex buffer binding. NOTE: multiple vertex layouts may contain
+	 * various (mutually exclusive) vertex input attachments to form one
+	 * complete vertex buffer binding!
+	 */
+	struct VertexBinding {
+		uint32_t bindingLocation;
+		uint32_t stride;
+		VertexAttachments vertexAttachments;
+	};
 
-        uint32_t                inputLocation;
-        std::string             name;
-        VertexAttachmentFormat  format;
-        uint32_t                offset;
-    };
+	/**
+	 * Creates a vertex binding with given parameters and calculates its strides
+	 * depending on its attachments.
+	 *
+	 * @param[in] bindingLocation Its entry in the buffers that make up the whole vertex buffer.
+	 * @param[in] attachments The vertex input attachments this specific buffer layout contains.
+	 * @return Vertex binding with calculated stride
+	 */
+	VertexBinding createVertexBinding(uint32_t bindingLocation,
+									  const VertexAttachments &attachments);
 
-    struct VertexBinding{
-        /**
-         * Describes all vertex input attachments _one_ buffer contains to create a vertex buffer binding.
-         * NOTE: multiple vertex layouts may contain various (mutually exclusive) vertex input attachments
-         * to form one complete vertex buffer binding!
-         * @param bindingLocation its entry in the buffers that make up the whole vertex buffer.
-         * @param attachments the vertex input attachments this specific buffer layout contains.
-         */
-        VertexBinding(uint32_t bindingLocation, const std::vector<VertexAttachment> &attachments) noexcept;
-        VertexBinding() = delete;
+	typedef std::vector<VertexBinding> VertexBindings;
 
-        uint32_t                        bindingLocation;
-        uint32_t                        stride;
-        std::vector<VertexAttachment>   vertexAttachments;
-    };
+	/**
+	 * Creates vertex bindings in a very simplified way with one vertex binding for
+	 * each attachment.
+	 *
+	 * @param[in] attachments The vertex input attachments.
+	 * @return Vertex bindings with calculated stride
+	 */
+	VertexBindings createVertexBindings(const VertexAttachments &attachments);
 
-    struct VertexLayout{
-        /**
-         * Describes the complete layout of one vertex, e.g. all of the vertex input attachments used,
-         * and all of the buffer bindings that refer to the attachments (for when multiple buffers are used).
-         * @param bindings bindings the complete vertex buffer is comprised of.
-         */
-        VertexLayout() noexcept;
-        VertexLayout(const std::vector<VertexBinding> &bindings) noexcept;
+	/**
+	 * @brief Structure to store the details of a vertex layout.
+	 *
+	 * Describes the complete layout of one vertex, e.g. all of the vertex input
+	 * attachments used, and all of the buffer bindings that refer to the attachments
+	 * (for when multiple buffers are used).
+	 */
+	struct VertexLayout {
+		VertexBindings vertexBindings;
+	};
+	
+	/**
+	 * Creates vertex layout from a list of vertex bindings in a simplified way.
+	 *
+	 * @param[in] bindings The vertex bindings
+	 * @return Vertex layout
+	 */
+	VertexLayout createVertexLayout(const VertexBindings &bindings);
 
-        std::vector<VertexBinding> vertexBindings;
-    };
-}
+} // namespace vkcv
diff --git a/include/vkcv/Window.hpp b/include/vkcv/Window.hpp
index 7dc6c1b7dc8fef4d5de7de5b0a9976bf714e6ac2..3d157b98220f17752d6b109401c3d21899299f18 100644
--- a/include/vkcv/Window.hpp
+++ b/include/vkcv/Window.hpp
@@ -1,162 +1,170 @@
 #pragma once
 /**
- * @authors Sebastian Gaida
- * @file src/vkcv/Window.hpp
- * @brief Window class to handle a basic rendering surface and input
+ * @authors Tobias Frisch, Sebastian Gaida, Vanessa Karolek, Artur Wasmut
+ * @file vkcv/Window.hpp
+ * @brief Class to represent and manage a window with its input.
  */
 
-#define NOMINMAX
+#ifndef NOMINMAX
+#define NOMINMAX 1
+#endif
+
 #include <algorithm>
+#include <string>
 
 #include "Event.hpp"
+#include "Handles.hpp"
 
 struct GLFWwindow;
 
 namespace vkcv {
 
-    class Window {
-	protected:
-		GLFWwindow *m_window;
-	
-		/**
-         *
-         * @param GLFWwindow of the class
-         */
-		explicit Window(GLFWwindow *window);
-		
-    private:
-        /**
-         * mouse callback for moving the mouse on the screen
-         * @param[in] window The window that received the event.
-         * @param[in] xpos The new cursor x-coordinate, relative to the left edge of the content area.
-         * @param[in] ypos The new cursor y-coordinate, relative to the top edge of the content area.
-         */
-        static void onMouseMoveEvent(GLFWwindow *window, double x, double y);
-
-        /**
-         * mouseButton callback for mouse buttons
-         * @param[in] button The [mouse button](@ref buttons) that was pressed or released.
-         * @param[in] action One of `GLFW_PRESS` or `GLFW_RELEASE`.  Future releases may add more actions.
-         * @param[in] mods Bit field describing which [modifier keys](@ref mods) were held down.
-         */
-        static void onMouseButtonEvent(GLFWwindow *callbackWindow, int button, int action, int mods);
-
-        /**
-         * @brief A callback function for handling mouse scrolling events.
-         * @param[in] callbackWindow The window that received the event.
-         * @param[in] xoffset The extent of horizontal scrolling.
-         * @param[in] yoffset The extent of vertical scrolling.
-         */
-        static void onMouseScrollEvent(GLFWwindow *callbackWindow, double xoffset, double yoffset);
-
-        /**
-         * resize callback for the resize option of the window
-         * @param[in] window The window that was resized.
-         * @param[in] width The new width, in screen coordinates, of the window.
-         * @param[in] height The new height, in screen coordinates, of the window.
-         */
-        static void onResize(GLFWwindow *callbackWindow, int width, int height);
-
-        /**
-         * key callback for the pressed key
-         * @param[in] window The window that received the event.
-         * @param[in] key The [keyboard key](@ref keys) that was pressed or released.
-         * @param[in] scancode The system-specific scancode of the key.
-         * @param[in] action `GLFW_PRESS`, `GLFW_RELEASE` or `GLFW_REPEAT`.
-         * @param[in] mods Bit field describing which [modifier keys](@ref mods) were held down.
-         */
-        static void onKeyEvent(GLFWwindow *callbackWindow, int key, int scancode, int action, int mods);
-	
-        /**
-         * char callback for any typed character
-         * @param[in] window The window that received the event
-         * @param[in] c The character that got typed
-         */
-		static void onCharEvent(GLFWwindow *callbackWindow, unsigned int c);
-
-        /**
-         * @brief A callback function for gamepad input events.
-         * @param gamepadIndex The gamepad index.
-         */
-        static void onGamepadEvent(int gamepadIndex);
-
-    public:
-        /**
-         * creates a GLFWwindow with the parameters in the function
-         * @param[in] windowTitle of the window
-         * @param[in] width of the window (optional)
-         * @param[in] height of the window (optional)
-         * @param[in] resizable resize ability of the window (optional)
-         * @return Window class
-         */
-        static Window create( const char *windowTitle, int width = -1, int height = -1, bool resizable = false);
-        /**
-         * checks if the window is still open, or the close event was called
-         * This function should be changed/removed later on
-         * @return bool if the window is still open
-         */
-        [[nodiscard]]
-        bool isWindowOpen() const;
-
-        /**
-         * polls all events on the GLFWwindow
-         */
-        static void pollEvents();
-
-        /**
-         * basic events of the window
-         */
-        event< int, int, int> e_mouseButton;
-        event< double, double > e_mouseMove;
-        event< double, double > e_mouseScroll;
-        event< int, int > e_resize;
-        event< int, int, int, int > e_key;
-        event< unsigned int > e_char;
-        event< int > e_gamepad;
-
-        /**
-         * returns the current window
-         * @return window handle
-         */
-        [[nodiscard]]
-        GLFWwindow *getWindow() const;
-
-        /**
-         * Copy-operator of #Window is deleted!
-         *
-         * @param other Other instance of #Window
-         * @return Reference to itself
-         */
-        Window &operator=(const Window &other) = delete;
-
-        /**
-         * Move-operator of #Window uses default behavior!
-         *
-         * @param other Other instance of #Window
-         * @return Reference to itself
-         */
-        Window &operator=(Window &&other) = default;
-
-        /**
-         * gets the window width
-         * @param window glfwWindow
-         * @return int with window width
-         */
-        [[nodiscard]]
-        int getWidth() const;
-
-        /**
-         * gets the window height
-         * @param window glfwWindow
-         * @return int with window height
-         */
-        [[nodiscard]]
-        int getHeight() const;
-
-        /**
-         * Destructor of #Window, terminates GLFW
-         */
-        virtual ~Window();
-    };
-
-}
+	/**
+	 * @brief Class to handle a window.
+	 */
+	class Window {
+		friend class WindowManager;
+		friend class SwapchainManager;
+
+	private:
+		std::string m_title;
+		bool m_resizable;
+		bool m_shouldClose;
+		GLFWwindow* m_window;
+		SwapchainHandle m_swapchainHandle;
+		event_handle<int, int> m_resizeHandle;
+
+	public:
+		/**
+		 * @brief Constructor of an uninitialized #Window
+		 */
+		Window();
+
+		/**
+		 * @brief Constructor of a #Window with an optional width,
+		 * height and resizable attribute.
+		 *
+		 * @param[in] title title of the window
+		 * @param[in] width width of the window (optional)
+		 * @param[in] height height of the window (optional)
+		 * @param[in] resizable resize ability of the window (optional)
+		 */
+		explicit Window(const std::string &title, int width = -1, int height = -1,
+						bool resizable = false);
+
+		/**
+		 * @brief Copy-constructor of a #Window
+		 *
+		 * @param[in] other Other instance of #Window
+		 */
+		Window(const Window &other) = delete;
+
+		/**
+		 * @brief Copy-operator of a #Window
+		 *
+		 * @param[in] other Other instance of #Window
+		 * @return Reference to itself
+		 */
+		Window &operator=(const Window &other) = delete;
+
+		/**
+		 * @brief Checks if the window is still open, or the close event was called.
+		 * TODO: This function should be changed/removed later on
+		 *
+		 * @return True, if the window is still open, else false
+		 */
+		[[nodiscard]] bool isOpen() const;
+
+		/**
+		 * @brief Returns the currently focused window.
+		 * TODO: only accessible to WindowManager
+		 *
+		 * @return Current window in focus
+		 */
+		static Window &getFocusedWindow();
+
+		/**
+		 * @brief Checks if any windows are active and open.
+		 *
+		 * @return True, if any window is open, else false
+		 */
+		static bool hasOpenWindow();
+
+		/**
+		 * @brief Polls all events on the active windows.
+		 */
+		static void pollEvents();
+
+		/**
+		 * @brief Returns the required extensions to use GLFW windows with Vulkan.
+		 *
+		 * @return Required surface extensions
+		 */
+		static const std::vector<std::string> &getExtensions();
+
+		event< int, int, int> e_mouseButton;
+		event< double, double > e_mouseMove;
+		event< double, double > e_mouseScroll;
+		event< int, int > e_resize;
+		event< int, int, int, int > e_key;
+		event< unsigned int > e_char;
+		event< int > e_gamepad;
+
+		/**
+		 * @brief Returns the GLFW window handle.
+		 *
+		 * @return GLFW window handle
+		 */
+		[[nodiscard]] GLFWwindow* getWindow() const;
+
+		/**
+		 * @brief Returns the title of the window.
+		 *
+		 * @return Window title
+		 */
+		[[nodiscard]] const std::string &getTitle() const;
+
+		/**
+		 * @brief Returns the width of the window.
+		 *
+		 * @return Window width
+		 */
+		[[nodiscard]] int getWidth() const;
+
+		/**
+		 * @brief Returns the height of the window.
+		 *
+		 * @return Window height
+		 */
+		[[nodiscard]] int getHeight() const;
+
+		/**
+		 * @brief Returns whether the window is resizable or not.
+		 *
+		 * @return True, if the window is resizable, else false
+		 */
+		[[nodiscard]] bool isResizable() const;
+
+		/**
+		 * @brief Destructor of the window which terminates GLFW in case
+		 * of the last window got destroyed.
+		 */
+		virtual ~Window();
+
+		/**
+		 * @brief Requests the windows framebuffer size.
+		 *
+		 * @param[out] width
+		 * @param[out] height
+		 */
+		void getFramebufferSize(int &width, int &height) const;
+
+		/**
+		 * @brief Retruns the handle of the swapchain in use by the window.
+		 *
+		 * @return Swapchain handle
+		 */
+		SwapchainHandle getSwapchain() const;
+	};
+
+} // namespace vkcv
diff --git a/lib/SPIRV-Cross b/lib/SPIRV-Cross
index ff61890722a91e97c44940494be5b6eed0d5ff5b..f09ba2777714871bddb70d049878af34b94fa54d 160000
--- a/lib/SPIRV-Cross
+++ b/lib/SPIRV-Cross
@@ -1 +1 @@
-Subproject commit ff61890722a91e97c44940494be5b6eed0d5ff5b
+Subproject commit f09ba2777714871bddb70d049878af34b94fa54d
diff --git a/lib/Vulkan-Headers b/lib/Vulkan-Headers
new file mode 160000
index 0000000000000000000000000000000000000000..8ba8294c86d0e99fcb457bedbd573dd678ccc9b3
--- /dev/null
+++ b/lib/Vulkan-Headers
@@ -0,0 +1 @@
+Subproject commit 8ba8294c86d0e99fcb457bedbd573dd678ccc9b3
diff --git a/lib/Vulkan-Hpp b/lib/Vulkan-Hpp
new file mode 160000
index 0000000000000000000000000000000000000000..ef609a2f77dd1756e672712f264e76b64acdba61
--- /dev/null
+++ b/lib/Vulkan-Hpp
@@ -0,0 +1 @@
+Subproject commit ef609a2f77dd1756e672712f264e76b64acdba61
diff --git a/lib/VulkanMemoryAllocator b/lib/VulkanMemoryAllocator
new file mode 160000
index 0000000000000000000000000000000000000000..c351692490513cdb0e5a2c925aaf7ea4a9b672f4
--- /dev/null
+++ b/lib/VulkanMemoryAllocator
@@ -0,0 +1 @@
+Subproject commit c351692490513cdb0e5a2c925aaf7ea4a9b672f4
diff --git a/lib/VulkanMemoryAllocator-Hpp b/lib/VulkanMemoryAllocator-Hpp
new file mode 160000
index 0000000000000000000000000000000000000000..e00a0b1ab8bba230e8c701423540ff3828aea2d5
--- /dev/null
+++ b/lib/VulkanMemoryAllocator-Hpp
@@ -0,0 +1 @@
+Subproject commit e00a0b1ab8bba230e8c701423540ff3828aea2d5
diff --git a/lib/glfw b/lib/glfw
index 0e9ec7788b4985a0df698080258e4091d18dcc3b..dd8a678a66f1967372e5a5e3deac41ebf65ee127 160000
--- a/lib/glfw
+++ b/lib/glfw
@@ -1 +1 @@
-Subproject commit 0e9ec7788b4985a0df698080258e4091d18dcc3b
+Subproject commit dd8a678a66f1967372e5a5e3deac41ebf65ee127
diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt
index 5edb802b3adf16878c2dec4050d8444278739026..e5bc34dfdf183f8dc26639771f44201597beaa73 100644
--- a/modules/CMakeLists.txt
+++ b/modules/CMakeLists.txt
@@ -1,7 +1,26 @@
 
+set(vkcv_modules_includes)
+set(vkcv_modules_libraries)
+
 # Add new modules here:
+add_subdirectory(algorithm)
 add_subdirectory(asset_loader)
 add_subdirectory(camera)
+add_subdirectory(effects)
+add_subdirectory(geometry)
 add_subdirectory(gui)
+add_subdirectory(material)
+add_subdirectory(meshlet)
+add_subdirectory(scene)
 add_subdirectory(shader_compiler)
 add_subdirectory(testing)
+add_subdirectory(upscaling)
+
+message(STATUS "Modules:")
+message(" - Includes: [ ${vkcv_modules_includes} ]")
+message(" - Libraries: [ ${vkcv_modules_libraries} ]")
+
+if (vkcv_parent_scope)
+	set(vkcv_modules_includes ${vkcv_modules_includes} PARENT_SCOPE)
+	set(vkcv_modules_libraries ${vkcv_modules_libraries} PARENT_SCOPE)
+endif()
diff --git a/modules/algorithm/CMakeLists.txt b/modules/algorithm/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d4eeaa261d10f50bd6c4bc8a43455cb252be50cf
--- /dev/null
+++ b/modules/algorithm/CMakeLists.txt
@@ -0,0 +1,41 @@
+cmake_minimum_required(VERSION 3.16)
+project(vkcv_algorithm)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(vkcv_algorithm_source ${PROJECT_SOURCE_DIR}/src)
+set(vkcv_algorithm_include ${PROJECT_SOURCE_DIR}/include)
+
+set(vkcv_algorithm_sources
+		${vkcv_algorithm_include}/vkcv/algorithm/SinglePassDownsampler.hpp
+		${vkcv_algorithm_source}/vkcv/algorithm/SinglePassDownsampler.cpp
+)
+
+# Setup some path variables to load libraries
+set(vkcv_algorithm_lib lib)
+set(vkcv_algorithm_lib_path ${PROJECT_SOURCE_DIR}/${vkcv_algorithm_lib})
+
+# Check and load FidelityFX_SPD
+include(config/FidelityFX_SPD.cmake)
+
+# adding source files to the project
+add_library(vkcv_algorithm ${vkcv_build_attribute} ${vkcv_algorithm_sources})
+
+# link the required libraries to the module
+target_link_libraries(vkcv_algorithm ${vkcv_algorithm_libraries} vkcv vkcv_shader_compiler vkcv_camera vkcv_asset_loader)
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(vkcv_algorithm SYSTEM BEFORE PRIVATE ${vkcv_algorithm_includes} ${vkcv_include} ${vkcv_includes} ${vkcv_shader_compiler_include})
+
+# add the own include directory for public headers
+target_include_directories(vkcv_algorithm BEFORE PUBLIC ${vkcv_algorithm_include})
+
+if (vkcv_parent_scope)
+	list(APPEND vkcv_modules_includes ${vkcv_algorithm_include})
+	list(APPEND vkcv_modules_libraries vkcv_algorithm)
+	
+	set(vkcv_modules_includes ${vkcv_modules_includes} PARENT_SCOPE)
+	set(vkcv_modules_libraries ${vkcv_modules_libraries} PARENT_SCOPE)
+endif()
diff --git a/modules/algorithm/README.md b/modules/algorithm/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..551fe0941f84c4417717b7b7dfbdebfa37c245f2
--- /dev/null
+++ b/modules/algorithm/README.md
@@ -0,0 +1,15 @@
+# Algorithm
+
+A VkCV module to use different optimized algorithms
+
+## Build
+
+### Dependencies (required):
+
+| Name of dependency                                                   | Used as submodule |
+|----------------------------------------------------------------------|---|
+| [FidelityFX-SPD](https://github.com/GPUOpen-Effects/FidelityFX-SPD/) | ✅ |
+
+## Docs
+
+Here is a [link](https://userpages.uni-koblenz.de/~vkcv/doc/group__vkcv__algorithm.html) to this module.
diff --git a/modules/algorithm/config/FidelityFX_SPD.cmake b/modules/algorithm/config/FidelityFX_SPD.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..eec11bfde46e7079417cabfbdc47a9e387877bff
--- /dev/null
+++ b/modules/algorithm/config/FidelityFX_SPD.cmake
@@ -0,0 +1,21 @@
+
+use_git_submodule("${vkcv_algorithm_lib_path}/FidelityFX-SPD" ffx_spd_status)
+
+if (${ffx_spd_status})
+	include_shader(${vkcv_algorithm_lib_path}/FidelityFX-SPD/ffx-spd/ffx_a.h ${vkcv_algorithm_include} ${vkcv_algorithm_source})
+	include_shader(${vkcv_algorithm_lib_path}/FidelityFX-SPD/ffx-spd/ffx_spd.h ${vkcv_algorithm_include} ${vkcv_algorithm_source})
+	include_shader(${vkcv_algorithm_lib_path}/FidelityFX-SPD/sample/src/VK/SPDIntegration.glsl ${vkcv_algorithm_include} ${vkcv_algorithm_source})
+	include_shader(${vkcv_algorithm_lib_path}/FidelityFX-SPD/sample/src/VK/SPDIntegrationLinearSampler.glsl ${vkcv_algorithm_include} ${vkcv_algorithm_source})
+
+	list(APPEND vkcv_algorithm_includes ${vkcv_algorithm_lib}/FidelityFX-SPD/ffx-spd)
+	
+	list(APPEND vkcv_algorithm_sources ${vkcv_algorithm_source}/ffx_a.h.cxx)
+	list(APPEND vkcv_algorithm_sources ${vkcv_algorithm_source}/ffx_spd.h.cxx)
+	list(APPEND vkcv_algorithm_sources ${vkcv_algorithm_source}/SPDIntegration.glsl.cxx)
+	list(APPEND vkcv_algorithm_sources ${vkcv_algorithm_source}/SPDIntegrationLinearSampler.glsl.cxx)
+	
+	list(APPEND vkcv_algorithm_sources ${vkcv_algorithm_include}/ffx_a.h.hxx)
+	list(APPEND vkcv_algorithm_sources ${vkcv_algorithm_include}/ffx_spd.h.hxx)
+	list(APPEND vkcv_algorithm_sources ${vkcv_algorithm_include}/SPDIntegration.glsl.hxx)
+	list(APPEND vkcv_algorithm_sources ${vkcv_algorithm_include}/SPDIntegrationLinearSampler.glsl.hxx)
+endif ()
diff --git a/modules/algorithm/include/vkcv/algorithm/SinglePassDownsampler.hpp b/modules/algorithm/include/vkcv/algorithm/SinglePassDownsampler.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a46ab4fc4e7ff237af52ba629455a06758e54e0f
--- /dev/null
+++ b/modules/algorithm/include/vkcv/algorithm/SinglePassDownsampler.hpp
@@ -0,0 +1,74 @@
+#pragma once
+
+#include <vector>
+
+#include <vkcv/Buffer.hpp>
+#include <vkcv/Core.hpp>
+#include <vkcv/Downsampler.hpp>
+#include <vkcv/ShaderProgram.hpp>
+
+namespace vkcv::algorithm {
+	
+	/**
+	* @defgroup vkcv_algorithm Algorithm Module
+	* A module to use different optimized algorithms.
+	* @{
+	*/
+
+	/**
+	 * @brief A class to handle downsampling via FidelityFX Single Pass Downsampler.
+	 * https://github.com/GPUOpen-Effects/FidelityFX-SPD
+	 */
+	class SinglePassDownsampler : public vkcv::Downsampler {
+	private:
+		/**
+		 * The SPD compute pipeline of the downsampler.
+		 */
+		ComputePipelineHandle m_pipeline;
+		
+		/**
+         * The descriptor set layout of the SPD pipeline.
+         */
+		DescriptorSetLayoutHandle m_descriptorSetLayout;
+		
+		/**
+		 * The vector of descriptor sets currently in use for downsampling.
+		 */
+		std::vector<DescriptorSetHandle> m_descriptorSets;
+		
+		/**
+		 * The buffer template to handle global atomic counters for SPD.
+		 */
+		Buffer<uint32_t> m_globalCounter;
+		
+		/**
+		 * The optional sampler handle to use for the downsampling.
+		 */
+		SamplerHandle m_sampler;
+		
+	public:
+		/**
+		 * @brief Constructor to create instance for single pass downsampling.
+		 *
+		 * @param[in,out] core Reference to a Core instance
+		 * @param[in] sampler Sampler handle
+		 */
+		explicit SinglePassDownsampler(Core& core,
+									   const SamplerHandle &sampler = SamplerHandle());
+		
+		/**
+		 * @brief Record the commands of the downsampling instance to
+		 * generate all mip levels of an input image via a
+		 * command stream.
+		 *
+		 * @param[in] cmdStream Command stream handle
+		 * @param[in] image Image handle
+		 */
+		void recordDownsampling(const CommandStreamHandle& cmdStream,
+								const ImageHandle& image) override;
+	
+	};
+	
+	/** @} */
+
+}
diff --git a/modules/algorithm/lib/FidelityFX-SPD b/modules/algorithm/lib/FidelityFX-SPD
new file mode 160000
index 0000000000000000000000000000000000000000..7c796c6d9fa6a9439e3610478148cfd742d97daf
--- /dev/null
+++ b/modules/algorithm/lib/FidelityFX-SPD
@@ -0,0 +1 @@
+Subproject commit 7c796c6d9fa6a9439e3610478148cfd742d97daf
diff --git a/modules/algorithm/src/vkcv/algorithm/SinglePassDownsampler.cpp b/modules/algorithm/src/vkcv/algorithm/SinglePassDownsampler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..051ae80d8d98e2ca5ce54f20d1e047b2151e87ca
--- /dev/null
+++ b/modules/algorithm/src/vkcv/algorithm/SinglePassDownsampler.cpp
@@ -0,0 +1,354 @@
+
+#include "vkcv/algorithm/SinglePassDownsampler.hpp"
+
+#include <cstdint>
+#include <cmath>
+#include <vector>
+
+#define A_CPU 1
+#include <ffx_a.h>
+#include <ffx_spd.h>
+
+#include "ffx_a.h.hxx"
+#include "ffx_spd.h.hxx"
+#include "SPDIntegration.glsl.hxx"
+#include "SPDIntegrationLinearSampler.glsl.hxx"
+
+#include <vkcv/ComputePipelineConfig.hpp>
+#include <vkcv/File.hpp>
+#include <vkcv/Logger.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+
+namespace vkcv::algorithm {
+	
+	#define SPD_MAX_MIP_LEVELS 12
+	
+	struct SPDConstants {
+		int mips;
+		int numWorkGroupsPerSlice;
+		int workGroupOffset[2];
+	};
+	
+	struct SPDConstantsSampler {
+		int mips;
+		int numWorkGroupsPerSlice;
+		int workGroupOffset[2];
+		float invInputSize[2];
+	};
+	
+	static DescriptorBindings getDescriptorBindings(const SamplerHandle &sampler) {
+		DescriptorBindings descriptorBindings = {};
+		
+		auto binding_0 = DescriptorBinding {
+				0,
+				DescriptorType::IMAGE_STORAGE,
+				SPD_MAX_MIP_LEVELS + 1,
+				ShaderStage::COMPUTE,
+				false,
+				true
+		};
+		
+		auto binding_1 = DescriptorBinding {
+				1,
+				DescriptorType::IMAGE_STORAGE,
+				1,
+				ShaderStage::COMPUTE,
+				false,
+				false
+		};
+		
+		auto binding_2 = DescriptorBinding{
+				2,
+				DescriptorType::STORAGE_BUFFER,
+				1,
+				ShaderStage::COMPUTE,
+				false,
+				false
+		};
+		
+		auto binding_3 = DescriptorBinding{
+				3,
+				DescriptorType::IMAGE_SAMPLED,
+				1,
+				ShaderStage::COMPUTE,
+				false,
+				false
+		};
+		
+		auto binding_4 = DescriptorBinding{
+				4,
+				DescriptorType::SAMPLER,
+				1,
+				ShaderStage::COMPUTE,
+				false,
+				false
+		};
+		
+		descriptorBindings.insert(std::make_pair(0, binding_0));
+		descriptorBindings.insert(std::make_pair(1, binding_1));
+		descriptorBindings.insert(std::make_pair(2, binding_2));
+		
+		if (sampler) {
+			descriptorBindings.insert(std::make_pair(3, binding_3));
+			descriptorBindings.insert(std::make_pair(4, binding_4));
+		}
+		
+		return descriptorBindings;
+	}
+	
+	static bool writeShaderCode(const std::filesystem::path &shaderPath, const std::string& code) {
+		std::ofstream file (shaderPath.string(), std::ios::out);
+		
+		if (!file.is_open()) {
+			vkcv_log(LogLevel::ERROR, "The file could not be opened (%s)", shaderPath.string().c_str());
+			return false;
+		}
+		
+		file.seekp(0);
+		file.write(code.c_str(), static_cast<std::streamsize>(code.length()));
+		file.close();
+		
+		return true;
+	}
+	
+	static bool compileSPDShader(vkcv::shader::GLSLCompiler& compiler,
+								 const std::string &source,
+								 const shader::ShaderCompiledFunction& compiled) {
+		std::filesystem::path directory = generateTemporaryDirectoryPath();
+		
+		if (!std::filesystem::create_directory(directory)) {
+			vkcv_log(LogLevel::ERROR, "The directory could not be created (%s)", directory.string().c_str());
+			return false;
+		}
+		
+		if (!writeShaderCode(directory / "ffx_a.h", FFX_A_H_SHADER)) {
+			return false;
+		}
+		
+		if (!writeShaderCode(directory / "ffx_spd.h", FFX_SPD_H_SHADER)) {
+			return false;
+		}
+		
+		return compiler.compileSource(
+				vkcv::ShaderStage::COMPUTE,
+				source.c_str(),
+				[&directory, &compiled] (vkcv::ShaderStage shaderStage,
+										 const std::filesystem::path& path) {
+					if (compiled) {
+						compiled(shaderStage, path);
+					}
+					
+					std::filesystem::remove_all(directory);
+				}, directory
+		);
+	}
+	
+	SinglePassDownsampler::SinglePassDownsampler(Core &core,
+												 const SamplerHandle &sampler) :
+		 vkcv::Downsampler(core),
+		 m_pipeline(),
+		
+		 m_descriptorSetLayout(),
+		 m_descriptorSets(),
+		
+		 m_globalCounter(buffer<uint32_t>(
+				 m_core,
+				 vkcv::BufferType::STORAGE,
+				 6
+		 )),
+		 
+		 m_sampler(sampler) {
+		const auto& featureManager = m_core.getContext().getFeatureManager();
+		
+		const bool partialBound = featureManager.checkFeatures<vk::PhysicalDeviceDescriptorIndexingFeatures>(
+				vk::StructureType::ePhysicalDeviceDescriptorIndexingFeatures,
+				[](const vk::PhysicalDeviceDescriptorIndexingFeatures& features) {
+					return features.descriptorBindingPartiallyBound;
+				}
+		);
+		
+		if (!partialBound) {
+			return;
+		}
+		
+		m_descriptorSetLayout = m_core.createDescriptorSetLayout(getDescriptorBindings(sampler));
+		
+		vkcv::shader::GLSLCompiler compiler (vkcv::shader::GLSLCompileTarget::SUBGROUP_OP);
+		
+		vk::PhysicalDeviceSubgroupProperties subgroupProperties;
+		
+		vk::PhysicalDeviceProperties2 properties2;
+		properties2.pNext = &subgroupProperties;
+		m_core.getContext().getPhysicalDevice().getProperties2(&properties2);
+		
+		const bool no_subgroup_quad = !(subgroupProperties.supportedOperations & vk::SubgroupFeatureFlagBits::eQuad);
+		
+		if (no_subgroup_quad) {
+			compiler.setDefine("SPD_NO_WAVE_OPERATIONS", "1");
+		}
+		
+		const bool float16Support = (
+				featureManager.checkFeatures<vk::PhysicalDeviceFloat16Int8FeaturesKHR>(
+						vk::StructureType::ePhysicalDeviceShaderFloat16Int8FeaturesKHR,
+						[](const vk::PhysicalDeviceFloat16Int8FeaturesKHR& features) {
+							return features.shaderFloat16;
+						}
+				) &&
+				featureManager.checkFeatures<vk::PhysicalDevice16BitStorageFeaturesKHR>(
+						vk::StructureType::ePhysicalDevice16BitStorageFeaturesKHR,
+						[](const vk::PhysicalDevice16BitStorageFeaturesKHR& features) {
+							return features.storageBuffer16BitAccess;
+						}
+				) &&
+				((no_subgroup_quad) ||
+				(featureManager.checkFeatures<vk::PhysicalDeviceShaderSubgroupExtendedTypesFeatures>(
+						vk::StructureType::ePhysicalDeviceShaderSubgroupExtendedTypesFeatures,
+						[](const vk::PhysicalDeviceShaderSubgroupExtendedTypesFeatures& features) {
+							return features.shaderSubgroupExtendedTypes;
+						}
+				)))
+		);
+		
+		if (float16Support) {
+			compiler.setDefine("A_HALF", "1");
+			compiler.setDefine("SPD_PACKED_ONLY", "1");
+		}
+		
+		ShaderProgram program;
+		if (m_sampler) {
+			compileSPDShader(
+					compiler,
+					SPDINTEGRATIONLINEARSAMPLER_GLSL_SHADER,
+					[&program](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+						program.addShader(shaderStage, path);
+					}
+			);
+		} else {
+			compileSPDShader(
+					compiler,
+					SPDINTEGRATION_GLSL_SHADER,
+					[&program](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+						program.addShader(shaderStage, path);
+					}
+			);
+		}
+		
+		m_pipeline = m_core.createComputePipeline(ComputePipelineConfig(
+			program,
+			{ m_descriptorSetLayout }
+		));
+		
+		std::vector<uint32_t> zeroes;
+		zeroes.resize(m_globalCounter.getCount());
+		memset(zeroes.data(), 0, m_globalCounter.getSize());
+		m_globalCounter.fill(zeroes);
+	}
+	
+	void SinglePassDownsampler::recordDownsampling(const CommandStreamHandle &cmdStream,
+												   const ImageHandle &image) {
+		Downsampler& fallback = m_core.getDownsampler();
+		
+		if (m_pipeline) {
+			fallback.recordDownsampling(cmdStream, image);
+			return;
+		}
+		
+		const uint32_t mipLevels = m_core.getImageMipLevels(image);
+		const uint32_t depth = m_core.getImageDepth(image);
+		
+		m_core.prepareImageForSampling(cmdStream, image);
+		
+		if ((mipLevels < 4) || (depth > 1) || (!m_core.isImageSupportingStorage(image))) {
+			fallback.recordDownsampling(cmdStream, image);
+			return;
+		}
+		
+		auto descriptorSet = m_core.createDescriptorSet(m_descriptorSetLayout);
+		
+		vkcv::DescriptorWrites writes;
+		writes.writeStorageImage(1, image, 6, 1, true);
+		writes.writeStorageBuffer(2, m_globalCounter.getHandle());
+		
+		if (m_sampler) {
+			writes.writeStorageImage(0, image, 1, mipLevels - 1, true);
+			
+			writes.writeSampledImage(3, image, 0, false, 0, 1, true);
+			writes.writeSampler(4, m_sampler);
+		} else {
+			writes.writeStorageImage(0, image, 0, mipLevels, true);
+		}
+		
+		m_core.writeDescriptorSet(descriptorSet, writes);
+		m_descriptorSets.push_back(descriptorSet);
+		
+		m_core.recordCommandsToStream(cmdStream, nullptr, [this]() {
+			m_descriptorSets.erase(m_descriptorSets.begin());
+		});
+		
+		varAU2(dispatchThreadGroupCountXY);
+		varAU2(workGroupOffset);
+		varAU2(numWorkGroupsAndMips);
+		varAU4(rectInfo) = initAU4(
+				0,
+				0,
+				m_core.getImageWidth(image),
+				m_core.getImageHeight(image)
+		);
+		
+		SpdSetup(
+				dispatchThreadGroupCountXY,
+				workGroupOffset,
+				numWorkGroupsAndMips,
+				rectInfo
+		);
+		
+		if (m_sampler) {
+			m_core.prepareImageForSampling(cmdStream, image, 1);
+			m_core.prepareImageForStorage(cmdStream, image, m_core.getImageMipLevels(image) - 1, 1);
+		} else {
+			m_core.prepareImageForStorage(cmdStream, image);
+		}
+		
+		vkcv::DispatchSize dispatch (
+				dispatchThreadGroupCountXY[0],
+				dispatchThreadGroupCountXY[1],
+				m_core.getImageArrayLayers(image)
+		);
+		
+		vkcv::PushConstants pushConstants = (m_sampler?
+				vkcv::pushConstants<SPDConstantsSampler>() :
+				vkcv::pushConstants<SPDConstants>()
+		);
+		
+		if (m_sampler) {
+			SPDConstantsSampler data;
+			data.numWorkGroupsPerSlice = numWorkGroupsAndMips[0];
+			data.mips = numWorkGroupsAndMips[1];
+			data.workGroupOffset[0] = workGroupOffset[0];
+			data.workGroupOffset[1] = workGroupOffset[1];
+			data.invInputSize[0] = 1.0f / m_core.getImageWidth(image);
+			data.invInputSize[1] = 1.0f / m_core.getImageHeight(image);
+			
+			pushConstants.appendDrawcall<SPDConstantsSampler>(data);
+		} else {
+			SPDConstants data;
+			data.numWorkGroupsPerSlice = numWorkGroupsAndMips[0];
+			data.mips = numWorkGroupsAndMips[1];
+			data.workGroupOffset[0] = workGroupOffset[0];
+			data.workGroupOffset[1] = workGroupOffset[1];
+			
+			pushConstants.appendDrawcall<SPDConstants>(data);
+		}
+		
+		m_core.recordComputeDispatchToCmdStream(cmdStream, m_pipeline, dispatch, {
+			useDescriptorSet(0, descriptorSet)
+		}, pushConstants);
+		
+		if (m_sampler) {
+			m_core.prepareImageForSampling(cmdStream, image, mipLevels - 1, 1);
+		} else {
+			m_core.prepareImageForSampling(cmdStream, image);
+		}
+	}
+	
+}
diff --git a/modules/asset_loader/CMakeLists.txt b/modules/asset_loader/CMakeLists.txt
index c5a1fd0eb9620d3a95af1c52a9e7456337047c7b..03e1888786d5382d854edaf77ee89d4e063e0fc6 100644
--- a/modules/asset_loader/CMakeLists.txt
+++ b/modules/asset_loader/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.16)
 project(vkcv_asset_loader)
 
 # setting c++ standard for the module
-set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD 20)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
 set(vkcv_asset_loader_source ${PROJECT_SOURCE_DIR}/src)
@@ -15,7 +15,7 @@ set(vkcv_asset_loader_sources
 )
 
 # adding source files to the module
-add_library(vkcv_asset_loader STATIC ${vkcv_asset_loader_sources})
+add_library(vkcv_asset_loader ${vkcv_build_attribute} ${vkcv_asset_loader_sources})
 
 # Setup some path variables to load libraries
 set(vkcv_asset_loader_lib lib)
@@ -31,12 +31,20 @@ include(config/FX_GLTF.cmake)
 include(config/STB.cmake)
 
 # link the required libraries to the module
-target_link_libraries(vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv)
+target_link_libraries(vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv ${vkcv_libraries})
 
 # including headers of dependencies and the VkCV framework
-target_include_directories(vkcv_asset_loader SYSTEM BEFORE PRIVATE ${vkcv_asset_loader_includes})
+target_include_directories(vkcv_asset_loader SYSTEM BEFORE PRIVATE ${vkcv_asset_loader_includes}  ${vkcv_include} ${vkcv_includes})
 
 # add the own include directory for public headers
 target_include_directories(vkcv_asset_loader BEFORE PUBLIC ${vkcv_asset_loader_include})
 
 target_compile_definitions(vkcv_asset_loader PUBLIC ${vkcv_asset_loader_definitions})
+
+if (vkcv_parent_scope)
+	list(APPEND vkcv_modules_includes ${vkcv_asset_loader_include})
+	list(APPEND vkcv_modules_libraries vkcv_asset_loader)
+	
+	set(vkcv_modules_includes ${vkcv_modules_includes} PARENT_SCOPE)
+	set(vkcv_modules_libraries ${vkcv_modules_libraries} PARENT_SCOPE)
+endif()
diff --git a/modules/asset_loader/README.md b/modules/asset_loader/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..aad7cdb53c7ae95d39e68a5cd814cb939b121d44
--- /dev/null
+++ b/modules/asset_loader/README.md
@@ -0,0 +1,17 @@
+# Asset-Loader
+
+A VkCV module to load basic assets like models, materials and images
+
+## Build
+
+### Dependencies (required):
+
+| Name of dependency | Used as submodule |
+|----------------------------------------------------|---|
+| [fx-gltf](https://github.com/jessey-git/fx-gltf)   | ✅ |
+| [nlohmann::json](https://github.com/nlohmann/json) | ✅ |
+| [stb](https://github.com/nothings/stb)             | ✅ |
+
+## Docs
+
+Here is a [link](https://userpages.uni-koblenz.de/~vkcv/doc/group__vkcv__asset.html) to this module.
diff --git a/modules/asset_loader/config/FX_GLTF.cmake b/modules/asset_loader/config/FX_GLTF.cmake
index 37cd162422d8277022067498f5d5ba3e26e2ae1b..4164c2786d9ada53c979b98bf23f973113edfda5 100644
--- a/modules/asset_loader/config/FX_GLTF.cmake
+++ b/modules/asset_loader/config/FX_GLTF.cmake
@@ -1,5 +1,7 @@
 
-if (EXISTS "${vkcv_asset_loader_lib_path}/fx-gltf")
+use_git_submodule("${vkcv_asset_loader_lib_path}/fx-gltf" fx_gltf_status)
+
+if (${fx_gltf_status})
 	set(FX_GLTF_INSTALL OFF CACHE INTERNAL "")
 	set(FX_GLTF_USE_INSTALLED_DEPS OFF CACHE INTERNAL "")
 	set(BUILD_TESTING OFF CACHE INTERNAL "")
@@ -7,6 +9,4 @@ if (EXISTS "${vkcv_asset_loader_lib_path}/fx-gltf")
 	add_subdirectory(${vkcv_asset_loader_lib}/fx-gltf)
 	
 	list(APPEND vkcv_asset_loader_libraries fx-gltf)
-else()
-	message(WARNING "FX-GLTF is required..! Update the submodules!")
 endif ()
diff --git a/modules/asset_loader/config/NLOHMANN_JSON.cmake b/modules/asset_loader/config/NLOHMANN_JSON.cmake
index 018f6a19809fd3e53e6e790a6fe6447348e43c09..3a082ae59c5778aa5d172fab9eb71b108b3f652b 100644
--- a/modules/asset_loader/config/NLOHMANN_JSON.cmake
+++ b/modules/asset_loader/config/NLOHMANN_JSON.cmake
@@ -1,10 +1,16 @@
 
-if (EXISTS "${vkcv_asset_loader_lib_path}/json")
-	set(JSON_BuildTests OFF CACHE INTERNAL "")
-	
-	add_subdirectory(${vkcv_asset_loader_lib}/json)
-	
-	list(APPEND vkcv_asset_loader_libraries nlohmann_json::nlohmann_json)
+find_package(nlohmann_json QUIET)
+
+if (nlohmann_json_FOUND)
+	list(APPEND vkcv_libraries nlohmann_json::nlohmann_json)
 else()
-	message(WARNING "NLOHMANN_JSON is required..! Update the submodules!")
+	use_git_submodule("${vkcv_asset_loader_lib_path}/json" json_status)
+	
+	if (${json_status})
+		set(JSON_BuildTests OFF CACHE INTERNAL "")
+		
+		add_subdirectory(${vkcv_asset_loader_lib}/json)
+		
+		list(APPEND vkcv_asset_loader_libraries nlohmann_json::nlohmann_json)
+	endif ()
 endif ()
diff --git a/modules/asset_loader/config/STB.cmake b/modules/asset_loader/config/STB.cmake
index 1287d0a9ddda559e061ddd680bc815e24b3e2075..61b7cc929b2b5ae96fbbbe479f6e9d48d9db5c69 100644
--- a/modules/asset_loader/config/STB.cmake
+++ b/modules/asset_loader/config/STB.cmake
@@ -1,10 +1,10 @@
 
-if (EXISTS "${vkcv_asset_loader_lib_path}/stb")
+use_git_submodule("${vkcv_asset_loader_lib_path}/stb" stb_status)
+
+if (${stb_status})
 	list(APPEND vkcv_asset_loader_includes ${vkcv_asset_loader_lib}/stb)
 	
 	list(APPEND vkcv_asset_loader_definitions STB_IMAGE_IMPLEMENTATION)
 	list(APPEND vkcv_asset_loader_definitions STBI_ONLY_JPEG)
 	list(APPEND vkcv_asset_loader_definitions STBI_ONLY_PNG)
-else()
-	message(WARNING "STB is required..! Update the submodules!")
 endif ()
diff --git a/modules/asset_loader/include/vkcv/asset/asset_loader.hpp b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
index 4107d57ee97a6efe0475c6d9dbd80d2603e0afe8..d6b5cf5d5382e0e03bd7950c1e87bf3c6a8ee445 100644
--- a/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
+++ b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp
@@ -9,18 +9,11 @@
 #include <vector>
 #include <array>
 #include <cstdint>
+#include <filesystem>
 
-/** These macros define limits of the following structs. Implementations can
- * test against these limits when performing sanity checks. The main constraint
- * expressed is that of the data type: Material indices are identified by a
- * uint8_t in the VertexGroup struct, so there can't be more than UINT8_MAX
- * materials in the mesh. Should these limits be too narrow, the data type has
- * to be changed, but the current ones should be generous enough for most use
- * cases. */
-#define MAX_MATERIALS_PER_MESH UINT8_MAX
-#define MAX_VERTICES_PER_VERTEX_GROUP UINT32_MAX
-
-/** LOADING MESHES
+#include "vkcv/VertexData.hpp"
+
+/* LOADING MESHES
  * The description of meshes is a hierarchy of structures with the Mesh at the
  * top.
  *
@@ -45,53 +38,95 @@
 
 namespace vkcv::asset {
 
-/** This enum matches modes in fx-gltf, the library returns a standard mode
- * (TRIANGLES) if no mode is given in the file. */
+/**
+ * @defgroup vkcv_asset Asset Loader Module
+ * A module to load assets like scenes, meshes and textures.
+ * @{
+ */
+
+/**
+ * These return codes are limited to the asset loader module. If unified return
+ * codes are defined for the vkcv framework, these will be used instead.
+ */
+#define ASSET_ERROR 0
+#define ASSET_SUCCESS 1
+
+/**
+ * This enum matches modes in fx-gltf, the library returns a standard mode
+ * (TRIANGLES) if no mode is given in the file.
+ */
 enum class PrimitiveMode : uint8_t {
-	POINTS=0, LINES, LINELOOP, LINESTRIP, TRIANGLES, TRIANGLESTRIP,
-	TRIANGLEFAN
+	POINTS = 0,
+	LINES = 1,
+	LINELOOP = 2,
+	LINESTRIP = 3,
+	TRIANGLES = 4,
+	TRIANGLESTRIP = 5,
+	TRIANGLEFAN = 6
 };
 
-/** The indices in the index buffer can be of different bit width. */
-enum class IndexType : uint8_t { UNDEFINED=0, UINT8=1, UINT16=2, UINT32=3 };
-
-typedef struct {
-	// TODO define struct for samplers (low priority)
-	// NOTE: glTF defines samplers based on OpenGL, which can not be
-	// directly translated to Vulkan. Specifically, OpenGL (and glTF)
-	// define a different set of Min/Mag-filters than Vulkan.
-} Sampler;
-
-/** struct for defining the loaded texture */
-typedef struct {
-	int sampler;		// index into the sampler array of the Scene
-	uint8_t channels;	// number of channels
-	uint16_t w, h;		// width and height of the texture
-	std::vector<uint8_t> data;	// binary data of the decoded texture
-} Texture;
+/**
+ * The indices in the index buffer can be of different bit width.
+ */
+enum class IndexType : uint8_t {
+	UNDEFINED=0,
+	UINT8=1,
+	UINT16=2,
+	UINT32=3
+};
 
-/** The asset loader module only supports the PBR-MetallicRoughness model for
- * materials.*/
-typedef struct {
-	uint16_t textureMask;	// bit mask with active texture targets
-	// Indices into the Array.textures array
-	int baseColor, metalRough, normal, occlusion, emissive;
-	// Scaling factors for each texture target
-	struct { float r, g, b, a; } baseColorFactor;
-	float metallicFactor, roughnessFactor;
-	float normalScale;
-	float occlusionStrength;
-	struct { float r, g, b; } emissiveFactor;
-} Material;
+/**
+ * This struct defines a sampler for a texture object. All values here can
+ * directly be passed to VkSamplerCreateInfo.
+ * NOTE that glTF defines samplers based on OpenGL, which can not be directly
+ * translated to Vulkan. The vkcv::asset::Sampler struct defined here adheres
+ * to the Vulkan spec, having alerady translated the flags from glTF to Vulkan.
+ * Since glTF does not specify border sampling for more than two dimensions,
+ * the addressModeW is hardcoded to a default: VK_SAMPLER_ADDRESS_MODE_REPEAT.
+ */
+struct Sampler {
+	int minFilter, magFilter;
+	int mipmapMode;
+	float minLOD, maxLOD;
+	int addressModeU, addressModeV, addressModeW;
+};
+
+/**
+ * This struct describes a (partially) loaded texture.
+ * The data member is not populated after calling probeScene() but only when
+ * calling loadMesh(), loadScene() or loadTexture(). Note that textures are
+ * currently always loaded with 4 channels as RGBA, even if the image has just
+ * RGB or is grayscale. In the case where the glTF-file does not provide a URI
+ * but references a buffer view for the raw data, the path member will be empty
+ * even though the rest is initialized properly.
+ * NOTE: Loading textures without URI is untested.
+ */
+struct Texture {
+	std::filesystem::path path;	// URI to the encoded texture data
+	int sampler;			// index into the sampler array of the Scene
+	
+	union { int width; int w; };
+	union { int height; int h; };
+	int channels;
+	
+	std::vector<uint8_t> data;	// binary data of the decoded texture
+};
 
-/** Flags for the bit-mask in the Material struct. To check if a material has a
+/**
+ * Flags for the bit-mask in the Material struct. To check if a material has a
  * certain texture target, you can use the hasTexture() function below, passing
- * the material struct and the enum. */
+ * the material struct and the enum.
+ */
 enum class PBRTextureTarget {
-	baseColor=1, metalRough=2, normal=4, occlusion=8, emissive=16
+	baseColor=1,
+	metalRough=2,
+	normal=4,
+	occlusion=8,
+	emissive=16
 };
 
-/** This macro translates the index of an enum in the defined order to an
+/**
+ * This macro translates the index of an enum in the defined order to an
  * integer with a single bit set in the corresponding place. It is used for
  * working with the bitmask of texture targets ("textureMask") in the Material
  * struct:
@@ -102,92 +137,212 @@ enum class PBRTextureTarget {
  * contact with bit-level operations. */
 #define bitflag(ENUM) (0x1u << ((unsigned)(ENUM)))
 
-/** To signal that a certain texture target is active in a Material struct, its
- * bit is set in the textureMask. You can use this function to check that:
- * Material mat = ...;
- * if (materialHasTexture(&mat, baseColor)) {...} */
-bool materialHasTexture(const Material *const m, const PBRTextureTarget t);
+/**
+ * The asset loader module only supports the PBR-MetallicRoughness model for
+ * materials.
+ */
+struct Material {
+	uint16_t textureMask;	// bit mask with active texture targets
+	
+	// Indices into the Scene.textures vector
+	int baseColor, metalRough, normal, occlusion, emissive;
+	
+	// Scaling factors for each texture target
+	struct { float r, g, b, a; } baseColorFactor;
+	float metallicFactor, roughnessFactor;
+	float normalScale;
+	float occlusionStrength;
+	struct { float r, g, b; } emissiveFactor;
+
+	/**
+	 * To signal that a certain texture target is active in this Material
+	 * struct, its bit is set in the textureMask. You can use this function
+	 * to check that:
+	 * if (myMaterial.hasTexture(baseColor)) {...}
+	 *
+	 * @param t The target to query for
+	 * @return Boolean to signal whether the texture target is active in
+	 * the material.
+	 */
+	bool hasTexture(PBRTextureTarget target) const;
+};
 
-/** With these enums, 0 is reserved to signal uninitialized or invalid data. */
+/* With these enums, 0 is reserved to signal uninitialized or invalid data. */
 enum class PrimitiveType : uint32_t {
     UNDEFINED = 0,
     POSITION = 1,
     NORMAL = 2,
     TEXCOORD_0 = 3,
-    TEXCOORD_1 = 4
+    TEXCOORD_1 = 4,
+    TANGENT = 5,
+    COLOR_0 = 6,
+    COLOR_1 = 7,
+    JOINTS_0 = 8,
+    WEIGHTS_0 = 9
 };
 
-/** These integer values are used the same way in OpenGL, Vulkan and glTF. This
+/**
+ * These integer values are used the same way in OpenGL, Vulkan and glTF. This
  * enum is not needed for translation, it's only for the programmers
  * convenience (easier to read in if/switch statements etc). While this enum
  * exists in (almost) the same definition in the fx-gltf library, we want to
- * avoid exposing that dependency, thus it is re-defined here. */
+ * avoid exposing that dependency, thus it is re-defined here.
+ */
 enum class ComponentType : uint16_t {
-    NONE = 0, INT8 = 5120, UINT8 = 5121, INT16 = 5122, UINT16 = 5123,
-    UINT32 = 5125, FLOAT32 = 5126
+    NONE = 0,
+    INT8 = 5120,
+    UINT8 = 5121,
+    INT16 = 5122,
+    UINT16 = 5123,
+    UINT32 = 5125,
+    FLOAT32 = 5126
 };
 
-/** This struct describes one vertex attribute of a vertex buffer. */
-typedef struct {
+/**
+ * This struct describes one vertex attribute of a vertex buffer.
+ */
+struct VertexAttribute {
     PrimitiveType type;			// POSITION, NORMAL, ...
+    
     uint32_t offset;			// offset in bytes
     uint32_t length;			// length of ... in bytes
     uint32_t stride;			// stride in bytes
-    ComponentType componentType;		// eg. 5126 for float
-    uint8_t  componentCount;	// eg. 3 for vec3
-} VertexAttribute;
+    
+    ComponentType componentType;	// eg. 5126 for float
+    uint8_t  componentCount;		// eg. 3 for vec3
+};
 
-/** This struct represents one (possibly the only) part of a mesh. There is
+/**
+ * This struct represents one (possibly the only) part of a mesh. There is
  * always one vertexBuffer and zero or one indexBuffer (indexed rendering is
  * common but not always used). If there is no index buffer, this is indicated
  * by indexBuffer.data being empty. Each vertex buffer can have one or more
- * vertex attributes. */
-typedef struct {
+ * vertex attributes.
+ */
+struct VertexGroup {
 	enum PrimitiveMode mode;	// draw as points, lines or triangle?
-	size_t numIndices, numVertices;
+	size_t numIndices;
+	size_t numVertices;
+	
 	struct {
 		enum IndexType type;	// data type of the indices
 		std::vector<uint8_t> data; // binary data of the index buffer
 	} indexBuffer;
+	
 	struct {
 		std::vector<uint8_t> data; // binary data of the vertex buffer
 		std::vector<VertexAttribute> attributes; // description of one
 	} vertexBuffer;
+	
 	struct { float x, y, z; } min;	// bounding box lower left
 	struct { float x, y, z; } max;	// bounding box upper right
+	
 	int materialIndex;		// index to one of the materials
-} VertexGroup;
+};
 
-/** This struct represents a single mesh as it was loaded from a glTF file. It
+/**
+ * This struct represents a single mesh as it was loaded from a glTF file. It
  * consists of at least one VertexGroup, which then references other resources
- * such as Materials. */
-typedef struct {
+ * such as Materials.
+ */
+struct Mesh {
 	std::string name;
 	std::array<float, 16> modelMatrix;
 	std::vector<int> vertexGroups;
-} Mesh;
+};
 
-/** The scene struct is simply a collection of objects in the scene as well as
+/**
+ * The scene struct is simply a collection of objects in the scene as well as
  * the resources used by those objects.
- * For now the only type of object are the meshes and they are represented in a
- * flat array.
- * Note that parent-child relations are not yet possible. */
-typedef struct {
+ * Note that parent-child relations are not yet possible.
+ */
+struct Scene {
 	std::vector<Mesh> meshes;
 	std::vector<VertexGroup> vertexGroups;
 	std::vector<Material> materials;
 	std::vector<Texture> textures;
 	std::vector<Sampler> samplers;
-} Scene;
+	std::vector<std::string> uris;
+};
+
+/**
+ * Parse the given glTF file and create a shallow description of the content.
+ * Only the meta-data of the objects in the scene is loaded, not the binary
+ * content. The rationale is to provide a means of probing the content of a
+ * glTF file without the costly process of loading and decoding large amounts
+ * of data. The returned Scene struct can be used to search for specific meshes
+ * in the scene, that can then be loaded on their own using the loadMesh()
+ * function. Note that the Scene struct received as output argument will be
+ * overwritten by this function.
+ * After this function completes, the returned Scene struct is completely
+ * initialized and all information is final, except for the missing binary
+ * data. This means that indices to vectors will remain valid even when the
+ * shallow scene struct is filled with data by loadMesh().
+ * Note that for URIs only (local) filesystem paths are supported, no
+ * URLs using network protocols etc.
+ *
+ * @param[in] path must be the path to a glTF- or glb-file.
+ * @param[out] scene is a reference to a Scene struct that will be filled with the
+ * 	meta-data of all objects described in the glTF file.
+ * @return ASSET_ERROR on failure, otherwise ASSET_SUCCESS
+ */
+int probeScene(const std::filesystem::path &path, Scene &scene);
+
+/**
+ * This function loads a single mesh from the given file and adds it to the
+ * given scene. The scene must already be initialized (via probeScene()).
+ * The mesh_index refers to the Scenes meshes array and identifies the mesh to
+ * load. To find the mesh you want, iterate over the probed scene and check the
+ * meshes details (eg. name).
+ * Besides the mesh, this function will also add any associated data to the
+ * Scene struct such as Materials and Textures required by the Mesh.
+ *
+ * @param[in,out] scene	is the scene struct to which the results will be written.
+ * @param[in] mesh_index Index of the mesh to load
+ * @return ASSET_ERROR on failure, otherwise ASSET_SUCCESS
+ */
+int loadMesh(Scene &scene, int mesh_index);
 
 /**
- * Load every mesh from the glTF file, as well as materials and textures.
+ * Load every mesh from the glTF file, as well as materials, textures and other
+ * associated objects.
  *
- * @param path must be the path to a glTF or glb file.
- * @param scene is a reference to a Scene struct that will be filled with the
+ * @param[in] path	must be the path to a glTF- or glb-file.
+ * @param[out] scene is a reference to a Scene struct that will be filled with the
  * 	content of the glTF file being loaded.
- * */
-int loadScene(const std::string &path, Scene &scene);
+ * @return ASSET_ERROR on failure, otherwise ASSET_SUCCESS
+ */
+int loadScene(const std::filesystem::path &path, Scene &scene);
+
+/**
+ * Simply loads a single image at the given path and returns a Texture
+ * struct describing it. This is for special use cases only (eg.
+ * loading a font atlas) and not meant to be used for regular assets.
+ * The sampler is set to -1, signalling that this Texture was loaded
+ * outside the context of a glTF-file.
+ * If there was an error loading or decoding the image, the returned struct
+ * will be cleared to all 0 with path and data being empty; make sure to always
+ * check that !data.empty() before using the struct.
+ *
+ * @param[in] path	must be the path to an image file.
+ * @return	Texture struct describing the loaded image.
+ */
+Texture loadTexture(const std::filesystem::path& path);
+
+/**
+ * Loads up the vertex attributes and creates usable vertex buffer bindings
+ * to match the desired order of primitive types as used in the vertex
+ * shader.
+ *
+ * @param[in] attributes Vertex attributes
+ * @param[in] buffer Buffer handle
+ * @param[in] types Primitive type order
+ * @return Vertex buffer bindings
+ */
+VertexBufferBindings loadVertexBufferBindings(const std::vector<VertexAttribute> &attributes,
+											  const BufferHandle &buffer,
+											  const std::vector<PrimitiveType> &types);
 
+/** @} */
 
-}
+}	// end namespace vkcv::asset
diff --git a/modules/asset_loader/lib/fx-gltf b/modules/asset_loader/lib/fx-gltf
index f4f18f2017a049a23748c9c9aad42ba2de20bfd5..7766c237ea81c0bb3759e78e5c0f22854843eef8 160000
--- a/modules/asset_loader/lib/fx-gltf
+++ b/modules/asset_loader/lib/fx-gltf
@@ -1 +1 @@
-Subproject commit f4f18f2017a049a23748c9c9aad42ba2de20bfd5
+Subproject commit 7766c237ea81c0bb3759e78e5c0f22854843eef8
diff --git a/modules/asset_loader/lib/json b/modules/asset_loader/lib/json
index 0972f7ff0e651f09a306dba791cc42024b8642c1..bc889afb4c5bf1c0d8ee29ef35eaaf4c8bef8a5d 160000
--- a/modules/asset_loader/lib/json
+++ b/modules/asset_loader/lib/json
@@ -1 +1 @@
-Subproject commit 0972f7ff0e651f09a306dba791cc42024b8642c1
+Subproject commit bc889afb4c5bf1c0d8ee29ef35eaaf4c8bef8a5d
diff --git a/modules/asset_loader/lib/stb b/modules/asset_loader/lib/stb
index c9064e317699d2e495f36ba4f9ac037e88ee371a..8b5f1f37b5b75829fc72d38e7b5d4bcbf8a26d55 160000
--- a/modules/asset_loader/lib/stb
+++ b/modules/asset_loader/lib/stb
@@ -1 +1 @@
-Subproject commit c9064e317699d2e495f36ba4f9ac037e88ee371a
+Subproject commit 8b5f1f37b5b75829fc72d38e7b5d4bcbf8a26d55
diff --git a/modules/asset_loader/src/vkcv/asset/asset_loader.cpp b/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
index c21d0c9f70bc81561e1078b15b8372e6dd4730f5..ef83785e6a7280dc7e6e45a850915f2ad4fc56c1 100644
--- a/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
+++ b/modules/asset_loader/src/vkcv/asset/asset_loader.cpp
@@ -1,366 +1,894 @@
 #include "vkcv/asset/asset_loader.hpp"
 #include <iostream>
-#include <string.h>	// memcpy(3)
-#include <stdlib.h>	// calloc(3)
+#include <cstring>	// memcpy(3)
+#include <set>
+#include <cstdlib>	// calloc(3)
+#include <vulkan/vulkan.hpp>
 #include <fx/gltf.h>
 #include <stb_image.h>
 #include <vkcv/Logger.hpp>
 #include <algorithm>
 
+
 namespace vkcv::asset {
 
-/**
-* convert the accessor type from the fx-gltf library to an unsigned int
-* @param type
-* @return unsigned integer representation
-*/
-// TODO Return proper error code (we need to define those as macros or enums,
-// will discuss during the next core meeting if that should happen on the scope
-// of the vkcv framework or just this module)
-uint8_t convertTypeToInt(const fx::gltf::Accessor::Type type) {
-	switch (type) {
-	case fx::gltf::Accessor::Type::None :
-		return 0;
-	case fx::gltf::Accessor::Type::Scalar :
-		return 1;
-	case fx::gltf::Accessor::Type::Vec2 :
-		return 2;
-	case fx::gltf::Accessor::Type::Vec3 :
-		return 3;
-	case fx::gltf::Accessor::Type::Vec4 :
-		return 4;
-	default: return 10; // TODO add cases for matrices (or maybe change the type in the struct itself)
+	/**
+	 * This function unrolls nested exceptions via recursion and prints them
+	 * @param e	The exception being thrown
+	 * @param path	The path to the file that was responsible for the exception
+	 */
+	static void recurseExceptionPrint(const std::exception& e, const std::string &path) {
+		vkcv_log(LogLevel::ERROR, "Loading file %s: %s", path.c_str(), e.what());
+		
+		try {
+			std::rethrow_if_nested(e);
+		} catch (const std::exception& nested) {
+			recurseExceptionPrint(nested, path);
+		}
 	}
-}
 
-/**
- * This function unrolls nested exceptions via recursion and prints them
- * @param e error code
- * @param path path to file that is responsible for error
- */
-void print_what (const std::exception& e, const std::string &path) {
-	vkcv_log(LogLevel::ERROR, "Loading file %s: %s",
-			 path.c_str(), e.what());
+	/**
+	 * Returns the component count for an accessor type of the fx-gltf library.
+	 * @param type	The accessor type
+	 * @return	An unsigned integer count
+	 */
+	static uint32_t getComponentCount(const fx::gltf::Accessor::Type type) {
+		switch (type) {
+		case fx::gltf::Accessor::Type::Scalar:
+			return 1;
+		case fx::gltf::Accessor::Type::Vec2:
+			return 2;
+		case fx::gltf::Accessor::Type::Vec3:
+			return 3;
+		case fx::gltf::Accessor::Type::Vec4:
+		case fx::gltf::Accessor::Type::Mat2:
+			return 4;
+		case fx::gltf::Accessor::Type::Mat3:
+			return 9;
+		case fx::gltf::Accessor::Type::Mat4:
+			return 16;
+		case fx::gltf::Accessor::Type::None:
+		default:
+			return 0;
+		}
+	}
 	
-	try {
-		std::rethrow_if_nested(e);
-	} catch (const std::exception& nested) {
-		print_what(nested, path);
+	static uint32_t getComponentSize(ComponentType type) {
+		switch (type) {
+			case ComponentType::INT8:
+			case ComponentType::UINT8:
+				return 1;
+			case ComponentType::INT16:
+			case ComponentType::UINT16:
+				return 2;
+			case ComponentType::UINT32:
+			case ComponentType::FLOAT32:
+				return 4;
+			default:
+				return 0;
+		}
 	}
-}
 
-/** Translate the component type used in the index accessor of fx-gltf to our
- * enum for index type. The reason we have defined an incompatible enum that
- * needs translation is that only a subset of component types is valid for
- * indices and we want to catch these incompatibilities here. */
-enum IndexType getIndexType(const enum fx::gltf::Accessor::ComponentType &t)
-{
-	switch (t) {
-	case fx::gltf::Accessor::ComponentType::UnsignedByte:
-		return IndexType::UINT8;
-	case fx::gltf::Accessor::ComponentType::UnsignedShort:
-		return IndexType::UINT16;
-	case fx::gltf::Accessor::ComponentType::UnsignedInt:
-		return IndexType::UINT32;
-	default:
-        std::cerr << "ERROR: Index type not supported: " <<
-			static_cast<uint16_t>(t) << std::endl;
-		return IndexType::UNDEFINED;
+	/**
+	 * Translate the component type used in the index accessor of fx-gltf to our
+	 * enum for index type. The reason we have defined an incompatible enum that
+	 * needs translation is that only a subset of component types is valid for
+	 * indices and we want to catch these incompatibilities here.
+	 * @param t	The component type
+	 * @return 	The vkcv::IndexType enum representation
+	 */
+	enum IndexType getIndexType(const enum fx::gltf::Accessor::ComponentType &type) {
+		switch (type) {
+		case fx::gltf::Accessor::ComponentType::UnsignedByte:
+			return IndexType::UINT8;
+		case fx::gltf::Accessor::ComponentType::UnsignedShort:
+			return IndexType::UINT16;
+		case fx::gltf::Accessor::ComponentType::UnsignedInt:
+			return IndexType::UINT32;
+		default:
+			vkcv_log(LogLevel::ERROR, "Index type not supported: %u", static_cast<uint16_t>(type));
+			return IndexType::UNDEFINED;
+		}
 	}
-}
-
-/**
- * This function computes the modelMatrix out of the data given in the gltf file. It also checks, whether a modelMatrix was given.
- * @param translation possible translation vector (default 0,0,0)
- * @param scale possible scale vector (default 1,1,1)
- * @param rotation possible rotation, given in quaternion (default 0,0,0,1)
- * @param matrix possible modelmatrix (default identity)
- * @return model Matrix as an array of floats
- */
-std::array<float, 16> computeModelMatrix(std::array<float, 3> translation, std::array<float, 3> scale, std::array<float, 4> rotation, std::array<float, 16> matrix){
-    std::array<float, 16> modelMatrix = {1,0,0,0,
-                                         0,1,0,0,
-                                         0,0,1,0,
-                                         0,0,0,1};
-    if (matrix != modelMatrix){
-        return matrix;
-    } else {
-        // translation
-        modelMatrix[3] = translation[0];
-        modelMatrix[7] = translation[1];
-        modelMatrix[11] = translation[2];
-        // rotation and scale
-        auto a = rotation[0];
-        auto q1 = rotation[1];
-        auto q2 = rotation[2];
-        auto q3 = rotation[3];
-
-        modelMatrix[0] = (2 * (a * a + q1 * q1) - 1) * scale[0];
-        modelMatrix[1] = (2 * (q1 * q2 - a * q3)) * scale[1];
-        modelMatrix[2] = (2 * (q1 * q3 + a * q2)) * scale[2];
-
-        modelMatrix[4] = (2 * (q1 * q2 + a * q3)) * scale[0];
-        modelMatrix[5] = (2 * (a * a + q2 * q2) - 1) * scale[1];
-        modelMatrix[6] = (2 * (q2 * q3 - a * q1)) * scale[2];
-
-        modelMatrix[8] = (2 * (q1 * q3 - a * q2)) * scale[0];
-        modelMatrix[9] = (2 * (q2 * q3 + a * q1)) * scale[1];
-        modelMatrix[10] = (2 * (a * a + q3 * q3) - 1) * scale[2];
-
-        // flip y, because GLTF uses y up, but vulkan -y up
-        modelMatrix[5] *= -1;
-
-        return modelMatrix;
-    }
-
-}
-
-bool materialHasTexture(const Material *const m, const PBRTextureTarget t)
-{
-	return m->textureMask & bitflag(t);
-}
-
-int loadScene(const std::string &path, Scene &scene){
-    fx::gltf::Document sceneObjects;
-
-    try {
-        if (path.rfind(".glb", (path.length()-4)) != std::string::npos) {
-            sceneObjects = fx::gltf::LoadFromBinary(path);
-        } else {
-            sceneObjects = fx::gltf::LoadFromText(path);
-        }
-    } catch (const std::system_error &err) {
-        print_what(err, path);
-        return 0;
-    } catch (const std::exception &e) {
-        print_what(e, path);
-        return 0;
-    }
-    size_t pos = path.find_last_of("/");
-    auto dir = path.substr(0, pos);
-
-    // file has to contain at least one mesh
-    if (sceneObjects.meshes.size() == 0) return 0;
-
-    fx::gltf::Accessor posAccessor;
-    std::vector<VertexAttribute> vertexAttributes;
-    std::vector<Material> materials;
-    std::vector<Texture> textures;
-    std::vector<Sampler> samplers;
-    std::vector<Mesh> meshes;
-    std::vector<VertexGroup> vertexGroups;
-    int groupCount = 0;
-
-    Mesh mesh = {};
-
-    for(int i = 0; i < sceneObjects.meshes.size(); i++){
-        std::vector<int> vertexGroupsIndices;
-        fx::gltf::Mesh const &objectMesh = sceneObjects.meshes[i];
-
-        for(int j = 0; j < objectMesh.primitives.size(); j++){
-            fx::gltf::Primitive const &objectPrimitive = objectMesh.primitives[j];
-            vertexAttributes.clear();
-            vertexAttributes.reserve(objectPrimitive.attributes.size());
-
-            for (auto const & attrib : objectPrimitive.attributes) {
-
-                fx::gltf::Accessor accessor =  sceneObjects.accessors[attrib.second];
-                VertexAttribute attribute;
-
-                if (attrib.first == "POSITION") {
-                    attribute.type = PrimitiveType::POSITION;
-                    posAccessor = accessor;
-                } else if (attrib.first == "NORMAL") {
-                    attribute.type = PrimitiveType::NORMAL;
-                } else if (attrib.first == "TEXCOORD_0") {
-                    attribute.type = PrimitiveType::TEXCOORD_0;
-                } else if (attrib.first == "TEXCOORD_1") {
-                    attribute.type = PrimitiveType::TEXCOORD_1;
-                } else {
-                    return 0;
-                }
-
-                attribute.offset = sceneObjects.bufferViews[accessor.bufferView].byteOffset;
-                attribute.length = sceneObjects.bufferViews[accessor.bufferView].byteLength;
-                attribute.stride = sceneObjects.bufferViews[accessor.bufferView].byteStride;
-		        attribute.componentType = static_cast<ComponentType>(accessor.componentType);
-
-                if (convertTypeToInt(accessor.type) != 10) {
-                    attribute.componentCount = convertTypeToInt(accessor.type);
-                } else {
-                    return 0;
-                }
-
-                vertexAttributes.push_back(attribute);
-            }
-
-            IndexType indexType;
-            std::vector<uint8_t> indexBufferData = {};
-            if (objectPrimitive.indices >= 0){ // if there is no index buffer, -1 is returned from fx-gltf
-                const fx::gltf::Accessor &indexAccessor = sceneObjects.accessors[objectPrimitive.indices];
-                const fx::gltf::BufferView &indexBufferView = sceneObjects.bufferViews[indexAccessor.bufferView];
-                const fx::gltf::Buffer &indexBuffer = sceneObjects.buffers[indexBufferView.buffer];
-
-                indexBufferData.resize(indexBufferView.byteLength);
-                {
-                    const size_t off = indexBufferView.byteOffset;
-                    const void *const ptr = ((char*)indexBuffer.data.data()) + off;
-                    if (!memcpy(indexBufferData.data(), ptr, indexBufferView.byteLength)) {
-                        vkcv_log(LogLevel::ERROR, "Copying index buffer data");
-                        return 0;
-                    }
-                }
-
-                indexType = getIndexType(indexAccessor.componentType);
-                if (indexType == IndexType::UNDEFINED){
-                    vkcv_log(LogLevel::ERROR, "Index Type undefined.");
-                    return 0;
-                }
-            }
-
-            const fx::gltf::BufferView&	vertexBufferView	= sceneObjects.bufferViews[posAccessor.bufferView];
-            const fx::gltf::Buffer&		vertexBuffer		= sceneObjects.buffers[vertexBufferView.buffer];
-
-            // only copy relevant part of vertex data
-            uint32_t relevantBufferOffset = std::numeric_limits<uint32_t>::max();
-            uint32_t relevantBufferEnd = 0;
-            for (const auto &attribute : vertexAttributes) {
-                relevantBufferOffset = std::min(attribute.offset, relevantBufferOffset);
-                const uint32_t attributeEnd = attribute.offset + attribute.length;
-                relevantBufferEnd = std::max(relevantBufferEnd, attributeEnd);    // TODO: need to incorporate stride?
-            }
-            const uint32_t relevantBufferSize = relevantBufferEnd - relevantBufferOffset;
-
-            // FIXME: This only works when all vertex attributes are in one buffer
-            std::vector<uint8_t> vertexBufferData;
-            vertexBufferData.resize(relevantBufferSize);
-            {
-                const void *const ptr = ((char*)vertexBuffer.data.data()) + relevantBufferOffset;
-                if (!memcpy(vertexBufferData.data(), ptr, relevantBufferSize)) {
-                    vkcv_log(LogLevel::ERROR, "Copying vertex buffer data");
-                    return 0;
-                }
-            }
-
-            // make vertex attributes relative to copied section
-            for (auto &attribute : vertexAttributes) {
-                attribute.offset -= relevantBufferOffset;
-            }
-
-            const size_t numVertexGroups = objectMesh.primitives.size();
-            vertexGroups.reserve(numVertexGroups);
-
-            vertexGroups.push_back({
-                static_cast<PrimitiveMode>(objectPrimitive.mode),
-                sceneObjects.accessors[objectPrimitive.indices].count,
-                posAccessor.count,
-                {indexType, indexBufferData},
-                {vertexBufferData, vertexAttributes},
-                {posAccessor.min[0], posAccessor.min[1], posAccessor.min[2]},
-                {posAccessor.max[0], posAccessor.max[1], posAccessor.max[2]},
-                static_cast<uint8_t>(objectPrimitive.material)
-            });
-
-            vertexGroupsIndices.push_back(groupCount);
-            groupCount++;
-        }
 
-        mesh.name = sceneObjects.meshes[i].name;
-        mesh.vertexGroups = vertexGroupsIndices;
-
-        meshes.push_back(mesh);
-    }
-
-    for(int m = 0; m < sceneObjects.nodes.size(); m++) {
-        meshes[sceneObjects.nodes[m].mesh].modelMatrix = computeModelMatrix(sceneObjects.nodes[m].translation,
-                                                                            sceneObjects.nodes[m].scale,
-                                                                            sceneObjects.nodes[m].rotation,
-                                                                            sceneObjects.nodes[m].matrix);
-    }
-
-    if (sceneObjects.textures.size() > 0){
-        textures.reserve(sceneObjects.textures.size());
-
-        for(int k = 0; k < sceneObjects.textures.size(); k++){
-            const fx::gltf::Texture &tex = sceneObjects.textures[k];
-            const fx::gltf::Image &img = sceneObjects.images[tex.source];
-            std::string img_uri = dir + "/" + img.uri;
-            int w, h, c;
-            uint8_t *data = stbi_load(img_uri.c_str(), &w, &h, &c, 4);
-            c = 4;	// FIXME hardcoded to always have RGBA channel layout
-            if (!data) {
-                vkcv_log(LogLevel::ERROR, "Loading texture image data.")
-                return 0;
-            }
-            const size_t byteLen = w * h * c;
+	/**
+	 * This function fills the array of vertex attributes of a VertexGroup (usually
+	 * part of a vkcv::asset::Mesh) object based on the description of attributes
+	 * for a fx::gltf::Primitive.
+	 *
+	 * @param src	The description of attribute objects from the fx-gltf library
+	 * @param gltf	The main glTF document
+	 * @param dst	The array of vertex attributes stored in an asset::Mesh object
+	 * @return	ASSET_ERROR when at least one VertexAttribute could not be
+	 * 		constructed properly, otherwise ASSET_SUCCESS
+	 */
+	static int loadVertexAttributes(const fx::gltf::Attributes &src,
+									const std::vector<fx::gltf::Accessor> &accessors,
+									const std::vector<fx::gltf::BufferView> &bufferViews,
+									std::vector<VertexAttribute> &dst) {
+		for (const auto &attrib : src) {
+			VertexAttribute att {};
+			
+			if (attrib.first == "POSITION") {
+				att.type = PrimitiveType::POSITION;
+			} else if (attrib.first == "NORMAL") {
+				att.type = PrimitiveType::NORMAL;
+			} else if (attrib.first == "TANGENT") {
+				att.type = PrimitiveType::TANGENT;
+			} else if (attrib.first == "TEXCOORD_0") {
+				att.type = PrimitiveType::TEXCOORD_0;
+			} else if (attrib.first == "TEXCOORD_1") {
+				att.type = PrimitiveType::TEXCOORD_1;
+			} else if (attrib.first == "COLOR_0") {
+				att.type = PrimitiveType::COLOR_0;
+			} else if (attrib.first == "COLOR_1") {
+				att.type = PrimitiveType::COLOR_1;
+			} else if (attrib.first == "JOINTS_0") {
+				att.type = PrimitiveType::JOINTS_0;
+			} else if (attrib.first == "WEIGHTS_0") {
+				att.type = PrimitiveType::WEIGHTS_0;
+			} else {
+				att.type = PrimitiveType::UNDEFINED;
+			}
+			
+			if (att.type != PrimitiveType::UNDEFINED) {
+				const fx::gltf::Accessor &accessor = accessors[attrib.second];
+				const fx::gltf::BufferView &buf = bufferViews[accessor.bufferView];
+				
+				att.offset = buf.byteOffset;
+				att.length = buf.byteLength;
+				att.stride = buf.byteStride;
+				att.componentType = static_cast<ComponentType>(accessor.componentType);
+				att.componentCount = getComponentCount(accessor.type);
+				
+				/* Assume tightly packed stride as not explicitly provided */
+				if (att.stride == 0) {
+					att.stride = att.componentCount * getComponentSize(att.componentType);
+				}
+			}
+			
+			if ((att.type == PrimitiveType::UNDEFINED) ||
+				(att.componentCount == 0)) {
+				return ASSET_ERROR;
+			}
+			
+			dst.push_back(att);
+		}
+		
+		return ASSET_SUCCESS;
+	}
 
-            std::vector<uint8_t> imgdata;
-            imgdata.resize(byteLen);
-            if (!memcpy(imgdata.data(), data, byteLen)) {
-                vkcv_log(LogLevel::ERROR, "Copying texture image data")
-                free(data);
-                return 0;
-            }
-            free(data);
+	/**
+	 * This function calculates the modelMatrix out of the data given in the gltf file.
+	 * It also checks, whether a modelMatrix was given.
+	 *
+	 * @param translation possible translation vector (default 0,0,0)
+	 * @param scale possible scale vector (default 1,1,1)
+	 * @param rotation possible rotation, given in quaternion (default 0,0,0,1)
+	 * @param matrix possible modelmatrix (default identity)
+	 * @return model Matrix as an array of floats
+	 */
+	static std::array<float, 16> calculateModelMatrix(const std::array<float, 3>& translation,
+													  const std::array<float, 3>& scale,
+													  const std::array<float, 4>& rotation,
+													  const std::array<float, 16>& matrix) {
+		std::array<float, 16> modelMatrix = {
+				1,0,0,0,
+				0,1,0,0,
+				0,0,1,0,
+				0,0,0,1
+		};
+		
+		if (matrix != modelMatrix){
+			return matrix;
+		} else {
+			// translation
+			modelMatrix[3] = translation[0];
+			modelMatrix[7] = translation[1];
+			modelMatrix[11] = -translation[2]; // flip Z to convert from GLTF to Vulkan
+			
+			// rotation and scale
+			auto a = rotation[3];
+			auto q1 = rotation[0];
+			auto q2 = rotation[1];
+			auto q3 = rotation[2];
+			
+			auto s = 2 / (a * a + q1 * q1 + q2 * q2 + q3 * q3);
+			
+			auto s1 = scale[0];
+			auto s2 = scale[1];
+			auto s3 = -scale[2]; // flip Z to convert from GLTF to Vulkan
+			
+			modelMatrix[0]  = (1 - s * (q2 * q2 + q3 * q3)) * s1;
+			modelMatrix[1]  = (    s * (q1 * q2 - q3 *  a)) * s2;
+			modelMatrix[2]  = (    s * (q1 * q3 + q2 *  a)) * s3;
+			
+			modelMatrix[4]  = (    s * (q1 * q2 + q3 *  a)) * s1;
+			modelMatrix[5]  = (1 - s * (q1 * q1 + q3 * q3)) * s2;
+			modelMatrix[6]  = (    s * (q2 * q3 - q1 *  a)) * s3;
+			
+			modelMatrix[8]  = (    s * (q1 * q3 - q2 *  a)) * s1;
+			modelMatrix[9]  = (    s * (q2 * q3 + q1 *  a)) * s2;
+			modelMatrix[10] = (1 - s * (q1 * q1 + q2 * q2)) * s3;
+	
+			return modelMatrix;
+		}
+	}
+	
+	static std::array<float, 16> multiplyMatrix(const std::array<float, 16>& matrix,
+												const std::array<float, 16>& other) {
+		std::array<float, 16> result;
+		size_t i, j, k;
+		
+		for (i = 0; i < 4; i++) {
+			for (j = 0; j < 4; j++) {
+				float sum = 0.0f;
+				
+				for (k = 0; k < 4; k++) {
+					sum += matrix[i * 4 + k] * other[k * 4 + j];
+				}
+				
+				result[i * 4 + j] = sum;
+			}
+		}
+		
+		return result;
+	}
 
-            textures.push_back({
-                0,
-                static_cast<uint8_t>(c),
-                static_cast<uint16_t>(w),
-                static_cast<uint16_t>(h),
-                imgdata
-            });
+	bool Material::hasTexture(const PBRTextureTarget target) const {
+		return textureMask & bitflag(target);
+	}
 
-        }
-    }
+	/**
+	 * This function translates a given fx-gltf-sampler-wrapping-mode-enum to its vulkan sampler-adress-mode counterpart.
+	 * @param mode: wrapping mode of a sampler given as fx-gltf-enum
+	 * @return int vulkan-enum representing the same wrapping mode
+	 */
+	static int translateSamplerMode(const fx::gltf::Sampler::WrappingMode mode) {
+		switch (mode) {
+		case fx::gltf::Sampler::WrappingMode::ClampToEdge:
+			return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		case fx::gltf::Sampler::WrappingMode::MirroredRepeat:
+			return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
+		case fx::gltf::Sampler::WrappingMode::Repeat:
+		default:
+			return VK_SAMPLER_ADDRESS_MODE_REPEAT;
+		}
+	}
 
-    if (sceneObjects.materials.size() > 0){
-        materials.reserve(sceneObjects.materials.size());
+	/**
+	 * If the glTF doesn't define samplers, we use the defaults defined by fx-gltf.
+	 * The following are details about the glTF/OpenGL to Vulkan translation.
+	 * magFilter (VkFilter?):
+	 * 	GL_NEAREST -> VK_FILTER_NEAREST
+	 * 	GL_LINEAR -> VK_FILTER_LINEAR
+	 * minFilter (VkFilter?):
+	 * mipmapMode (VkSamplerMipmapMode?):
+	 * Vulkans minFilter and mipmapMode combined correspond to OpenGLs
+	 * GL_minFilter_MIPMAP_mipmapMode:
+	 * 	GL_NEAREST_MIPMAP_NEAREST:
+	 * 		minFilter=VK_FILTER_NEAREST
+	 * 		mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST
+	 * 	GL_LINEAR_MIPMAP_NEAREST:
+	 * 		minFilter=VK_FILTER_LINEAR
+	 * 		mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST
+	 * 	GL_NEAREST_MIPMAP_LINEAR:
+	 * 		minFilter=VK_FILTER_NEAREST
+	 * 		mipmapMode=VK_SAMPLER_MIPMAP_MODE_LINEAR
+	 * 	GL_LINEAR_MIPMAP_LINEAR:
+	 * 		minFilter=VK_FILTER_LINEAR
+	 * 		mipmapMode=VK_SAMPLER_MIPMAP_MODE_LINEAR
+	 * The modes of GL_LINEAR and GL_NEAREST have to be emulated using
+	 * mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST with specific minLOD and maxLOD:
+	 * 	GL_LINEAR:
+	 * 		minFilter=VK_FILTER_LINEAR
+	 * 		mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST
+	 * 		minLOD=0, maxLOD=0.25
+	 * 	GL_NEAREST:
+	 * 		minFilter=VK_FILTER_NEAREST
+	 * 		mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST
+	 * 		minLOD=0, maxLOD=0.25
+	 * Setting maxLOD=0 causes magnification to always be performed (using
+	 * the defined magFilter), this may be valid if the min- and magFilter
+	 * are equal, otherwise it won't be the expected behaviour from OpenGL
+	 * and glTF; instead using maxLod=0.25 allows the minFilter to be
+	 * performed while still always rounding to the base level.
+	 * With other modes, minLOD and maxLOD default to:
+	 * 	minLOD=0
+	 * 	maxLOD=VK_LOD_CLAMP_NONE
+	 * wrapping:
+	 * gltf has wrapS, wrapT with {clampToEdge, MirroredRepeat, Repeat} while
+	 * Vulkan has addressModeU, addressModeV, addressModeW with values
+	 * VK_SAMPLER_ADDRESS_MODE_{REPEAT,MIRRORED_REPEAT,CLAMP_TO_EDGE,
+	 * 			    CAMP_TO_BORDER,MIRROR_CLAMP_TO_EDGE}
+	 * Translation from glTF to Vulkan is straight forward for the 3 existing
+	 * modes, default is repeat, the other modes aren't available.
+	 */
+	static vkcv::asset::Sampler loadSampler(const fx::gltf::Sampler &src) {
+		Sampler dst {};
+		
+		dst.minLOD = 0;
+		dst.maxLOD = VK_LOD_CLAMP_NONE;
+	
+		switch (src.minFilter) {
+		case fx::gltf::Sampler::MinFilter::None:
+		case fx::gltf::Sampler::MinFilter::Nearest:
+			dst.minFilter = VK_FILTER_NEAREST;
+			dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
+			dst.maxLOD = 0.25;
+			break;
+		case fx::gltf::Sampler::MinFilter::Linear:
+			dst.minFilter = VK_FILTER_LINEAR;
+			dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
+			dst.maxLOD = 0.25;
+			break;
+		case fx::gltf::Sampler::MinFilter::NearestMipMapNearest:
+			dst.minFilter = VK_FILTER_NEAREST;
+			dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
+			break;
+		case fx::gltf::Sampler::MinFilter::LinearMipMapNearest:
+			dst.minFilter = VK_FILTER_LINEAR;
+			dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
+			break;
+		case fx::gltf::Sampler::MinFilter::NearestMipMapLinear:
+			dst.minFilter = VK_FILTER_NEAREST;
+			dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+			break;
+		case fx::gltf::Sampler::MinFilter::LinearMipMapLinear:
+			dst.minFilter = VK_FILTER_LINEAR;
+			dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+			break;
+		default:
+			break;
+		}
+	
+		switch (src.magFilter) {
+		case fx::gltf::Sampler::MagFilter::None:
+		case fx::gltf::Sampler::MagFilter::Nearest:
+			dst.magFilter = VK_FILTER_NEAREST;
+			break;
+		case fx::gltf::Sampler::MagFilter::Linear:
+			dst.magFilter = VK_FILTER_LINEAR;
+			break;
+		default:
+			break;
+		}
+	
+		dst.addressModeU = translateSamplerMode(src.wrapS);
+		dst.addressModeV = translateSamplerMode(src.wrapT);
+		
+		// There is no information about wrapping for a third axis in glTF and
+		// we have to hardcode this value.
+		dst.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+		
+		return dst;
+	}
 
-        for (int l = 0; l < sceneObjects.materials.size(); l++){
-            fx::gltf::Material material = sceneObjects.materials[l];
-	    // TODO I think we shouldn't set the index for a texture target if
-	    // it isn't defined. So we need to test first if there is a normal
-	    // texture before assigning material.normalTexture.index.
-	    // About the bitmask: If a normal texture is there, modify the
-	    // materials textureMask like this:
-	    // 		mat.textureMask |= bitflag(asset::normal);
-            materials.push_back({
-               0,
-               material.pbrMetallicRoughness.baseColorTexture.index,
-               material.pbrMetallicRoughness.metallicRoughnessTexture.index,
-               material.normalTexture.index,
-               material.occlusionTexture.index,
-               material.emissiveTexture.index,
-               {
-                   material.pbrMetallicRoughness.baseColorFactor[0],
-                   material.pbrMetallicRoughness.baseColorFactor[1],
-                   material.pbrMetallicRoughness.baseColorFactor[2],
-                   material.pbrMetallicRoughness.baseColorFactor[3]
-               },
-               material.pbrMetallicRoughness.metallicFactor,
-               material.pbrMetallicRoughness.roughnessFactor,
-               material.normalTexture.scale,
-               material.occlusionTexture.strength,
-               {
-                   material.emissiveFactor[0],
-                   material.emissiveFactor[1],
-                   material.emissiveFactor[2]
-               }
+	/**
+	 * Initializes vertex groups of a Mesh, including copying the data to
+	 * index- and vertex-buffers.
+	 */
+	static int loadVertexGroups(const fx::gltf::Mesh &objectMesh,
+								const fx::gltf::Document &sceneObjects,
+								Scene &scene, Mesh &mesh) {
+		mesh.vertexGroups.reserve(objectMesh.primitives.size());
+	
+		for (const auto &objectPrimitive : objectMesh.primitives) {
+			VertexGroup vertexGroup;
+			
+			vertexGroup.vertexBuffer.attributes.reserve(
+					objectPrimitive.attributes.size()
+			);
+	
+			if (ASSET_SUCCESS != loadVertexAttributes(
+					objectPrimitive.attributes,
+					sceneObjects.accessors,
+					sceneObjects.bufferViews,
+					vertexGroup.vertexBuffer.attributes)) {
+				vkcv_log(LogLevel::ERROR, "Failed to get vertex attributes of '%s'",
+						 mesh.name.c_str());
+				return ASSET_ERROR;
+			}
+			
+			// The accessor for the position attribute is used for
+			// 1) getting the vertex buffer view which is only needed to get
+			//    the vertex buffer
+			// 2) getting the vertex count for the VertexGroup
+			// 3) getting the min/max of the bounding box for the VertexGroup
+			fx::gltf::Accessor posAccessor;
+			bool noPosition = true;
+			
+			for (auto const& attrib : objectPrimitive.attributes) {
+				if (attrib.first == "POSITION") {
+					posAccessor = sceneObjects.accessors[attrib.second];
+					noPosition = false;
+					break;
+				}
+			}
+			
+			if (noPosition) {
+				vkcv_log(LogLevel::ERROR, "Position attribute not found from '%s'",
+						 mesh.name.c_str());
+				return ASSET_ERROR;
+			}
+	
+			const fx::gltf::Accessor& indexAccessor = sceneObjects.accessors[objectPrimitive.indices];
+			
+			int indexBufferURI;
+			if (objectPrimitive.indices >= 0) { // if there is no index buffer, -1 is returned from fx-gltf
+				const fx::gltf::BufferView& indexBufferView = sceneObjects.bufferViews[indexAccessor.bufferView];
+				const fx::gltf::Buffer& indexBuffer = sceneObjects.buffers[indexBufferView.buffer];
+				
+				// Because the buffers are already preloaded into the memory by the gltf-library,
+				// it makes no sense to load them later on manually again into memory.
+				vertexGroup.indexBuffer.data.resize(indexBufferView.byteLength);
+				memcpy(vertexGroup.indexBuffer.data.data(),
+					   indexBuffer.data.data() + indexBufferView.byteOffset,
+					   indexBufferView.byteLength);
+			} else {
+				indexBufferURI = -1;
+			}
+			
+			vertexGroup.indexBuffer.type = getIndexType(indexAccessor.componentType);
+			
+			if (IndexType::UNDEFINED == vertexGroup.indexBuffer.type) {
+				vkcv_log(LogLevel::ERROR, "Index Type undefined or not supported.");
+				return ASSET_ERROR;
+			}
+	
+			if (posAccessor.bufferView >= sceneObjects.bufferViews.size()) {
+				vkcv_log(LogLevel::ERROR, "Access to bufferView out of bounds: %d",
+						posAccessor.bufferView);
+				return ASSET_ERROR;
+			}
+			const fx::gltf::BufferView& vertexBufferView = sceneObjects.bufferViews[posAccessor.bufferView];
+			if (vertexBufferView.buffer >= sceneObjects.buffers.size()) {
+				vkcv_log(LogLevel::ERROR, "Access to buffer out of bounds: %d",
+						vertexBufferView.buffer);
+				return ASSET_ERROR;
+			}
+			const fx::gltf::Buffer& vertexBuffer = sceneObjects.buffers[vertexBufferView.buffer];
+			
+			// only copy relevant part of vertex data
+			uint32_t relevantBufferOffset = std::numeric_limits<uint32_t>::max();
+			uint32_t relevantBufferEnd = 0;
+			
+			for (const auto& attribute : vertexGroup.vertexBuffer.attributes) {
+				relevantBufferOffset = std::min(attribute.offset, relevantBufferOffset);
+				relevantBufferEnd = std::max(relevantBufferEnd, attribute.offset + attribute.length);
+			}
+			
+			const uint32_t relevantBufferSize = relevantBufferEnd - relevantBufferOffset;
+			
+			vertexGroup.vertexBuffer.data.resize(relevantBufferSize);
+			memcpy(vertexGroup.vertexBuffer.data.data(),
+				   vertexBuffer.data.data() + relevantBufferOffset,
+				   relevantBufferSize);
+			
+			// make vertex attributes relative to copied section
+			for (auto& attribute : vertexGroup.vertexBuffer.attributes) {
+				attribute.offset -= relevantBufferOffset;
+			}
+			
+			vertexGroup.mode = static_cast<PrimitiveMode>(objectPrimitive.mode);
+			vertexGroup.numIndices = sceneObjects.accessors[objectPrimitive.indices].count;
+			vertexGroup.numVertices = posAccessor.count;
+			
+			memcpy(&(vertexGroup.min), posAccessor.min.data(), sizeof(vertexGroup.min));
+			memcpy(&(vertexGroup.max), posAccessor.max.data(), sizeof(vertexGroup.max));
+			
+			vertexGroup.materialIndex = static_cast<uint8_t>(objectPrimitive.material);
+			
+			mesh.vertexGroups.push_back(static_cast<int>(scene.vertexGroups.size()));
+			scene.vertexGroups.push_back(vertexGroup);
+		}
+		
+		return ASSET_SUCCESS;
+	}
 
-            });
-        }
-    }
+	/**
+	 * Returns an integer with specific bits set corresponding to the
+	 * textures that appear in the given material. This mask is used in the
+	 * vkcv::asset::Material struct and can be tested via the hasTexture
+	 * method.
+	 */
+	static uint16_t generateTextureMask(fx::gltf::Material &material) {
+		uint16_t textureMask = 0;
+		
+		if (material.pbrMetallicRoughness.baseColorTexture.index >= 0) {
+			textureMask |= bitflag(asset::PBRTextureTarget::baseColor);
+		}
+		if (material.pbrMetallicRoughness.metallicRoughnessTexture.index >= 0) {
+			textureMask |= bitflag(asset::PBRTextureTarget::metalRough);
+		}
+		if (material.normalTexture.index >= 0) {
+			textureMask |= bitflag(asset::PBRTextureTarget::normal);
+		}
+		if (material.occlusionTexture.index >= 0) {
+			textureMask |= bitflag(asset::PBRTextureTarget::occlusion);
+		}
+		if (material.emissiveTexture.index >= 0) {
+			textureMask |= bitflag(asset::PBRTextureTarget::emissive);
+		}
+		
+		return textureMask;
+	}
 
-    scene = {
-            meshes,
-            vertexGroups,
-            materials,
-            textures,
-            samplers
-    };
+	int probeScene(const std::filesystem::path& path, Scene& scene) {
+		fx::gltf::Document sceneObjects;
+	
+		try {
+			if (path.extension() == ".glb") {
+				sceneObjects = fx::gltf::LoadFromBinary(
+						path.string(),
+						{
+								fx::gltf::detail::DefaultMaxBufferCount,
+								fx::gltf::detail::DefaultMaxMemoryAllocation * 16,
+								fx::gltf::detail::DefaultMaxMemoryAllocation * 8
+						}
+				);
+			} else {
+				sceneObjects = fx::gltf::LoadFromText(
+						path.string(),
+						{
+							fx::gltf::detail::DefaultMaxBufferCount,
+							fx::gltf::detail::DefaultMaxMemoryAllocation,
+							fx::gltf::detail::DefaultMaxMemoryAllocation * 8
+						}
+				);
+			}
+		} catch (const std::system_error& err) {
+			recurseExceptionPrint(err, path.string());
+			return ASSET_ERROR;
+		} catch (const std::exception& e) {
+			recurseExceptionPrint(e, path.string());
+			return ASSET_ERROR;
+		}
+		
+		const auto directory = path.parent_path();
+		
+		scene.meshes.clear();
+		scene.vertexGroups.clear();
+		scene.materials.clear();
+		scene.textures.clear();
+		scene.samplers.clear();
+	
+		// file has to contain at least one mesh
+		if (sceneObjects.meshes.empty()) {
+			vkcv_log(LogLevel::ERROR, "No meshes found! (%s)", path.c_str());
+			return ASSET_ERROR;
+		} else {
+			scene.meshes.reserve(sceneObjects.meshes.size());
+			
+			for (size_t i = 0; i < sceneObjects.meshes.size(); i++) {
+				Mesh mesh;
+				mesh.name = sceneObjects.meshes[i].name;
+				
+				if (loadVertexGroups(sceneObjects.meshes[i], sceneObjects, scene, mesh) != ASSET_SUCCESS) {
+					vkcv_log(LogLevel::ERROR, "Failed to load vertex groups of '%s'! (%s)",
+							 mesh.name.c_str(), path.c_str());
+					return ASSET_ERROR;
+				}
+				
+				scene.meshes.push_back(mesh);
+			}
+			
+			std::vector< std::array<float, 16> > matrices;
+			std::vector< int32_t > parents;
+			
+			matrices.reserve(sceneObjects.nodes.size());
+			parents.resize(sceneObjects.nodes.size(), -1);
+			
+			for (size_t i = 0; i < sceneObjects.nodes.size(); i++) {
+				const auto &node = sceneObjects.nodes[i];
+				
+				matrices.push_back(calculateModelMatrix(
+						node.translation,
+						node.scale,
+						node.rotation,
+						node.matrix
+				));
+				
+				for (int32_t child : node.children)
+					if ((child >= 0) && (child < parents.size()))
+						parents[child] = static_cast<int32_t>(i);
+			}
+			
+			std::vector< std::array<float, 16> > final_matrices;
+			final_matrices.reserve(matrices.size());
+			
+			for (size_t i = 0; i < matrices.size(); i++) {
+				std::vector<int> order;
+				order.push_back(static_cast<int32_t>(i));
+				
+				while (parents[ order[order.size() - 1] ] >= 0)
+					order.push_back(parents[ order[order.size() - 1] ]);
+				
+				std::array<float, 16> matrix = matrices[ order[order.size() - 1] ];
+				
+				for (size_t j = order.size() - 1; j > 0; j--) {
+					const auto id = order[j - 1];
+					const std::array<float, 16> matrix_other = matrices[ id ];
+					
+					matrix = multiplyMatrix(matrix, matrix_other);
+				}
+				
+				final_matrices.push_back(matrix);
+			}
+			
+			for (size_t i = 0; i < sceneObjects.nodes.size(); i++) {
+				const auto &node = sceneObjects.nodes[i];
+				
+				if ((node.mesh >= 0) && (node.mesh < scene.meshes.size())) {
+					scene.meshes[node.mesh].modelMatrix = final_matrices[i];
+				}
+			}
+		}
+		
+		if (sceneObjects.samplers.empty()) {
+			vkcv_log(LogLevel::WARNING, "No samplers found! (%s)", path.c_str());
+		} else {
+			scene.samplers.reserve(sceneObjects.samplers.size());
+			
+			for (const auto &samplerObject : sceneObjects.samplers) {
+				scene.samplers.push_back(loadSampler(samplerObject));
+			}
+		}
+		
+		if (sceneObjects.textures.empty()) {
+			vkcv_log(LogLevel::WARNING, "No textures found! (%s)", path.c_str());
+		} else {
+			scene.textures.reserve(sceneObjects.textures.size());
+			
+			for (const auto& textureObject : sceneObjects.textures) {
+				Texture texture;
+				
+				if (textureObject.sampler < 0) {
+					texture.sampler = -1;
+				} else
+				if (static_cast<size_t>(textureObject.sampler) >= scene.samplers.size()) {
+					vkcv_log(LogLevel::ERROR, "Sampler of texture '%s' missing (%s)",
+							 textureObject.name.c_str(), path.c_str());
+					return ASSET_ERROR;
+				} else {
+					texture.sampler = textureObject.sampler;
+				}
+				
+				if ((textureObject.source < 0) ||
+					(static_cast<size_t>(textureObject.source) >= sceneObjects.images.size())) {
+					vkcv_log(LogLevel::ERROR, "Failed to load texture '%s' (%s)",
+							 textureObject.name.c_str(), path.c_str());
+					return ASSET_ERROR;
+				}
+				
+				const auto& image = sceneObjects.images[textureObject.source];
+				
+				if (image.uri.empty()) {
+					const fx::gltf::BufferView bufferView = sceneObjects.bufferViews[image.bufferView];
+					
+					texture.path.clear();
+					texture.data.resize(bufferView.byteLength);
+					memcpy(texture.data.data(),
+						   sceneObjects.buffers[bufferView.buffer].data.data() + bufferView.byteOffset,
+						   bufferView.byteLength);
+				} else {
+					texture.path = directory / image.uri;
+				}
+				
+				scene.textures.push_back(texture);
+			}
+		}
+		
+		if (sceneObjects.materials.empty()) {
+			vkcv_log(LogLevel::WARNING, "No materials found! (%s)", path.c_str());
+		} else {
+			scene.materials.reserve(sceneObjects.materials.size());
+			
+			for (auto material : sceneObjects.materials) {
+				scene.materials.push_back({
+						generateTextureMask(material),
+						material.pbrMetallicRoughness.baseColorTexture.index,
+						material.pbrMetallicRoughness.metallicRoughnessTexture.index,
+						material.normalTexture.index,
+						material.occlusionTexture.index,
+						material.emissiveTexture.index,
+						{
+								material.pbrMetallicRoughness.baseColorFactor[0],
+								material.pbrMetallicRoughness.baseColorFactor[1],
+								material.pbrMetallicRoughness.baseColorFactor[2],
+								material.pbrMetallicRoughness.baseColorFactor[3]
+						},
+						material.pbrMetallicRoughness.metallicFactor,
+						material.pbrMetallicRoughness.roughnessFactor,
+						material.normalTexture.scale,
+						material.occlusionTexture.strength,
+						{
+								material.emissiveFactor[0],
+								material.emissiveFactor[1],
+								material.emissiveFactor[2]
+						}
+				});
+			}
+		}
+	
+		return ASSET_SUCCESS;
+	}
+	
+	/**
+	 * Loads and decodes the textures data based on the textures file path.
+	 * The path member is the only one that has to be initialized before
+	 * calling this function, the others (width, height, channels, data)
+	 * are set by this function and the sampler is of no concern here.
+	 */
+	static int loadTextureData(Texture& texture) {
+		if ((texture.width > 0) && (texture.height > 0) && (texture.channels > 0) &&
+			(!texture.data.empty())) {
+			return ASSET_SUCCESS; // Texture data was loaded already!
+		}
+		
+		uint8_t* data;
+		
+		if (texture.path.empty()) {
+			data = stbi_load_from_memory(
+					reinterpret_cast<uint8_t*>(texture.data.data()),
+					static_cast<int>(texture.data.size()),
+					&texture.width,
+					&texture.height,
+					&texture.channels, 4
+			);
+		} else {
+			data = stbi_load(
+					texture.path.string().c_str(),
+					&texture.width,
+					&texture.height,
+					&texture.channels, 4
+			);
+		}
+		
+		if (!data) {
+			vkcv_log(LogLevel::ERROR, "Texture could not be loaded from '%s'",
+					 texture.path.c_str());
+			
+			texture.width = 0;
+			texture.height = 0;
+			texture.channels = 0;
+			return ASSET_ERROR;
+		}
+		
+		texture.data.resize(texture.width * texture.height * 4);
+		memcpy(texture.data.data(), data, texture.data.size());
+		stbi_image_free(data);
+	
+		return ASSET_SUCCESS;
+	}
 
-    return 1;
-}
+	int loadMesh(Scene &scene, int index) {
+		if ((index < 0) || (static_cast<size_t>(index) >= scene.meshes.size())) {
+			vkcv_log(LogLevel::ERROR, "Mesh index out of range: %d", index);
+			return ASSET_ERROR;
+		}
+		
+		const Mesh &mesh = scene.meshes[index];
+		
+		for (const auto& vg : mesh.vertexGroups) {
+			const VertexGroup &vertexGroup = scene.vertexGroups[vg];
+			const Material& material = scene.materials[vertexGroup.materialIndex];
+			
+			if (material.hasTexture(PBRTextureTarget::baseColor)) {
+				const int result = loadTextureData(scene.textures[material.baseColor]);
+				if (ASSET_SUCCESS != result) {
+					vkcv_log(LogLevel::ERROR, "Failed loading baseColor texture of mesh '%s'",
+							 mesh.name.c_str())
+					return result;
+				}
+			}
+			
+			if (material.hasTexture(PBRTextureTarget::metalRough)) {
+				const int result = loadTextureData(scene.textures[material.metalRough]);
+				if (ASSET_SUCCESS != result) {
+					vkcv_log(LogLevel::ERROR, "Failed loading metalRough texture of mesh '%s'",
+							 mesh.name.c_str())
+					return result;
+				}
+			}
+			
+			if (material.hasTexture(PBRTextureTarget::normal)) {
+				const int result = loadTextureData(scene.textures[material.normal]);
+				if (ASSET_SUCCESS != result) {
+					vkcv_log(LogLevel::ERROR, "Failed loading normal texture of mesh '%s'",
+							 mesh.name.c_str())
+					return result;
+				}
+			}
+			
+			if (material.hasTexture(PBRTextureTarget::occlusion)) {
+				const int result = loadTextureData(scene.textures[material.occlusion]);
+				if (ASSET_SUCCESS != result) {
+					vkcv_log(LogLevel::ERROR, "Failed loading occlusion texture of mesh '%s'",
+							 mesh.name.c_str())
+					return result;
+				}
+			}
+			
+			if (material.hasTexture(PBRTextureTarget::emissive)) {
+				const int result = loadTextureData(scene.textures[material.emissive]);
+				if (ASSET_SUCCESS != result) {
+					vkcv_log(LogLevel::ERROR, "Failed loading emissive texture of mesh '%s'",
+							 mesh.name.c_str())
+					return result;
+				}
+			}
+		}
+	
+		return ASSET_SUCCESS;
+	}
+	
+	int loadScene(const std::filesystem::path &path, Scene &scene) {
+		int result = probeScene(path, scene);
+		size_t i;
+		
+		if (result != ASSET_SUCCESS) {
+			vkcv_log(LogLevel::ERROR, "Loading scene failed '%s'",
+					 path.c_str());
+			return result;
+		}
+		
+		/* Preloading the textures of the scene to improve performance */
+		#pragma omp parallel for shared(scene.textures) private(i)
+		for (i = 0; i < scene.textures.size(); i++) {
+			loadTextureData(scene.textures[i]);
+		}
+		
+		for (i = 0; i < scene.meshes.size(); i++) {
+			result = loadMesh(scene, static_cast<int>(i));
+			
+			if (result != ASSET_SUCCESS) {
+				vkcv_log(LogLevel::ERROR, "Loading mesh with index %d failed '%s'",
+						 static_cast<int>(i), path.c_str());
+				return result;
+			}
+		}
+		
+		return ASSET_SUCCESS;
+	}
+	
+	Texture loadTexture(const std::filesystem::path& path) {
+		Texture texture;
+		texture.path = path;
+		texture.sampler = -1;
+		
+		if (loadTextureData(texture) != ASSET_SUCCESS) {
+			texture.path.clear();
+			texture.w = texture.h = texture.channels = 0;
+			texture.data.clear();
+		}
+		return texture;
+	}
+	
+	VertexBufferBindings loadVertexBufferBindings(const std::vector<VertexAttribute> &attributes,
+												  const BufferHandle &buffer,
+												  const std::vector<PrimitiveType> &types) {
+		VertexBufferBindings bindings;
+		
+		for (const auto& type : types) {
+			const VertexAttribute* attribute = nullptr;
+			
+			for (const auto& attr : attributes) {
+				if (type == attr.type) {
+					attribute = &(attr);
+					break;
+				}
+			}
+			
+			if (!attribute) {
+				vkcv_log(LogLevel::ERROR, "Missing primitive type in vertex attributes");
+				break;
+			}
+			
+			bindings.push_back(vkcv::vertexBufferBinding(buffer, attribute->offset));
+		}
+		
+		return bindings;
+	}
 
 }
diff --git a/modules/camera/CMakeLists.txt b/modules/camera/CMakeLists.txt
index 41617da3905a118a7f963a95f2dd71d259e0fd70..0bc5ce6cbbab55abebce376820f2ba398267cdd5 100644
--- a/modules/camera/CMakeLists.txt
+++ b/modules/camera/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.16)
 project(vkcv_camera)
 
 # setting c++ standard for the project
-set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD 20)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
 set(vkcv_camera_source ${PROJECT_SOURCE_DIR}/src)
@@ -23,19 +23,33 @@ set(vkcv_camera_sources
 )
 
 # adding source files to the project
-add_library(vkcv_camera STATIC ${vkcv_camera_sources})
+add_library(vkcv_camera ${vkcv_build_attribute} ${vkcv_camera_sources})
 
 # Setup some path variables to load libraries
 set(vkcv_camera_lib lib)
 set(vkcv_camera_lib_path ${PROJECT_SOURCE_DIR}/${vkcv_camera_lib})
 
-include(config/GLM.cmake)
-
-target_link_libraries(vkcv_camera PUBLIC ${vkcv_camera_libraries} vkcv)
+target_link_libraries(vkcv_camera PUBLIC
+		${vkcv_camera_libraries}
+		vkcv
+		vkcv_geometry
+)
 
-target_include_directories(vkcv_camera SYSTEM BEFORE PRIVATE ${vkcv_camera_includes} ${vkcv_include})
+target_include_directories(vkcv_camera SYSTEM BEFORE PRIVATE
+		${vkcv_camera_includes}
+		${vkcv_include}
+		${vkcv_includes}
+		${vkcv_geometry_include}
+)
 
 # add the own include directory for public headers
 target_include_directories(vkcv_camera BEFORE PUBLIC ${vkcv_camera_include} ${vkcv_camera_includes})
-
 target_compile_definitions(vkcv_camera PUBLIC ${vkcv_camera_definitions})
+
+if (vkcv_parent_scope)
+	list(APPEND vkcv_modules_includes ${vkcv_camera_include})
+	list(APPEND vkcv_modules_libraries vkcv_camera)
+	
+	set(vkcv_modules_includes ${vkcv_modules_includes} PARENT_SCOPE)
+	set(vkcv_modules_libraries ${vkcv_modules_libraries} PARENT_SCOPE)
+endif()
diff --git a/modules/camera/README.md b/modules/camera/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..c88253a1fc343337d1b2b0178306ed9c04505fed
--- /dev/null
+++ b/modules/camera/README.md
@@ -0,0 +1,7 @@
+# Camera
+
+A VkCV module to manage cameras and their handle view and projection matrices
+
+## Docs
+
+Here is a [link](https://userpages.uni-koblenz.de/~vkcv/doc/group__vkcv__camera.html) to this module.
diff --git a/modules/camera/config/GLM.cmake b/modules/camera/config/GLM.cmake
deleted file mode 100644
index efd6444451100b912aa0b5b4a532dc8f448b0b40..0000000000000000000000000000000000000000
--- a/modules/camera/config/GLM.cmake
+++ /dev/null
@@ -1,21 +0,0 @@
-
-find_package(glm QUIET)
-
-if (glm_FOUND)
-    list(APPEND vkcv_camera_includes ${GLM_INCLUDE_DIRS})
-    list(APPEND vkcv_camera_libraries glm)
-
-    list(APPEND vkcv_camera_definitions GLM_DEPTH_ZERO_TO_ONE)
-    list(APPEND vkcv_camera_definitions GLM_FORCE_LEFT_HANDED)
-else()
-    if (EXISTS "${vkcv_camera_lib_path}/glm")
-        add_subdirectory(${vkcv_camera_lib}/glm)
-        
-        list(APPEND vkcv_camera_libraries glm)
-        
-        list(APPEND vkcv_camera_definitions GLM_DEPTH_ZERO_TO_ONE)
-        list(APPEND vkcv_camera_definitions GLM_FORCE_LEFT_HANDED)
-    else()
-        message(WARNING "GLM is required..! Update the submodules!")
-    endif ()
-endif ()
diff --git a/modules/camera/include/vkcv/camera/Camera.hpp b/modules/camera/include/vkcv/camera/Camera.hpp
index ce32d3f8a0c6ee3e0dd882f24a9ac2d12c14a024..b02e9cfb2d40ae6c2500a85572f894c64652eafd 100644
--- a/modules/camera/include/vkcv/camera/Camera.hpp
+++ b/modules/camera/include/vkcv/camera/Camera.hpp
@@ -1,11 +1,25 @@
 #pragma once
+/**
+ * @authors Vanessa Karolek, Josch Morgenstern, Sebastian Gaida, Katharina Krämer, Tobias Frisch, Alexander Gauggel
+ * @file include/vkcv/camera/Camera.hpp
+ * @brief Camera class of the camera module for the vkcv framework.
+ */
 
 #include <glm/glm.hpp>
 #include <glm/gtc/matrix_transform.hpp>
 #include <glm/gtc/matrix_access.hpp>
+#include <glm/vec3.hpp>
+#include <glm/gtc/type_ptr.hpp>
+#include <glm/mat4x4.hpp>
 
 namespace vkcv::camera {
 
+    /**
+     * @defgroup vkcv_camera Camera Module
+     * A module to manage intrinsic and extrinsic parameters with camera instances.
+     * @{
+     */
+
     /**
      * @brief Used to create a camera which governs the view and projection matrices.
      */
@@ -20,9 +34,6 @@ namespace vkcv::camera {
 		glm::vec3 m_up;
         glm::vec3 m_position;
         glm::vec3 m_center;
-
-        float m_pitch;
-        float m_yaw;
 	
 		/**
 		 * @brief Sets the view matrix of the camera to @p view
@@ -58,9 +69,10 @@ namespace vkcv::camera {
         void setPerspective(float fov, float ratio, float near, float far);
 
         /**
-         * @brief Gets the view matrix of the camera
+         * @brief Returns the view matrix of the camera
          * @return The view matrix of the camera
          */
+		[[nodiscard]]
         const glm::mat4& getView() const;
 
         /**
@@ -72,28 +84,31 @@ namespace vkcv::camera {
         void lookAt(const glm::vec3& position, const glm::vec3& center, const glm::vec3& up);
 
         /**
-         * @brief Gets the current projection of the camera
+         * @brief Returns the current projection of the camera
          * @return The current projection matrix
          */
+		[[nodiscard]]
         const glm::mat4& getProjection() const;
 
         /**
-         * @brief Gets the model-view-projection matrix of the camera with y-axis-correction applied
+         * @brief Returns the model-view-projection matrix of the camera with y-axis-correction applied
          * @return The model-view-projection matrix
          */
+		[[nodiscard]]
         glm::mat4 getMVP() const;
 
         /**
-         * @brief Gets the near and far bounds of the view frustum of the camera.
+         * @brief Returns the near and far bounds of the view frustum of the camera.
          * @param[out] near The near bound of the view frustum
          * @param[out] far The far bound of the view frustum
          */
         void getNearFar(float &near, float &far) const;
 
         /**
-         * @brief Gets the current field of view of the camera in radians
+         * @brief Returns the current field of view of the camera in radians
          * @return[in] The current field of view in radians
          */
+		[[nodiscard]]
         float getFov() const;
 
         /**
@@ -103,9 +118,10 @@ namespace vkcv::camera {
         void setFov(float fov);
 
         /**
-         * @brief Gets the current aspect ratio of the camera
+         * @brief Returns the current aspect ratio of the camera
          * @return The current aspect ratio of the camera
          */
+		[[nodiscard]]
         float getRatio() const;
 
         /**
@@ -122,9 +138,10 @@ namespace vkcv::camera {
         void setNearFar(float near, float far);
 
         /**
-         * @brief Gets the current front vector of the camera in world space
+         * @brief Returns the current front vector of the camera in world space
          * @return The current front vector of the camera
          */
+		[[nodiscard]]
         glm::vec3 getFront() const;
         
         /**
@@ -134,9 +151,10 @@ namespace vkcv::camera {
         void setFront(const glm::vec3& front);
 
         /**
-         * @brief Gets the current position of the camera in world space
+         * @brief Returns the current position of the camera in world space
          * @return The current position of the camera in world space
          */
+		[[nodiscard]]
         const glm::vec3& getPosition() const;
 
         /**
@@ -146,9 +164,10 @@ namespace vkcv::camera {
         void setPosition( const glm::vec3& position );
 
         /**
-         * @brief Gets the center point.
+         * @brief Returns the center point.
          * @return The center point.
          */
+		[[nodiscard]]
         const glm::vec3& getCenter() const;
 
         /**
@@ -156,11 +175,26 @@ namespace vkcv::camera {
          * @param[in] center The new center point.
          */
         void setCenter(const glm::vec3& center);
+        
+        /**
+         * @brief Returns the angles of the camera.
+         * @param[out] pitch The pitch value in radians
+         * @param[out] yaw The yaw value in radians
+         */
+		void getAngles(float& pitch, float& yaw);
+  
+		/**
+		 * @brief Sets the angles of the camera.
+		 * @param pitch The new pitch value in radians
+		 * @param yaw The new yaw value in radians
+		 */
+		void setAngles(float pitch, float yaw);
 
         /**
-         * @brief Gets the pitch value of the camera in degrees.
+         * @brief Returns the pitch value of the camera in degrees.
          * @return The pitch value in degrees.
          */
+		[[nodiscard]]
         float getPitch() const;
 
         /**
@@ -170,9 +204,10 @@ namespace vkcv::camera {
         void setPitch(float pitch);
 
         /**
-         * @brief Gets the yaw value of the camera in degrees.
+         * @brief Returns the yaw value of the camera in degrees.
          * @return The yaw value in degrees.
          */
+		[[nodiscard]]
         float getYaw() const;
 
         /**
@@ -182,9 +217,10 @@ namespace vkcv::camera {
         void setYaw(float yaw);
 
         /**
-         * @brief Gets the up vector.
+         * @brief Returns the up vector.
          * @return The up vector.
          */
+		[[nodiscard]]
         const glm::vec3& getUp() const;
 
         /**
@@ -192,6 +228,9 @@ namespace vkcv::camera {
          * @param[in] up The new up vector.
          */
         void setUp(const glm::vec3 &up);
+		
     };
 
+    /** @} */
+
 }
diff --git a/modules/camera/include/vkcv/camera/CameraController.hpp b/modules/camera/include/vkcv/camera/CameraController.hpp
index 90fc97401851851194ec89a10757bbfb1453990d..0b5a17faf178a01f6e1460fb5b86bff901576c68 100644
--- a/modules/camera/include/vkcv/camera/CameraController.hpp
+++ b/modules/camera/include/vkcv/camera/CameraController.hpp
@@ -1,16 +1,27 @@
 #pragma once
+/**
+ * @authors Vanessa Karolek, Josch Morgenstern, Tobias Frisch
+ * @file include/vkcv/camera/CameraController.hpp
+ * @brief CameraController class of the camera module for the vkcv framework. A camera object is controlled by a camera
+ * controller object. Using inheritance of this base class a camera controller object defines a specific control
+ * behaviour of the camera object in the scene.
+ */
 
 #include "Camera.hpp"
 #include "vkcv/Window.hpp"
 
 namespace vkcv::camera {
 
+    /**
+     * @addtogroup vkcv_camera
+     * @{
+     */
+
     /**
      * @brief Used as a base class for defining camera controller classes with different behaviors, e.g. the
-     * #PilotCameraController.
+     * PilotCameraController.
      */
     class CameraController {
-
     public:
 
         /**
@@ -69,4 +80,6 @@ namespace vkcv::camera {
         virtual void gamepadCallback(int gamepadIndex, Camera &camera, double frametime) = 0;
     };
 
+    /** @} */
+
 }
\ No newline at end of file
diff --git a/modules/camera/include/vkcv/camera/CameraHandle.hpp b/modules/camera/include/vkcv/camera/CameraHandle.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..b681f93cb0e753af8d383f890ccc263ed650f998
--- /dev/null
+++ b/modules/camera/include/vkcv/camera/CameraHandle.hpp
@@ -0,0 +1,31 @@
+#pragma once
+/**
+ * @authors Tobias Frisch
+ * @file include/vkcv/camera/CameraHandle.hpp
+ * @brief CameraHandle class of the camera module for the vkcv framework.
+ */
+
+#include <vkcv/Handles.hpp>
+
+namespace vkcv::camera {
+	
+	/**
+     * @addtogroup vkcv_camera
+     * @{
+     */
+	
+	class CameraManager;
+	
+	/**
+	 * @brief Handle class for cameras.
+	 */
+	class CameraHandle : public Handle {
+		friend class CameraManager;
+	
+	private:
+		using Handle::Handle;
+	};
+	
+	/** @} */
+	
+}
\ No newline at end of file
diff --git a/modules/camera/include/vkcv/camera/CameraManager.hpp b/modules/camera/include/vkcv/camera/CameraManager.hpp
index aa8880e4040a93d8d04a73f1c1f972033a4e8eab..01b11eedcffcbbf588fa93c48f4762e4f4adbdc6 100644
--- a/modules/camera/include/vkcv/camera/CameraManager.hpp
+++ b/modules/camera/include/vkcv/camera/CameraManager.hpp
@@ -1,23 +1,28 @@
 #pragma once
-
+/**
+ * @authors Vanessa Karolek, Josch Morgenstern, Sebastian Gaida, Katharina Krämer, Tobias Frisch, Alexander Gauggel
+ * @file include/vkcv/camera/CameraManager.hpp
+ * @brief CameraManager class of the camera module for the vkcv framework. The camera manager manages several camera
+ * controller objects. Camera objects can be created and bound to a specific camera controller via this class.
+ */
+
+#include "CameraHandle.hpp"
+#include "ControllerType.hpp"
 #include "PilotCameraController.hpp"
 #include "TrackballCameraController.hpp"
 #include "CameraController.hpp"
+
 #include "vkcv/Window.hpp"
+
 #include <GLFW/glfw3.h>
 #include <functional>
 
 namespace vkcv::camera {
 
     /**
-     * @brief Used for specifying existing types of camera controllers when adding a new controller object to the
-     * #CameraManager.
+     * @addtogroup vkcv_camera
+     * @{
      */
-    enum class ControllerType {
-        NONE,
-        PILOT,
-        TRACKBALL,
-    };
 
     /**
      * @brief Used for managing an arbitrary amount of camera controllers.
@@ -34,7 +39,7 @@ namespace vkcv::camera {
         Window& m_window;
         std::vector<Camera> m_cameras;
         std::vector<ControllerType> m_cameraControllerTypes;
-        uint32_t m_activeCameraIndex;
+        uint64_t m_activeCameraIndex;
 
         PilotCameraController m_pilotController;
         TrackballCameraController m_trackController;
@@ -98,26 +103,11 @@ namespace vkcv::camera {
         void gamepadCallback(int gamepadIndex);
 	
 		/**
-		 * @brief Gets a camera controller object of specified @p controllerType.
+		 * @brief Returns a camera controller object of specified @p controllerType.
 		 * @param[in] controllerType The type of the camera controller.
 		 * @return The specified camera controller object.
 		 */
 		CameraController& getControllerByType(ControllerType controllerType);
-        
-        /**
-         * @briof A method to get the currently active controller for the active camera.
-         * @return Reference to the active #CameraController
-         */
-        CameraController& getActiveController();
-
-        /**
-         * @brief Returns 'true' if the camera has a controller.
-         * 
-         * @param cameraIndex 
-         * @return true 
-         * @return false 
-         */
-        bool cameraHasController(uint32_t cameraIndex);
 
     public:
 
@@ -125,7 +115,7 @@ namespace vkcv::camera {
          * @brief The constructor of the #CameraManager.
          * @param[in] window The window.
          */
-        CameraManager(Window &window);
+        explicit CameraManager(Window &window);
 
         /**
          * @brief The destructor of the #CameraManager. Destroying the #CameraManager leads to deletion of all stored
@@ -137,67 +127,74 @@ namespace vkcv::camera {
          * @brief Adds a new camera object to the #CameraManager and binds it to a camera controller object of specified
          * @p controllerType.
          * @param[in] controllerType The type of the camera controller.
-         * @return The index of the newly created camera object.
+         * @return The handle of the newly created camera object.
          */
-		uint32_t addCamera(ControllerType controllerType = ControllerType::NONE);
+		CameraHandle addCamera(ControllerType controllerType = ControllerType::NONE);
 	
 		/**
 		 * @brief Adds a new camera object to the #CameraManager and binds it to a camera controller object of specified
 		 * @p controllerType.
 		 * @param[in] controllerType The type of the camera controller.
 		 * @param[in] camera The new camera object.
-		 * @return The index of the newly bound camera object.
+		 * @return The handle of the newly bound camera object.
 		 */
-		uint32_t addCamera(ControllerType controllerType, const Camera& camera);
+		CameraHandle addCamera(ControllerType controllerType, const Camera& camera);
 
         /**
-         * @brief Gets the stored camera object located at @p cameraIndex.
-         * @param[in] cameraIndex The camera index.
-         * @return The camera object at @p cameraIndex.
-         * @throws std::runtime_error If @p cameraIndex is not a valid camera index.
+         * @brief Returns the stored camera object located by @p cameraHandle.
+         * @param[in] cameraHandle The camera handle.
+         * @return The camera object by @p cameraHandle.
+         * @throws std::runtime_error If @p cameraHandle is not a valid camera handle.
          */
-        Camera& getCamera(uint32_t cameraIndex);
+		[[nodiscard]]
+        Camera& getCamera(const CameraHandle& cameraHandle);
 
         /**
-         * @brief Gets the stored camera object set as the active camera.
+         * @brief Returns the stored camera object set as the active camera.
          * @return The active camera.
          */
+		[[nodiscard]]
         Camera& getActiveCamera();
 
         /**
-         * @brief Sets the stored camera object located at @p cameraIndex as the active camera.
-         * @param[in] cameraIndex The camera index.
-         * @throws std::runtime_error If @p cameraIndex is not a valid camera index.
+         * @brief Sets the stored camera object located at @p cameraHandle as the active camera.
+         * @param[in] cameraHandle The camera handle.
+         * @throws std::runtime_error If @p cameraHandle is not a valid camera handle.
          */
-        void setActiveCamera(uint32_t cameraIndex);
+        void setActiveCamera(const CameraHandle& cameraHandle);
 
         /**
-         * @brief Gets the index of the stored active camera object.
-         * @return The active camera index.
+         * @brief Returns the handle of the stored active camera object.
+         * @return The active camera handle.
          */
-        uint32_t getActiveCameraIndex() const;
+		[[nodiscard]]
+		CameraHandle getActiveCameraHandle() const;
 
         /**
-         * @brief Binds a stored camera object located at @p cameraIndex to a camera controller of specified
+         * @brief Binds a stored camera object located by @p cameraHandle to a camera controller of specified
          * @p controllerType.
-         * @param[in] cameraIndex The camera index.
+         * @param[in] cameraHandle The camera handle.
          * @param[in] controllerType The type of the camera controller.
-         * @throws std::runtime_error If @p cameraIndex is not a valid camera index.
+         * @throws std::runtime_error If @p cameraHandle is not a valid camera handle.
          */
-        void setControllerType(uint32_t cameraIndex, ControllerType controllerType);
+        void setControllerType(const CameraHandle& cameraHandle, ControllerType controllerType);
 
         /**
-         * @brief Gets the currently bound camera controller type of the stored camera object located at @p cameraIndex.
-         * @param[in] cameraIndex The camera index.
+         * @brief Returns the currently bound camera controller type of the stored camera object located by @p cameraHandle.
+         * @param[in] cameraHandle The camera handle.
          * @return The type of the camera controller of the specified camera object.
-         * @throws std::runtime_error If @p cameraIndex is not a valid camera index.
+         * @throws std::runtime_error If @p cameraHandle is not a valid camera handle.
          */
-        ControllerType getControllerType(uint32_t cameraIndex);
-        
+		[[nodiscard]]
+        ControllerType getControllerType(const CameraHandle& cameraHandle);
+
         /**
          * @brief Updates all stored camera controllers in respect to @p deltaTime.
          * @param[in] deltaTime The time that has passed since last update.
          */
         void update(double deltaTime);
     };
+
+    /** @} */
+
 }
diff --git a/modules/camera/include/vkcv/camera/ControllerType.hpp b/modules/camera/include/vkcv/camera/ControllerType.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..3e85c1f9749ced9680030e95d08a1c6bc39aafb3
--- /dev/null
+++ b/modules/camera/include/vkcv/camera/ControllerType.hpp
@@ -0,0 +1,27 @@
+#pragma once
+/**
+ * @authors Tobias Frisch
+ * @file include/vkcv/camera/ControllerType.hpp
+ * @brief ControllerType enum of the camera module for the vkcv framework.
+ */
+
+namespace vkcv::camera {
+	
+	/**
+     * @addtogroup vkcv_camera
+     * @{
+     */
+	
+	/**
+     * @brief Used for specifying existing types of camera controllers when adding a new controller object to the
+     * #CameraManager.
+     */
+	enum class ControllerType {
+		NONE,
+		PILOT,
+		TRACKBALL
+	};
+	
+	/** @} */
+	
+}
diff --git a/modules/camera/include/vkcv/camera/PilotCameraController.hpp b/modules/camera/include/vkcv/camera/PilotCameraController.hpp
index 2b64cdc0dd3045714aba7b3b7c6241af2337c706..40ccd56bd3033b7e831bfc1f8d24b5a646eb0efd 100644
--- a/modules/camera/include/vkcv/camera/PilotCameraController.hpp
+++ b/modules/camera/include/vkcv/camera/PilotCameraController.hpp
@@ -1,9 +1,20 @@
 #pragma once
+/**
+ * @authors Vanessa Karolek, Josch Morgenstern, Tobias Frisch
+ * @file include/vkcv/camera/PilotCameraController.hpp
+ * @brief PilotCameraController class of the camera module for the vkcv framework. This class inherits from the base
+ * class @#CameraController and enables camera objects to be moved freely within the scene.
+ */
 
 #include <vkcv/camera/CameraController.hpp>
 
 namespace vkcv::camera {
 
+    /**
+     * @addtogroup vkcv_camera
+     * @{
+     */
+
     /**
      * @brief Used to move around a camera object in world space.
      */
@@ -29,42 +40,6 @@ namespace vkcv::camera {
         float m_fov_min;
         float m_fov_max;
 
-        /**
-         * @brief Indicates forward movement of the camera depending on the performed @p action.
-         * @param[in] action The performed action.
-         */
-        void moveForward(int action);
-
-        /**
-         * @brief Indicates backward movement of the camera depending on the performed @p action.
-         * @param[in] action The performed action.
-         */
-        void moveBackward(int action);
-
-        /**
-         * @brief Indicates left movement of the camera depending on the performed @p action.
-         * @param[in] action The performed action.
-         */
-        void moveLeft(int action);
-
-        /**
-         * @brief Indicates right movement of the camera depending on the performed @p action.
-         * @param[in] action The performed action.
-         */
-        void moveRight(int action);
-
-        /**
-         * @brief Indicates upward movement of the camera depending on the performed @p action.
-         * @param[in] action The performed action.
-         */
-        void moveUpward(int action);
-
-        /**
-         * @brief Indicates downward movement of the camera depending on the performed @p action.
-         * @param[in] action The performed action.
-         */
-        void moveDownward(int action);
-
     public:
 
         /**
@@ -98,7 +73,7 @@ namespace vkcv::camera {
          * @param[in] deltaTime The time that has passed since last update.
          * @param[in] camera The camera object.
          */
-        void updateCamera(double deltaTime, Camera &camera);
+        void updateCamera(double deltaTime, Camera &camera) override;
 
         /**
          * @brief A callback function for key events. Currently, 3D camera movement via W, A, S, D, E, Q are supported.
@@ -108,7 +83,7 @@ namespace vkcv::camera {
          * @param[in] mods The modifier bits.
          * @param[in] camera The camera object.
          */
-        void keyCallback(int key, int scancode, int action, int mods, Camera &camera);
+        void keyCallback(int key, int scancode, int action, int mods, Camera &camera) override;
 
         /**
          * @brief A callback function for mouse scrolling events. Currently, this leads to changes in the field of view
@@ -117,7 +92,7 @@ namespace vkcv::camera {
          * @param[in] offsetY The offset in vertical direction.
          * @param[in] camera The camera object.
          */
-        void scrollCallback(double offsetX, double offsetY, Camera &camera);
+        void scrollCallback(double offsetX, double offsetY, Camera &camera) override;
 
         /**
          * @brief A callback function for mouse movement events. Currently, this leads to panning the view of the camera,
@@ -126,7 +101,7 @@ namespace vkcv::camera {
          * @param[in] y The vertical mouse position
          * @param[in] camera The camera object.
          */
-        void mouseMoveCallback(double x, double y, Camera &camera);
+        void mouseMoveCallback(double x, double y, Camera &camera) override;
 
         /**
          * @brief A callback function for mouse button events. Currently, the right mouse button enables panning the
@@ -136,7 +111,7 @@ namespace vkcv::camera {
          * @param[in] mods The modifier bits
          * @param[in] camera The camera object.
          */
-        void mouseButtonCallback(int button, int action, int mods, Camera &camera);
+        void mouseButtonCallback(int button, int action, int mods, Camera &camera) override;
 
         /**
          * @brief A callback function for gamepad input events.
@@ -144,7 +119,9 @@ namespace vkcv::camera {
          * @param camera The camera object.
          * @param frametime The current frametime.
          */
-        void gamepadCallback(int gamepadIndex, Camera &camera, double frametime);
+        void gamepadCallback(int gamepadIndex, Camera &camera, double frametime) override;
     };
 
+    /** @} */
+
 }
\ No newline at end of file
diff --git a/modules/camera/include/vkcv/camera/TrackballCameraController.hpp b/modules/camera/include/vkcv/camera/TrackballCameraController.hpp
index 4166bda9f6cb62e4c8f1b650557b00c6ec94b2a1..5df56fd839f1c654541baaa75bd3d9553cdedd62 100644
--- a/modules/camera/include/vkcv/camera/TrackballCameraController.hpp
+++ b/modules/camera/include/vkcv/camera/TrackballCameraController.hpp
@@ -1,19 +1,30 @@
 #pragma once
+/**
+ * @authors Vanessa Karolek, Josch Morgenstern, Sebastian Gaida,Tobias Frisch
+ * @file include/vkcv/camera/TrackballCameraController.hpp
+ * @brief TrackballCameraController class of the camera module for the vkcv framework. This class inherits from the base
+ * class @#CameraController and enables camera objects to be orbited around a specific center point.
+ */
 
 #include "CameraController.hpp"
 
 namespace vkcv::camera {
 
+    /**
+     * @addtogroup vkcv_camera
+     * @{
+     */
+
     /**
      * @brief Used to orbit a camera around its center point.
      */
     class TrackballCameraController final : public CameraController {
     private:
         bool m_rotationActive;
-
         float m_cameraSpeed;
         float m_scrollSensitivity;
-        float m_radius;
+        float m_pitch;
+        float m_yaw;
 
         /**
          * @brief Updates the current radius of @p camera in respect to the @p offset.
@@ -34,12 +45,6 @@ namespace vkcv::camera {
          */
         ~TrackballCameraController() = default;
 
-        /**
-         * @brief Sets @p radius as the new radius for orbiting around the camera's center point.
-         * @param[in] radius The new radius.
-         */
-        void setRadius(const float radius);
-
         /**
          * @brief Pans the view of @p camera according to the pitch and yaw values and additional offsets @p xOffset
          * and @p yOffset.
@@ -54,7 +59,7 @@ namespace vkcv::camera {
          * @param[in] deltaTime The time that has passed since last update.
          * @param[in] camera The camera object
          */
-        void updateCamera(double deltaTime, Camera &camera);
+        void updateCamera(double deltaTime, Camera &camera) override;
 
         /**
          * @brief A callback function for key events. Currently, the trackball camera does not support camera movement.
@@ -65,7 +70,7 @@ namespace vkcv::camera {
          * @param[in] mods The modifier bits.
          * @param[in] camera The camera object.
          */
-        void keyCallback(int key, int scancode, int action, int mods, Camera &camera);
+        void keyCallback(int key, int scancode, int action, int mods, Camera &camera) override;
 
         /**
          * @brief A callback function for mouse scrolling events. Currently, this leads to changes in the field of view
@@ -74,7 +79,7 @@ namespace vkcv::camera {
          * @param[in] offsetY The offset in vertical direction.
          * @param[in] camera The camera object.
          */
-        void scrollCallback(double offsetX, double offsetY, Camera &camera);
+        void scrollCallback(double offsetX, double offsetY, Camera &camera) override;
 
         /**
          * @brief A callback function for mouse movement events. Currently, this leads to panning the view of the
@@ -83,7 +88,7 @@ namespace vkcv::camera {
          * @param[in] yoffset The vertical mouse position.
          * @param[in] camera The camera object.
          */
-        void mouseMoveCallback(double xoffset, double yoffset, Camera &camera);
+        void mouseMoveCallback(double xoffset, double yoffset, Camera &camera) override;
 
         /**
          * @brief A callback function for mouse button events. Currently, the right mouse button enables panning the
@@ -93,7 +98,7 @@ namespace vkcv::camera {
          * @param[in] mods The modifier bits.
          * @param[in] camera The camera object.
          */
-        void mouseButtonCallback(int button, int action, int mods, Camera &camera);
+        void mouseButtonCallback(int button, int action, int mods, Camera &camera) override;
 
         /**
          * @brief A callback function for gamepad input events.
@@ -101,7 +106,9 @@ namespace vkcv::camera {
          * @param camera The camera object.
          * @param frametime The current frametime.
          */
-        void gamepadCallback(int gamepadIndex, Camera &camera, double frametime);
+        void gamepadCallback(int gamepadIndex, Camera &camera, double frametime) override;
     };
 
+    /** @} */
+
 }
\ No newline at end of file
diff --git a/modules/camera/lib/glm b/modules/camera/lib/glm
deleted file mode 160000
index 66062497b104ca7c297321bd0e970869b1e6ece5..0000000000000000000000000000000000000000
--- a/modules/camera/lib/glm
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 66062497b104ca7c297321bd0e970869b1e6ece5
diff --git a/modules/camera/src/vkcv/camera/Camera.cpp b/modules/camera/src/vkcv/camera/Camera.cpp
index 18bf94463a0e2c4cb7d64526f4c30835cb451eb2..4b3ae9274733a070463f553bd79183789d0701b6 100644
--- a/modules/camera/src/vkcv/camera/Camera.cpp
+++ b/modules/camera/src/vkcv/camera/Camera.cpp
@@ -1,6 +1,6 @@
 #include "vkcv/camera/Camera.hpp"
 
-#include <math.h>
+#include <cmath>
 
 namespace vkcv::camera {
 
@@ -10,8 +10,6 @@ namespace vkcv::camera {
 			glm::vec3(0.0f, 0.0f, 0.0f),
 			glm::vec3(0.0f, 1.0f, 0.0f)
 		);
-  
-		setFront(glm::normalize(m_center - m_position));
     }
 
     Camera::~Camera() = default;
@@ -37,27 +35,27 @@ namespace vkcv::camera {
 		m_view = view;
 	}
 
+    const glm::mat4 y_correction(
+        1.0f, 0.0f, 0.0f, 0.0f,
+        0.0f, -1.0f, 0.0f, 0.0f,
+        0.0f, 0.0f, 1.0f, 0.0f,
+        0.0f, 0.0f, 0.0f, 1.0f
+    );
+
     const glm::mat4& Camera::getProjection() const {
         return m_projection;
     }
 
     void Camera::setProjection(const glm::mat4& projection) {
-        m_projection =  projection;
+        m_projection = y_correction * projection;
     }
 
     glm::mat4 Camera::getMVP() const {
-		const glm::mat4 y_correction (
-				1.0f,  0.0f,  0.0f,  0.0f,
-				0.0f, -1.0f,  0.0f,  0.0f,
-				0.0f,  0.0f,  1.0f,  0.0f,
-				0.0f,  0.0f,  0.0f,  1.0f
-		);
-    	
-        return y_correction * m_projection * m_view;
+        return m_projection * m_view;
     }
 
     float Camera::getFov() const {
-    	const float tanHalfFovy = 1.0f / m_projection[1][1];
+    	const float tanHalfFovy = -1.0f / m_projection[1][1];
     	float halfFovy = std::atan(tanHalfFovy);
     	
     	if (halfFovy < 0) {
@@ -73,7 +71,7 @@ namespace vkcv::camera {
 
     float Camera::getRatio() const {
     	const float aspectProduct = 1.0f / m_projection[0][0];
-		const float tanHalfFovy = 1.0f / m_projection[1][1];
+		const float tanHalfFovy = -1.0f / m_projection[1][1];
 		
         return aspectProduct / tanHalfFovy;
     }
@@ -93,16 +91,11 @@ namespace vkcv::camera {
     }
 
     glm::vec3 Camera::getFront() const {
-        glm::vec3 direction;
-        direction.x = std::sin(glm::radians(m_yaw)) * std::cos(glm::radians(m_pitch));
-        direction.y = std::sin(glm::radians(m_pitch));
-        direction.z = std::cos(glm::radians(m_yaw)) * std::cos(glm::radians(m_pitch));
-        return glm::normalize(direction);
+        return glm::normalize(m_center - m_position);
     }
     
     void Camera::setFront(const glm::vec3 &front) {
-		m_pitch = std::atan2(front.y, std::sqrt(front.x * front.x + front.z * front.z));
-		m_yaw = std::atan2(front.x, front.z);
+		setCenter(m_position + front);
     }
 
     const glm::vec3& Camera::getPosition() const {
@@ -128,21 +121,47 @@ namespace vkcv::camera {
 	void Camera::setUp(const glm::vec3 &up) {
 		lookAt(m_position, m_center, up);
 	}
-
-    float Camera::getPitch() const {
-        return m_pitch;
+	
+	void Camera::getAngles(float& pitch, float& yaw) {
+		const auto front = getFront();
+		
+		pitch = std::atan2(front[1], std::sqrt(
+				front[0] * front[0] + front[2] * front[2]
+		));
+		
+		yaw = std::atan2(front[0], front[2]);
+	}
+	
+	void Camera::setAngles(float pitch, float yaw) {
+		float cosPitch = std::cos(pitch);
+		
+		setFront(glm::vec3(
+				std::sin(yaw) * cosPitch,
+				std::sin(pitch),
+				std::cos(yaw) * cosPitch
+		));
+	}
+	
+	float Camera::getPitch() const {
+    	const auto front = getFront();
+    	
+        return glm::degrees(std::atan2(front[1], std::sqrt(
+        		front[0] * front[0] + front[2] * front[2]
+		)));
     }
 
     void Camera::setPitch(float pitch) {
-        m_pitch = pitch;
+		setAngles(glm::radians(pitch), glm::radians(getYaw()));
     }
 
     float Camera::getYaw() const {
-        return m_yaw;
+		const auto front = getFront();
+	
+		return glm::degrees(std::atan2(front[0], front[2]));
     }
 
     void Camera::setYaw(float yaw) {
-        m_yaw = yaw;
+		setAngles(glm::radians(getPitch()), glm::radians(yaw));
     }
 
-}
\ No newline at end of file
+}
diff --git a/modules/camera/src/vkcv/camera/CameraManager.cpp b/modules/camera/src/vkcv/camera/CameraManager.cpp
index c0525d849095f07b6a6988e26c21dc32674f08f0..ad5de753a17e7f207cd3e538cb3b96a6c2753083 100644
--- a/modules/camera/src/vkcv/camera/CameraManager.cpp
+++ b/modules/camera/src/vkcv/camera/CameraManager.cpp
@@ -35,59 +35,84 @@ namespace vkcv::camera {
 
     void CameraManager::resizeCallback(int width, int height) {
         if (glfwGetWindowAttrib(m_window.getWindow(), GLFW_ICONIFIED) == GLFW_FALSE) {
-            for (size_t i = 0; i < m_cameras.size(); i++) {
-                getCamera(i).setRatio(static_cast<float>(width) / static_cast<float>(height));;
+            for (auto& camera : m_cameras) {
+				camera.setRatio(static_cast<float>(width) / static_cast<float>(height));;
             }
         }
     }
 
-    void CameraManager::mouseButtonCallback(int button, int action, int mods){
-        if(button == GLFW_MOUSE_BUTTON_2 && action == GLFW_PRESS){
+    void CameraManager::mouseButtonCallback(int button, int action, int mods) {
+		const ControllerType type = getControllerType(getActiveCameraHandle());
+		
+        if ((button == GLFW_MOUSE_BUTTON_2) && (action == GLFW_PRESS)) {
             glfwSetInputMode(m_window.getWindow(), GLFW_CURSOR, GLFW_CURSOR_DISABLED);
-        }
-        else if(button == GLFW_MOUSE_BUTTON_2 && action == GLFW_RELEASE){
+        } else
+		if ((button == GLFW_MOUSE_BUTTON_2) && (action == GLFW_RELEASE)) {
             glfwSetInputMode(m_window.getWindow(), GLFW_CURSOR, GLFW_CURSOR_NORMAL);
         }
-		getActiveController().mouseButtonCallback(button, action, mods, getActiveCamera());
+	
+		if (type == ControllerType::NONE) {
+			return;
+		}
+		
+		getControllerByType(type).mouseButtonCallback(button, action, mods, getActiveCamera());
     }
 
-    void CameraManager::mouseMoveCallback(double x, double y){
-        auto xoffset = static_cast<float>(x - m_lastX);
-		auto yoffset = static_cast<float>(y - m_lastY);
+    void CameraManager::mouseMoveCallback(double x, double y) {
+		const ControllerType type = getControllerType(getActiveCameraHandle());
+		
+        auto xoffset = static_cast<float>(x - m_lastX) / m_window.getWidth();
+		auto yoffset = static_cast<float>(y - m_lastY) / m_window.getHeight();
         m_lastX = x;
         m_lastY = y;
-		getActiveController().mouseMoveCallback(xoffset, yoffset, getActiveCamera());
+	
+		if (type == ControllerType::NONE) {
+			return;
+		}
+		
+		getControllerByType(type).mouseMoveCallback(xoffset, yoffset, getActiveCamera());
     }
 
     void CameraManager::scrollCallback(double offsetX, double offsetY) {
-		getActiveController().scrollCallback(offsetX, offsetY, getActiveCamera());
+		const ControllerType type = getControllerType(getActiveCameraHandle());
+	
+		if (type == ControllerType::NONE) {
+			return;
+		}
+		
+		getControllerByType(type).scrollCallback(offsetX, offsetY, getActiveCamera());
     }
 
     void CameraManager::keyCallback(int key, int scancode, int action, int mods)  {
-        switch (action) {
-            case GLFW_RELEASE:
-                switch (key) {
-                    case GLFW_KEY_TAB:
-                        if (m_activeCameraIndex + 1 == m_cameras.size()) {
-                            m_activeCameraIndex = 0;
-                        }
-                        else {
-                            m_activeCameraIndex++;
-                        }
-                        return;
-                    case GLFW_KEY_ESCAPE:
-                        glfwSetWindowShouldClose(m_window.getWindow(), 1);
-                        return;
-					default:
-						break;
-                }
-            default:
-				getActiveController().keyCallback(key, scancode, action, mods, getActiveCamera());
-                break;
+		const ControllerType type = getControllerType(getActiveCameraHandle());
+		
+        if (action == GLFW_RELEASE) {
+			switch (key) {
+				case GLFW_KEY_TAB:
+					if (m_activeCameraIndex + 1 == m_cameras.size()) {
+						m_activeCameraIndex = 0;
+					} else {
+						m_activeCameraIndex++;
+					}
+					return;
+				case GLFW_KEY_ESCAPE:
+					glfwSetWindowShouldClose(m_window.getWindow(), 1);
+					return;
+				default:
+					break;
+			}
         }
+		
+		if (type == ControllerType::NONE) {
+			return;
+		}
+	
+		getControllerByType(type).keyCallback(key, scancode, action, mods, getActiveCamera());
     }
 
     void CameraManager::gamepadCallback(int gamepadIndex) {
+		const ControllerType type = getControllerType(getActiveCameraHandle());
+		
         // handle camera switching
         GLFWgamepadstate gamepadState;
         glfwGetGamepadState(gamepadIndex, &gamepadState);
@@ -96,81 +121,82 @@ namespace vkcv::camera {
         if (time - m_inputDelayTimer > 0.2) {
             int switchDirection = gamepadState.buttons[GLFW_GAMEPAD_BUTTON_DPAD_RIGHT] - gamepadState.buttons[GLFW_GAMEPAD_BUTTON_DPAD_LEFT];
             m_activeCameraIndex += switchDirection;
-            if (std::greater<int>{}(m_activeCameraIndex, m_cameras.size() - 1)) {
+            
+			if (std::greater<int>{}(m_activeCameraIndex, m_cameras.size() - 1)) {
                 m_activeCameraIndex = 0;
-            }
-            else if (std::less<int>{}(m_activeCameraIndex, 0)) {
+            } else
+			if (std::less<int>{}(m_activeCameraIndex, 0)) {
                 m_activeCameraIndex = m_cameras.size() - 1;
             }
+			
             uint32_t triggered = abs(switchDirection);
             m_inputDelayTimer = (1-triggered)*m_inputDelayTimer + triggered * time; // Only reset timer, if dpad was pressed - is this cheaper than if-clause?
         }
-
-        getActiveController().gamepadCallback(gamepadIndex, getActiveCamera(), m_frameTime);     // handle camera rotation, translation
-    }
-
-    CameraController& CameraManager::getActiveController() {
-    	const ControllerType type = getControllerType(getActiveCameraIndex());
-    	return getControllerByType(type);
+		
+		if (type == ControllerType::NONE) {
+			return;
+		}
+	
+		getControllerByType(type).gamepadCallback(gamepadIndex, getActiveCamera(), m_frameTime);     // handle camera rotation, translation
     }
 	
-	uint32_t CameraManager::addCamera(ControllerType controllerType) {
+	CameraHandle CameraManager::addCamera(ControllerType controllerType) {
     	const float ratio = static_cast<float>(m_window.getWidth()) / static_cast<float>(m_window.getHeight());
     	
         Camera camera;
         camera.setPerspective(glm::radians(60.0f), ratio, 0.1f, 10.0f);
         return addCamera(controllerType, camera);
     }
-    
-    uint32_t CameraManager::addCamera(ControllerType controllerType, const Camera &camera) {
+	
+	CameraHandle CameraManager::addCamera(ControllerType controllerType, const Camera &camera) {
     	const uint32_t index = static_cast<uint32_t>(m_cameras.size());
     	m_cameras.push_back(camera);
 		m_cameraControllerTypes.push_back(controllerType);
-		return index;
+		return CameraHandle(index);
     }
 
-    Camera& CameraManager::getCamera(uint32_t cameraIndex) {
-        if (cameraIndex < 0 || cameraIndex > m_cameras.size() - 1) {
+    Camera& CameraManager::getCamera(const CameraHandle& cameraHandle) {
+        if (cameraHandle.getId() < 0 || cameraHandle.getId() >= m_cameras.size()) {
         	vkcv_log(LogLevel::ERROR, "Invalid camera index: The index must range from 0 to %lu", m_cameras.size());
         	return getActiveCamera();
         }
         
-        return m_cameras[cameraIndex];
+        return m_cameras[cameraHandle.getId()];
     }
 
     Camera& CameraManager::getActiveCamera() {
-        return m_cameras[getActiveCameraIndex()];
+        return m_cameras[m_activeCameraIndex];
     }
 
-    void CameraManager::setActiveCamera(uint32_t cameraIndex) {
-        if (cameraIndex < 0 || cameraIndex > m_cameras.size() - 1) {
+    void CameraManager::setActiveCamera(const CameraHandle& cameraHandle) {
+        if (cameraHandle.getId() < 0 || cameraHandle.getId() >= m_cameras.size()) {
 			vkcv_log(LogLevel::ERROR, "Invalid camera index: The index must range from 0 to %lu", m_cameras.size());
 			return;
         }
         
-        m_activeCameraIndex = cameraIndex;
+        m_activeCameraIndex = cameraHandle.getId();
     }
 
-    uint32_t CameraManager::getActiveCameraIndex() const {
-        return m_activeCameraIndex;
+    CameraHandle CameraManager::getActiveCameraHandle() const {
+        return CameraHandle(m_activeCameraIndex);
     }
 
-    void CameraManager::setControllerType(uint32_t cameraIndex, ControllerType controllerType) {
-        if (cameraIndex < 0 || cameraIndex > m_cameras.size() - 1) {
+    void CameraManager::setControllerType(const CameraHandle& cameraHandle, ControllerType controllerType) {
+        if (cameraHandle.getId() < 0 || cameraHandle.getId() >= m_cameras.size()) {
 			vkcv_log(LogLevel::ERROR, "Invalid camera index: The index must range from 0 to %lu", m_cameras.size());
 			return;
         }
         
-        m_cameraControllerTypes[cameraIndex] = controllerType;
+        m_cameraControllerTypes[cameraHandle.getId()] = controllerType;
     }
 
-    ControllerType CameraManager::getControllerType(uint32_t cameraIndex) {
-        if (cameraIndex < 0 || cameraIndex > m_cameras.size() - 1) {
+    ControllerType CameraManager::getControllerType(const CameraHandle& cameraHandle) {
+        if (cameraHandle.getId() < 0 || cameraHandle.getId() >= m_cameras.size()) {
 			vkcv_log(LogLevel::ERROR, "Invalid camera index: The index must range from 0 to %lu", m_cameras.size());
 			return ControllerType::NONE;
         }
         
-        return m_cameraControllerTypes[cameraIndex];
+        return m_cameraControllerTypes[cameraHandle.getId()];
     }
 
     CameraController& CameraManager::getControllerByType(ControllerType controllerType) {
@@ -184,14 +210,18 @@ namespace vkcv::camera {
         }
     }
 
-    bool CameraManager::cameraHasController(uint32_t cameraIndex) {
-        return (m_cameraControllerTypes[cameraIndex] != ControllerType::NONE);
-    }
-
     void CameraManager::update(double deltaTime) {
-        m_frameTime = deltaTime;
-        if ((glfwGetWindowAttrib(m_window.getWindow(), GLFW_FOCUSED) == GLFW_TRUE) && cameraHasController(getActiveCameraIndex())) {
-            getActiveController().updateCamera(deltaTime, getActiveCamera());
+		const ControllerType type = getControllerType(getActiveCameraHandle());
+		
+		if (type != ControllerType::NONE) {
+			m_frameTime = deltaTime;
+		} else {
+			m_frameTime = 0.0;
+			return;
+		}
+		
+        if (glfwGetWindowAttrib(m_window.getWindow(), GLFW_FOCUSED) == GLFW_TRUE) {
+			getControllerByType(type).updateCamera(m_frameTime, getActiveCamera());
         }
 	}
 	
diff --git a/modules/camera/src/vkcv/camera/PilotCameraController.cpp b/modules/camera/src/vkcv/camera/PilotCameraController.cpp
index 5460858ab48d81252787b3c0141dd72982faca7d..37a79fe1012d6b81f12e50dba0e0edb6b76390e7 100644
--- a/modules/camera/src/vkcv/camera/PilotCameraController.cpp
+++ b/modules/camera/src/vkcv/camera/PilotCameraController.cpp
@@ -50,12 +50,11 @@ namespace vkcv::camera {
         }
 
         // handle yaw rotation
-        float yaw = camera.getYaw() + static_cast<float>(xOffset);
-        yaw += 360.0f * (yaw < -180.0f) - 360.0f * (yaw > 180.0f);
+        float yaw = camera.getYaw() + static_cast<float>(xOffset) * 90.0f * m_cameraSpeed;
         camera.setYaw(yaw);
 
         // handle pitch rotation
-        float pitch = camera.getPitch() - static_cast<float>(yOffset);
+        float pitch = camera.getPitch() - static_cast<float>(yOffset) * 90.0f * m_cameraSpeed;
         pitch = glm::clamp(pitch, -89.0f, 89.0f);
         camera.setPitch(pitch);
     }
@@ -83,22 +82,22 @@ namespace vkcv::camera {
     void PilotCameraController::keyCallback(int key, int scancode, int action, int mods, Camera &camera) {
         switch (key) {
             case GLFW_KEY_W:
-                moveForward(action);
+            	m_forward = static_cast<bool>(action);
                 break;
             case GLFW_KEY_S:
-                moveBackward(action);
+            	m_backward = static_cast<bool>(action);
                 break;
             case GLFW_KEY_A:
-                moveLeft(action);
+            	m_left = static_cast<bool>(action);
                 break;
             case GLFW_KEY_D:
-                moveRight(action);
+            	m_right = static_cast<bool>(action);
                 break;
             case GLFW_KEY_E:
-                moveUpward(action);
+            	m_upward = static_cast<bool>(action);
                 break;
             case GLFW_KEY_Q:
-                moveDownward(action);
+            	m_downward = static_cast<bool>(action);
                 break;
             default:
                 break;
@@ -110,31 +109,25 @@ namespace vkcv::camera {
     }
 
     void PilotCameraController::mouseMoveCallback(double xoffset, double yoffset, Camera &camera) {
-        if(!m_rotationActive){
-            return;
-        }
-
-        float sensitivity = 0.05f;
-        xoffset *= sensitivity;
-        yoffset *= sensitivity;
+    	xoffset *= static_cast<float>(m_rotationActive);
+    	yoffset *= static_cast<float>(m_rotationActive);
 
-        panView(xoffset , yoffset, camera);
+        panView(xoffset, yoffset, camera);
     }
 
     void PilotCameraController::mouseButtonCallback(int button, int action, int mods, Camera &camera) {
-        if(button == GLFW_MOUSE_BUTTON_2 && m_rotationActive == false && action == GLFW_PRESS){
-            m_rotationActive = true;
-        }
-        else if(button == GLFW_MOUSE_BUTTON_2 && m_rotationActive == true && action == GLFW_RELEASE){
-            m_rotationActive = false;
-        }
+    	if (button == GLFW_MOUSE_BUTTON_2) {
+    		if (m_rotationActive != (action == GLFW_PRESS)) {
+    			m_rotationActive = (action == GLFW_PRESS);
+    		}
+    	}
     }
 
     void PilotCameraController::gamepadCallback(int gamepadIndex, Camera &camera, double frametime) {
         GLFWgamepadstate gamepadState;
         glfwGetGamepadState(gamepadIndex, &gamepadState);
 
-        float sensitivity = 100.0f;
+        float sensitivity = 1.0f;
         double threshold = 0.1;
 
         // handle rotations
@@ -163,29 +156,4 @@ namespace vkcv::camera {
                      * -copysign(1.0, stickLeftX);
     }
 
-
-    void PilotCameraController::moveForward(int action){
-        m_forward = static_cast<bool>(action);
-    }
-
-    void PilotCameraController::moveBackward(int action){
-        m_backward = static_cast<bool>(action);
-    }
-
-    void PilotCameraController::moveLeft(int action){
-        m_left = static_cast<bool>(action);
-    }
-
-    void PilotCameraController::moveRight(int action){
-        m_right = static_cast<bool>(action);
-    }
-
-    void PilotCameraController::moveUpward(int action){
-        m_upward = static_cast<bool>(action);
-    }
-
-    void PilotCameraController::moveDownward(int action){
-        m_downward = static_cast<bool>(action);
-    }
-
 }
\ No newline at end of file
diff --git a/modules/camera/src/vkcv/camera/TrackballCameraController.cpp b/modules/camera/src/vkcv/camera/TrackballCameraController.cpp
index cdd66cdb7fdd650d5112fe7bb4738f1fcded7783..5e89ce1a7b2fec5c203573cf4b58b45025e608f6 100644
--- a/modules/camera/src/vkcv/camera/TrackballCameraController.cpp
+++ b/modules/camera/src/vkcv/camera/TrackballCameraController.cpp
@@ -5,82 +5,78 @@ namespace vkcv::camera {
 
     TrackballCameraController::TrackballCameraController() {
         m_rotationActive = false;
-        m_radius = 3.0f;
         m_cameraSpeed = 2.5f;
         m_scrollSensitivity = 0.2f;
-    }
-
-    void TrackballCameraController::setRadius(const float radius) {
-        m_radius = 0.1f * (radius < 0.1f) + radius * (1 - (radius < 0.1f));
+		m_pitch = 0.0f;
+		m_yaw = 0.0f;
     }
 
     void TrackballCameraController::panView(double xOffset, double yOffset, Camera &camera) {
-        // update only if there is (valid) input
-        if (xOffset == 0.0 && yOffset == 0.0) {
-            return;
-        }
-
-        // handle yaw rotation
-        float yaw = camera.getYaw() + static_cast<float>(xOffset) * m_cameraSpeed;
-        yaw += 360.0f * (yaw < 0.0f) - 360.0f * (yaw > 360.0f);
-        camera.setYaw(yaw);
-
-        // handle pitch rotation
-        float pitch = camera.getPitch() + static_cast<float>(yOffset) * m_cameraSpeed;
-        pitch += 360.0f * (pitch < 0.0f) - 360.0f * (pitch > 360.0f);
-        camera.setPitch(pitch);
-    }
+		// update only if there is (valid) input
+		if (xOffset == 0.0 && yOffset == 0.0) {
+			return;
+		}
+	
+		m_yaw += static_cast<float>(xOffset) * 90.0f * m_cameraSpeed;
+		m_pitch += static_cast<float>(yOffset) * 90.0f * m_cameraSpeed;
+	}
 
     void TrackballCameraController::updateRadius(double offset, Camera &camera) {
         // update only if there is (valid) input
         if (offset == 0.0) {
             return;
         }
-
-        glm::vec3 cameraPosition = camera.getPosition();
-        glm::vec3 cameraCenter = camera.getCenter();
-        float radius = glm::length(cameraCenter - cameraPosition);  // get current camera radius
-        setRadius(radius - static_cast<float>(offset) * m_scrollSensitivity);
+		
+		camera.setPosition(
+				camera.getPosition() +
+				camera.getFront() * static_cast<float>(offset)
+		);
     }
 
     void TrackballCameraController::updateCamera(double deltaTime, Camera &camera) {
-		float yaw = camera.getYaw();
-		float pitch = camera.getPitch();
-		
-		const glm::vec3 yAxis = glm::vec3(0.0f, 1.0f, 0.0f);
-		const glm::vec3 xAxis = glm::vec3(1.0f, 0.0f, 0.0f);
+		const auto center = camera.getCenter();
+		const auto distance = center - camera.getPosition();
+		const float radius = glm::length(distance);
+	
+		glm::vec3 front = distance / radius;
+		glm::vec3 up = camera.getUp();
+		glm::vec3 left;
 	
-		const glm::mat4 rotationY = glm::rotate(glm::mat4(1.0f), glm::radians(yaw), yAxis);
-		const glm::mat4 rotationX = glm::rotate(rotationY, -glm::radians(pitch), xAxis);
-		const glm::vec3 translation = glm::vec3(
-				rotationX * glm::vec4(0.0f, 0.0f, m_radius, 0.0f)
+		const auto rotationY = glm::rotate(
+				glm::identity<glm::mat4>(),
+				glm::radians(m_yaw),
+				up
 		);
-		
-		const glm::vec3 center = camera.getCenter();
-		const glm::vec3 position = center + translation;
-		const glm::vec3 up = glm::vec3(
-				rotationX * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f)
+	
+		front = glm::vec3(rotationY * glm::vec4(front, 0.0f));
+		left = glm::normalize(glm::cross(up, front));
+	
+		const auto rotationX = glm::rotate(
+				rotationY,
+				glm::radians(m_pitch),
+				left
 		);
+	
+		up = glm::vec3(rotationX * glm::vec4(up, 0.0f));
+		front = glm::normalize(glm::cross(up, left));
 		
-		camera.lookAt(position, center, up);
-    }
+		m_yaw = 0.0f;
+		m_pitch = 0.0f;
+	
+		camera.lookAt(center + front * radius, center, up);
+	}
 
     void TrackballCameraController::keyCallback(int key, int scancode, int action, int mods, Camera &camera) {}
 
     void TrackballCameraController::scrollCallback(double offsetX, double offsetY, Camera &camera) {
-        updateRadius(offsetY, camera);
+        updateRadius(offsetY * m_scrollSensitivity, camera);
     }
 
     void TrackballCameraController::mouseMoveCallback(double xoffset, double yoffset, Camera &camera) {
-        if(!m_rotationActive){
-            return;
-        }
-
-        float sensitivity = 0.025f;
-        xoffset *= sensitivity;
-        yoffset *= sensitivity;
+        xoffset *= static_cast<float>(m_rotationActive);
+        yoffset *= static_cast<float>(m_rotationActive);
 
-        panView(xoffset , yoffset, camera);
+        panView(xoffset, yoffset, camera);
     }
 
     void TrackballCameraController::mouseButtonCallback(int button, int action, int mods, Camera &camera) {
@@ -96,23 +92,23 @@ namespace vkcv::camera {
         GLFWgamepadstate gamepadState;
         glfwGetGamepadState(gamepadIndex, &gamepadState);
 
-        float sensitivity = 100.0f;
+        float sensitivity = 1.0f;
         double threshold = 0.1;
 
         // handle rotations
-        double stickRightX = static_cast<double>(gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_X]);
-        double stickRightY = static_cast<double>(gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y]);
+        auto stickRightX = static_cast<double>(gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_X]);
+        auto stickRightY = static_cast<double>(gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y]);
         
-        double rightXVal = glm::clamp((abs(stickRightX)-threshold), 0.0, 1.0)
-                * std::copysign(1.0, stickRightX) * sensitivity * frametime;
-        double rightYVal = glm::clamp((abs(stickRightY)-threshold), 0.0, 1.0)
-                * std::copysign(1.0, stickRightY) * sensitivity * frametime;
+        double rightXVal = glm::clamp((glm::abs(stickRightX)-threshold), 0.0, 1.0)
+                * glm::sign(stickRightX) * sensitivity * frametime;
+        double rightYVal = glm::clamp((glm::abs(stickRightY)-threshold), 0.0, 1.0)
+                * glm::sign(stickRightY) * sensitivity * frametime;
         panView(rightXVal, rightYVal, camera);
 
         // handle translation
-        double stickLeftY = static_cast<double>(gamepadState.axes[GLFW_GAMEPAD_AXIS_LEFT_Y]);
-        double leftYVal = glm::clamp((abs(stickLeftY)-threshold), 0.0, 1.0)
-                * std::copysign(1.0, stickLeftY) * sensitivity * frametime;
+        auto stickLeftY = static_cast<double>(gamepadState.axes[GLFW_GAMEPAD_AXIS_LEFT_Y]);
+        double leftYVal = glm::clamp((glm::abs(stickLeftY)-threshold), 0.0, 1.0)
+                * glm::sign(stickLeftY) * sensitivity * frametime;
         updateRadius(-leftYVal, camera);
     }
 }
\ No newline at end of file
diff --git a/modules/effects/CMakeLists.txt b/modules/effects/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ea350fd4492c201bd247a40b94273829c1654a89
--- /dev/null
+++ b/modules/effects/CMakeLists.txt
@@ -0,0 +1,54 @@
+cmake_minimum_required(VERSION 3.16)
+project(vkcv_effects)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(vkcv_effects_source ${PROJECT_SOURCE_DIR}/src)
+set(vkcv_effects_include ${PROJECT_SOURCE_DIR}/include)
+
+set(vkcv_effects_sources
+		${vkcv_effects_include}/vkcv/effects/Effect.hpp
+		${vkcv_effects_source}/vkcv/effects/Effect.cpp
+		
+		${vkcv_effects_include}/vkcv/effects/BloomAndFlaresEffect.hpp
+		${vkcv_effects_source}/vkcv/effects/BloomAndFlaresEffect.cpp
+)
+
+set(vkcv_effects_shaders ${PROJECT_SOURCE_DIR}/shaders)
+
+include_shader(${vkcv_effects_shaders}/bloomDownsample.comp ${vkcv_effects_include} ${vkcv_effects_source})
+include_shader(${vkcv_effects_shaders}/bloomFlaresComposite.comp ${vkcv_effects_include} ${vkcv_effects_source})
+include_shader(${vkcv_effects_shaders}/bloomUpsample.comp ${vkcv_effects_include} ${vkcv_effects_source})
+include_shader(${vkcv_effects_shaders}/lensFlares.comp ${vkcv_effects_include} ${vkcv_effects_source})
+
+list(APPEND vkcv_effects_sources ${vkcv_effects_source}/bloomDownsample.comp.cxx)
+list(APPEND vkcv_effects_sources ${vkcv_effects_source}/bloomFlaresComposite.comp.cxx)
+list(APPEND vkcv_effects_sources ${vkcv_effects_source}/bloomUpsample.comp.cxx)
+list(APPEND vkcv_effects_sources ${vkcv_effects_source}/lensFlares.comp.cxx)
+
+list(APPEND vkcv_effects_sources ${vkcv_effects_include}/bloomDownsample.comp.hxx)
+list(APPEND vkcv_effects_sources ${vkcv_effects_include}/bloomFlaresComposite.comp.hxx)
+list(APPEND vkcv_effects_sources ${vkcv_effects_include}/bloomUpsample.comp.hxx)
+list(APPEND vkcv_effects_sources ${vkcv_effects_include}/lensFlares.comp.hxx)
+
+# adding source files to the project
+add_library(vkcv_effects ${vkcv_build_attribute} ${vkcv_effects_sources})
+
+# link the required libraries to the module
+target_link_libraries(vkcv_effects ${vkcv_effects_libraries} vkcv vkcv_shader_compiler vkcv_camera vkcv_asset_loader)
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(vkcv_effects SYSTEM BEFORE PRIVATE ${vkcv_effects_includes} ${vkcv_include} ${vkcv_includes} ${vkcv_shader_compiler_include} ${vkcv_camera_include} {vkcv_asset_loader_include})
+
+# add the own include directory for public headers
+target_include_directories(vkcv_effects BEFORE PUBLIC ${vkcv_effects_include})
+
+if (vkcv_parent_scope)
+	list(APPEND vkcv_modules_includes ${vkcv_effects_include})
+	list(APPEND vkcv_modules_libraries vkcv_effects)
+	
+	set(vkcv_modules_includes ${vkcv_modules_includes} PARENT_SCOPE)
+	set(vkcv_modules_libraries ${vkcv_modules_libraries} PARENT_SCOPE)
+endif()
diff --git a/modules/effects/README.md b/modules/effects/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..87639f4bff19fd8151d359b36c6ff34960782e3e
--- /dev/null
+++ b/modules/effects/README.md
@@ -0,0 +1,15 @@
+# Effects
+
+A VkCV module to add post-processing effects to images in realtime after rendering
+
+## Build
+
+### Dependencies (required):
+
+| Name of dependency | Used as submodule |
+|--------------------|-------------------|
+|                    |                   |
+
+## Docs
+
+Here is a [link](https://userpages.uni-koblenz.de/~vkcv/doc/group__vkcv__effects.html) to this module.
diff --git a/modules/effects/include/vkcv/effects/BloomAndFlaresEffect.hpp b/modules/effects/include/vkcv/effects/BloomAndFlaresEffect.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f4a9d03d8afcaf3532570a0807b246a0d641e02d
--- /dev/null
+++ b/modules/effects/include/vkcv/effects/BloomAndFlaresEffect.hpp
@@ -0,0 +1,73 @@
+#pragma once
+
+#include <vector>
+#include <vkcv/camera/Camera.hpp>
+
+#include "Effect.hpp"
+
+namespace vkcv::effects {
+	
+	class BloomAndFlaresEffect : public Effect {
+	private:
+		bool m_advanced;
+		
+		ComputePipelineHandle m_downsamplePipeline;
+		ComputePipelineHandle m_upsamplePipeline;
+		ComputePipelineHandle m_lensFlaresPipeline;
+		ComputePipelineHandle m_compositePipeline;
+		
+		DescriptorSetLayoutHandle m_downsampleDescriptorSetLayout;
+		std::vector<DescriptorSetHandle> m_downsampleDescriptorSets;
+		
+		DescriptorSetLayoutHandle m_upsampleDescriptorSetLayout;
+		std::vector<DescriptorSetHandle> m_upsampleDescriptorSets;
+		std::vector<DescriptorSetHandle> m_flaresDescriptorSets;
+		
+		DescriptorSetLayoutHandle m_lensFlaresDescriptorSetLayout;
+		DescriptorSetHandle m_lensFlaresDescriptorSet;
+		
+		DescriptorSetLayoutHandle m_compositeDescriptorSetLayout;
+		DescriptorSetHandle m_compositeDescriptorSet;
+		
+		ImageHandle m_blurImage;
+		ImageHandle m_flaresImage;
+		
+		SamplerHandle m_linearSampler;
+		SamplerHandle m_radialLutSampler;
+		
+		ImageHandle m_radialLut;
+		ImageHandle m_lensDirt;
+		
+		glm::vec3 m_cameraDirection;
+		uint32_t m_upsampleLimit;
+		
+		void recordDownsampling(const CommandStreamHandle &cmdStream,
+								const ImageHandle &input,
+								const ImageHandle &sample,
+								const std::vector<DescriptorSetHandle> &mipDescriptorSets);
+		
+		void recordUpsampling(const CommandStreamHandle &cmdStream,
+							  const ImageHandle &sample,
+							  const std::vector<DescriptorSetHandle> &mipDescriptorSets);
+		
+		void recordLensFlares(const CommandStreamHandle &cmdStream,
+							  uint32_t mipLevel);
+		
+		void recordComposition(const CommandStreamHandle &cmdStream,
+							   const ImageHandle &output);
+		
+	public:
+		BloomAndFlaresEffect(Core& core,
+							 bool advanced = false);
+		
+		void recordEffect(const CommandStreamHandle &cmdStream,
+						  const ImageHandle &input,
+						  const ImageHandle &output) override;
+		
+		void updateCameraDirection(const camera::Camera &camera);
+		
+		void setUpsamplingLimit(uint32_t limit);
+		
+	};
+	
+}
diff --git a/modules/effects/include/vkcv/effects/Effect.hpp b/modules/effects/include/vkcv/effects/Effect.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d068fb59404080aa421e49f6a183844e731c1c5e
--- /dev/null
+++ b/modules/effects/include/vkcv/effects/Effect.hpp
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <vkcv/Core.hpp>
+#include <vkcv/Handles.hpp>
+
+namespace vkcv::effects {
+	
+	class Effect {
+	protected:
+		Core& m_core;
+	
+	public:
+		explicit Effect(Core& core);
+		
+		~Effect() = default;
+		
+		virtual void recordEffect(const CommandStreamHandle& cmdStream,
+								  const ImageHandle& input,
+								  const ImageHandle& output) = 0;
+		
+	};
+	
+}
diff --git a/projects/bloom/resources/shaders/downsample.comp b/modules/effects/shaders/bloomDownsample.comp
similarity index 100%
rename from projects/bloom/resources/shaders/downsample.comp
rename to modules/effects/shaders/bloomDownsample.comp
diff --git a/modules/effects/shaders/bloomFlaresComposite.comp b/modules/effects/shaders/bloomFlaresComposite.comp
new file mode 100644
index 0000000000000000000000000000000000000000..21d67393b634c8e639c0b81669e96070c380e6f6
--- /dev/null
+++ b/modules/effects/shaders/bloomFlaresComposite.comp
@@ -0,0 +1,96 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(set=0, binding=0) uniform texture2D                          blurImage;
+layout(set=0, binding=1) uniform texture2D                          lensImage;
+layout(set=0, binding=2) uniform sampler                            linearSampler;
+layout(set=0, binding=3, r11f_g11f_b10f) uniform image2D            colorBuffer;
+
+#ifdef ADVANCED_FEATURES
+layout(set=0, binding=4) uniform texture2D                          radialLUT;
+layout(set=0, binding=5) uniform sampler                            radialLUTSampler;
+layout(set=0, binding=6) uniform texture2D                          dirtTexture;
+#endif
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+#ifdef ADVANCED_FEATURES
+
+layout( push_constant ) uniform constants{
+    vec3 cameraForward;
+};
+
+float starburst(vec2 uv){
+    vec2 toCenter   = vec2(0.5) - uv;
+    float d2        = dot(toCenter, toCenter);
+    float falloff   = clamp(pow(d2 * 2, 2.5), 0, 1);
+    
+    float cosTheta  = acos(normalize(toCenter).x) * sign(toCenter.y);
+    cosTheta        *= 4;
+    
+    float thetaOffset   = cameraForward.x + cameraForward.y;
+    thetaOffset         *= 10;
+    cosTheta            += thetaOffset;
+    
+    float burst     = texture(sampler2D(radialLUT, radialLUTSampler), vec2(cosTheta, 0.5)).r;
+    burst           = pow(burst, 2);
+    return mix(1, burst, falloff);
+}
+
+float getLensDirtWeight(vec2 uv){
+    vec2    targetTextureRes    = imageSize(colorBuffer);
+    float   targetAspectRatio   = targetTextureRes.x / targetTextureRes.y;
+    
+    vec2    dirtTextureRes    = textureSize(sampler2D(dirtTexture, linearSampler), 0);
+    float   dirtAspectRatio   = dirtTextureRes.x / dirtTextureRes.y;
+    
+    uv.x                        *= targetAspectRatio / dirtAspectRatio;
+    float   dirt                = texture(sampler2D(dirtTexture, radialLUTSampler), uv).r;
+    float   dirtStrength        = 0.4f;
+    
+    // manually looked up in gimp, must be adjusted when changing dirt texture
+    float dirtMean = 0.132;
+    // make sure no energy is lost
+    // otherwise bloom is darkened when the dirt increases
+    dirt /= dirtMean;   
+    
+    return mix(1, dirt, dirtStrength);
+}
+
+#endif
+
+void main()
+{
+    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(colorBuffer)))){
+        return;
+    }
+
+    ivec2 pixel_coord   = ivec2(gl_GlobalInvocationID.xy);
+    vec2  pixel_size    = vec2(1.0f) / imageSize(colorBuffer);
+    vec2  UV            = pixel_coord.xy * pixel_size;
+
+    vec4 composite_color = vec4(0.0f);
+
+    vec3 blur_color   = texture(sampler2D(blurImage, linearSampler), UV).rgb;
+    vec3 lens_color   = texture(sampler2D(lensImage, linearSampler), UV).rgb;
+    vec3 main_color   = imageLoad(colorBuffer, pixel_coord).rgb;
+
+    // composite blur and lens features
+    float bloom_weight = 0.06f;
+    float lens_weight  = 0.02f;
+    float main_weight = 1 - (bloom_weight + lens_weight);
+
+#ifdef ADVANCED_FEATURES
+    lens_color *= starburst(UV);
+    
+    float lensDirtWeight = getLensDirtWeight(UV);
+    bloom_weight        *= lensDirtWeight;
+    lens_weight         *= lensDirtWeight;
+#endif
+    
+    composite_color.rgb = blur_color * bloom_weight +
+                          lens_color * lens_weight  +
+                          main_color * main_weight;
+
+    imageStore(colorBuffer, pixel_coord, composite_color);
+}
\ No newline at end of file
diff --git a/projects/bloom/resources/shaders/upsample.comp b/modules/effects/shaders/bloomUpsample.comp
similarity index 100%
rename from projects/bloom/resources/shaders/upsample.comp
rename to modules/effects/shaders/bloomUpsample.comp
diff --git a/projects/bloom/resources/shaders/lensFlares.comp b/modules/effects/shaders/lensFlares.comp
similarity index 79%
rename from projects/bloom/resources/shaders/lensFlares.comp
rename to modules/effects/shaders/lensFlares.comp
index ce27d8850b709f61332d467914ddc944dc63109f..afcad375c1cd3e8f547ad2386b6f1d7bdfdfa85a 100644
--- a/projects/bloom/resources/shaders/lensFlares.comp
+++ b/modules/effects/shaders/lensFlares.comp
@@ -12,7 +12,7 @@ vec3 sampleColorChromaticAberration(vec2 _uv)
     vec2 toCenter = (vec2(0.5) - _uv);
 
     vec3    colorScales     = vec3(-1, 0, 1);
-    float   aberrationScale = 0.1;
+    float   aberrationScale = 0.15;
     vec3 scaleFactors = colorScales * aberrationScale;
 
     float r = texture(sampler2D(blurBuffer, linearSampler), _uv + toCenter * scaleFactors.r).r;
@@ -26,7 +26,7 @@ vec3 ghost_vectors(vec2 _uv)
 {
     vec2 ghost_vec = (vec2(0.5f) - _uv);
 
-    const uint c_ghost_count = 64;
+    const uint c_ghost_count = 8;
     const float c_ghost_spacing = length(ghost_vec) / c_ghost_count;
 
     ghost_vec *= c_ghost_spacing;
@@ -53,18 +53,19 @@ vec3 ghost_vectors(vec2 _uv)
 
 vec3 halo(vec2 _uv)
 {
-    const float c_aspect_ratio = float(imageSize(lensBuffer).x) / float(imageSize(lensBuffer).y);
-    const float c_radius = 0.6f;
-    const float c_halo_thickness = 0.1f;
+    float c_aspect_ratio = float(imageSize(lensBuffer).x) / float(imageSize(lensBuffer).y);
+    c_aspect_ratio *= 0.55;
+    const float c_radius = 0.5f;
+    const float c_halo_thickness = 0.15f;
 
-    vec2 halo_vec = vec2(0.5) - _uv;
-    //halo_vec.x /= c_aspect_ratio;
-    halo_vec = normalize(halo_vec);
-    //halo_vec.x *= c_aspect_ratio;
+    vec2 halo_vec   = vec2(0.5) - _uv;
+    halo_vec.x      /= c_aspect_ratio;
+    halo_vec        = normalize(halo_vec);
+    halo_vec.x      *= c_aspect_ratio;
 
 
-    //vec2 w_uv = (_uv - vec2(0.5, 0.0)) * vec2(c_aspect_ratio, 1.0) + vec2(0.5, 0.0);
-    vec2 w_uv = _uv;
+    vec2 w_uv = (_uv - vec2(0.5, 0.0)) * vec2(c_aspect_ratio, 1.0) + vec2(0.5, 0.0);
+    //vec2 w_uv = _uv;
     float d = distance(w_uv, vec2(0.5)); // distance to center
 
     float distance_to_halo = abs(d - c_radius);
@@ -75,11 +76,7 @@ vec3 halo(vec2 _uv)
         float distance_to_border = c_halo_thickness - distance_to_halo;
         halo_weight = distance_to_border / c_halo_thickness;
 
-        //halo_weight = clamp((halo_weight / 0.4f), 0.0f, 1.0f);
-        halo_weight = pow(halo_weight, 2.0f);
-
-
-        //halo_weight = 1.0f;
+        halo_weight = pow(clamp(halo_weight + 0.1, 0, 1), 2);
     }
 
     return sampleColorChromaticAberration(_uv + halo_vec) * halo_weight;
diff --git a/modules/effects/src/vkcv/effects/BloomAndFlaresEffect.cpp b/modules/effects/src/vkcv/effects/BloomAndFlaresEffect.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..133b380095abc6c729ef94da675cc9f394475ab4
--- /dev/null
+++ b/modules/effects/src/vkcv/effects/BloomAndFlaresEffect.cpp
@@ -0,0 +1,545 @@
+
+#include "vkcv/effects/BloomAndFlaresEffect.hpp"
+
+#include <vkcv/PushConstants.hpp>
+#include <vkcv/Image.hpp>
+#include <vkcv/Sampler.hpp>
+
+#include <vkcv/shader/GLSLCompiler.hpp>
+#include <vkcv/asset/asset_loader.hpp>
+
+#include "bloomDownsample.comp.hxx"
+#include "bloomFlaresComposite.comp.hxx"
+#include "bloomUpsample.comp.hxx"
+#include "lensFlares.comp.hxx"
+
+namespace vkcv::effects {
+	
+	static DescriptorBindings getDescriptorBindings() {
+		DescriptorBindings descriptorBindings = {};
+		
+		auto binding_0 = DescriptorBinding {
+				0,
+				DescriptorType::IMAGE_SAMPLED,
+				1,
+				ShaderStage::COMPUTE,
+				false
+		};
+		
+		auto binding_1 = DescriptorBinding {
+				1,
+				DescriptorType::SAMPLER,
+				1,
+				ShaderStage::COMPUTE,
+				false
+		};
+		
+		auto binding_2 = DescriptorBinding{
+				2,
+				DescriptorType::IMAGE_STORAGE,
+				1,
+				ShaderStage::COMPUTE,
+				false
+		};
+		
+		descriptorBindings.insert(std::make_pair(0, binding_0));
+		descriptorBindings.insert(std::make_pair(1, binding_1));
+		descriptorBindings.insert(std::make_pair(2, binding_2));
+		
+		return descriptorBindings;
+	}
+	
+	static DescriptorBindings getCompositeDescriptorBindings(bool advanced) {
+		DescriptorBindings descriptorBindings = {};
+		
+		auto binding_0 = DescriptorBinding {
+				0,
+				DescriptorType::IMAGE_SAMPLED,
+				1,
+				ShaderStage::COMPUTE,
+				false
+		};
+		
+		auto binding_1 = DescriptorBinding {
+				1,
+				DescriptorType::IMAGE_SAMPLED,
+				1,
+				ShaderStage::COMPUTE,
+				false
+		};
+		
+		auto binding_2 = DescriptorBinding{
+				2,
+				DescriptorType::SAMPLER,
+				1,
+				ShaderStage::COMPUTE,
+				false
+		};
+		
+		auto binding_3 = DescriptorBinding{
+				3,
+				DescriptorType::IMAGE_STORAGE,
+				1,
+				ShaderStage::COMPUTE,
+				false
+		};
+		
+		descriptorBindings.insert(std::make_pair(0, binding_0));
+		descriptorBindings.insert(std::make_pair(1, binding_1));
+		descriptorBindings.insert(std::make_pair(2, binding_2));
+		descriptorBindings.insert(std::make_pair(3, binding_3));
+		
+		if (advanced) {
+			auto binding_4 = DescriptorBinding{
+					4,
+					DescriptorType::IMAGE_SAMPLED,
+					1,
+					ShaderStage::COMPUTE,
+					false
+			};
+			
+			auto binding_5 = DescriptorBinding{
+					5,
+					DescriptorType::SAMPLER,
+					1,
+					ShaderStage::COMPUTE,
+					false
+			};
+			
+			auto binding_6 = DescriptorBinding{
+					6,
+					DescriptorType::IMAGE_SAMPLED,
+					1,
+					ShaderStage::COMPUTE,
+					false
+			};
+			
+			descriptorBindings.insert(std::make_pair(4, binding_4));
+			descriptorBindings.insert(std::make_pair(5, binding_5));
+			descriptorBindings.insert(std::make_pair(6, binding_6));
+		}
+		
+		return descriptorBindings;
+	}
+	
+	static ImageHandle loadTexture(Core &core,
+								   const std::string &texturePath) {
+		const auto texture = vkcv::asset::loadTexture(texturePath);
+		
+		auto image = vkcv::image(
+				core,
+				vk::Format::eR8G8B8A8Unorm,
+				texture.width,
+				texture.height
+		);
+		
+		image.fill(texture.data.data(), texture.data.size());
+		return image.getHandle();
+	}
+	
+	static ComputePipelineHandle compilePipeline(Core &core,
+												 const std::string &shaderSource,
+												 const DescriptorSetLayoutHandle &descriptorSetLayout,
+												 bool advanced = false) {
+		vkcv::shader::GLSLCompiler compiler;
+		
+		if (advanced) {
+			compiler.setDefine("ADVANCED_FEATURES", "1");
+		}
+		
+		ShaderProgram program;
+		compiler.compileSource(
+				vkcv::ShaderStage::COMPUTE,
+				shaderSource.c_str(),
+				[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+					program.addShader(shaderStage, path);
+				},
+				""
+		);
+		
+		ComputePipelineHandle pipeline = core.createComputePipeline({
+			program, { descriptorSetLayout }
+		});
+		
+		return pipeline;
+	}
+	
+	BloomAndFlaresEffect::BloomAndFlaresEffect(Core &core,
+											   bool advanced) :
+	Effect(core),
+	m_advanced(advanced),
+	
+	m_downsamplePipeline(),
+	m_upsamplePipeline(),
+	m_lensFlaresPipeline(),
+	m_compositePipeline(),
+	
+	m_downsampleDescriptorSetLayout(m_core.createDescriptorSetLayout(getDescriptorBindings())),
+	m_downsampleDescriptorSets({}),
+	
+	m_upsampleDescriptorSetLayout(m_core.createDescriptorSetLayout(getDescriptorBindings())),
+	m_upsampleDescriptorSets({}),
+	m_flaresDescriptorSets({}),
+	
+	m_lensFlaresDescriptorSetLayout(m_core.createDescriptorSetLayout(getDescriptorBindings())),
+	m_lensFlaresDescriptorSet(m_core.createDescriptorSet(m_lensFlaresDescriptorSetLayout)),
+	
+	m_compositeDescriptorSetLayout(),
+	m_compositeDescriptorSet(),
+	
+	m_blurImage(),
+	m_flaresImage(),
+	
+	m_linearSampler(samplerLinear(m_core, true)),
+	
+	m_radialLutSampler(),
+	
+	m_radialLut(),
+	m_lensDirt(),
+	
+	m_cameraDirection(),
+	m_upsampleLimit(5) {
+		m_downsamplePipeline = compilePipeline(
+				m_core,
+				BLOOMDOWNSAMPLE_COMP_SHADER,
+				m_downsampleDescriptorSetLayout
+		);
+		
+		m_upsamplePipeline = compilePipeline(
+				m_core,
+				BLOOMUPSAMPLE_COMP_SHADER,
+				m_upsampleDescriptorSetLayout
+		);
+		
+		m_lensFlaresPipeline = compilePipeline(
+				m_core,
+				LENSFLARES_COMP_SHADER,
+				m_lensFlaresDescriptorSetLayout
+		);
+		
+		m_compositeDescriptorSetLayout = m_core.createDescriptorSetLayout(
+				getCompositeDescriptorBindings(m_advanced)
+		);
+		
+		m_compositeDescriptorSet = m_core.createDescriptorSet(
+				m_compositeDescriptorSetLayout
+		);
+		
+		m_compositePipeline = compilePipeline(
+				m_core,
+				BLOOMFLARESCOMPOSITE_COMP_SHADER,
+				m_compositeDescriptorSetLayout,
+				m_advanced
+		);
+		
+		if (m_advanced) {
+			m_radialLutSampler = samplerLinear(m_core);
+			m_radialLut = loadTexture(m_core, "assets/RadialLUT.png");
+			m_lensDirt = loadTexture(m_core, "assets/lensDirt.jpg");
+		}
+	}
+	
+	static uint32_t calcDispatchSize(float sampleSizeDim, uint32_t threadGroupWorkRegionDim) {
+		return std::max<uint32_t>(
+				static_cast<uint32_t>(std::ceil(
+						sampleSizeDim / static_cast<float>(threadGroupWorkRegionDim)
+				)),
+				1
+		);
+	}
+	
+	void BloomAndFlaresEffect::recordDownsampling(const CommandStreamHandle &cmdStream,
+												  const ImageHandle &input,
+												  const ImageHandle &sample,
+												  const std::vector<DescriptorSetHandle> &mipDescriptorSets) {
+		const uint32_t sampleWidth = m_core.getImageWidth(sample);
+		const uint32_t sampleHeight = m_core.getImageHeight(sample);
+		
+		m_core.prepareImageForSampling(cmdStream, input);
+		m_core.prepareImageForStorage(cmdStream, m_blurImage);
+		
+		for (uint32_t mipLevel = 0; mipLevel < mipDescriptorSets.size(); mipLevel++) {
+			// mip descriptor writes
+			DescriptorWrites mipDownsampleWrites;
+			
+			if (mipLevel > 0) {
+				mipDownsampleWrites.writeSampledImage(0, sample, mipLevel - 1, true);
+			} else {
+				mipDownsampleWrites.writeSampledImage(0, input);
+			}
+			
+			mipDownsampleWrites.writeSampler(1, m_linearSampler);
+			mipDownsampleWrites.writeStorageImage(2, sample, mipLevel);
+			
+			m_core.writeDescriptorSet(mipDescriptorSets[mipLevel], mipDownsampleWrites);
+			
+			float mipDivisor = 1.0f;
+			
+			for (uint32_t i = 0; i < mipLevel; i++) {
+				mipDivisor *= 2.0f;
+			}
+			
+			const auto downsampleSizeX  = static_cast<float>(sampleWidth) / mipDivisor;
+			const auto downsampleSizeY  = static_cast<float>(sampleHeight) / mipDivisor;
+			
+			static const uint32_t threadGroupWorkRegionDim = 8;
+			
+			DispatchSize dispatch (
+					calcDispatchSize(downsampleSizeX, threadGroupWorkRegionDim),
+					calcDispatchSize(downsampleSizeY, threadGroupWorkRegionDim)
+			);
+			
+			// mip blur dispatch
+			m_core.recordComputeDispatchToCmdStream(
+					cmdStream,
+					m_downsamplePipeline,
+					dispatch,
+					{ useDescriptorSet(0, mipDescriptorSets[mipLevel]) },
+					PushConstants(0)
+			);
+			
+			// image barrier between mips
+			m_core.recordImageMemoryBarrier(cmdStream, sample);
+		}
+	}
+	
+	void BloomAndFlaresEffect::recordUpsampling(const CommandStreamHandle &cmdStream,
+												const ImageHandle &sample,
+												const std::vector<DescriptorSetHandle> &mipDescriptorSets) {
+		// upsample dispatch
+		m_core.prepareImageForStorage(cmdStream, sample);
+		
+		const uint32_t sampleWidth = m_core.getImageWidth(sample);
+		const uint32_t sampleHeight = m_core.getImageHeight(sample);
+		
+		// upsample dispatch for each mip map
+		for(uint32_t mipLevel = mipDescriptorSets.size(); mipLevel > 0; mipLevel--) {
+			// mip descriptor writes
+			DescriptorWrites mipUpsampleWrites;
+			mipUpsampleWrites.writeSampledImage(0, sample, mipLevel, true);
+			mipUpsampleWrites.writeSampler(1, m_linearSampler);
+			mipUpsampleWrites.writeStorageImage(2, sample, mipLevel - 1);
+			
+			m_core.writeDescriptorSet(mipDescriptorSets[mipLevel - 1], mipUpsampleWrites);
+			
+			float mipDivisor = 1.0f;
+			
+			for (uint32_t i = 0; i < mipLevel - 1; i++) {
+				mipDivisor *= 2.0f;
+			}
+			
+			const auto upsampleSizeX  = static_cast<float>(sampleWidth) / mipDivisor;
+			const auto upsampleSizeY  = static_cast<float>(sampleHeight) / mipDivisor;
+			
+			static const uint32_t threadGroupWorkRegionDim = 8;
+			
+			DispatchSize dispatch (
+					calcDispatchSize(upsampleSizeX, threadGroupWorkRegionDim),
+					calcDispatchSize(upsampleSizeY, threadGroupWorkRegionDim)
+			);
+			
+			m_core.recordComputeDispatchToCmdStream(
+					cmdStream,
+					m_upsamplePipeline,
+					dispatch,
+					{ useDescriptorSet(0, mipDescriptorSets[mipLevel - 1]) },
+					PushConstants(0)
+			);
+			
+			// image barrier between mips
+			m_core.recordImageMemoryBarrier(cmdStream, sample);
+		}
+	}
+	
+	void BloomAndFlaresEffect::recordLensFlares(const CommandStreamHandle &cmdStream,
+												uint32_t mipLevel) {
+		// lens feature generation descriptor writes
+		m_core.prepareImageForSampling(cmdStream, m_blurImage);
+		m_core.prepareImageForStorage(cmdStream, m_flaresImage);
+		
+		const uint32_t flaresWidth = m_core.getImageWidth(m_flaresImage);
+		const uint32_t flaresHeight = m_core.getImageHeight(m_flaresImage);
+		
+		DescriptorWrites lensFlaresWrites;
+		lensFlaresWrites.writeSampledImage(0, m_blurImage, 0);
+		lensFlaresWrites.writeSampler(1, m_linearSampler);
+		lensFlaresWrites.writeStorageImage(2, m_flaresImage, mipLevel);
+		
+		m_core.writeDescriptorSet(m_lensFlaresDescriptorSet, lensFlaresWrites);
+		
+		const auto sampleSizeX = static_cast<float>(flaresWidth);
+		const auto sampleSizeY = static_cast<float>(flaresHeight);
+		
+		float mipDivisor = 1.0f;
+		
+		for (uint32_t i = 0; i < mipLevel - 1; i++) {
+			mipDivisor *= 2.0f;
+		}
+		
+		static const uint32_t threadGroupWorkRegionDim = 8;
+		
+		// lens feature generation dispatch
+		DispatchSize dispatch (
+				calcDispatchSize(sampleSizeX / mipDivisor, threadGroupWorkRegionDim),
+				calcDispatchSize(sampleSizeY / mipDivisor, threadGroupWorkRegionDim)
+		);
+		
+		m_core.recordComputeDispatchToCmdStream(
+				cmdStream,
+				m_lensFlaresPipeline,
+				dispatch,
+				{ useDescriptorSet(0, m_lensFlaresDescriptorSet) },
+				PushConstants(0)
+		);
+	}
+	
+	void BloomAndFlaresEffect::recordComposition(const CommandStreamHandle &cmdStream,
+												 const ImageHandle &output) {
+		const uint32_t outputWidth = m_core.getImageWidth(output);
+		const uint32_t outputHeight = m_core.getImageHeight(output);
+		
+		m_core.prepareImageForSampling(cmdStream, m_blurImage);
+		m_core.prepareImageForSampling(cmdStream, m_flaresImage);
+		m_core.prepareImageForStorage(cmdStream, output);
+		
+		// bloom composite descriptor write
+		vkcv::DescriptorWrites compositeWrites;
+		
+		if (m_advanced) {
+			compositeWrites.writeSampledImage(
+					0, m_blurImage
+			).writeSampledImage(
+					1, m_flaresImage
+			).writeSampledImage(
+					4, m_radialLut
+			).writeSampledImage(
+					6, m_lensDirt
+			);
+			
+			compositeWrites.writeSampler(
+					2, m_linearSampler
+			).writeSampler(
+					5, m_radialLutSampler
+			);
+			
+			compositeWrites.writeStorageImage(3, output);
+		} else {
+			compositeWrites.writeSampledImage(
+					0, m_blurImage
+			).writeSampledImage(
+					1, m_flaresImage
+			);
+			
+			compositeWrites.writeSampler(2, m_linearSampler);
+			
+			compositeWrites.writeStorageImage(3, output);
+		}
+		
+		m_core.writeDescriptorSet(m_compositeDescriptorSet, compositeWrites);
+		
+		const auto sampleWidth = static_cast<float>(outputWidth);
+		const auto sampleHeight = static_cast<float>(outputHeight);
+		
+		static const uint32_t threadGroupWorkRegionDim = 8;
+		
+		DispatchSize dispatch (
+				calcDispatchSize(sampleWidth, threadGroupWorkRegionDim),
+				calcDispatchSize(sampleHeight, threadGroupWorkRegionDim)
+		);
+		
+		PushConstants pushConstants = vkcv::pushConstants<glm::vec3>();
+		pushConstants.appendDrawcall(m_cameraDirection);
+		
+		// bloom composite dispatch
+		m_core.recordComputeDispatchToCmdStream(
+				cmdStream,
+				m_compositePipeline,
+				dispatch,
+				{ useDescriptorSet(0, m_compositeDescriptorSet) },
+				m_advanced? pushConstants : PushConstants(0)
+		);
+	}
+	
+	void BloomAndFlaresEffect::recordEffect(const CommandStreamHandle &cmdStream,
+											const ImageHandle &input,
+											const ImageHandle &output) {
+		m_core.recordBeginDebugLabel(cmdStream, "vkcv::post_processing::BloomAndFlaresEffect", {
+				0.0f, 1.0f, 1.0f, 1.0f
+		});
+		
+		const auto halfWidth = static_cast<uint32_t>(std::ceil(
+				static_cast<float>(m_core.getImageWidth(output)) * 0.5f
+		));
+		
+		const auto halfHeight = static_cast<uint32_t>(std::ceil(
+				static_cast<float>(m_core.getImageHeight(output)) * 0.5f
+		));
+		
+		vkcv::ImageConfig imageConfig (halfWidth, halfHeight);
+		imageConfig.setSupportingStorage(true);
+		
+		if ((!m_blurImage) ||
+			(halfWidth != m_core.getImageWidth(m_blurImage)) ||
+			(halfHeight != m_core.getImageHeight(m_blurImage))) {
+			m_blurImage = m_core.createImage(
+					m_core.getImageFormat(output),
+					imageConfig,
+					true
+			);
+			
+			m_downsampleDescriptorSets.clear();
+			m_upsampleDescriptorSets.clear();
+			
+			const uint32_t mipLevels = m_core.getImageMipLevels(m_blurImage);
+			
+			for (uint32_t i = 0; i < mipLevels; i++) {
+				m_downsampleDescriptorSets.push_back(m_core.createDescriptorSet(
+						m_downsampleDescriptorSetLayout
+				));
+			}
+			
+			for (uint32_t i = 0; i < std::min<uint32_t>(m_upsampleLimit, mipLevels); i++) {
+				m_upsampleDescriptorSets.push_back(m_core.createDescriptorSet(
+						m_upsampleDescriptorSetLayout
+				));
+			}
+		}
+		
+		if ((!m_flaresImage) ||
+			(halfWidth != m_core.getImageWidth(m_flaresImage)) ||
+			(halfHeight != m_core.getImageHeight(m_flaresImage))) {
+			m_flaresImage = m_core.createImage(
+					m_core.getImageFormat(output),
+					imageConfig,
+					true
+			);
+			
+			m_flaresDescriptorSets.clear();
+			
+			const uint32_t mipLevels = m_core.getImageMipLevels(m_flaresImage);
+			
+			for (uint32_t i = 0; i < std::min<uint32_t>(2, mipLevels); i++) {
+				m_flaresDescriptorSets.push_back(m_core.createDescriptorSet(
+						m_upsampleDescriptorSetLayout
+				));
+			}
+		}
+		
+		recordDownsampling(cmdStream, input, m_blurImage, m_downsampleDescriptorSets);
+		recordUpsampling(cmdStream, m_blurImage, m_upsampleDescriptorSets);
+		recordLensFlares(cmdStream, m_flaresDescriptorSets.size());
+		recordUpsampling(cmdStream, m_flaresImage, m_flaresDescriptorSets);
+		recordComposition(cmdStream, output);
+		
+		m_core.recordEndDebugLabel(cmdStream);
+	}
+	
+	void BloomAndFlaresEffect::updateCameraDirection(const camera::Camera &camera) {
+		m_cameraDirection = camera.getFront();
+	}
+	
+	void BloomAndFlaresEffect::setUpsamplingLimit(uint32_t limit) {
+		m_upsampleLimit = limit;
+	}
+	
+}
diff --git a/modules/effects/src/vkcv/effects/Effect.cpp b/modules/effects/src/vkcv/effects/Effect.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3246db15144bcafcc253df34f358594c85868647
--- /dev/null
+++ b/modules/effects/src/vkcv/effects/Effect.cpp
@@ -0,0 +1,8 @@
+
+#include "vkcv/effects/Effect.hpp"
+
+namespace vkcv::effects {
+	
+	Effect::Effect(Core &core) : m_core(core) {}
+	
+}
diff --git a/modules/geometry/CMakeLists.txt b/modules/geometry/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ffadd9c60733aa6cadda3837e3f6a7ab4d26f3e3
--- /dev/null
+++ b/modules/geometry/CMakeLists.txt
@@ -0,0 +1,58 @@
+cmake_minimum_required(VERSION 3.16)
+project(vkcv_geometry)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(vkcv_geometry_source ${PROJECT_SOURCE_DIR}/src)
+set(vkcv_geometry_include ${PROJECT_SOURCE_DIR}/include)
+
+set(vkcv_geometry_sources
+		${vkcv_geometry_include}/vkcv/geometry/Geometry.hpp
+		${vkcv_geometry_source}/vkcv/geometry/Geometry.cpp
+		${vkcv_geometry_include}/vkcv/geometry/Volume.hpp
+		${vkcv_geometry_source}/vkcv/geometry/Volume.cpp
+		${vkcv_geometry_include}/vkcv/geometry/Circular.hpp
+		${vkcv_geometry_source}/vkcv/geometry/Circular.cpp
+		${vkcv_geometry_include}/vkcv/geometry/Sphere.hpp
+		${vkcv_geometry_source}/vkcv/geometry/Sphere.cpp
+		${vkcv_geometry_include}/vkcv/geometry/Cuboid.hpp
+		${vkcv_geometry_source}/vkcv/geometry/Cuboid.cpp
+		${vkcv_geometry_include}/vkcv/geometry/Cylinder.hpp
+		${vkcv_geometry_source}/vkcv/geometry/Cylinder.cpp
+		${vkcv_geometry_include}/vkcv/geometry/Teapot.hpp
+		${vkcv_geometry_source}/vkcv/geometry/Teapot.cpp
+)
+
+# adding source files to the project
+add_library(vkcv_geometry ${vkcv_build_attribute} ${vkcv_geometry_sources})
+
+# Setup some path variables to load libraries
+set(vkcv_geometry_lib lib)
+set(vkcv_geometry_lib_path ${PROJECT_SOURCE_DIR}/${vkcv_geometry_lib})
+
+include(config/GLM.cmake)
+
+target_link_libraries(vkcv_geometry PUBLIC
+		${vkcv_geometry_libraries}
+		vkcv
+)
+
+target_include_directories(vkcv_geometry SYSTEM BEFORE PRIVATE
+		${vkcv_geometry_includes}
+		${vkcv_include}
+		${vkcv_includes}
+)
+
+# add the own include directory for public headers
+target_include_directories(vkcv_geometry BEFORE PUBLIC ${vkcv_geometry_include} ${vkcv_geometry_includes})
+target_compile_definitions(vkcv_geometry PUBLIC ${vkcv_geometry_definitions})
+
+if (vkcv_parent_scope)
+	list(APPEND vkcv_modules_includes ${vkcv_geometry_include})
+	list(APPEND vkcv_modules_libraries vkcv_geometry)
+	
+	set(vkcv_modules_includes ${vkcv_modules_includes} PARENT_SCOPE)
+	set(vkcv_modules_libraries ${vkcv_modules_libraries} PARENT_SCOPE)
+endif()
diff --git a/modules/geometry/README.md b/modules/geometry/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..3148a94f9c8d782cd4f1a127fd6b8f94e43d9b60
--- /dev/null
+++ b/modules/geometry/README.md
@@ -0,0 +1,15 @@
+# Geometry
+
+A VkCV module to use basic geometry for rendering.
+
+## Build
+
+### Dependencies (required):
+
+| Name of dependency                   | Used as submodule |
+|--------------------------------------|---|
+| [GLM](https://github.com/g-truc/glm) | ✅ |
+
+## Docs
+
+Here is a [link](https://userpages.uni-koblenz.de/~vkcv/doc/group__vkcv__geometry.html) to this module.
diff --git a/modules/geometry/config/GLM.cmake b/modules/geometry/config/GLM.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..6a1e463ba94de14fcc707270671e535df1e45a2f
--- /dev/null
+++ b/modules/geometry/config/GLM.cmake
@@ -0,0 +1,23 @@
+
+find_package(glm QUIET)
+
+if (glm_FOUND)
+    list(APPEND vkcv_geometry_includes ${GLM_INCLUDE_DIRS})
+    list(APPEND vkcv_geometry_libraries glm)
+else()
+    use_git_submodule("${vkcv_geometry_lib_path}/glm" glm_status)
+
+    if (${glm_status})
+        add_subdirectory(${vkcv_geometry_lib}/glm)
+
+        list(APPEND vkcv_geometry_includes ${vkcv_geometry_lib_path}/glm)
+        list(APPEND vkcv_geometry_libraries glm)
+    endif ()
+endif ()
+
+list(APPEND vkcv_geometry_definitions GLM_DEPTH_ZERO_TO_ONE)
+list(APPEND vkcv_geometry_definitions GLM_FORCE_LEFT_HANDED)
+
+if ((WIN32) AND (${CMAKE_SIZEOF_VOID_P} MATCHES 4))
+    list(APPEND vkcv_geometry_definitions GLM_ENABLE_EXPERIMENTAL)
+endif()
diff --git a/modules/geometry/include/vkcv/geometry/Circular.hpp b/modules/geometry/include/vkcv/geometry/Circular.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..09165f9d8e984144bd9c2e21e8cfbbd2d32d1010
--- /dev/null
+++ b/modules/geometry/include/vkcv/geometry/Circular.hpp
@@ -0,0 +1,109 @@
+#pragma once
+
+#include <cstdlib>
+
+namespace vkcv::geometry {
+	
+	/**
+     * @addtogroup vkcv_geometry
+     * @{
+     */
+	
+	/**
+	 * A basic class to provide attributes for circular geometry.
+	 */
+	class Circular {
+	private:
+		/**
+		 * Radius of the circular part of the geometry.
+		 */
+		float m_radius;
+		
+		/**
+		 * Resolution in case of generating the geometry in a
+		 * discrete way.
+		 */
+		size_t m_resolution;
+	
+	public:
+		/**
+		 * Constructor creating circular geometry by a given
+		 * radius and a resolution also provides a default.
+		 *
+		 * @param[in] radius Radius of the circular geometry
+		 * @param[in] resoltion Resolution of the circular geometry
+		 */
+		explicit Circular(float radius, size_t resoltion = 10);
+		
+		/**
+         * Copy-constructor of a circular geometry.
+         *
+         * @param[in] other Other circular geometry
+         */
+		Circular(const Circular& other) = default;
+		
+		/**
+         * Move-constructor of a circular geometry.
+         *
+         * @param[in] other Other circular geometry
+         */
+		Circular(Circular&& other) = default;
+		
+		/**
+         * Destructor of a circular geometry.
+         */
+		~Circular() = default;
+		
+		/**
+         * Copy-operator of a circular geometry.
+         *
+         * @param[in] other Other circular geometry
+         * @return Reference to this circular geometry
+         */
+		Circular& operator=(const Circular& other) = default;
+		
+		/**
+         * Move-operator of a circular geometry.
+         *
+         * @param[in] other Other circular geometry
+         * @return Reference to this circular geometry
+         */
+		Circular& operator=(Circular&& other) = default;
+		
+		/**
+		 * Return the radius of the circular part of the geometry.
+		 *
+		 * @return Radius of the circular geometry
+		 */
+		[[nodiscard]]
+		float getRadius() const;
+		
+		/**
+		 * Set the radius of the circular part of the geometry.
+		 *
+		 * @param[in] radius Radius of the circular geometry
+		 */
+		void setRadius(float radius);
+		
+		/**
+		 * Return the resolution of the geometry for discrete
+		 * generation.
+		 *
+		 * @return Resolution of the circular geometry
+		 */
+		[[nodiscard]]
+		size_t getResolution() const;
+		
+		/**
+		 * Set the resolution of the geometry for any discrete
+		 * generation.
+		 *
+		 * @param[in] resolution Resolution of the circular geometry
+		 */
+		void setResolution(size_t resolution);
+		
+	};
+	
+	/** @} */
+	
+}
\ No newline at end of file
diff --git a/modules/geometry/include/vkcv/geometry/Cuboid.hpp b/modules/geometry/include/vkcv/geometry/Cuboid.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..411152513eb6a4c5d7c368c1defa70258ff9ac9c
--- /dev/null
+++ b/modules/geometry/include/vkcv/geometry/Cuboid.hpp
@@ -0,0 +1,123 @@
+#pragma once
+
+#include "Volume.hpp"
+
+namespace vkcv::geometry {
+	
+	/**
+     * @addtogroup vkcv_geometry
+     * @{
+     */
+	
+	/**
+	 * A class to use geometry in form of a cuboid.
+	 */
+	class Cuboid : public Volume {
+	private:
+		/**
+		 * Size of the cuboid in 3D-space.
+		 */
+		glm::vec3 m_size;
+		
+	public:
+		/**
+		 * Constructor creating cuboid by a given position
+		 * and size as 3D vectors.
+		 *
+		 * @param[in] position Position of the cuboid as 3D vector
+		 * @param[in] size Size of the cuboid as 3D vector
+		 */
+		Cuboid(const glm::vec3& position, const glm::vec3& size);
+		
+		/**
+		 * Constructor creating cube by a given position
+		 * as 3D vector and uniform size.
+		 *
+		 * @param[in] position Position of the cube as 3D vector
+		 * @param[in] size Uniform size of the cube
+		 */
+		Cuboid(const glm::vec3& position, float size);
+		
+		/**
+         * Copy-constructor of a cuboid.
+         *
+         * @param[in] other Other cuboid
+         */
+		Cuboid(const Cuboid& other) = default;
+		
+		/**
+         * Move-constructor of a cuboid.
+         *
+         * @param[in] other Other cuboid
+         */
+		Cuboid(Cuboid&& other) = default;
+		
+		/**
+         * Destructor of a cuboid.
+         */
+		~Cuboid() = default;
+		
+		/**
+         * Copy-operator of a cuboid.
+         *
+         * @param[in] other Other cuboid
+         * @return Reference to this cuboid
+         */
+		Cuboid& operator=(const Cuboid& other) = default;
+		
+		/**
+         * Move-operator of a cuboid.
+         *
+         * @param[in] other Other cuboid
+         * @return Reference to this cuboid
+         */
+		Cuboid& operator=(Cuboid&& other) = default;
+		
+		/**
+		 * Return the size of a cuboid as 3D vector.
+		 *
+		 * @return Size of the cuboid as 3D vector
+		 */
+		[[nodiscard]]
+		const glm::vec3& getSize() const;
+		
+		/**
+		 * Set the size of a cuboid to a specific
+		 * 3D vector.
+		 *
+		 * @param[in] position Size as 3D vector
+		 */
+		void setSize(const glm::vec3& size);
+		
+		/**
+		 * Returns the signed distance from a point to the closest
+		 * surface of the cuboid.
+		 *
+		 * The result is negative if the point is contained by the
+		 * cuboid.
+		 *
+		 * @param[in] point Point as 3D vector
+		 * @return Signed distance from point to surface
+		 */
+		[[nodiscard]]
+		float distanceTo(const glm::vec3& point) override;
+		
+		/**
+		 * Generates a vertex data structure, which can be
+		 * used for rendering via draw calls, containing
+		 * the cuboid in a discrete form.
+		 *
+		 * The vertex data will store positions, normals and
+		 * UV-coordinates as vertex attributes.
+		 *
+		 * @param[in,out] core Core instance
+		 * @return Vertex data with generated geometry
+		 */
+		[[nodiscard]]
+		VertexData generateVertexData(Core& core) const override;
+		
+	};
+	
+	/** @} */
+	
+}
diff --git a/modules/geometry/include/vkcv/geometry/Cylinder.hpp b/modules/geometry/include/vkcv/geometry/Cylinder.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..fe6ccc74b9ade02b4a8b7bc530557315aceb3703
--- /dev/null
+++ b/modules/geometry/include/vkcv/geometry/Cylinder.hpp
@@ -0,0 +1,115 @@
+#pragma once
+
+#include "Circular.hpp"
+#include "Volume.hpp"
+
+namespace vkcv::geometry {
+	
+	/**
+     * @addtogroup vkcv_geometry
+     * @{
+     */
+	
+	/**
+	 * A class to use geometry in form of a cylinder.
+	 */
+	class Cylinder : public Volume, public Circular {
+	private:
+		/**
+		 * Height of the cylinder (Y-axis)
+		 */
+		float m_height;
+	
+	public:
+		/**
+		 * Constructor creating cylinder by a given position
+		 * as 3D vector, a given height and a radius.
+		 *
+		 * @param[in] position Position of the cylinder as 3D vector
+		 * @param[in] height Height of the cylinder
+		 * @param[in] radius Radius of the cylinder
+		 */
+		Cylinder(const glm::vec3& position, float height, float radius);
+		
+		/**
+         * Copy-constructor of a cylinder.
+         *
+         * @param[in] other Other cylinder
+         */
+		Cylinder(const Cylinder& other) = default;
+		
+		/**
+         * Move-constructor of a cylinder.
+         *
+         * @param[in] other Other cylinder
+         */
+		Cylinder(Cylinder&& other) = default;
+		
+		/**
+         * Destructor of a cylinder.
+         */
+		~Cylinder() = default;
+		
+		/**
+         * Copy-operator of a cylinder.
+         *
+         * @param[in] other Other cylinder
+         * @return Reference to this cylinder
+         */
+		Cylinder& operator=(const Cylinder& other) = default;
+		
+		/**
+         * Move-operator of a cylinder.
+         *
+         * @param[in] other Other cylinder
+         * @return Reference to this cylinder
+         */
+		Cylinder& operator=(Cylinder&& other) = default;
+		
+		/**
+		 * Return the height of the cylinder.
+		 *
+		 * @return Height of the cylinder
+		 */
+		[[nodiscard]]
+		float getHeight() const;
+		
+		/**
+		 * Set the height of the cylinder.
+		 *
+		 * @param[in] height Height of the cylinder
+		 */
+		void setHeight(float height);
+		
+		/**
+		 * Returns the signed distance from a point to the closest
+		 * surface of the cylinder.
+		 *
+		 * The result is negative if the point is contained by the
+		 * cylinder.
+		 *
+		 * @param[in] point Point as 3D vector
+		 * @return Signed distance from point to surface
+		 */
+		[[nodiscard]]
+		float distanceTo(const glm::vec3& point) override;
+		
+		/**
+		 * Generates a vertex data structure, which can be
+		 * used for rendering via draw calls, containing
+		 * the cylinder in a discrete form.
+		 *
+		 * The vertex data will store positions, normals and
+		 * UV-coordinates as vertex attributes.
+		 *
+		 * @param[in,out] core Core instance
+		 * @return Vertex data with generated geometry
+		 */
+		[[nodiscard]]
+		VertexData generateVertexData(Core& core) const override;
+		
+	};
+	
+	/** @} */
+	
+}
\ No newline at end of file
diff --git a/modules/geometry/include/vkcv/geometry/Geometry.hpp b/modules/geometry/include/vkcv/geometry/Geometry.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..cf2b6eb5132e46883a81dc6d45eae18f51f48bea
--- /dev/null
+++ b/modules/geometry/include/vkcv/geometry/Geometry.hpp
@@ -0,0 +1,118 @@
+#pragma once
+
+#include <vkcv/Buffer.hpp>
+#include <vkcv/Core.hpp>
+#include <vkcv/VertexData.hpp>
+
+#include <glm/glm.hpp>
+
+namespace vkcv::geometry {
+	
+	/**
+     * @defgroup vkcv_geometry Geometry Module
+     * A module to use basic geometry for rendering.
+     * @{
+     */
+	
+	/**
+	 * A basic class to provide attributes for any kind of geometry.
+	 */
+	class Geometry {
+	private:
+		/**
+		 * Position of the geometry in 3D-coordinates.
+		 */
+		glm::vec3 m_position;
+		
+	protected:
+		/**
+		 * Generate tangent from positions and uv-coordinates
+		 * for a given triangle.
+		 *
+		 * @param[in] positions Array of positions
+		 * @param[in] uvs Array of uv-coordinates
+		 * @return Calculated tangent
+		 */
+		[[nodiscard]]
+		glm::vec3 generateTangent(const std::array<glm::vec3, 3>& positions,
+								  const  std::array<glm::vec2, 3>& uvs) const;
+		
+	public:
+		/**
+		 * Constructor creating geometry by a given position
+		 * as 3D vector.
+		 *
+		 * @param[in] position Position of the geometry as 3D vector
+		 */
+		explicit Geometry(const glm::vec3& position);
+		
+		/**
+         * Copy-constructor of a geometry.
+         *
+         * @param[in] other Other geometry
+         */
+		Geometry(const Geometry& other) = default;
+		
+		/**
+         * Move-constructor of a geometry.
+         *
+         * @param[in] other Other geometry
+         */
+		Geometry(Geometry&& other) = default;
+		
+		/**
+         * Destructor of a geometry.
+         */
+		~Geometry() = default;
+		
+		/**
+         * Copy-operator of a geometry.
+         *
+         * @param[in] other Other geometry
+         * @return Reference to this geometry
+         */
+		Geometry& operator=(const Geometry& other) = default;
+		
+		/**
+         * Move-operator of a geometry.
+         *
+         * @param[in] other Other geometry
+         * @return Reference to this geometry
+         */
+		Geometry& operator=(Geometry&& other) = default;
+		
+		/**
+		 * Return the position of a geometry as 3D vector.
+		 *
+		 * @return Position of the geometry as 3D vector
+		 */
+		[[nodiscard]]
+		const glm::vec3& getPosition() const;
+		
+		/**
+		 * Set the position of a geometry to a specific
+		 * 3D vector.
+		 *
+		 * @param[in] position Position as 3D vector
+		 */
+		void setPosition(const glm::vec3& position);
+		
+		/**
+		 * Generates a vertex data structure, which can be
+		 * used for rendering via draw calls, containing
+		 * the geometry in a discrete form.
+		 *
+		 * The vertex data will store positions, normals and
+		 * UV-coordinates as vertex attributes.
+		 *
+		 * @param[in,out] core Core instance
+		 * @return Vertex data with generated geometry
+		 */
+		[[nodiscard]]
+		virtual VertexData generateVertexData(Core& core) const = 0;
+		
+	};
+	
+	/** @} */
+
+}
diff --git a/modules/geometry/include/vkcv/geometry/Sphere.hpp b/modules/geometry/include/vkcv/geometry/Sphere.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..173d1f157dfd4e3987e278a1516dc08ba2f7cf9b
--- /dev/null
+++ b/modules/geometry/include/vkcv/geometry/Sphere.hpp
@@ -0,0 +1,93 @@
+#pragma once
+
+#include "Circular.hpp"
+#include "Volume.hpp"
+
+namespace vkcv::geometry {
+	
+	/**
+     * @addtogroup vkcv_geometry
+     * @{
+     */
+	
+	/**
+	 * A class to use geometry in form of a sphere.
+	 */
+	class Sphere : public Volume, public Circular {
+	public:
+		/**
+		 * Constructor creating sphere by a given position
+		 * as 3D vector and a radius.
+		 *
+		 * @param[in] position Position of the sphere as 3D vector
+		 * @param[in] radius Radius of the sphere
+		 */
+		Sphere(const glm::vec3& position, float radius);
+		
+		/**
+         * Copy-constructor of a sphere.
+         *
+         * @param[in] other Other sphere
+         */
+		Sphere(const Sphere& other) = default;
+		
+		/**
+         * Move-constructor of a sphere.
+         *
+         * @param[in] other Other sphere
+         */
+		Sphere(Sphere&& other) = default;
+		
+		/**
+         * Destructor of a sphere.
+         */
+		~Sphere() = default;
+		
+		/**
+         * Copy-operator of a sphere.
+         *
+         * @param[in] other Other sphere
+         * @return Reference to this sphere
+         */
+		Sphere& operator=(const Sphere& other) = default;
+		
+		/**
+         * Move-operator of a sphere.
+         *
+         * @param[in] other Other sphere
+         * @return Reference to this sphere
+         */
+		Sphere& operator=(Sphere&& other) = default;
+		
+		/**
+		 * Returns the signed distance from a point to the closest
+		 * surface of the sphere.
+		 *
+		 * The result is negative if the point is contained by the
+		 * sphere.
+		 *
+		 * @param[in] point Point as 3D vector
+		 * @return Signed distance from point to surface
+		 */
+		[[nodiscard]]
+		float distanceTo(const glm::vec3& point) override;
+		
+		/**
+		 * Generates a vertex data structure, which can be
+		 * used for rendering via draw calls, containing
+		 * the sphere in a discrete form.
+		 *
+		 * The vertex data will store positions, normals and
+		 * UV-coordinates as vertex attributes.
+		 *
+		 * @param[in,out] core Core instance
+		 * @return Vertex data with generated geometry
+		 */
+		[[nodiscard]]
+		VertexData generateVertexData(Core& core) const override;
+		
+	};
+	
+	/** @} */
+
+}
diff --git a/modules/geometry/include/vkcv/geometry/Teapot.hpp b/modules/geometry/include/vkcv/geometry/Teapot.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ef0f973dee8a617573cf91274afc51f4a499b5f0
--- /dev/null
+++ b/modules/geometry/include/vkcv/geometry/Teapot.hpp
@@ -0,0 +1,100 @@
+#pragma once
+
+#include "Geometry.hpp"
+
+namespace vkcv::geometry {
+
+	/**
+     * @addtogroup vkcv_geometry
+     * @{
+     */
+	
+	/**
+	 * A class to use geometry in form of a teapot.
+	 */
+	class Teapot : public Geometry {
+	private:
+		/**
+		 * Uniform scale of the teapot.
+		 */
+		float m_scale;
+		
+	public:
+		/**
+		 * Constructor creating teapot by a given position
+		 * as 3D vector and uniform scale.
+		 *
+		 * @param[in] position Position of the teapot as 3D vector
+		 * @param[in] scale Uniform scale of the teapot
+		 */
+		explicit Teapot(const glm::vec3& position, float scale);
+		
+		/**
+         * Copy-constructor of a teapot.
+         *
+         * @param[in] other Other teapot
+         */
+		Teapot(const Teapot& other) = default;
+		
+		/**
+         * Move-constructor of a teapot.
+         *
+         * @param[in] other Other teapot
+         */
+		Teapot(Teapot&& other) = default;
+		
+		/**
+         * Destructor of a teapot.
+         */
+		~Teapot() = default;
+		
+		/**
+         * Copy-operator of a teapot.
+         *
+         * @param[in] other Other teapot
+         * @return Reference to this teapot
+         */
+		Teapot& operator=(const Teapot& other) = default;
+		
+		/**
+         * Move-operator of a teapot.
+         *
+         * @param[in] other Other teapot
+         * @return Reference to this teapot
+         */
+		Teapot& operator=(Teapot&& other) = default;
+		
+		/**
+		 * Return the uniform scale of a teapot.
+		 *
+		 * @return Uniform scale of the teapot
+		 */
+		[[nodiscard]]
+		float getScale() const;
+		
+		/**
+		 * Set the uniform scale of a teapot.
+		 *
+		 * @param scale Uniform scale
+		 */
+		void setScale(float scale);
+		
+		/**
+		 * Generates a vertex data structure, which can be
+		 * used for rendering via draw calls, containing
+		 * the teapot in a discrete form.
+		 *
+		 * The vertex data will store positions, normals and
+		 * UV-coordinates as vertex attributes.
+		 *
+		 * @param[in,out] core Core instance
+		 * @return Vertex data with generated geometry
+		 */
+		[[nodiscard]]
+		VertexData generateVertexData(Core& core) const override;
+		
+	};
+	
+	/** @} */
+
+}
diff --git a/modules/geometry/include/vkcv/geometry/Volume.hpp b/modules/geometry/include/vkcv/geometry/Volume.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..5705b6687492f4425e7ac0dfd060a9575606704a
--- /dev/null
+++ b/modules/geometry/include/vkcv/geometry/Volume.hpp
@@ -0,0 +1,88 @@
+#pragma once
+
+#include "Geometry.hpp"
+
+namespace vkcv::geometry {
+	
+	/**
+     * @addtogroup vkcv_geometry
+     * @{
+     */
+	
+	/**
+	 * A basic class to provide functions for any volumetric geometry.
+	 */
+	class Volume : public Geometry {
+	private:
+	public:
+		/**
+		 * Constructor creating volumetric geometry by a given
+		 * position as 3D vector.
+		 *
+		 * @param[in] position Position of the geometry as 3D vector
+		 */
+		explicit Volume(const glm::vec3& position);
+		
+		/**
+         * Copy-constructor of a volumetric geometry.
+         *
+         * @param[in] other Other volumetric geometry
+         */
+		Volume(const Volume& other) = default;
+		
+		/**
+         * Move-constructor of a volumetric geometry.
+         *
+         * @param[in] other Other volumetric geometry
+         */
+		Volume(Volume&& other) = default;
+		
+		/**
+         * Destructor of a volumetric geometry.
+         */
+		~Volume() = default;
+		
+		/**
+         * Copy-operator of a volumetric geometry.
+         *
+         * @param[in] other Other volumetric geometry
+         * @return Reference to this volumetric geometry
+         */
+		Volume& operator=(const Volume& other) = default;
+		
+		/**
+         * Move-operator of a volumetric geometry.
+         *
+         * @param[in] other Other volumetric geometry
+         * @return Reference to this volumetric geometry
+         */
+		Volume& operator=(Volume&& other) = default;
+
+		/**
+		 * Returns the signed distance from a point to the closest
+		 * surface of the volumetric geometry.
+		 *
+		 * The result is negative if the point is contained by the
+		 * volumetric geometry.
+		 *
+		 * @param[in] point Point as 3D vector
+		 * @return Signed distance from point to surface
+		 */
+		[[nodiscard]]
+		virtual float distanceTo(const glm::vec3& point) = 0;
+		
+		/**
+		 * Returns as boolean value whether a point is contained
+		 * by a volumetric geometry.
+		 *
+		 * @param[in] point Point as 3D vector
+		 * @return true if the point is contained, other false.
+		 */
+		[[nodiscard]]
+		bool contains(const glm::vec3& point);
+		
+	};
+	
+	/** @} */
+	
+}
diff --git a/modules/geometry/lib/glm b/modules/geometry/lib/glm
new file mode 160000
index 0000000000000000000000000000000000000000..cc98465e3508535ba8c7f6208df934c156a018dc
--- /dev/null
+++ b/modules/geometry/lib/glm
@@ -0,0 +1 @@
+Subproject commit cc98465e3508535ba8c7f6208df934c156a018dc
diff --git a/modules/geometry/src/vkcv/geometry/Circular.cpp b/modules/geometry/src/vkcv/geometry/Circular.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f546b2cf75c517971ce314de73b72e2ce990c7f7
--- /dev/null
+++ b/modules/geometry/src/vkcv/geometry/Circular.cpp
@@ -0,0 +1,25 @@
+
+#include "vkcv/geometry/Circular.hpp"
+
+namespace vkcv::geometry {
+	
+	Circular::Circular(float radius, size_t resolution)
+	: m_radius(radius), m_resolution(resolution) {}
+	
+	float Circular::getRadius() const {
+		return m_radius;
+	}
+	
+	void Circular::setRadius(float radius) {
+		m_radius = radius;
+	}
+	
+	size_t Circular::getResolution() const {
+		return m_resolution;
+	}
+	
+	void Circular::setResolution(size_t resolution) {
+		m_resolution = resolution;
+	}
+	
+}
diff --git a/modules/geometry/src/vkcv/geometry/Cuboid.cpp b/modules/geometry/src/vkcv/geometry/Cuboid.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..73a701b2826a7694201304b029c91af3e7191e5c
--- /dev/null
+++ b/modules/geometry/src/vkcv/geometry/Cuboid.cpp
@@ -0,0 +1,261 @@
+
+#include "vkcv/geometry/Cuboid.hpp"
+
+namespace vkcv::geometry {
+	
+	Cuboid::Cuboid(const glm::vec3 &position, const glm::vec3 &size)
+	: Volume(position), m_size(size) {}
+	
+	Cuboid::Cuboid(const glm::vec3 &position, float size)
+	: Cuboid(position, glm::vec3(size)) {}
+	
+	const glm::vec3 &Cuboid::getSize() const {
+		return m_size;
+	}
+	
+	void Cuboid::setSize(const glm::vec3 &size) {
+		m_size = size;
+	}
+	
+	float Cuboid::distanceTo(const glm::vec3 &point) {
+		const auto offset = (point - getPosition());
+		const auto distance = (glm::abs(offset) - getSize() * 0.5f);
+		const auto inside = glm::lessThanEqual(distance, glm::vec3(0.0f));
+		
+		if (glm::all(inside)) {
+			return glm::max(glm::max(distance.x, distance.y), distance.z);
+		} else {
+			return glm::length(glm::vec3(glm::not_(inside)) * distance);
+		}
+	}
+	
+	VertexData Cuboid::generateVertexData(vkcv::Core &core) const {
+		std::array<float, 72> cuboidPositions = {
+				-0.5f, -0.5f, -0.5f,
+				-0.5f, -0.5f, +0.5f,
+				-0.5f, +0.5f, -0.5f,
+				-0.5f, +0.5f, +0.5f,
+				+0.5f, -0.5f, -0.5f,
+				+0.5f, -0.5f, +0.5f,
+				+0.5f, +0.5f, -0.5f,
+				+0.5f, +0.5f, +0.5f,
+				
+				-0.5f, -0.5f, -0.5f,
+				+0.5f, -0.5f, -0.5f,
+				-0.5f, -0.5f, +0.5f,
+				+0.5f, -0.5f, +0.5f,
+				-0.5f, +0.5f, -0.5f,
+				+0.5f, +0.5f, -0.5f,
+				-0.5f, +0.5f, +0.5f,
+				+0.5f, +0.5f, +0.5f,
+				
+				-0.5f, -0.5f, -0.5f,
+				-0.5f, +0.5f, -0.5f,
+				+0.5f, -0.5f, -0.5f,
+				+0.5f, +0.5f, -0.5f,
+				-0.5f, -0.5f, +0.5f,
+				-0.5f, +0.5f, +0.5f,
+				+0.5f, -0.5f, +0.5f,
+				+0.5f, +0.5f, +0.5f
+		};
+		
+		const std::array<float, 72> cuboidNormals = {
+				-1.0f, 0.0f, 0.0f,
+				-1.0f, 0.0f, 0.0f,
+				-1.0f, 0.0f, 0.0f,
+				-1.0f, 0.0f, 0.0f,
+				+1.0f, 0.0f, 0.0f,
+				+1.0f, 0.0f, 0.0f,
+				+1.0f, 0.0f, 0.0f,
+				+1.0f, 0.0f, 0.0f,
+				
+				0.0f, -1.0f, 0.0f,
+				0.0f, -1.0f, 0.0f,
+				0.0f, -1.0f, 0.0f,
+				0.0f, -1.0f, 0.0f,
+				0.0f, +1.0f, 0.0f,
+				0.0f, +1.0f, 0.0f,
+				0.0f, +1.0f, 0.0f,
+				0.0f, +1.0f, 0.0f,
+				
+				0.0f, 0.0f, -1.0f,
+				0.0f, 0.0f, -1.0f,
+				0.0f, 0.0f, -1.0f,
+				0.0f, 0.0f, -1.0f,
+				0.0f, 0.0f, +1.0f,
+				0.0f, 0.0f, +1.0f,
+				0.0f, 0.0f, +1.0f,
+				0.0f, 0.0f, +1.0f
+		};
+		
+		const std::array<float, 48> cuboidUVCoords = {
+			0.0f, 0.0f,
+			0.0f, 1.0f,
+			1.0f, 0.0f,
+			1.0f, 1.0f,
+			0.0f, 0.0f,
+			0.0f, 1.0f,
+			1.0f, 0.0f,
+			1.0f, 1.0f,
+			
+			0.0f, 0.0f,
+			0.0f, 1.0f,
+			1.0f, 0.0f,
+			1.0f, 1.0f,
+			0.0f, 0.0f,
+			0.0f, 1.0f,
+			1.0f, 0.0f,
+			1.0f, 1.0f,
+			
+			0.0f, 0.0f,
+			0.0f, 1.0f,
+			1.0f, 0.0f,
+			1.0f, 1.0f,
+			0.0f, 0.0f,
+			0.0f, 1.0f,
+			1.0f, 0.0f,
+			1.0f, 1.0f,
+		};
+		
+		const std::array<uint8_t, 36> cuboidIndices = {
+				0, 1, 3,
+				0, 3, 2,
+				4, 6, 7,
+				4, 7, 5,
+				
+				8, 9, 11,
+				8, 11, 10,
+				12, 14, 15,
+				12, 15, 13,
+				
+				16, 17, 19,
+				16, 19, 18,
+				20, 22, 23,
+				20, 23, 21
+		};
+		
+		std::vector<glm::vec3> cuboidTangents;
+		cuboidTangents.resize(24, glm::vec3(0.0f));
+		
+		std::vector<size_t> cuboidTangentWeights;
+		cuboidTangentWeights.resize(cuboidTangents.size(), 0);
+		
+		for (size_t i = 0; i < cuboidIndices.size(); i += 3) {
+			const auto index0 = cuboidIndices[i + 0];
+			const auto index1 = cuboidIndices[i + 1];
+			const auto index2 = cuboidIndices[i + 2];
+			
+			const std::array<glm::vec3, 3> positions = {
+					glm::vec3(
+							cuboidPositions[index0 * 3 + 0],
+							cuboidPositions[index0 * 3 + 1],
+							cuboidPositions[index0 * 3 + 2]
+					),
+					glm::vec3(
+							cuboidPositions[index1 * 3 + 0],
+							cuboidPositions[index1 * 3 + 1],
+							cuboidPositions[index1 * 3 + 2]
+					),
+					glm::vec3(
+							cuboidPositions[index2 * 3 + 0],
+							cuboidPositions[index2 * 3 + 1],
+							cuboidPositions[index2 * 3 + 2]
+					)
+			};
+			
+			const std::array<glm::vec2, 3> uvs = {
+					glm::vec2(
+							cuboidUVCoords[index0 * 3 + 0],
+							cuboidUVCoords[index0 * 3 + 1]
+					),
+					glm::vec2(
+							cuboidUVCoords[index1 * 3 + 0],
+							cuboidUVCoords[index1 * 3 + 1]
+					),
+					glm::vec2(
+							cuboidUVCoords[index2 * 3 + 0],
+							cuboidUVCoords[index2 * 3 + 1]
+					)
+			};
+			
+			const glm::vec3 tangent = generateTangent(positions, uvs);
+			
+			cuboidTangents[index0] += tangent;
+			cuboidTangents[index1] += tangent;
+			cuboidTangents[index2] += tangent;
+			
+			cuboidTangentWeights[index0]++;
+			cuboidTangentWeights[index1]++;
+			cuboidTangentWeights[index2]++;
+		}
+		
+		for (size_t i = 0; i < cuboidTangents.size(); i++) {
+			if (cuboidTangentWeights[i] <= 0) {
+				continue;
+			}
+			
+			cuboidTangents[i] /= cuboidTangentWeights[i];
+		}
+		
+		const auto& position = getPosition();
+		const auto& size = getSize();
+		
+		for (size_t i = 0; i < 24; i++) {
+			cuboidPositions[i * 3 + 0] = cuboidPositions[i * 3 + 0] * size.x + position.x;
+			cuboidPositions[i * 3 + 1] = cuboidPositions[i * 3 + 1] * size.y + position.y;
+			cuboidPositions[i * 3 + 2] = cuboidPositions[i * 3 + 2] * size.z + position.z;
+		}
+		
+		auto positionBuffer = buffer<float>(core, BufferType::VERTEX, cuboidPositions.size());
+		positionBuffer.fill(cuboidPositions);
+		
+		auto normalBuffer = buffer<float>(core, BufferType::VERTEX, cuboidNormals.size());
+		normalBuffer.fill(cuboidNormals);
+		
+		auto uvBuffer = buffer<float>(core, BufferType::VERTEX, cuboidUVCoords.size());
+		uvBuffer.fill(cuboidUVCoords);
+		
+		auto tangentBuffer = buffer<glm::vec3>(core, BufferType::VERTEX, cuboidTangents.size());
+		tangentBuffer.fill(cuboidTangents);
+		
+		VertexData data ({
+			vkcv::vertexBufferBinding(positionBuffer.getHandle()),
+			vkcv::vertexBufferBinding(normalBuffer.getHandle()),
+			vkcv::vertexBufferBinding(uvBuffer.getHandle()),
+			vkcv::vertexBufferBinding(tangentBuffer.getHandle())
+		});
+		
+		const auto& featureManager = core.getContext().getFeatureManager();
+		
+		const bool index8Bit = featureManager.checkFeatures<vk::PhysicalDeviceIndexTypeUint8FeaturesEXT>(
+				vk::StructureType::ePhysicalDeviceIndexTypeUint8FeaturesEXT,
+				[](const vk::PhysicalDeviceIndexTypeUint8FeaturesEXT& features) {
+					return features.indexTypeUint8;
+				}
+		);
+		
+		if (index8Bit) {
+			auto indexBuffer = buffer<uint8_t>(core, BufferType::INDEX, cuboidIndices.size());
+			indexBuffer.fill(cuboidIndices);
+			
+			data.setIndexBuffer(indexBuffer.getHandle(), IndexBitCount::Bit8);
+			data.setCount(indexBuffer.getCount());
+		} else {
+			std::vector<uint16_t> cuboidIndices16;
+			cuboidIndices16.resize(cuboidIndices.size());
+			
+			for (size_t i = 0; i < cuboidIndices16.size(); i++) {
+				cuboidIndices16[i] = static_cast<uint16_t>(cuboidIndices[i]);
+			}
+			
+			auto indexBuffer = buffer<uint16_t>(core, BufferType::INDEX, cuboidIndices16.size());
+			indexBuffer.fill(cuboidIndices16);
+			
+			data.setIndexBuffer(indexBuffer.getHandle());
+			data.setCount(indexBuffer.getCount());
+		}
+		
+		return data;
+	}
+	
+}
diff --git a/modules/geometry/src/vkcv/geometry/Cylinder.cpp b/modules/geometry/src/vkcv/geometry/Cylinder.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0583c4c134922f16ed00d8ff36f81c027051bb90
--- /dev/null
+++ b/modules/geometry/src/vkcv/geometry/Cylinder.cpp
@@ -0,0 +1,192 @@
+
+#include "vkcv/geometry/Cylinder.hpp"
+
+#include <numbers>
+
+namespace vkcv::geometry {
+	
+	Cylinder::Cylinder(const glm::vec3 &position, float height, float radius)
+	: Volume(position), Circular(radius), m_height(height) {}
+	
+	float Cylinder::getHeight() const {
+		return m_height;
+	}
+	
+	void Cylinder::setHeight(float height) {
+		m_height = height;
+	}
+	
+	float Cylinder::distanceTo(const glm::vec3 &point) {
+		const auto& position = getPosition();
+		
+		const auto verticalDistance = glm::abs(position.y - point.y) - getHeight();
+		const auto circularDistance = glm::distance(
+				glm::vec2(position.x, position.z),
+				glm::vec2(point.x, point.z)
+		) - getRadius();
+		
+		if (circularDistance <= 0.0f) {
+			return glm::max(verticalDistance, circularDistance);
+		} else
+		if (verticalDistance <= 0.0f) {
+			return circularDistance;
+		} else {
+			return glm::length(glm::vec2(verticalDistance, circularDistance));
+		}
+	}
+	
+	VertexData Cylinder::generateVertexData(vkcv::Core &core) const {
+		const auto& position = getPosition();
+		const auto radius = getRadius();
+		const auto height = getHeight();
+		const auto resolution = getResolution();
+		
+		const size_t vertexCount = (resolution * 4 + 2);
+		
+		std::vector<glm::vec3> cylinderVertices;
+		std::vector<glm::vec3> cylinderNormals;
+		std::vector<glm::vec2> cylinderUVCoords;
+		
+		cylinderVertices.reserve(vertexCount);
+		cylinderNormals.reserve(vertexCount);
+		cylinderUVCoords.reserve(vertexCount);
+		
+		std::vector<uint32_t> cylinderIndices;
+		cylinderIndices.reserve(resolution * 12);
+		
+		size_t i, j;
+		float u, v;
+		float phi;
+		float sinPhi, cosPhi;
+		float x, y, z;
+		
+		for (j = 0; j < 2; j++) {
+			v = static_cast<float>(j);
+			
+			x = position.x;
+			y = position.y + height * (v - 0.5f);
+			z = position.z;
+			
+			cylinderVertices.push_back(glm::vec3(x, y, z));
+			cylinderNormals.push_back(glm::vec3(0.0f, v * 2.0f - 1.0f, 0.0f));
+			cylinderUVCoords.push_back(glm::vec2(0.5f, 0.5f));
+		}
+		
+		size_t offset = 0;
+		
+		for (i = 0; i < resolution; i++) {
+			u = static_cast<float>(i) / static_cast<float>(resolution - 1);
+			phi = 2.0f * std::numbers::pi_v<float> * u;
+			
+			sinPhi = std::sin(phi);
+			cosPhi = std::cos(phi);
+			
+			x = position.x + radius * sinPhi;
+			z = position.z + radius * cosPhi;
+			
+			for (j = 0; j < 2; j++) {
+				v = static_cast<float>(j);
+				
+				y = position.y + height * (v - 0.5f);
+				
+				cylinderVertices.push_back(glm::vec3(x, y, z));
+				cylinderNormals.push_back(glm::vec3(0.0f, v * 2.0f - 1.0f, 0.0f));
+				cylinderUVCoords.push_back(glm::vec2((sinPhi + 1.0f) * 0.5f, (cosPhi + 1.0f) * 0.5f));
+				
+				cylinderVertices.push_back(glm::vec3(x, y, z));
+				cylinderNormals.push_back(glm::vec3(sinPhi, 0.0f, cosPhi));
+				cylinderUVCoords.push_back(glm::vec2(u, v));
+				
+				if (j == 1) {
+					cylinderIndices.push_back(2 + (offset + j * 2) % (vertexCount - 2));
+					cylinderIndices.push_back(2 + (offset + j * 2 + 4) % (vertexCount - 2));
+					cylinderIndices.push_back(j);
+					
+					cylinderIndices.push_back(2 + (offset + j * 2 + 5) % (vertexCount - 2));
+					cylinderIndices.push_back(2 + (offset + j * 2 + 1) % (vertexCount - 2));
+					cylinderIndices.push_back(2 + (offset + j * 2 + 3) % (vertexCount - 2));
+				} else {
+					cylinderIndices.push_back(2 + (offset + j * 2 + 4) % (vertexCount - 2));
+					cylinderIndices.push_back(2 + (offset + j * 2) % (vertexCount - 2));
+					cylinderIndices.push_back(j);
+					
+					cylinderIndices.push_back(2 + (offset + j * 2 + 1) % (vertexCount - 2));
+					cylinderIndices.push_back(2 + (offset + j * 2 + 5) % (vertexCount - 2));
+					cylinderIndices.push_back(2 + (offset + j * 2 + 3) % (vertexCount - 2));
+				}
+			}
+			
+			offset += 4;
+		}
+		
+		std::vector<glm::vec3> cylinderTangents;
+		cylinderTangents.resize(cylinderVertices.size(), glm::vec3(0.0f));
+		
+		std::vector<size_t> cylinderTangentWeights;
+		cylinderTangentWeights.resize(cylinderTangents.size(), 0);
+		
+		for (i = 0; i < cylinderIndices.size(); i += 3) {
+			const auto index0 = cylinderIndices[i + 0];
+			const auto index1 = cylinderIndices[i + 1];
+			const auto index2 = cylinderIndices[i + 2];
+			
+			const std::array<glm::vec3, 3> positions = {
+					cylinderVertices[index0],
+					cylinderVertices[index1],
+					cylinderVertices[index2]
+			};
+			
+			const std::array<glm::vec2, 3> uvs = {
+					cylinderUVCoords[index0],
+					cylinderUVCoords[index1],
+					cylinderUVCoords[index2]
+			};
+			
+			const glm::vec3 tangent = generateTangent(positions, uvs);
+			
+			cylinderTangents[index0] += tangent;
+			cylinderTangents[index1] += tangent;
+			cylinderTangents[index2] += tangent;
+			
+			cylinderTangentWeights[index0]++;
+			cylinderTangentWeights[index1]++;
+			cylinderTangentWeights[index2]++;
+		}
+		
+		for (i = 0; i < cylinderTangents.size(); i++) {
+			if (cylinderTangentWeights[i] <= 0) {
+				continue;
+			}
+			
+			cylinderTangents[i] /= cylinderTangentWeights[i];
+		}
+		
+		auto positionBuffer = buffer<glm::vec3>(core, BufferType::VERTEX, cylinderVertices.size());
+		positionBuffer.fill(cylinderVertices);
+		
+		auto normalBuffer = buffer<glm::vec3>(core, BufferType::VERTEX, cylinderNormals.size());
+		normalBuffer.fill(cylinderNormals);
+		
+		auto uvBuffer = buffer<glm::vec2>(core, BufferType::VERTEX, cylinderUVCoords.size());
+		uvBuffer.fill(cylinderUVCoords);
+		
+		auto tangentBuffer = buffer<glm::vec3>(core, BufferType::VERTEX, cylinderTangents.size());
+		tangentBuffer.fill(cylinderTangents);
+		
+		auto indexBuffer = buffer<uint32_t>(core, BufferType::INDEX, cylinderIndices.size());
+		indexBuffer.fill(cylinderIndices);
+		
+		VertexData data ({
+			vkcv::vertexBufferBinding(positionBuffer.getHandle()),
+			vkcv::vertexBufferBinding(normalBuffer.getHandle()),
+			vkcv::vertexBufferBinding(uvBuffer.getHandle()),
+			vkcv::vertexBufferBinding(tangentBuffer.getHandle())
+		});
+		
+		data.setIndexBuffer(indexBuffer.getHandle(), IndexBitCount::Bit32);
+		data.setCount(indexBuffer.getCount());
+		
+		return data;
+	}
+	
+}
diff --git a/modules/geometry/src/vkcv/geometry/Geometry.cpp b/modules/geometry/src/vkcv/geometry/Geometry.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..feaa84a04f2e180b08fd92977d6494ac997af6b2
--- /dev/null
+++ b/modules/geometry/src/vkcv/geometry/Geometry.cpp
@@ -0,0 +1,31 @@
+
+#include "vkcv/geometry/Geometry.hpp"
+
+namespace vkcv::geometry {
+	
+	Geometry::Geometry(const glm::vec3 &position)
+	: m_position(position) {}
+	
+	const glm::vec3 &Geometry::getPosition() const {
+		return m_position;
+	}
+	
+	void Geometry::setPosition(const glm::vec3 &position) {
+		m_position = position;
+	}
+	
+	glm::vec3 Geometry::generateTangent(const std::array<glm::vec3, 3>& positions,
+										const  std::array<glm::vec2, 3>& uvs) const {
+		auto delta1 = positions[1] - positions[0];
+		auto delta2 = positions[2] - positions[0];
+		
+		auto deltaUV1 = uvs[1] - uvs[0];
+		auto deltaUV2 = uvs[2] - uvs[0];
+		
+		return glm::normalize(glm::vec3(
+				delta1 * deltaUV2.y -
+				delta2 * deltaUV1.y
+		));
+	}
+	
+}
diff --git a/modules/geometry/src/vkcv/geometry/Sphere.cpp b/modules/geometry/src/vkcv/geometry/Sphere.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d516a1dad4e11651e4ad6d6c056dcf9442d29f18
--- /dev/null
+++ b/modules/geometry/src/vkcv/geometry/Sphere.cpp
@@ -0,0 +1,152 @@
+
+#include "vkcv/geometry/Sphere.hpp"
+
+#include <numbers>
+
+namespace vkcv::geometry {
+	
+	Sphere::Sphere(const glm::vec3& position, float radius)
+	: Volume(position), Circular(radius) {}
+	
+	float Sphere::distanceTo(const glm::vec3 &point) {
+		return glm::distance(getPosition(), point) - getRadius();
+	}
+	
+	VertexData Sphere::generateVertexData(vkcv::Core &core) const {
+		const auto& position = getPosition();
+		const auto radius = getRadius();
+		const auto resolution = getResolution();
+		
+		const size_t vertexCount = resolution * (resolution + 1);
+		
+		std::vector<glm::vec3> sphereVertices;
+		std::vector<glm::vec3> sphereNormals;
+		std::vector<glm::vec2> sphereUVCoords;
+		
+		sphereVertices.reserve(vertexCount);
+		sphereNormals.reserve(vertexCount);
+		sphereUVCoords.reserve(vertexCount);
+		
+		std::vector<uint32_t> sphereIndices;
+		sphereIndices.reserve((resolution - 1) * resolution * 6);
+		
+		size_t i, j;
+		float u, v;
+		float phi, theta;
+		float sinTheta;
+		float x, y, z;
+		
+		for (i = 0; i < resolution; i++) {
+			v = static_cast<float>(i) / static_cast<float>(resolution - 1);
+			theta = std::numbers::pi_v<float> * v;
+			sinTheta = std::sin(theta);
+			
+			y = position.y + radius * std::cos(theta);
+			
+			for (j = 0; j < resolution; j++) {
+				u = static_cast<float>(j) / static_cast<float>(resolution);
+				phi = 2.0f * std::numbers::pi_v<float> * u;
+				
+				x = position.x + radius * sinTheta * std::sin(phi);
+				z = position.z + radius * sinTheta * std::cos(phi);
+				
+				sphereVertices.push_back(glm::vec3(x, y, z));
+				sphereNormals.push_back(glm::vec3(x, y, z) / radius);
+				sphereUVCoords.push_back(glm::vec2(u, 1.0f - v));
+			}
+			
+			x = position.x;
+			z = position.z + radius * sinTheta;
+			
+			sphereVertices.push_back(glm::vec3(x, y, z));
+			sphereNormals.push_back(glm::vec3(x, y, z) / radius);
+			sphereUVCoords.push_back(glm::vec2(1.0f, 1.0f - v));
+		}
+		
+		size_t offset = 0;
+		
+		for (i = 1; i < resolution; i++) {
+			for (j = 0; j < resolution; j++) {
+				sphereIndices.push_back((offset + j) % vertexCount);
+				sphereIndices.push_back((offset + resolution + j + 1) % vertexCount);
+				sphereIndices.push_back((offset + resolution + j + 2) % vertexCount);
+				
+				sphereIndices.push_back((offset + resolution + j + 2) % vertexCount);
+				sphereIndices.push_back((offset + j + 1) % vertexCount);
+				sphereIndices.push_back((offset + j) % vertexCount);
+			}
+			
+			offset += (resolution + 1);
+		}
+		
+		std::vector<glm::vec3> sphereTangents;
+		sphereTangents.resize(sphereVertices.size(), glm::vec3(0.0f));
+		
+		std::vector<size_t> sphereTangentWeights;
+		sphereTangentWeights.resize(sphereTangents.size(), 0);
+		
+		for (i = 0; i < sphereIndices.size(); i += 3) {
+			const auto index0 = sphereIndices[i + 0];
+			const auto index1 = sphereIndices[i + 1];
+			const auto index2 = sphereIndices[i + 2];
+			
+			const std::array<glm::vec3, 3> positions = {
+					sphereVertices[index0],
+					sphereVertices[index1],
+					sphereVertices[index2]
+			};
+			
+			const std::array<glm::vec2, 3> uvs = {
+					sphereUVCoords[index0],
+					sphereUVCoords[index1],
+					sphereUVCoords[index2]
+			};
+			
+			const glm::vec3 tangent = generateTangent(positions, uvs);
+			
+			sphereTangents[index0] += tangent;
+			sphereTangents[index1] += tangent;
+			sphereTangents[index2] += tangent;
+			
+			sphereTangentWeights[index0]++;
+			sphereTangentWeights[index1]++;
+			sphereTangentWeights[index2]++;
+		}
+		
+		for (i = 0; i < sphereTangents.size(); i++) {
+			if (sphereTangentWeights[i] <= 0) {
+				continue;
+			}
+			
+			sphereTangents[i] /= sphereTangentWeights[i];
+		}
+		
+		auto positionBuffer = buffer<glm::vec3>(core, BufferType::VERTEX, sphereVertices.size());
+		positionBuffer.fill(sphereVertices);
+		
+		auto normalBuffer = buffer<glm::vec3>(core, BufferType::VERTEX, sphereNormals.size());
+		normalBuffer.fill(sphereNormals);
+		
+		auto uvBuffer = buffer<glm::vec2>(core, BufferType::VERTEX, sphereUVCoords.size());
+		uvBuffer.fill(sphereUVCoords);
+		
+		auto tangentBuffer = buffer<glm::vec3>(core, BufferType::VERTEX, sphereTangents.size());
+		tangentBuffer.fill(sphereTangents);
+		
+		auto indexBuffer = buffer<uint32_t>(core, BufferType::INDEX, sphereIndices.size());
+		indexBuffer.fill(sphereIndices);
+		
+		VertexData data ({
+			vkcv::vertexBufferBinding(positionBuffer.getHandle()),
+			vkcv::vertexBufferBinding(normalBuffer.getHandle()),
+			vkcv::vertexBufferBinding(uvBuffer.getHandle()),
+			vkcv::vertexBufferBinding(tangentBuffer.getHandle())
+		});
+		
+		data.setIndexBuffer(indexBuffer.getHandle(), IndexBitCount::Bit32);
+		data.setCount(indexBuffer.getCount());
+		
+		return data;
+	}
+
+}
diff --git a/modules/geometry/src/vkcv/geometry/Teapot.cpp b/modules/geometry/src/vkcv/geometry/Teapot.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7becdef5941769c79da0b0b17a45ec3e5d0a1cb6
--- /dev/null
+++ b/modules/geometry/src/vkcv/geometry/Teapot.cpp
@@ -0,0 +1,14949 @@
+
+#include "vkcv/geometry/Teapot.hpp"
+
+namespace vkcv::geometry {
+	
+	Teapot::Teapot(const glm::vec3 &position, float scale)
+	: Geometry(position), m_scale(scale) {}
+	
+	float Teapot::getScale() const {
+		return m_scale;
+	}
+	
+	void Teapot::setScale(float scale) {
+		m_scale = scale;
+	}
+	
+	VertexData Teapot::generateVertexData(Core& core) const {
+		const size_t vertexCount = 3872;
+		const size_t indexCount = 19200;
+		
+		std::array<float, (vertexCount * 3)> teapotVertices = {
+				0.69999999f, 0.45000005f, 0.00000001f,
+				0.69098389f, 0.44999996f, 0.11485600f,
+				0.66483200f, 0.45000011f, 0.22332802f,
+				0.62288797f, 0.45000002f, 0.32407200f,
+				0.56649601f, 0.45000008f, 0.41574404f,
+				0.49699998f, 0.45000005f, 0.49700001f,
+				0.41574395f, 0.45000005f, 0.56649601f,
+				0.32407200f, 0.45000008f, 0.62288797f,
+				0.22332799f, 0.45000005f, 0.66483200f,
+				0.11485596f, 0.45000005f, 0.69098401f,
+				0.00000000f, 0.45000005f, 0.69999999f,
+				0.69296241f, 0.46771872f, 0.00000001f,
+				0.68403697f, 0.46771869f, 0.11370128f,
+				0.65814799f, 0.46771878f, 0.22108276f,
+				0.61662567f, 0.46771872f, 0.32081389f,
+				0.56080067f, 0.46771872f, 0.41156429f,
+				0.49200332f, 0.46771872f, 0.49200332f,
+				0.41156423f, 0.46771872f, 0.56080067f,
+				0.32081389f, 0.46771872f, 0.61662567f,
+				0.22108275f, 0.46771872f, 0.65814799f,
+				0.11370124f, 0.46771872f, 0.68403703f,
+				0.00000000f, 0.46771872f, 0.69296241f,
+				0.69020003f, 0.48150003f, 0.00000001f,
+				0.68131018f, 0.48149997f, 0.11324803f,
+				0.65552443f, 0.48150006f, 0.22020143f,
+				0.61416763f, 0.48150000f, 0.31953502f,
+				0.55856514f, 0.48150009f, 0.40992361f,
+				0.49004203f, 0.48150003f, 0.49004203f,
+				0.40992361f, 0.48150006f, 0.55856514f,
+				0.31953505f, 0.48150003f, 0.61416763f,
+				0.22020143f, 0.48150003f, 0.65552437f,
+				0.11324799f, 0.48150003f, 0.68131030f,
+				0.00000000f, 0.48150003f, 0.69020003f,
+				0.69111252f, 0.49134374f, 0.00000001f,
+				0.68221092f, 0.49134374f, 0.11339775f,
+				0.65639102f, 0.49134377f, 0.22049254f,
+				0.61497957f, 0.49134377f, 0.31995746f,
+				0.55930358f, 0.49134377f, 0.41046557f,
+				0.49068987f, 0.49134374f, 0.49068987f,
+				0.41046554f, 0.49134374f, 0.55930358f,
+				0.31995746f, 0.49134374f, 0.61497957f,
+				0.22049253f, 0.49134377f, 0.65639102f,
+				0.11339770f, 0.49134374f, 0.68221098f,
+				0.00000000f, 0.49134374f, 0.69111252f,
+				0.69510001f, 0.49725008f, 0.00000001f,
+				0.68614709f, 0.49725005f, 0.11405202f,
+				0.66017824f, 0.49725014f, 0.22176473f,
+				0.61852777f, 0.49725008f, 0.32180351f,
+				0.56253058f, 0.49725008f, 0.41283381f,
+				0.49352103f, 0.49725008f, 0.49352100f,
+				0.41283381f, 0.49725008f, 0.56253058f,
+				0.32180351f, 0.49725008f, 0.61852777f,
+				0.22176471f, 0.49725008f, 0.66017818f,
+				0.11405198f, 0.49725008f, 0.68614715f,
+				0.00000000f, 0.49725008f, 0.69510001f,
+				0.70156252f, 0.49921876f, 0.00000001f,
+				0.69252634f, 0.49921870f, 0.11511238f,
+				0.66631603f, 0.49921876f, 0.22382653f,
+				0.62427837f, 0.49921873f, 0.32479537f,
+				0.56776059f, 0.49921879f, 0.41667202f,
+				0.49810940f, 0.49921876f, 0.49810940f,
+				0.41667199f, 0.49921879f, 0.56776053f,
+				0.32479542f, 0.49921876f, 0.62427843f,
+				0.22382650f, 0.49921876f, 0.66631603f,
+				0.11511234f, 0.49921876f, 0.69252640f,
+				0.00000000f, 0.49921876f, 0.70156252f,
+				0.70989996f, 0.49725002f, 0.00000001f,
+				0.70075637f, 0.49724996f, 0.11648039f,
+				0.67423457f, 0.49725008f, 0.22648652f,
+				0.63169742f, 0.49724999f, 0.32865530f,
+				0.57450789f, 0.49725002f, 0.42162383f,
+				0.50402898f, 0.49725002f, 0.50402898f,
+				0.42162377f, 0.49725002f, 0.57450783f,
+				0.32865530f, 0.49725002f, 0.63169742f,
+				0.22648649f, 0.49725005f, 0.67423463f,
+				0.11648035f, 0.49725002f, 0.70075643f,
+				0.00000000f, 0.49725002f, 0.70989996f,
+				0.71951252f, 0.49134377f, 0.00000001f,
+				0.71024513f, 0.49134374f, 0.11805762f,
+				0.68336421f, 0.49134380f, 0.22955328f,
+				0.64025098f, 0.49134377f, 0.33310553f,
+				0.58228713f, 0.49134383f, 0.42733288f,
+				0.51085389f, 0.49134377f, 0.51085389f,
+				0.42733288f, 0.49134377f, 0.58228713f,
+				0.33310553f, 0.49134377f, 0.64025104f,
+				0.22955328f, 0.49134380f, 0.68336427f,
+				0.11805756f, 0.49134377f, 0.71024519f,
+				0.00000000f, 0.49134377f, 0.71951252f,
+				0.72979999f, 0.48150003f, 0.00000001f,
+				0.72040009f, 0.48149997f, 0.11974559f,
+				0.69313490f, 0.48150006f, 0.23283541f,
+				0.64940518f, 0.48150000f, 0.33786824f,
+				0.59061253f, 0.48150009f, 0.43344283f,
+				0.51815796f, 0.48150003f, 0.51815796f,
+				0.43344280f, 0.48150006f, 0.59061259f,
+				0.33786821f, 0.48150003f, 0.64940524f,
+				0.23283540f, 0.48150003f, 0.69313484f,
+				0.11974555f, 0.48150003f, 0.72040015f,
+				0.00000000f, 0.48150003f, 0.72979999f,
+				0.74016249f, 0.46771878f, 0.00000001f,
+				0.73062909f, 0.46771872f, 0.12144586f,
+				0.70297670f, 0.46771881f, 0.23614147f,
+				0.65862614f, 0.46771878f, 0.34266564f,
+				0.59899879f, 0.46771878f, 0.43959734f,
+				0.52551538f, 0.46771878f, 0.52551538f,
+				0.43959731f, 0.46771878f, 0.59899873f,
+				0.34266564f, 0.46771878f, 0.65862620f,
+				0.23614144f, 0.46771878f, 0.70297670f,
+				0.12144582f, 0.46771878f, 0.73062921f,
+				0.00000000f, 0.46771878f, 0.74016249f,
+				0.75000000f, 0.45000005f, 0.00000001f,
+				0.74033999f, 0.44999996f, 0.12306000f,
+				0.71232003f, 0.45000011f, 0.23928000f,
+				0.66737998f, 0.45000002f, 0.34721997f,
+				0.60696000f, 0.45000008f, 0.44544002f,
+				0.53250003f, 0.45000005f, 0.53250003f,
+				0.44543999f, 0.45000005f, 0.60696000f,
+				0.34722000f, 0.45000008f, 0.66738003f,
+				0.23927999f, 0.45000005f, 0.71231997f,
+				0.12305996f, 0.45000005f, 0.74033999f,
+				0.00000000f, 0.45000005f, 0.75000000f,
+				0.00000000f, 0.45000005f, -0.69999999f,
+				0.11485599f, 0.44999996f, -0.69098389f,
+				0.22332802f, 0.45000011f, -0.66483200f,
+				0.32407200f, 0.45000002f, -0.62288797f,
+				0.41574404f, 0.45000008f, -0.56649601f,
+				0.49700001f, 0.45000005f, -0.49699998f,
+				0.56649601f, 0.45000005f, -0.41574395f,
+				0.62288797f, 0.45000008f, -0.32407200f,
+				0.66483200f, 0.45000005f, -0.22332799f,
+				0.69098401f, 0.45000005f, -0.11485595f,
+				0.69999999f, 0.45000005f, 0.00000001f,
+				0.00000000f, 0.46771872f, -0.69296241f,
+				0.11370128f, 0.46771869f, -0.68403697f,
+				0.22108276f, 0.46771878f, -0.65814799f,
+				0.32081389f, 0.46771872f, -0.61662567f,
+				0.41156429f, 0.46771872f, -0.56080067f,
+				0.49200332f, 0.46771872f, -0.49200332f,
+				0.56080067f, 0.46771872f, -0.41156423f,
+				0.61662567f, 0.46771872f, -0.32081389f,
+				0.65814799f, 0.46771872f, -0.22108275f,
+				0.68403703f, 0.46771872f, -0.11370123f,
+				0.69296241f, 0.46771872f, 0.00000001f,
+				0.00000000f, 0.48150003f, -0.69020003f,
+				0.11324802f, 0.48149997f, -0.68131018f,
+				0.22020143f, 0.48150006f, -0.65552443f,
+				0.31953502f, 0.48150000f, -0.61416763f,
+				0.40992361f, 0.48150009f, -0.55856514f,
+				0.49004203f, 0.48150003f, -0.49004203f,
+				0.55856514f, 0.48150006f, -0.40992361f,
+				0.61416763f, 0.48150003f, -0.31953505f,
+				0.65552437f, 0.48150003f, -0.22020143f,
+				0.68131030f, 0.48150003f, -0.11324798f,
+				0.69020003f, 0.48150003f, 0.00000001f,
+				0.00000000f, 0.49134374f, -0.69111252f,
+				0.11339774f, 0.49134374f, -0.68221092f,
+				0.22049254f, 0.49134377f, -0.65639102f,
+				0.31995746f, 0.49134377f, -0.61497957f,
+				0.41046557f, 0.49134377f, -0.55930358f,
+				0.49068987f, 0.49134374f, -0.49068987f,
+				0.55930358f, 0.49134374f, -0.41046554f,
+				0.61497957f, 0.49134374f, -0.31995746f,
+				0.65639102f, 0.49134377f, -0.22049253f,
+				0.68221098f, 0.49134374f, -0.11339770f,
+				0.69111252f, 0.49134374f, 0.00000001f,
+				0.00000000f, 0.49725008f, -0.69510001f,
+				0.11405201f, 0.49725005f, -0.68614709f,
+				0.22176473f, 0.49725014f, -0.66017824f,
+				0.32180351f, 0.49725008f, -0.61852777f,
+				0.41283381f, 0.49725008f, -0.56253058f,
+				0.49352100f, 0.49725008f, -0.49352103f,
+				0.56253058f, 0.49725008f, -0.41283381f,
+				0.61852777f, 0.49725008f, -0.32180351f,
+				0.66017818f, 0.49725008f, -0.22176471f,
+				0.68614715f, 0.49725008f, -0.11405197f,
+				0.69510001f, 0.49725008f, 0.00000001f,
+				0.00000000f, 0.49921876f, -0.70156252f,
+				0.11511238f, 0.49921870f, -0.69252634f,
+				0.22382653f, 0.49921876f, -0.66631603f,
+				0.32479537f, 0.49921873f, -0.62427837f,
+				0.41667202f, 0.49921879f, -0.56776059f,
+				0.49810940f, 0.49921876f, -0.49810940f,
+				0.56776053f, 0.49921879f, -0.41667199f,
+				0.62427843f, 0.49921876f, -0.32479542f,
+				0.66631603f, 0.49921876f, -0.22382650f,
+				0.69252640f, 0.49921876f, -0.11511233f,
+				0.70156252f, 0.49921876f, 0.00000001f,
+				0.00000000f, 0.49725002f, -0.70989996f,
+				0.11648039f, 0.49724996f, -0.70075637f,
+				0.22648652f, 0.49725008f, -0.67423457f,
+				0.32865530f, 0.49724999f, -0.63169742f,
+				0.42162383f, 0.49725002f, -0.57450789f,
+				0.50402898f, 0.49725002f, -0.50402898f,
+				0.57450783f, 0.49725002f, -0.42162377f,
+				0.63169742f, 0.49725002f, -0.32865530f,
+				0.67423463f, 0.49725005f, -0.22648649f,
+				0.70075643f, 0.49725002f, -0.11648034f,
+				0.70989996f, 0.49725002f, 0.00000001f,
+				0.00000000f, 0.49134377f, -0.71951252f,
+				0.11805761f, 0.49134374f, -0.71024513f,
+				0.22955328f, 0.49134380f, -0.68336421f,
+				0.33310553f, 0.49134377f, -0.64025098f,
+				0.42733288f, 0.49134383f, -0.58228713f,
+				0.51085389f, 0.49134377f, -0.51085389f,
+				0.58228713f, 0.49134377f, -0.42733288f,
+				0.64025104f, 0.49134377f, -0.33310553f,
+				0.68336427f, 0.49134380f, -0.22955328f,
+				0.71024519f, 0.49134377f, -0.11805756f,
+				0.71951252f, 0.49134377f, 0.00000001f,
+				0.00000000f, 0.48150003f, -0.72979999f,
+				0.11974558f, 0.48149997f, -0.72040009f,
+				0.23283541f, 0.48150006f, -0.69313490f,
+				0.33786824f, 0.48150000f, -0.64940518f,
+				0.43344283f, 0.48150009f, -0.59061253f,
+				0.51815796f, 0.48150003f, -0.51815796f,
+				0.59061259f, 0.48150006f, -0.43344280f,
+				0.64940524f, 0.48150003f, -0.33786821f,
+				0.69313484f, 0.48150003f, -0.23283540f,
+				0.72040015f, 0.48150003f, -0.11974554f,
+				0.72979999f, 0.48150003f, 0.00000001f,
+				0.00000000f, 0.46771878f, -0.74016249f,
+				0.12144586f, 0.46771872f, -0.73062909f,
+				0.23614147f, 0.46771881f, -0.70297670f,
+				0.34266564f, 0.46771878f, -0.65862614f,
+				0.43959734f, 0.46771878f, -0.59899879f,
+				0.52551538f, 0.46771878f, -0.52551538f,
+				0.59899873f, 0.46771878f, -0.43959731f,
+				0.65862620f, 0.46771878f, -0.34266564f,
+				0.70297670f, 0.46771878f, -0.23614144f,
+				0.73062921f, 0.46771878f, -0.12144581f,
+				0.74016249f, 0.46771878f, 0.00000001f,
+				0.00000000f, 0.45000005f, -0.75000000f,
+				0.12305999f, 0.44999996f, -0.74033999f,
+				0.23928000f, 0.45000011f, -0.71232003f,
+				0.34721997f, 0.45000002f, -0.66737998f,
+				0.44544002f, 0.45000008f, -0.60696000f,
+				0.53250003f, 0.45000005f, -0.53250003f,
+				0.60696000f, 0.45000005f, -0.44543999f,
+				0.66738003f, 0.45000008f, -0.34722000f,
+				0.71231997f, 0.45000005f, -0.23927999f,
+				0.74033999f, 0.45000005f, -0.12305995f,
+				0.75000000f, 0.45000005f, 0.00000001f,
+				0.00000000f, 0.45000005f, 0.69999999f,
+				-0.11485599f, 0.44999996f, 0.69098389f,
+				-0.22332802f, 0.45000011f, 0.66483200f,
+				-0.32407200f, 0.45000002f, 0.62288797f,
+				-0.41574404f, 0.45000008f, 0.56649601f,
+				-0.49700001f, 0.45000005f, 0.49699998f,
+				-0.56649601f, 0.45000005f, 0.41574395f,
+				-0.62288797f, 0.45000008f, 0.32407200f,
+				-0.66483200f, 0.45000005f, 0.22332799f,
+				-0.69098401f, 0.45000005f, 0.11485597f,
+				-0.69999999f, 0.45000005f, 0.00000001f,
+				0.00000000f, 0.46771872f, 0.69296241f,
+				-0.11370128f, 0.46771869f, 0.68403697f,
+				-0.22108276f, 0.46771878f, 0.65814799f,
+				-0.32081389f, 0.46771872f, 0.61662567f,
+				-0.41156429f, 0.46771872f, 0.56080067f,
+				-0.49200332f, 0.46771872f, 0.49200332f,
+				-0.56080067f, 0.46771872f, 0.41156423f,
+				-0.61662567f, 0.46771872f, 0.32081389f,
+				-0.65814799f, 0.46771872f, 0.22108275f,
+				-0.68403703f, 0.46771872f, 0.11370125f,
+				-0.69296241f, 0.46771872f, 0.00000001f,
+				0.00000000f, 0.48150003f, 0.69020003f,
+				-0.11324802f, 0.48149997f, 0.68131018f,
+				-0.22020143f, 0.48150006f, 0.65552443f,
+				-0.31953502f, 0.48150000f, 0.61416763f,
+				-0.40992361f, 0.48150009f, 0.55856514f,
+				-0.49004203f, 0.48150003f, 0.49004203f,
+				-0.55856514f, 0.48150006f, 0.40992361f,
+				-0.61416763f, 0.48150003f, 0.31953505f,
+				-0.65552437f, 0.48150003f, 0.22020143f,
+				-0.68131030f, 0.48150003f, 0.11324800f,
+				-0.69020003f, 0.48150003f, 0.00000001f,
+				0.00000000f, 0.49134374f, 0.69111252f,
+				-0.11339774f, 0.49134374f, 0.68221092f,
+				-0.22049254f, 0.49134377f, 0.65639102f,
+				-0.31995746f, 0.49134377f, 0.61497957f,
+				-0.41046557f, 0.49134377f, 0.55930358f,
+				-0.49068987f, 0.49134374f, 0.49068987f,
+				-0.55930358f, 0.49134374f, 0.41046554f,
+				-0.61497957f, 0.49134374f, 0.31995746f,
+				-0.65639102f, 0.49134377f, 0.22049253f,
+				-0.68221098f, 0.49134374f, 0.11339771f,
+				-0.69111252f, 0.49134374f, 0.00000001f,
+				0.00000000f, 0.49725008f, 0.69510001f,
+				-0.11405201f, 0.49725005f, 0.68614709f,
+				-0.22176473f, 0.49725014f, 0.66017824f,
+				-0.32180351f, 0.49725008f, 0.61852777f,
+				-0.41283381f, 0.49725008f, 0.56253058f,
+				-0.49352100f, 0.49725008f, 0.49352103f,
+				-0.56253058f, 0.49725008f, 0.41283381f,
+				-0.61852777f, 0.49725008f, 0.32180351f,
+				-0.66017818f, 0.49725008f, 0.22176471f,
+				-0.68614715f, 0.49725008f, 0.11405198f,
+				-0.69510001f, 0.49725008f, 0.00000001f,
+				0.00000000f, 0.49921876f, 0.70156252f,
+				-0.11511238f, 0.49921870f, 0.69252634f,
+				-0.22382653f, 0.49921876f, 0.66631603f,
+				-0.32479537f, 0.49921873f, 0.62427837f,
+				-0.41667202f, 0.49921879f, 0.56776059f,
+				-0.49810940f, 0.49921876f, 0.49810940f,
+				-0.56776053f, 0.49921879f, 0.41667199f,
+				-0.62427843f, 0.49921876f, 0.32479542f,
+				-0.66631603f, 0.49921876f, 0.22382650f,
+				-0.69252640f, 0.49921876f, 0.11511235f,
+				-0.70156252f, 0.49921876f, 0.00000001f,
+				0.00000000f, 0.49725002f, 0.70989996f,
+				-0.11648039f, 0.49724996f, 0.70075637f,
+				-0.22648652f, 0.49725008f, 0.67423457f,
+				-0.32865530f, 0.49724999f, 0.63169742f,
+				-0.42162383f, 0.49725002f, 0.57450789f,
+				-0.50402898f, 0.49725002f, 0.50402898f,
+				-0.57450783f, 0.49725002f, 0.42162377f,
+				-0.63169742f, 0.49725002f, 0.32865530f,
+				-0.67423463f, 0.49725005f, 0.22648649f,
+				-0.70075643f, 0.49725002f, 0.11648036f,
+				-0.70989996f, 0.49725002f, 0.00000001f,
+				0.00000000f, 0.49134377f, 0.71951252f,
+				-0.11805761f, 0.49134374f, 0.71024513f,
+				-0.22955328f, 0.49134380f, 0.68336421f,
+				-0.33310553f, 0.49134377f, 0.64025098f,
+				-0.42733288f, 0.49134383f, 0.58228713f,
+				-0.51085389f, 0.49134377f, 0.51085389f,
+				-0.58228713f, 0.49134377f, 0.42733288f,
+				-0.64025104f, 0.49134377f, 0.33310553f,
+				-0.68336427f, 0.49134380f, 0.22955328f,
+				-0.71024519f, 0.49134377f, 0.11805757f,
+				-0.71951252f, 0.49134377f, 0.00000001f,
+				0.00000000f, 0.48150003f, 0.72979999f,
+				-0.11974558f, 0.48149997f, 0.72040009f,
+				-0.23283541f, 0.48150006f, 0.69313490f,
+				-0.33786824f, 0.48150000f, 0.64940518f,
+				-0.43344283f, 0.48150009f, 0.59061253f,
+				-0.51815796f, 0.48150003f, 0.51815796f,
+				-0.59061259f, 0.48150006f, 0.43344280f,
+				-0.64940524f, 0.48150003f, 0.33786821f,
+				-0.69313484f, 0.48150003f, 0.23283540f,
+				-0.72040015f, 0.48150003f, 0.11974555f,
+				-0.72979999f, 0.48150003f, 0.00000001f,
+				0.00000000f, 0.46771878f, 0.74016249f,
+				-0.12144586f, 0.46771872f, 0.73062909f,
+				-0.23614147f, 0.46771881f, 0.70297670f,
+				-0.34266564f, 0.46771878f, 0.65862614f,
+				-0.43959734f, 0.46771878f, 0.59899879f,
+				-0.52551538f, 0.46771878f, 0.52551538f,
+				-0.59899873f, 0.46771878f, 0.43959731f,
+				-0.65862620f, 0.46771878f, 0.34266564f,
+				-0.70297670f, 0.46771878f, 0.23614144f,
+				-0.73062921f, 0.46771878f, 0.12144583f,
+				-0.74016249f, 0.46771878f, 0.00000001f,
+				0.00000000f, 0.45000005f, 0.75000000f,
+				-0.12305999f, 0.44999996f, 0.74033999f,
+				-0.23928000f, 0.45000011f, 0.71232003f,
+				-0.34721997f, 0.45000002f, 0.66737998f,
+				-0.44544002f, 0.45000008f, 0.60696000f,
+				-0.53250003f, 0.45000005f, 0.53250003f,
+				-0.60696000f, 0.45000005f, 0.44543999f,
+				-0.66738003f, 0.45000008f, 0.34722000f,
+				-0.71231997f, 0.45000005f, 0.23927999f,
+				-0.74033999f, 0.45000005f, 0.12305997f,
+				-0.75000000f, 0.45000005f, 0.00000001f,
+				-0.69999999f, 0.45000005f, 0.00000001f,
+				-0.69098389f, 0.44999996f, -0.11485598f,
+				-0.66483200f, 0.45000011f, -0.22332802f,
+				-0.62288797f, 0.45000002f, -0.32407200f,
+				-0.56649601f, 0.45000008f, -0.41574404f,
+				-0.49699998f, 0.45000005f, -0.49700001f,
+				-0.41574395f, 0.45000005f, -0.56649601f,
+				-0.32407200f, 0.45000008f, -0.62288797f,
+				-0.22332799f, 0.45000005f, -0.66483200f,
+				-0.11485596f, 0.45000005f, -0.69098401f,
+				0.00000000f, 0.45000005f, -0.69999999f,
+				-0.69296241f, 0.46771872f, 0.00000001f,
+				-0.68403697f, 0.46771869f, -0.11370127f,
+				-0.65814799f, 0.46771878f, -0.22108276f,
+				-0.61662567f, 0.46771872f, -0.32081389f,
+				-0.56080067f, 0.46771872f, -0.41156429f,
+				-0.49200332f, 0.46771872f, -0.49200332f,
+				-0.41156423f, 0.46771872f, -0.56080067f,
+				-0.32081389f, 0.46771872f, -0.61662567f,
+				-0.22108275f, 0.46771872f, -0.65814799f,
+				-0.11370124f, 0.46771872f, -0.68403703f,
+				0.00000000f, 0.46771872f, -0.69296241f,
+				-0.69020003f, 0.48150003f, 0.00000001f,
+				-0.68131018f, 0.48149997f, -0.11324801f,
+				-0.65552443f, 0.48150006f, -0.22020143f,
+				-0.61416763f, 0.48150000f, -0.31953502f,
+				-0.55856514f, 0.48150009f, -0.40992361f,
+				-0.49004203f, 0.48150003f, -0.49004203f,
+				-0.40992361f, 0.48150006f, -0.55856514f,
+				-0.31953505f, 0.48150003f, -0.61416763f,
+				-0.22020143f, 0.48150003f, -0.65552437f,
+				-0.11324799f, 0.48150003f, -0.68131030f,
+				0.00000000f, 0.48150003f, -0.69020003f,
+				-0.69111252f, 0.49134374f, 0.00000001f,
+				-0.68221092f, 0.49134374f, -0.11339773f,
+				-0.65639102f, 0.49134377f, -0.22049254f,
+				-0.61497957f, 0.49134377f, -0.31995746f,
+				-0.55930358f, 0.49134377f, -0.41046557f,
+				-0.49068987f, 0.49134374f, -0.49068987f,
+				-0.41046554f, 0.49134374f, -0.55930358f,
+				-0.31995746f, 0.49134374f, -0.61497957f,
+				-0.22049253f, 0.49134377f, -0.65639102f,
+				-0.11339770f, 0.49134374f, -0.68221098f,
+				0.00000000f, 0.49134374f, -0.69111252f,
+				-0.69510001f, 0.49725008f, 0.00000001f,
+				-0.68614709f, 0.49725005f, -0.11405201f,
+				-0.66017824f, 0.49725014f, -0.22176473f,
+				-0.61852777f, 0.49725008f, -0.32180351f,
+				-0.56253058f, 0.49725008f, -0.41283381f,
+				-0.49352103f, 0.49725008f, -0.49352100f,
+				-0.41283381f, 0.49725008f, -0.56253058f,
+				-0.32180351f, 0.49725008f, -0.61852777f,
+				-0.22176471f, 0.49725008f, -0.66017818f,
+				-0.11405198f, 0.49725008f, -0.68614715f,
+				0.00000000f, 0.49725008f, -0.69510001f,
+				-0.70156252f, 0.49921876f, 0.00000001f,
+				-0.69252634f, 0.49921870f, -0.11511236f,
+				-0.66631603f, 0.49921876f, -0.22382653f,
+				-0.62427837f, 0.49921873f, -0.32479537f,
+				-0.56776059f, 0.49921879f, -0.41667202f,
+				-0.49810940f, 0.49921876f, -0.49810940f,
+				-0.41667199f, 0.49921879f, -0.56776053f,
+				-0.32479542f, 0.49921876f, -0.62427843f,
+				-0.22382650f, 0.49921876f, -0.66631603f,
+				-0.11511234f, 0.49921876f, -0.69252640f,
+				0.00000000f, 0.49921876f, -0.70156252f,
+				-0.70989996f, 0.49725002f, 0.00000001f,
+				-0.70075637f, 0.49724996f, -0.11648037f,
+				-0.67423457f, 0.49725008f, -0.22648652f,
+				-0.63169742f, 0.49724999f, -0.32865530f,
+				-0.57450789f, 0.49725002f, -0.42162383f,
+				-0.50402898f, 0.49725002f, -0.50402898f,
+				-0.42162377f, 0.49725002f, -0.57450783f,
+				-0.32865530f, 0.49725002f, -0.63169742f,
+				-0.22648649f, 0.49725005f, -0.67423463f,
+				-0.11648035f, 0.49725002f, -0.70075643f,
+				0.00000000f, 0.49725002f, -0.70989996f,
+				-0.71951252f, 0.49134377f, 0.00000001f,
+				-0.71024513f, 0.49134374f, -0.11805760f,
+				-0.68336421f, 0.49134380f, -0.22955328f,
+				-0.64025098f, 0.49134377f, -0.33310553f,
+				-0.58228713f, 0.49134383f, -0.42733288f,
+				-0.51085389f, 0.49134377f, -0.51085389f,
+				-0.42733288f, 0.49134377f, -0.58228713f,
+				-0.33310553f, 0.49134377f, -0.64025104f,
+				-0.22955328f, 0.49134380f, -0.68336427f,
+				-0.11805756f, 0.49134377f, -0.71024519f,
+				0.00000000f, 0.49134377f, -0.71951252f,
+				-0.72979999f, 0.48150003f, 0.00000001f,
+				-0.72040009f, 0.48149997f, -0.11974557f,
+				-0.69313490f, 0.48150006f, -0.23283541f,
+				-0.64940518f, 0.48150000f, -0.33786824f,
+				-0.59061253f, 0.48150009f, -0.43344283f,
+				-0.51815796f, 0.48150003f, -0.51815796f,
+				-0.43344280f, 0.48150006f, -0.59061259f,
+				-0.33786821f, 0.48150003f, -0.64940524f,
+				-0.23283540f, 0.48150003f, -0.69313484f,
+				-0.11974555f, 0.48150003f, -0.72040015f,
+				0.00000000f, 0.48150003f, -0.72979999f,
+				-0.74016249f, 0.46771878f, 0.00000001f,
+				-0.73062909f, 0.46771872f, -0.12144585f,
+				-0.70297670f, 0.46771881f, -0.23614147f,
+				-0.65862614f, 0.46771878f, -0.34266564f,
+				-0.59899879f, 0.46771878f, -0.43959734f,
+				-0.52551538f, 0.46771878f, -0.52551538f,
+				-0.43959731f, 0.46771878f, -0.59899873f,
+				-0.34266564f, 0.46771878f, -0.65862620f,
+				-0.23614144f, 0.46771878f, -0.70297670f,
+				-0.12144582f, 0.46771878f, -0.73062921f,
+				0.00000000f, 0.46771878f, -0.74016249f,
+				-0.75000000f, 0.45000005f, 0.00000001f,
+				-0.74033999f, 0.44999996f, -0.12305998f,
+				-0.71232003f, 0.45000011f, -0.23928000f,
+				-0.66737998f, 0.45000002f, -0.34721997f,
+				-0.60696000f, 0.45000008f, -0.44544002f,
+				-0.53250003f, 0.45000005f, -0.53250003f,
+				-0.44543999f, 0.45000005f, -0.60696000f,
+				-0.34722000f, 0.45000008f, -0.66738003f,
+				-0.23927999f, 0.45000005f, -0.71231997f,
+				-0.12305996f, 0.45000005f, -0.74033999f,
+				0.00000000f, 0.45000005f, -0.75000000f,
+				0.75000000f, 0.45000005f, 0.00000001f,
+				0.74033999f, 0.44999996f, 0.12306000f,
+				0.71232003f, 0.45000011f, 0.23928000f,
+				0.66737998f, 0.45000002f, 0.34721997f,
+				0.60696000f, 0.45000008f, 0.44544002f,
+				0.53250003f, 0.45000005f, 0.53250003f,
+				0.44543999f, 0.45000005f, 0.60696000f,
+				0.34722000f, 0.45000008f, 0.66738003f,
+				0.23927999f, 0.45000005f, 0.71231997f,
+				0.12305996f, 0.45000005f, 0.74033999f,
+				0.00000000f, 0.45000005f, 0.75000000f,
+				0.78737491f, 0.37128749f, 0.00000000f,
+				0.77723348f, 0.37128747f, 0.12919247f,
+				0.74781722f, 0.37128752f, 0.25120407f,
+				0.70063770f, 0.37128749f, 0.36452308f,
+				0.63720679f, 0.37128752f, 0.46763775f,
+				0.55903620f, 0.37128749f, 0.55903620f,
+				0.46763766f, 0.37128749f, 0.63720679f,
+				0.36452311f, 0.37128749f, 0.70063770f,
+				0.25120407f, 0.37128752f, 0.74781728f,
+				0.12919241f, 0.37128749f, 0.77723348f,
+				0.00000000f, 0.37128749f, 0.78737491f,
+				0.82400006f, 0.29280004f, 0.00000000f,
+				0.81338686f, 0.29280004f, 0.13520193f,
+				0.78260237f, 0.29280004f, 0.26288900f,
+				0.73322815f, 0.29280004f, 0.38147905f,
+				0.66684681f, 0.29280004f, 0.48939016f,
+				0.58504003f, 0.29280001f, 0.58504003f,
+				0.48939011f, 0.29280004f, 0.66684675f,
+				0.38147908f, 0.29280007f, 0.73322821f,
+				0.26288897f, 0.29280004f, 0.78260231f,
+				0.13520187f, 0.29280004f, 0.81338698f,
+				0.00000000f, 0.29280004f, 0.82400006f,
+				0.85912502f, 0.21476251f, 0.00000000f,
+				0.84805942f, 0.21476249f, 0.14096522f,
+				0.81596267f, 0.21476252f, 0.27409527f,
+				0.76448381f, 0.21476249f, 0.39774051f,
+				0.69527274f, 0.21476252f, 0.51025152f,
+				0.60997874f, 0.21476251f, 0.60997874f,
+				0.51025152f, 0.21476251f, 0.69527274f,
+				0.39774054f, 0.21476251f, 0.76448381f,
+				0.27409524f, 0.21476251f, 0.81596261f,
+				0.14096518f, 0.21476251f, 0.84805954f,
+				0.00000000f, 0.21476251f, 0.85912502f,
+				0.89200008f, 0.13740003f, 0.00000000f,
+				0.88051099f, 0.13740002f, 0.14635937f,
+				0.84718603f, 0.13740005f, 0.28458369f,
+				0.79373735f, 0.13740002f, 0.41296035f,
+				0.72187787f, 0.13740005f, 0.52977669f,
+				0.63332003f, 0.13740002f, 0.63332003f,
+				0.52977669f, 0.13740002f, 0.72187781f,
+				0.41296035f, 0.13740002f, 0.79373741f,
+				0.28458369f, 0.13740000f, 0.84718597f,
+				0.14635932f, 0.13740000f, 0.88051111f,
+				0.00000000f, 0.13740000f, 0.89200008f,
+				0.92187500f, 0.06093751f, 0.00000000f,
+				0.91000110f, 0.06093750f, 0.15126126f,
+				0.87556010f, 0.06093751f, 0.29411504f,
+				0.82032120f, 0.06093750f, 0.42679128f,
+				0.74605507f, 0.06093750f, 0.54752004f,
+				0.65453124f, 0.06093751f, 0.65453124f,
+				0.54751998f, 0.06093750f, 0.74605501f,
+				0.42679128f, 0.06093750f, 0.82032126f,
+				0.29411501f, 0.06093750f, 0.87556005f,
+				0.15126120f, 0.06093750f, 0.91000128f,
+				0.00000000f, 0.06093750f, 0.92187500f,
+				0.94800001f, -0.01440001f, -0.00000000f,
+				0.93578976f, -0.01440001f, 0.15554784f,
+				0.90037251f, -0.01440001f, 0.30244994f,
+				0.84356833f, -0.01440001f, 0.43888608f,
+				0.76719749f, -0.01440001f, 0.56303620f,
+				0.67308003f, -0.01440001f, 0.67308003f,
+				0.56303614f, -0.01440002f, 0.76719743f,
+				0.43888611f, -0.01440002f, 0.84356833f,
+				0.30244991f, -0.01440002f, 0.90037251f,
+				0.15554780f, -0.01440002f, 0.93578976f,
+				0.00000000f, -0.01440002f, 0.94800001f,
+				0.96962506f, -0.08838750f, -0.00000000f,
+				0.95713621f, -0.08838749f, 0.15909605f,
+				0.92091113f, -0.08838750f, 0.30934918f,
+				0.86281121f, -0.08838750f, 0.44889757f,
+				0.78469825f, -0.08838750f, 0.57587969f,
+				0.68843377f, -0.08838750f, 0.68843377f,
+				0.57587969f, -0.08838750f, 0.78469819f,
+				0.44889760f, -0.08838750f, 0.86281121f,
+				0.30934915f, -0.08838750f, 0.92091107f,
+				0.15909600f, -0.08838750f, 0.95713627f,
+				0.00000000f, -0.08838750f, 0.96962506f,
+				0.98600000f, -0.16080001f, -0.00000000f,
+				0.97330022f, -0.16080000f, 0.16178288f,
+				0.93646342f, -0.16080002f, 0.31457347f,
+				0.87738222f, -0.16080001f, 0.45647857f,
+				0.79795015f, -0.16080002f, 0.58560514f,
+				0.70006001f, -0.16080001f, 0.70006001f,
+				0.58560514f, -0.16080001f, 0.79795015f,
+				0.45647860f, -0.16080001f, 0.87738228f,
+				0.31457344f, -0.16080001f, 0.93646336f,
+				0.16178282f, -0.16080001f, 0.97330034f,
+				0.00000000f, -0.16080001f, 0.98600000f,
+				0.99637496f, -0.23141253f, -0.00000000f,
+				0.98354161f, -0.23141250f, 0.16348520f,
+				0.94631720f, -0.23141254f, 0.31788349f,
+				0.88661432f, -0.23141253f, 0.46128178f,
+				0.80634636f, -0.23141254f, 0.59176707f,
+				0.70742619f, -0.23141253f, 0.70742619f,
+				0.59176701f, -0.23141253f, 0.80634630f,
+				0.46128178f, -0.23141253f, 0.88661432f,
+				0.31788346f, -0.23141253f, 0.94631708f,
+				0.16348514f, -0.23141253f, 0.98354173f,
+				0.00000000f, -0.23141253f, 0.99637496f,
+				1.00000000f, -0.30000001f, -0.00000000f,
+				0.98711991f, -0.29999998f, 0.16407999f,
+				0.94976002f, -0.30000004f, 0.31904000f,
+				0.88984001f, -0.30000004f, 0.46296000f,
+				0.80928004f, -0.30000001f, 0.59392005f,
+				0.71000004f, -0.30000001f, 0.71000004f,
+				0.59391999f, -0.30000001f, 0.80928004f,
+				0.46296003f, -0.30000001f, 0.88984001f,
+				0.31904000f, -0.30000001f, 0.94976002f,
+				0.16407993f, -0.30000001f, 0.98712003f,
+				0.00000000f, -0.30000001f, 1.00000000f,
+				0.00000000f, 0.45000005f, -0.75000000f,
+				0.12305999f, 0.44999996f, -0.74033999f,
+				0.23928000f, 0.45000011f, -0.71232003f,
+				0.34721997f, 0.45000002f, -0.66737998f,
+				0.44544002f, 0.45000008f, -0.60696000f,
+				0.53250003f, 0.45000005f, -0.53250003f,
+				0.60696000f, 0.45000005f, -0.44543999f,
+				0.66738003f, 0.45000008f, -0.34722000f,
+				0.71231997f, 0.45000005f, -0.23927999f,
+				0.74033999f, 0.45000005f, -0.12305995f,
+				0.75000000f, 0.45000005f, 0.00000001f,
+				0.00000000f, 0.37128749f, -0.78737491f,
+				0.12919247f, 0.37128747f, -0.77723348f,
+				0.25120407f, 0.37128752f, -0.74781722f,
+				0.36452308f, 0.37128749f, -0.70063770f,
+				0.46763775f, 0.37128752f, -0.63720679f,
+				0.55903620f, 0.37128749f, -0.55903620f,
+				0.63720679f, 0.37128749f, -0.46763766f,
+				0.70063770f, 0.37128749f, -0.36452311f,
+				0.74781728f, 0.37128752f, -0.25120407f,
+				0.77723348f, 0.37128749f, -0.12919241f,
+				0.78737491f, 0.37128749f, 0.00000000f,
+				0.00000000f, 0.29280004f, -0.82400006f,
+				0.13520193f, 0.29280004f, -0.81338686f,
+				0.26288900f, 0.29280004f, -0.78260237f,
+				0.38147905f, 0.29280004f, -0.73322815f,
+				0.48939016f, 0.29280004f, -0.66684681f,
+				0.58504003f, 0.29280001f, -0.58504003f,
+				0.66684675f, 0.29280004f, -0.48939011f,
+				0.73322821f, 0.29280007f, -0.38147908f,
+				0.78260231f, 0.29280004f, -0.26288897f,
+				0.81338698f, 0.29280004f, -0.13520187f,
+				0.82400006f, 0.29280004f, 0.00000000f,
+				0.00000000f, 0.21476251f, -0.85912502f,
+				0.14096522f, 0.21476249f, -0.84805942f,
+				0.27409527f, 0.21476252f, -0.81596267f,
+				0.39774051f, 0.21476249f, -0.76448381f,
+				0.51025152f, 0.21476252f, -0.69527274f,
+				0.60997874f, 0.21476251f, -0.60997874f,
+				0.69527274f, 0.21476251f, -0.51025152f,
+				0.76448381f, 0.21476251f, -0.39774054f,
+				0.81596261f, 0.21476251f, -0.27409524f,
+				0.84805954f, 0.21476251f, -0.14096518f,
+				0.85912502f, 0.21476251f, 0.00000000f,
+				0.00000000f, 0.13740005f, -0.89200008f,
+				0.14635937f, 0.13740005f, -0.88051099f,
+				0.28458369f, 0.13740005f, -0.84718603f,
+				0.41296035f, 0.13740005f, -0.79373735f,
+				0.52977669f, 0.13740005f, -0.72187787f,
+				0.63332003f, 0.13740003f, -0.63332003f,
+				0.72187781f, 0.13740003f, -0.52977669f,
+				0.79373741f, 0.13740005f, -0.41296035f,
+				0.84718597f, 0.13740003f, -0.28458369f,
+				0.88051111f, 0.13740003f, -0.14635932f,
+				0.89200008f, 0.13740003f, 0.00000000f,
+				0.00000000f, 0.06093752f, -0.92187500f,
+				0.15126126f, 0.06093751f, -0.91000110f,
+				0.29411504f, 0.06093752f, -0.87556010f,
+				0.42679128f, 0.06093752f, -0.82032120f,
+				0.54752004f, 0.06093752f, -0.74605507f,
+				0.65453124f, 0.06093752f, -0.65453124f,
+				0.74605501f, 0.06093752f, -0.54751998f,
+				0.82032126f, 0.06093751f, -0.42679128f,
+				0.87556005f, 0.06093752f, -0.29411501f,
+				0.91000128f, 0.06093751f, -0.15126120f,
+				0.92187500f, 0.06093751f, 0.00000000f,
+				0.00000000f, -0.01440000f, -0.94800001f,
+				0.15554784f, -0.01440000f, -0.93578976f,
+				0.30244994f, -0.01440000f, -0.90037251f,
+				0.43888608f, -0.01440000f, -0.84356833f,
+				0.56303620f, -0.01440000f, -0.76719749f,
+				0.67308003f, -0.01440000f, -0.67308003f,
+				0.76719743f, -0.01440001f, -0.56303614f,
+				0.84356833f, -0.01440001f, -0.43888611f,
+				0.90037251f, -0.01440001f, -0.30244991f,
+				0.93578976f, -0.01440001f, -0.15554780f,
+				0.94800001f, -0.01440001f, -0.00000000f,
+				0.00000000f, -0.08838749f, -0.96962506f,
+				0.15909605f, -0.08838748f, -0.95713621f,
+				0.30934918f, -0.08838750f, -0.92091113f,
+				0.44889757f, -0.08838750f, -0.86281121f,
+				0.57587969f, -0.08838750f, -0.78469825f,
+				0.68843377f, -0.08838749f, -0.68843377f,
+				0.78469819f, -0.08838750f, -0.57587969f,
+				0.86281121f, -0.08838750f, -0.44889760f,
+				0.92091107f, -0.08838750f, -0.30934915f,
+				0.95713627f, -0.08838749f, -0.15909600f,
+				0.96962506f, -0.08838750f, -0.00000000f,
+				0.00000000f, -0.16080000f, -0.98600000f,
+				0.16178288f, -0.16079998f, -0.97330022f,
+				0.31457347f, -0.16080001f, -0.93646342f,
+				0.45647857f, -0.16080000f, -0.87738222f,
+				0.58560514f, -0.16080001f, -0.79795015f,
+				0.70006001f, -0.16080000f, -0.70006001f,
+				0.79795015f, -0.16080000f, -0.58560514f,
+				0.87738228f, -0.16080001f, -0.45647860f,
+				0.93646336f, -0.16080001f, -0.31457344f,
+				0.97330034f, -0.16080001f, -0.16178282f,
+				0.98600000f, -0.16080001f, -0.00000000f,
+				0.00000000f, -0.23141253f, -0.99637496f,
+				0.16348520f, -0.23141250f, -0.98354161f,
+				0.31788349f, -0.23141254f, -0.94631720f,
+				0.46128178f, -0.23141253f, -0.88661432f,
+				0.59176707f, -0.23141254f, -0.80634636f,
+				0.70742619f, -0.23141253f, -0.70742619f,
+				0.80634630f, -0.23141253f, -0.59176701f,
+				0.88661432f, -0.23141253f, -0.46128178f,
+				0.94631708f, -0.23141253f, -0.31788346f,
+				0.98354173f, -0.23141253f, -0.16348514f,
+				0.99637496f, -0.23141253f, -0.00000000f,
+				0.00000000f, -0.30000001f, -1.00000000f,
+				0.16407999f, -0.29999998f, -0.98711991f,
+				0.31904000f, -0.30000004f, -0.94976002f,
+				0.46296000f, -0.30000004f, -0.88984001f,
+				0.59392005f, -0.30000001f, -0.80928004f,
+				0.71000004f, -0.30000001f, -0.71000004f,
+				0.80928004f, -0.30000001f, -0.59391999f,
+				0.88984001f, -0.30000001f, -0.46296003f,
+				0.94976002f, -0.30000001f, -0.31904000f,
+				0.98712003f, -0.30000001f, -0.16407993f,
+				1.00000000f, -0.30000001f, -0.00000000f,
+				0.00000000f, 0.45000005f, 0.75000000f,
+				-0.12305999f, 0.44999996f, 0.74033999f,
+				-0.23928000f, 0.45000011f, 0.71232003f,
+				-0.34721997f, 0.45000002f, 0.66737998f,
+				-0.44544002f, 0.45000008f, 0.60696000f,
+				-0.53250003f, 0.45000005f, 0.53250003f,
+				-0.60696000f, 0.45000005f, 0.44543999f,
+				-0.66738003f, 0.45000008f, 0.34722000f,
+				-0.71231997f, 0.45000005f, 0.23927999f,
+				-0.74033999f, 0.45000005f, 0.12305997f,
+				-0.75000000f, 0.45000005f, 0.00000001f,
+				0.00000000f, 0.37128749f, 0.78737491f,
+				-0.12919247f, 0.37128747f, 0.77723348f,
+				-0.25120407f, 0.37128752f, 0.74781722f,
+				-0.36452308f, 0.37128749f, 0.70063770f,
+				-0.46763775f, 0.37128752f, 0.63720679f,
+				-0.55903620f, 0.37128749f, 0.55903620f,
+				-0.63720679f, 0.37128749f, 0.46763766f,
+				-0.70063770f, 0.37128749f, 0.36452311f,
+				-0.74781728f, 0.37128752f, 0.25120407f,
+				-0.77723348f, 0.37128749f, 0.12919241f,
+				-0.78737491f, 0.37128749f, 0.00000000f,
+				0.00000000f, 0.29280004f, 0.82400006f,
+				-0.13520193f, 0.29280004f, 0.81338686f,
+				-0.26288900f, 0.29280004f, 0.78260237f,
+				-0.38147905f, 0.29280004f, 0.73322815f,
+				-0.48939016f, 0.29280004f, 0.66684681f,
+				-0.58504003f, 0.29280001f, 0.58504003f,
+				-0.66684675f, 0.29280004f, 0.48939011f,
+				-0.73322821f, 0.29280007f, 0.38147908f,
+				-0.78260231f, 0.29280004f, 0.26288897f,
+				-0.81338698f, 0.29280004f, 0.13520187f,
+				-0.82400006f, 0.29280004f, 0.00000000f,
+				0.00000000f, 0.21476251f, 0.85912502f,
+				-0.14096522f, 0.21476249f, 0.84805942f,
+				-0.27409527f, 0.21476252f, 0.81596267f,
+				-0.39774051f, 0.21476249f, 0.76448381f,
+				-0.51025152f, 0.21476252f, 0.69527274f,
+				-0.60997874f, 0.21476251f, 0.60997874f,
+				-0.69527274f, 0.21476251f, 0.51025152f,
+				-0.76448381f, 0.21476251f, 0.39774054f,
+				-0.81596261f, 0.21476251f, 0.27409524f,
+				-0.84805954f, 0.21476251f, 0.14096518f,
+				-0.85912502f, 0.21476251f, 0.00000000f,
+				0.00000000f, 0.13740000f, 0.89200008f,
+				-0.14635937f, 0.13740000f, 0.88051099f,
+				-0.28458369f, 0.13740002f, 0.84718603f,
+				-0.41296035f, 0.13740002f, 0.79373735f,
+				-0.52977669f, 0.13740002f, 0.72187787f,
+				-0.63332003f, 0.13740002f, 0.63332003f,
+				-0.72187781f, 0.13740002f, 0.52977669f,
+				-0.79373741f, 0.13740003f, 0.41296035f,
+				-0.84718597f, 0.13740003f, 0.28458369f,
+				-0.88051111f, 0.13740003f, 0.14635932f,
+				-0.89200008f, 0.13740003f, 0.00000000f,
+				0.00000000f, 0.06093750f, 0.92187500f,
+				-0.15126126f, 0.06093750f, 0.91000110f,
+				-0.29411504f, 0.06093751f, 0.87556010f,
+				-0.42679128f, 0.06093750f, 0.82032120f,
+				-0.54752004f, 0.06093750f, 0.74605507f,
+				-0.65453124f, 0.06093750f, 0.65453124f,
+				-0.74605501f, 0.06093750f, 0.54751998f,
+				-0.82032126f, 0.06093751f, 0.42679128f,
+				-0.87556005f, 0.06093751f, 0.29411501f,
+				-0.91000128f, 0.06093751f, 0.15126120f,
+				-0.92187500f, 0.06093751f, 0.00000000f,
+				0.00000000f, -0.01440002f, 0.94800001f,
+				-0.15554784f, -0.01440002f, 0.93578976f,
+				-0.30244994f, -0.01440002f, 0.90037251f,
+				-0.43888608f, -0.01440002f, 0.84356833f,
+				-0.56303620f, -0.01440002f, 0.76719749f,
+				-0.67308003f, -0.01440001f, 0.67308003f,
+				-0.76719743f, -0.01440001f, 0.56303614f,
+				-0.84356833f, -0.01440001f, 0.43888611f,
+				-0.90037251f, -0.01440001f, 0.30244991f,
+				-0.93578976f, -0.01440001f, 0.15554780f,
+				-0.94800001f, -0.01440001f, -0.00000000f,
+				0.00000000f, -0.08838750f, 0.96962506f,
+				-0.15909605f, -0.08838750f, 0.95713621f,
+				-0.30934918f, -0.08838750f, 0.92091113f,
+				-0.44889757f, -0.08838750f, 0.86281121f,
+				-0.57587969f, -0.08838751f, 0.78469825f,
+				-0.68843377f, -0.08838750f, 0.68843377f,
+				-0.78469819f, -0.08838750f, 0.57587969f,
+				-0.86281121f, -0.08838750f, 0.44889760f,
+				-0.92091107f, -0.08838750f, 0.30934915f,
+				-0.95713627f, -0.08838750f, 0.15909600f,
+				-0.96962506f, -0.08838750f, -0.00000000f,
+				0.00000000f, -0.16080001f, 0.98600000f,
+				-0.16178288f, -0.16080000f, 0.97330022f,
+				-0.31457347f, -0.16080002f, 0.93646342f,
+				-0.45647857f, -0.16080001f, 0.87738222f,
+				-0.58560514f, -0.16080002f, 0.79795015f,
+				-0.70006001f, -0.16080001f, 0.70006001f,
+				-0.79795015f, -0.16080001f, 0.58560514f,
+				-0.87738228f, -0.16080001f, 0.45647860f,
+				-0.93646336f, -0.16080001f, 0.31457344f,
+				-0.97330034f, -0.16080001f, 0.16178282f,
+				-0.98600000f, -0.16080001f, -0.00000000f,
+				0.00000000f, -0.23141253f, 0.99637496f,
+				-0.16348520f, -0.23141250f, 0.98354161f,
+				-0.31788349f, -0.23141254f, 0.94631720f,
+				-0.46128178f, -0.23141253f, 0.88661432f,
+				-0.59176707f, -0.23141254f, 0.80634636f,
+				-0.70742619f, -0.23141253f, 0.70742619f,
+				-0.80634630f, -0.23141253f, 0.59176701f,
+				-0.88661432f, -0.23141253f, 0.46128178f,
+				-0.94631708f, -0.23141253f, 0.31788346f,
+				-0.98354173f, -0.23141253f, 0.16348514f,
+				-0.99637496f, -0.23141253f, -0.00000000f,
+				0.00000000f, -0.30000001f, 1.00000000f,
+				-0.16407999f, -0.29999998f, 0.98711991f,
+				-0.31904000f, -0.30000004f, 0.94976002f,
+				-0.46296000f, -0.30000004f, 0.88984001f,
+				-0.59392005f, -0.30000001f, 0.80928004f,
+				-0.71000004f, -0.30000001f, 0.71000004f,
+				-0.80928004f, -0.30000001f, 0.59391999f,
+				-0.88984001f, -0.30000001f, 0.46296003f,
+				-0.94976002f, -0.30000001f, 0.31904000f,
+				-0.98712003f, -0.30000001f, 0.16407993f,
+				-1.00000000f, -0.30000001f, -0.00000000f,
+				-0.75000000f, 0.45000005f, 0.00000001f,
+				-0.74033999f, 0.44999996f, -0.12305998f,
+				-0.71232003f, 0.45000011f, -0.23928000f,
+				-0.66737998f, 0.45000002f, -0.34721997f,
+				-0.60696000f, 0.45000008f, -0.44544002f,
+				-0.53250003f, 0.45000005f, -0.53250003f,
+				-0.44543999f, 0.45000005f, -0.60696000f,
+				-0.34722000f, 0.45000008f, -0.66738003f,
+				-0.23927999f, 0.45000005f, -0.71231997f,
+				-0.12305996f, 0.45000005f, -0.74033999f,
+				0.00000000f, 0.45000005f, -0.75000000f,
+				-0.78737491f, 0.37128749f, 0.00000000f,
+				-0.77723348f, 0.37128747f, -0.12919247f,
+				-0.74781722f, 0.37128752f, -0.25120407f,
+				-0.70063770f, 0.37128749f, -0.36452308f,
+				-0.63720679f, 0.37128752f, -0.46763775f,
+				-0.55903620f, 0.37128749f, -0.55903620f,
+				-0.46763766f, 0.37128749f, -0.63720679f,
+				-0.36452311f, 0.37128749f, -0.70063770f,
+				-0.25120407f, 0.37128752f, -0.74781728f,
+				-0.12919241f, 0.37128749f, -0.77723348f,
+				0.00000000f, 0.37128749f, -0.78737491f,
+				-0.82400006f, 0.29280004f, 0.00000000f,
+				-0.81338686f, 0.29280004f, -0.13520193f,
+				-0.78260237f, 0.29280004f, -0.26288900f,
+				-0.73322815f, 0.29280004f, -0.38147905f,
+				-0.66684681f, 0.29280004f, -0.48939016f,
+				-0.58504003f, 0.29280001f, -0.58504003f,
+				-0.48939011f, 0.29280004f, -0.66684675f,
+				-0.38147908f, 0.29280007f, -0.73322821f,
+				-0.26288897f, 0.29280004f, -0.78260231f,
+				-0.13520187f, 0.29280004f, -0.81338698f,
+				0.00000000f, 0.29280004f, -0.82400006f,
+				-0.85912502f, 0.21476251f, 0.00000000f,
+				-0.84805942f, 0.21476249f, -0.14096522f,
+				-0.81596267f, 0.21476252f, -0.27409527f,
+				-0.76448381f, 0.21476249f, -0.39774051f,
+				-0.69527274f, 0.21476252f, -0.51025152f,
+				-0.60997874f, 0.21476251f, -0.60997874f,
+				-0.51025152f, 0.21476251f, -0.69527274f,
+				-0.39774054f, 0.21476251f, -0.76448381f,
+				-0.27409524f, 0.21476251f, -0.81596261f,
+				-0.14096518f, 0.21476251f, -0.84805954f,
+				0.00000000f, 0.21476251f, -0.85912502f,
+				-0.89200008f, 0.13740003f, 0.00000000f,
+				-0.88051099f, 0.13740003f, -0.14635937f,
+				-0.84718603f, 0.13740005f, -0.28458369f,
+				-0.79373735f, 0.13740005f, -0.41296035f,
+				-0.72187787f, 0.13740005f, -0.52977669f,
+				-0.63332003f, 0.13740005f, -0.63332003f,
+				-0.52977669f, 0.13740005f, -0.72187781f,
+				-0.41296035f, 0.13740005f, -0.79373741f,
+				-0.28458369f, 0.13740005f, -0.84718597f,
+				-0.14635932f, 0.13740005f, -0.88051111f,
+				0.00000000f, 0.13740005f, -0.89200008f,
+				-0.92187500f, 0.06093751f, 0.00000000f,
+				-0.91000110f, 0.06093751f, -0.15126126f,
+				-0.87556010f, 0.06093752f, -0.29411504f,
+				-0.82032120f, 0.06093751f, -0.42679128f,
+				-0.74605507f, 0.06093752f, -0.54752004f,
+				-0.65453124f, 0.06093752f, -0.65453124f,
+				-0.54751998f, 0.06093752f, -0.74605501f,
+				-0.42679128f, 0.06093752f, -0.82032126f,
+				-0.29411501f, 0.06093752f, -0.87556005f,
+				-0.15126120f, 0.06093752f, -0.91000128f,
+				0.00000000f, 0.06093752f, -0.92187500f,
+				-0.94800001f, -0.01440001f, -0.00000000f,
+				-0.93578976f, -0.01440001f, -0.15554784f,
+				-0.90037251f, -0.01440001f, -0.30244994f,
+				-0.84356833f, -0.01440001f, -0.43888608f,
+				-0.76719749f, -0.01440001f, -0.56303620f,
+				-0.67308003f, -0.01440000f, -0.67308003f,
+				-0.56303614f, -0.01440000f, -0.76719743f,
+				-0.43888611f, -0.01440000f, -0.84356833f,
+				-0.30244991f, -0.01440000f, -0.90037251f,
+				-0.15554780f, -0.01440000f, -0.93578976f,
+				0.00000000f, -0.01440000f, -0.94800001f,
+				-0.96962506f, -0.08838750f, -0.00000000f,
+				-0.95713621f, -0.08838749f, -0.15909605f,
+				-0.92091113f, -0.08838750f, -0.30934918f,
+				-0.86281121f, -0.08838750f, -0.44889757f,
+				-0.78469825f, -0.08838750f, -0.57587969f,
+				-0.68843377f, -0.08838749f, -0.68843377f,
+				-0.57587969f, -0.08838749f, -0.78469819f,
+				-0.44889760f, -0.08838749f, -0.86281121f,
+				-0.30934915f, -0.08838749f, -0.92091107f,
+				-0.15909600f, -0.08838749f, -0.95713627f,
+				0.00000000f, -0.08838749f, -0.96962506f,
+				-0.98600000f, -0.16080001f, -0.00000000f,
+				-0.97330022f, -0.16080000f, -0.16178288f,
+				-0.93646342f, -0.16080001f, -0.31457347f,
+				-0.87738222f, -0.16080000f, -0.45647857f,
+				-0.79795015f, -0.16080001f, -0.58560514f,
+				-0.70006001f, -0.16080001f, -0.70006001f,
+				-0.58560514f, -0.16080000f, -0.79795015f,
+				-0.45647860f, -0.16080001f, -0.87738228f,
+				-0.31457344f, -0.16080000f, -0.93646336f,
+				-0.16178282f, -0.16080000f, -0.97330034f,
+				0.00000000f, -0.16080000f, -0.98600000f,
+				-0.99637496f, -0.23141253f, -0.00000000f,
+				-0.98354161f, -0.23141250f, -0.16348520f,
+				-0.94631720f, -0.23141254f, -0.31788349f,
+				-0.88661432f, -0.23141253f, -0.46128178f,
+				-0.80634636f, -0.23141254f, -0.59176707f,
+				-0.70742619f, -0.23141253f, -0.70742619f,
+				-0.59176701f, -0.23141253f, -0.80634630f,
+				-0.46128178f, -0.23141253f, -0.88661432f,
+				-0.31788346f, -0.23141253f, -0.94631708f,
+				-0.16348514f, -0.23141253f, -0.98354173f,
+				0.00000000f, -0.23141253f, -0.99637496f,
+				-1.00000000f, -0.30000001f, -0.00000000f,
+				-0.98711991f, -0.29999998f, -0.16407999f,
+				-0.94976002f, -0.30000004f, -0.31904000f,
+				-0.88984001f, -0.30000004f, -0.46296000f,
+				-0.80928004f, -0.30000001f, -0.59392005f,
+				-0.71000004f, -0.30000001f, -0.71000004f,
+				-0.59391999f, -0.30000001f, -0.80928004f,
+				-0.46296003f, -0.30000001f, -0.88984001f,
+				-0.31904000f, -0.30000001f, -0.94976002f,
+				-0.16407993f, -0.30000001f, -0.98712003f,
+				0.00000000f, -0.30000001f, -1.00000000f,
+				1.00000000f, -0.30000001f, -0.00000000f,
+				0.98711991f, -0.29999998f, 0.16407999f,
+				0.94976002f, -0.30000004f, 0.31904000f,
+				0.88984001f, -0.30000004f, 0.46296000f,
+				0.80928004f, -0.30000001f, 0.59392005f,
+				0.71000004f, -0.30000001f, 0.71000004f,
+				0.59391999f, -0.30000001f, 0.80928004f,
+				0.46296003f, -0.30000001f, 0.88984001f,
+				0.31904000f, -0.30000001f, 0.94976002f,
+				0.16407993f, -0.30000001f, 0.98712003f,
+				0.00000000f, -0.30000001f, 1.00000000f,
+				0.99299991f, -0.36416247f, -0.00000000f,
+				0.98021001f, -0.36416242f, 0.16293143f,
+				0.94311166f, -0.36416250f, 0.31680670f,
+				0.88361102f, -0.36416245f, 0.45971927f,
+				0.80361503f, -0.36416247f, 0.58976257f,
+				0.70502996f, -0.36416247f, 0.70502996f,
+				0.58976251f, -0.36416250f, 0.80361497f,
+				0.45971930f, -0.36416247f, 0.88361108f,
+				0.31680670f, -0.36416250f, 0.94311166f,
+				0.16293137f, -0.36416247f, 0.98021007f,
+				0.00000000f, -0.36416247f, 0.99299991f,
+				0.97400004f, -0.42180002f, -0.00000001f,
+				0.96145481f, -0.42179996f, 0.15981390f,
+				0.92506635f, -0.42180005f, 0.31074497f,
+				0.86670411f, -0.42180002f, 0.45092306f,
+				0.78823876f, -0.42180011f, 0.57847816f,
+				0.69154000f, -0.42180008f, 0.69154000f,
+				0.57847810f, -0.42180005f, 0.78823876f,
+				0.45092303f, -0.42180008f, 0.86670417f,
+				0.31074494f, -0.42180008f, 0.92506629f,
+				0.15981385f, -0.42180008f, 0.96145493f,
+				0.00000000f, -0.42180005f, 0.97400004f,
+				0.94600004f, -0.47313750f, -0.00000001f,
+				0.93381548f, -0.47313747f, 0.15521967f,
+				0.89847302f, -0.47313756f, 0.30181187f,
+				0.84178865f, -0.47313750f, 0.43796018f,
+				0.76557899f, -0.47313753f, 0.56184840f,
+				0.67166001f, -0.47313753f, 0.67166001f,
+				0.56184828f, -0.47313753f, 0.76557899f,
+				0.43796021f, -0.47313753f, 0.84178865f,
+				0.30181184f, -0.47313753f, 0.89847302f,
+				0.15521961f, -0.47313753f, 0.93381560f,
+				0.00000000f, -0.47313753f, 0.94600004f,
+				0.91200000f, -0.51840001f, -0.00000001f,
+				0.90025330f, -0.51839995f, 0.14964095f,
+				0.86618114f, -0.51840007f, 0.29096451f,
+				0.81153411f, -0.51840001f, 0.42221951f,
+				0.73806345f, -0.51840007f, 0.54165506f,
+				0.64752001f, -0.51840007f, 0.64752001f,
+				0.54165506f, -0.51840007f, 0.73806340f,
+				0.42221954f, -0.51840007f, 0.81153411f,
+				0.29096448f, -0.51840007f, 0.86618114f,
+				0.14964090f, -0.51840007f, 0.90025342f,
+				0.00000000f, -0.51840007f, 0.91200000f,
+				0.87500000f, -0.55781251f, -0.00000001f,
+				0.86372989f, -0.55781251f, 0.14356998f,
+				0.83104002f, -0.55781251f, 0.27916002f,
+				0.77860999f, -0.55781251f, 0.40509003f,
+				0.70812005f, -0.55781257f, 0.51968002f,
+				0.62125003f, -0.55781251f, 0.62125003f,
+				0.51967996f, -0.55781251f, 0.70812005f,
+				0.40509003f, -0.55781257f, 0.77860999f,
+				0.27915999f, -0.55781251f, 0.83104002f,
+				0.14356995f, -0.55781251f, 0.86373001f,
+				0.00000000f, -0.55781251f, 0.87500000f,
+				0.83800000f, -0.59160000f, -0.00000001f,
+				0.82720655f, -0.59159994f, 0.13749902f,
+				0.79589891f, -0.59160000f, 0.26735553f,
+				0.74568588f, -0.59160000f, 0.38796049f,
+				0.67817670f, -0.59160006f, 0.49770495f,
+				0.59498000f, -0.59160000f, 0.59497994f,
+				0.49770495f, -0.59160000f, 0.67817664f,
+				0.38796049f, -0.59160006f, 0.74568594f,
+				0.26735550f, -0.59160000f, 0.79589891f,
+				0.13749899f, -0.59160000f, 0.82720655f,
+				0.00000000f, -0.59160000f, 0.83800000f,
+				0.80400008f, -0.61998749f, -0.00000001f,
+				0.79364455f, -0.61998743f, 0.13192031f,
+				0.76360714f, -0.61998749f, 0.25650817f,
+				0.71543139f, -0.61998749f, 0.37221986f,
+				0.65066117f, -0.61998749f, 0.47751173f,
+				0.57084000f, -0.61998749f, 0.57084000f,
+				0.47751170f, -0.61998749f, 0.65066117f,
+				0.37221986f, -0.61998749f, 0.71543145f,
+				0.25650814f, -0.61998749f, 0.76360714f,
+				0.13192026f, -0.61998749f, 0.79364455f,
+				0.00000000f, -0.61998749f, 0.80400008f,
+				0.77600002f, -0.64320004f, -0.00000001f,
+				0.76600504f, -0.64319998f, 0.12732607f,
+				0.73701382f, -0.64320010f, 0.24757506f,
+				0.69051588f, -0.64320004f, 0.35925698f,
+				0.62800133f, -0.64320010f, 0.46088195f,
+				0.55096000f, -0.64320004f, 0.55096000f,
+				0.46088192f, -0.64320004f, 0.62800133f,
+				0.35925698f, -0.64320004f, 0.69051588f,
+				0.24757503f, -0.64320004f, 0.73701382f,
+				0.12732603f, -0.64320004f, 0.76600516f,
+				0.00000000f, -0.64320004f, 0.77600002f,
+				0.75699997f, -0.66146249f, -0.00000001f,
+				0.74724966f, -0.66146243f, 0.12420854f,
+				0.71896833f, -0.66146255f, 0.24151328f,
+				0.67360884f, -0.66146243f, 0.35046071f,
+				0.61262494f, -0.66146255f, 0.44959745f,
+				0.53746998f, -0.66146249f, 0.53746998f,
+				0.44959742f, -0.66146249f, 0.61262494f,
+				0.35046071f, -0.66146249f, 0.67360890f,
+				0.24151327f, -0.66146255f, 0.71896827f,
+				0.12420851f, -0.66146249f, 0.74724984f,
+				0.00000000f, -0.66146249f, 0.75699997f,
+				0.75000000f, -0.67500001f, -0.00000001f,
+				0.74033999f, -0.67499995f, 0.12305998f,
+				0.71232003f, -0.67500007f, 0.23928000f,
+				0.66737998f, -0.67500001f, 0.34721997f,
+				0.60696000f, -0.67500007f, 0.44544002f,
+				0.53250003f, -0.67500007f, 0.53250003f,
+				0.44543999f, -0.67500001f, 0.60696000f,
+				0.34722000f, -0.67500001f, 0.66738003f,
+				0.23927999f, -0.67500001f, 0.71231997f,
+				0.12305996f, -0.67500001f, 0.74033999f,
+				0.00000000f, -0.67500001f, 0.75000000f,
+				0.00000000f, -0.30000001f, -1.00000000f,
+				0.16407999f, -0.29999998f, -0.98711991f,
+				0.31904000f, -0.30000004f, -0.94976002f,
+				0.46296000f, -0.30000004f, -0.88984001f,
+				0.59392005f, -0.30000001f, -0.80928004f,
+				0.71000004f, -0.30000001f, -0.71000004f,
+				0.80928004f, -0.30000001f, -0.59391999f,
+				0.88984001f, -0.30000001f, -0.46296003f,
+				0.94976002f, -0.30000001f, -0.31904000f,
+				0.98712003f, -0.30000001f, -0.16407993f,
+				1.00000000f, -0.30000001f, -0.00000000f,
+				0.00000000f, -0.36416247f, -0.99299991f,
+				0.16293143f, -0.36416242f, -0.98021001f,
+				0.31680670f, -0.36416250f, -0.94311166f,
+				0.45971927f, -0.36416245f, -0.88361102f,
+				0.58976257f, -0.36416247f, -0.80361503f,
+				0.70502996f, -0.36416247f, -0.70502996f,
+				0.80361497f, -0.36416250f, -0.58976251f,
+				0.88361108f, -0.36416247f, -0.45971930f,
+				0.94311166f, -0.36416250f, -0.31680670f,
+				0.98021007f, -0.36416247f, -0.16293137f,
+				0.99299991f, -0.36416247f, -0.00000000f,
+				0.00000000f, -0.42180002f, -0.97400004f,
+				0.15981390f, -0.42179996f, -0.96145481f,
+				0.31074497f, -0.42180005f, -0.92506635f,
+				0.45092306f, -0.42180002f, -0.86670411f,
+				0.57847816f, -0.42180005f, -0.78823876f,
+				0.69154000f, -0.42180002f, -0.69154000f,
+				0.78823876f, -0.42180002f, -0.57847810f,
+				0.86670417f, -0.42180002f, -0.45092303f,
+				0.92506629f, -0.42180002f, -0.31074494f,
+				0.96145493f, -0.42180002f, -0.15981385f,
+				0.97400004f, -0.42180002f, -0.00000001f,
+				0.00000000f, -0.47313750f, -0.94600004f,
+				0.15521967f, -0.47313747f, -0.93381548f,
+				0.30181187f, -0.47313756f, -0.89847302f,
+				0.43796018f, -0.47313750f, -0.84178865f,
+				0.56184840f, -0.47313753f, -0.76557899f,
+				0.67166001f, -0.47313750f, -0.67166001f,
+				0.76557899f, -0.47313750f, -0.56184828f,
+				0.84178865f, -0.47313750f, -0.43796021f,
+				0.89847302f, -0.47313750f, -0.30181184f,
+				0.93381560f, -0.47313750f, -0.15521961f,
+				0.94600004f, -0.47313750f, -0.00000001f,
+				0.00000000f, -0.51840001f, -0.91200000f,
+				0.14964095f, -0.51839995f, -0.90025330f,
+				0.29096451f, -0.51840001f, -0.86618114f,
+				0.42221951f, -0.51840001f, -0.81153411f,
+				0.54165506f, -0.51840007f, -0.73806345f,
+				0.64752001f, -0.51840001f, -0.64752001f,
+				0.73806340f, -0.51840001f, -0.54165506f,
+				0.81153411f, -0.51840007f, -0.42221954f,
+				0.86618114f, -0.51840007f, -0.29096448f,
+				0.90025342f, -0.51840001f, -0.14964090f,
+				0.91200000f, -0.51840001f, -0.00000001f,
+				0.00000000f, -0.55781251f, -0.87500000f,
+				0.14356999f, -0.55781251f, -0.86372989f,
+				0.27916002f, -0.55781251f, -0.83104002f,
+				0.40509003f, -0.55781251f, -0.77860999f,
+				0.51968002f, -0.55781257f, -0.70812005f,
+				0.62125003f, -0.55781251f, -0.62125003f,
+				0.70812005f, -0.55781251f, -0.51967996f,
+				0.77860999f, -0.55781257f, -0.40509003f,
+				0.83104002f, -0.55781251f, -0.27915999f,
+				0.86373001f, -0.55781251f, -0.14356995f,
+				0.87500000f, -0.55781251f, -0.00000001f,
+				0.00000000f, -0.59160000f, -0.83800000f,
+				0.13749902f, -0.59159994f, -0.82720655f,
+				0.26735553f, -0.59160000f, -0.79589891f,
+				0.38796049f, -0.59160000f, -0.74568588f,
+				0.49770495f, -0.59160006f, -0.67817670f,
+				0.59497994f, -0.59160000f, -0.59498000f,
+				0.67817664f, -0.59160000f, -0.49770495f,
+				0.74568594f, -0.59160006f, -0.38796049f,
+				0.79589891f, -0.59160000f, -0.26735550f,
+				0.82720655f, -0.59160000f, -0.13749899f,
+				0.83800000f, -0.59160000f, -0.00000001f,
+				0.00000000f, -0.61998749f, -0.80400008f,
+				0.13192032f, -0.61998743f, -0.79364455f,
+				0.25650817f, -0.61998749f, -0.76360714f,
+				0.37221986f, -0.61998749f, -0.71543139f,
+				0.47751173f, -0.61998749f, -0.65066117f,
+				0.57084000f, -0.61998749f, -0.57084000f,
+				0.65066117f, -0.61998749f, -0.47751170f,
+				0.71543145f, -0.61998749f, -0.37221986f,
+				0.76360714f, -0.61998749f, -0.25650814f,
+				0.79364455f, -0.61998749f, -0.13192026f,
+				0.80400008f, -0.61998749f, -0.00000001f,
+				0.00000000f, -0.64320004f, -0.77600002f,
+				0.12732607f, -0.64319998f, -0.76600504f,
+				0.24757506f, -0.64320010f, -0.73701382f,
+				0.35925698f, -0.64320004f, -0.69051588f,
+				0.46088195f, -0.64320010f, -0.62800133f,
+				0.55096000f, -0.64320004f, -0.55096000f,
+				0.62800133f, -0.64320004f, -0.46088192f,
+				0.69051588f, -0.64320004f, -0.35925698f,
+				0.73701382f, -0.64320004f, -0.24757503f,
+				0.76600516f, -0.64320004f, -0.12732603f,
+				0.77600002f, -0.64320004f, -0.00000001f,
+				0.00000000f, -0.66146249f, -0.75699997f,
+				0.12420855f, -0.66146243f, -0.74724966f,
+				0.24151328f, -0.66146255f, -0.71896833f,
+				0.35046071f, -0.66146243f, -0.67360884f,
+				0.44959745f, -0.66146255f, -0.61262494f,
+				0.53746998f, -0.66146249f, -0.53746998f,
+				0.61262494f, -0.66146249f, -0.44959742f,
+				0.67360890f, -0.66146249f, -0.35046071f,
+				0.71896827f, -0.66146255f, -0.24151327f,
+				0.74724984f, -0.66146249f, -0.12420852f,
+				0.75699997f, -0.66146249f, -0.00000001f,
+				0.00000000f, -0.67500001f, -0.75000000f,
+				0.12305999f, -0.67499995f, -0.74033999f,
+				0.23928000f, -0.67500007f, -0.71232003f,
+				0.34721997f, -0.67500001f, -0.66737998f,
+				0.44544002f, -0.67500007f, -0.60696000f,
+				0.53250003f, -0.67500007f, -0.53250003f,
+				0.60696000f, -0.67500001f, -0.44543999f,
+				0.66738003f, -0.67500001f, -0.34722000f,
+				0.71231997f, -0.67500001f, -0.23927999f,
+				0.74033999f, -0.67500001f, -0.12305997f,
+				0.75000000f, -0.67500001f, -0.00000001f,
+				0.00000000f, -0.30000001f, 1.00000000f,
+				-0.16407999f, -0.29999998f, 0.98711991f,
+				-0.31904000f, -0.30000004f, 0.94976002f,
+				-0.46296000f, -0.30000004f, 0.88984001f,
+				-0.59392005f, -0.30000001f, 0.80928004f,
+				-0.71000004f, -0.30000001f, 0.71000004f,
+				-0.80928004f, -0.30000001f, 0.59391999f,
+				-0.88984001f, -0.30000001f, 0.46296003f,
+				-0.94976002f, -0.30000001f, 0.31904000f,
+				-0.98712003f, -0.30000001f, 0.16407993f,
+				-1.00000000f, -0.30000001f, -0.00000000f,
+				0.00000000f, -0.36416247f, 0.99299991f,
+				-0.16293143f, -0.36416242f, 0.98021001f,
+				-0.31680670f, -0.36416250f, 0.94311166f,
+				-0.45971927f, -0.36416245f, 0.88361102f,
+				-0.58976257f, -0.36416247f, 0.80361503f,
+				-0.70502996f, -0.36416247f, 0.70502996f,
+				-0.80361497f, -0.36416250f, 0.58976251f,
+				-0.88361108f, -0.36416247f, 0.45971930f,
+				-0.94311166f, -0.36416250f, 0.31680670f,
+				-0.98021007f, -0.36416247f, 0.16293137f,
+				-0.99299991f, -0.36416247f, -0.00000000f,
+				0.00000000f, -0.42180005f, 0.97400004f,
+				-0.15981390f, -0.42179999f, 0.96145481f,
+				-0.31074497f, -0.42180005f, 0.92506635f,
+				-0.45092306f, -0.42180002f, 0.86670411f,
+				-0.57847816f, -0.42180011f, 0.78823876f,
+				-0.69154000f, -0.42180008f, 0.69154000f,
+				-0.78823876f, -0.42180002f, 0.57847810f,
+				-0.86670417f, -0.42180005f, 0.45092303f,
+				-0.92506629f, -0.42180005f, 0.31074494f,
+				-0.96145493f, -0.42180002f, 0.15981385f,
+				-0.97400004f, -0.42180002f, -0.00000001f,
+				0.00000000f, -0.47313753f, 0.94600004f,
+				-0.15521967f, -0.47313747f, 0.93381548f,
+				-0.30181187f, -0.47313756f, 0.89847302f,
+				-0.43796018f, -0.47313750f, 0.84178865f,
+				-0.56184840f, -0.47313753f, 0.76557899f,
+				-0.67166001f, -0.47313750f, 0.67166001f,
+				-0.76557899f, -0.47313753f, 0.56184828f,
+				-0.84178865f, -0.47313753f, 0.43796021f,
+				-0.89847302f, -0.47313750f, 0.30181184f,
+				-0.93381560f, -0.47313750f, 0.15521961f,
+				-0.94600004f, -0.47313750f, -0.00000001f,
+				0.00000000f, -0.51840007f, 0.91200000f,
+				-0.14964095f, -0.51839995f, 0.90025330f,
+				-0.29096451f, -0.51840007f, 0.86618114f,
+				-0.42221951f, -0.51840007f, 0.81153411f,
+				-0.54165506f, -0.51840007f, 0.73806345f,
+				-0.64752001f, -0.51840007f, 0.64752001f,
+				-0.73806340f, -0.51840007f, 0.54165506f,
+				-0.81153411f, -0.51840007f, 0.42221954f,
+				-0.86618114f, -0.51840007f, 0.29096448f,
+				-0.90025342f, -0.51840001f, 0.14964090f,
+				-0.91200000f, -0.51840001f, -0.00000001f,
+				0.00000000f, -0.55781251f, 0.87500000f,
+				-0.14356999f, -0.55781251f, 0.86372989f,
+				-0.27916002f, -0.55781251f, 0.83104002f,
+				-0.40509003f, -0.55781251f, 0.77860999f,
+				-0.51968002f, -0.55781257f, 0.70812005f,
+				-0.62125003f, -0.55781251f, 0.62125003f,
+				-0.70812005f, -0.55781251f, 0.51967996f,
+				-0.77860999f, -0.55781257f, 0.40509003f,
+				-0.83104002f, -0.55781251f, 0.27915999f,
+				-0.86373001f, -0.55781251f, 0.14356995f,
+				-0.87500000f, -0.55781251f, -0.00000001f,
+				0.00000000f, -0.59160000f, 0.83800000f,
+				-0.13749902f, -0.59159994f, 0.82720655f,
+				-0.26735553f, -0.59160000f, 0.79589891f,
+				-0.38796049f, -0.59160000f, 0.74568588f,
+				-0.49770495f, -0.59160006f, 0.67817670f,
+				-0.59497994f, -0.59160000f, 0.59498000f,
+				-0.67817664f, -0.59160000f, 0.49770495f,
+				-0.74568594f, -0.59160006f, 0.38796049f,
+				-0.79589891f, -0.59160000f, 0.26735550f,
+				-0.82720655f, -0.59160000f, 0.13749899f,
+				-0.83800000f, -0.59160000f, -0.00000001f,
+				0.00000000f, -0.61998749f, 0.80400008f,
+				-0.13192032f, -0.61998743f, 0.79364455f,
+				-0.25650817f, -0.61998749f, 0.76360714f,
+				-0.37221986f, -0.61998749f, 0.71543139f,
+				-0.47751173f, -0.61998749f, 0.65066117f,
+				-0.57084000f, -0.61998749f, 0.57084000f,
+				-0.65066117f, -0.61998749f, 0.47751170f,
+				-0.71543145f, -0.61998749f, 0.37221986f,
+				-0.76360714f, -0.61998749f, 0.25650814f,
+				-0.79364455f, -0.61998749f, 0.13192026f,
+				-0.80400008f, -0.61998749f, -0.00000001f,
+				0.00000000f, -0.64320004f, 0.77600002f,
+				-0.12732607f, -0.64319998f, 0.76600504f,
+				-0.24757506f, -0.64320010f, 0.73701382f,
+				-0.35925698f, -0.64320004f, 0.69051588f,
+				-0.46088195f, -0.64320010f, 0.62800133f,
+				-0.55096000f, -0.64320004f, 0.55096000f,
+				-0.62800133f, -0.64320004f, 0.46088192f,
+				-0.69051588f, -0.64320004f, 0.35925698f,
+				-0.73701382f, -0.64320004f, 0.24757503f,
+				-0.76600516f, -0.64320004f, 0.12732603f,
+				-0.77600002f, -0.64320004f, -0.00000001f,
+				0.00000000f, -0.66146249f, 0.75699997f,
+				-0.12420855f, -0.66146243f, 0.74724966f,
+				-0.24151328f, -0.66146255f, 0.71896833f,
+				-0.35046071f, -0.66146243f, 0.67360884f,
+				-0.44959745f, -0.66146255f, 0.61262494f,
+				-0.53746998f, -0.66146249f, 0.53746998f,
+				-0.61262494f, -0.66146249f, 0.44959742f,
+				-0.67360890f, -0.66146249f, 0.35046071f,
+				-0.71896827f, -0.66146255f, 0.24151327f,
+				-0.74724984f, -0.66146249f, 0.12420850f,
+				-0.75699997f, -0.66146249f, -0.00000001f,
+				0.00000000f, -0.67500001f, 0.75000000f,
+				-0.12305999f, -0.67499995f, 0.74033999f,
+				-0.23928000f, -0.67500007f, 0.71232003f,
+				-0.34721997f, -0.67500001f, 0.66737998f,
+				-0.44544002f, -0.67500007f, 0.60696000f,
+				-0.53250003f, -0.67500007f, 0.53250003f,
+				-0.60696000f, -0.67500001f, 0.44543999f,
+				-0.66738003f, -0.67500001f, 0.34722000f,
+				-0.71231997f, -0.67500001f, 0.23927999f,
+				-0.74033999f, -0.67500001f, 0.12305995f,
+				-0.75000000f, -0.67500001f, -0.00000001f,
+				-1.00000000f, -0.30000001f, -0.00000000f,
+				-0.98711991f, -0.29999998f, -0.16407999f,
+				-0.94976002f, -0.30000004f, -0.31904000f,
+				-0.88984001f, -0.30000004f, -0.46296000f,
+				-0.80928004f, -0.30000001f, -0.59392005f,
+				-0.71000004f, -0.30000001f, -0.71000004f,
+				-0.59391999f, -0.30000001f, -0.80928004f,
+				-0.46296003f, -0.30000001f, -0.88984001f,
+				-0.31904000f, -0.30000001f, -0.94976002f,
+				-0.16407993f, -0.30000001f, -0.98712003f,
+				0.00000000f, -0.30000001f, -1.00000000f,
+				-0.99299991f, -0.36416247f, -0.00000000f,
+				-0.98021001f, -0.36416242f, -0.16293143f,
+				-0.94311166f, -0.36416250f, -0.31680670f,
+				-0.88361102f, -0.36416245f, -0.45971927f,
+				-0.80361503f, -0.36416247f, -0.58976257f,
+				-0.70502996f, -0.36416247f, -0.70502996f,
+				-0.58976251f, -0.36416250f, -0.80361497f,
+				-0.45971930f, -0.36416247f, -0.88361108f,
+				-0.31680670f, -0.36416250f, -0.94311166f,
+				-0.16293137f, -0.36416247f, -0.98021007f,
+				0.00000000f, -0.36416247f, -0.99299991f,
+				-0.97400004f, -0.42180002f, -0.00000001f,
+				-0.96145481f, -0.42179996f, -0.15981390f,
+				-0.92506635f, -0.42180005f, -0.31074497f,
+				-0.86670411f, -0.42180002f, -0.45092306f,
+				-0.78823876f, -0.42180005f, -0.57847816f,
+				-0.69154000f, -0.42180002f, -0.69154000f,
+				-0.57847810f, -0.42180002f, -0.78823876f,
+				-0.45092303f, -0.42180002f, -0.86670417f,
+				-0.31074494f, -0.42180002f, -0.92506629f,
+				-0.15981385f, -0.42180002f, -0.96145493f,
+				0.00000000f, -0.42180002f, -0.97400004f,
+				-0.94600004f, -0.47313750f, -0.00000001f,
+				-0.93381548f, -0.47313747f, -0.15521967f,
+				-0.89847302f, -0.47313756f, -0.30181187f,
+				-0.84178865f, -0.47313750f, -0.43796018f,
+				-0.76557899f, -0.47313753f, -0.56184840f,
+				-0.67166001f, -0.47313750f, -0.67166001f,
+				-0.56184828f, -0.47313750f, -0.76557899f,
+				-0.43796021f, -0.47313750f, -0.84178865f,
+				-0.30181184f, -0.47313750f, -0.89847302f,
+				-0.15521961f, -0.47313750f, -0.93381560f,
+				0.00000000f, -0.47313750f, -0.94600004f,
+				-0.91200000f, -0.51840001f, -0.00000001f,
+				-0.90025330f, -0.51839995f, -0.14964096f,
+				-0.86618114f, -0.51840001f, -0.29096451f,
+				-0.81153411f, -0.51840001f, -0.42221951f,
+				-0.73806345f, -0.51840007f, -0.54165506f,
+				-0.64752001f, -0.51840001f, -0.64752001f,
+				-0.54165506f, -0.51840001f, -0.73806340f,
+				-0.42221954f, -0.51840007f, -0.81153411f,
+				-0.29096448f, -0.51840007f, -0.86618114f,
+				-0.14964090f, -0.51840001f, -0.90025342f,
+				0.00000000f, -0.51840001f, -0.91200000f,
+				-0.87500000f, -0.55781251f, -0.00000001f,
+				-0.86372989f, -0.55781251f, -0.14357001f,
+				-0.83104002f, -0.55781251f, -0.27916002f,
+				-0.77860999f, -0.55781251f, -0.40509003f,
+				-0.70812005f, -0.55781257f, -0.51968002f,
+				-0.62125003f, -0.55781251f, -0.62125003f,
+				-0.51967996f, -0.55781251f, -0.70812005f,
+				-0.40509003f, -0.55781257f, -0.77860999f,
+				-0.27915999f, -0.55781251f, -0.83104002f,
+				-0.14356995f, -0.55781251f, -0.86373001f,
+				0.00000000f, -0.55781251f, -0.87500000f,
+				-0.83800000f, -0.59160000f, -0.00000001f,
+				-0.82720655f, -0.59159994f, -0.13749903f,
+				-0.79589891f, -0.59160000f, -0.26735553f,
+				-0.74568588f, -0.59160000f, -0.38796049f,
+				-0.67817670f, -0.59160006f, -0.49770495f,
+				-0.59498000f, -0.59160000f, -0.59497994f,
+				-0.49770495f, -0.59160000f, -0.67817664f,
+				-0.38796049f, -0.59160006f, -0.74568594f,
+				-0.26735550f, -0.59160000f, -0.79589891f,
+				-0.13749899f, -0.59160000f, -0.82720655f,
+				0.00000000f, -0.59160000f, -0.83800000f,
+				-0.80400008f, -0.61998749f, -0.00000001f,
+				-0.79364455f, -0.61998743f, -0.13192032f,
+				-0.76360714f, -0.61998749f, -0.25650817f,
+				-0.71543139f, -0.61998749f, -0.37221986f,
+				-0.65066117f, -0.61998749f, -0.47751173f,
+				-0.57084000f, -0.61998749f, -0.57084000f,
+				-0.47751170f, -0.61998749f, -0.65066117f,
+				-0.37221986f, -0.61998749f, -0.71543145f,
+				-0.25650814f, -0.61998749f, -0.76360714f,
+				-0.13192026f, -0.61998749f, -0.79364455f,
+				0.00000000f, -0.61998749f, -0.80400008f,
+				-0.77600002f, -0.64320004f, -0.00000001f,
+				-0.76600504f, -0.64319998f, -0.12732607f,
+				-0.73701382f, -0.64320010f, -0.24757506f,
+				-0.69051588f, -0.64320004f, -0.35925698f,
+				-0.62800133f, -0.64320010f, -0.46088195f,
+				-0.55096000f, -0.64320004f, -0.55096000f,
+				-0.46088192f, -0.64320004f, -0.62800133f,
+				-0.35925698f, -0.64320004f, -0.69051588f,
+				-0.24757503f, -0.64320004f, -0.73701382f,
+				-0.12732603f, -0.64320004f, -0.76600516f,
+				0.00000000f, -0.64320004f, -0.77600002f,
+				-0.75699997f, -0.66146249f, -0.00000001f,
+				-0.74724966f, -0.66146243f, -0.12420855f,
+				-0.71896833f, -0.66146255f, -0.24151328f,
+				-0.67360884f, -0.66146243f, -0.35046071f,
+				-0.61262494f, -0.66146255f, -0.44959745f,
+				-0.53746998f, -0.66146249f, -0.53746998f,
+				-0.44959742f, -0.66146249f, -0.61262494f,
+				-0.35046071f, -0.66146249f, -0.67360890f,
+				-0.24151327f, -0.66146255f, -0.71896827f,
+				-0.12420851f, -0.66146249f, -0.74724984f,
+				0.00000000f, -0.66146249f, -0.75699997f,
+				-0.75000000f, -0.67500001f, -0.00000001f,
+				-0.74033999f, -0.67499995f, -0.12306000f,
+				-0.71232003f, -0.67500007f, -0.23928000f,
+				-0.66737998f, -0.67500001f, -0.34721997f,
+				-0.60696000f, -0.67500007f, -0.44544002f,
+				-0.53250003f, -0.67500007f, -0.53250003f,
+				-0.44543999f, -0.67500001f, -0.60696000f,
+				-0.34722000f, -0.67500001f, -0.66738003f,
+				-0.23927999f, -0.67500001f, -0.71231997f,
+				-0.12305996f, -0.67500001f, -0.74033999f,
+				0.00000000f, -0.67500001f, -0.75000000f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500017f, 0.00000001f,
+				0.00000000f, 0.82500011f, 0.00000001f,
+				0.00000000f, 0.82500011f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.09730000f, 0.82072502f, 0.00000001f,
+				0.09605332f, 0.82072490f, 0.01602404f,
+				0.09243499f, 0.82072508f, 0.03113591f,
+				0.08662736f, 0.82072502f, 0.04515318f,
+				0.07881293f, 0.82072508f, 0.05789340f,
+				0.06917413f, 0.82072502f, 0.06917413f,
+				0.05789339f, 0.82072502f, 0.07881294f,
+				0.04515317f, 0.82072508f, 0.08662736f,
+				0.03113590f, 0.82072502f, 0.09243499f,
+				0.01602403f, 0.82072502f, 0.09605335f,
+				0.00000000f, 0.82072502f, 0.09730001f,
+				0.15440002f, 0.80880016f, 0.00000001f,
+				0.15242170f, 0.80880004f, 0.02542728f,
+				0.14667983f, 0.80880022f, 0.04940725f,
+				0.13746388f, 0.80880022f, 0.07165039f,
+				0.12506345f, 0.80880028f, 0.09186716f,
+				0.10976801f, 0.80880016f, 0.10976802f,
+				0.09186715f, 0.80880016f, 0.12506345f,
+				0.07165038f, 0.80880022f, 0.13746390f,
+				0.04940724f, 0.80880022f, 0.14667983f,
+				0.02542726f, 0.80880016f, 0.15242171f,
+				0.00000000f, 0.80880016f, 0.15440002f,
+				0.17909999f, 0.79057503f, 0.00000001f,
+				0.17680508f, 0.79057491f, 0.02949390f,
+				0.17014435f, 0.79057509f, 0.05730941f,
+				0.15945368f, 0.79057509f, 0.08311062f,
+				0.14506906f, 0.79057503f, 0.10656159f,
+				0.12732637f, 0.79057503f, 0.12732637f,
+				0.10656157f, 0.79057503f, 0.14506905f,
+				0.08311062f, 0.79057503f, 0.15945369f,
+				0.05730940f, 0.79057503f, 0.17014435f,
+				0.02949388f, 0.79057503f, 0.17680509f,
+				0.00000000f, 0.79057503f, 0.17909999f,
+				0.17920001f, 0.76740009f, 0.00000001f,
+				0.17690356f, 0.76740003f, 0.02950812f,
+				0.17023848f, 0.76740015f, 0.05733787f,
+				0.15954098f, 0.76740009f, 0.08315296f,
+				0.14514741f, 0.76740015f, 0.10661709f,
+				0.12739401f, 0.76740009f, 0.12739401f,
+				0.10661709f, 0.76740009f, 0.14514741f,
+				0.08315295f, 0.76740009f, 0.15954098f,
+				0.05733785f, 0.76740015f, 0.17023847f,
+				0.02950810f, 0.76740009f, 0.17690358f,
+				0.00000000f, 0.76740009f, 0.17920001f,
+				0.16250001f, 0.74062496f, 0.00000001f,
+				0.16041712f, 0.74062496f, 0.02675413f,
+				0.15437202f, 0.74062502f, 0.05198801f,
+				0.14466989f, 0.74062496f, 0.07539638f,
+				0.13161601f, 0.74062496f, 0.09667401f,
+				0.11551563f, 0.74062496f, 0.11551563f,
+				0.09667400f, 0.74062496f, 0.13161600f,
+				0.07539637f, 0.74062502f, 0.14466989f,
+				0.05198800f, 0.74062502f, 0.15437201f,
+				0.02675411f, 0.74062496f, 0.16041712f,
+				0.00000000f, 0.74062496f, 0.16250001f,
+				0.13680001f, 0.71160007f, 0.00000001f,
+				0.13504578f, 0.71160001f, 0.02251614f,
+				0.12995483f, 0.71160012f, 0.04375527f,
+				0.12178454f, 0.71160007f, 0.06345994f,
+				0.11079245f, 0.71160012f, 0.08137268f,
+				0.09723600f, 0.71160007f, 0.09723601f,
+				0.08137267f, 0.71160007f, 0.11079245f,
+				0.06345994f, 0.71160007f, 0.12178455f,
+				0.04375526f, 0.71160007f, 0.12995481f,
+				0.02251612f, 0.71160007f, 0.13504580f,
+				0.00000000f, 0.71160007f, 0.13680001f,
+				0.10990001f, 0.68167502f, 0.00000001f,
+				0.10848958f, 0.68167496f, 0.01807833f,
+				0.10439678f, 0.68167502f, 0.03513508f,
+				0.09782913f, 0.68167496f, 0.05096266f,
+				0.08899431f, 0.68167502f, 0.06535347f,
+				0.07809988f, 0.68167502f, 0.07809989f,
+				0.06535345f, 0.68167502f, 0.08899432f,
+				0.05096266f, 0.68167502f, 0.09782915f,
+				0.03513507f, 0.68167502f, 0.10439678f,
+				0.01807831f, 0.68167502f, 0.10848960f,
+				0.00000000f, 0.68167502f, 0.10990001f,
+				0.08960000f, 0.65219998f, 0.00000001f,
+				0.08844854f, 0.65219992f, 0.01472490f,
+				0.08510771f, 0.65220004f, 0.02862286f,
+				0.07974781f, 0.65219998f, 0.04152356f,
+				0.07253914f, 0.65219998f, 0.05325671f,
+				0.06365200f, 0.65219998f, 0.06365201f,
+				0.05325670f, 0.65219998f, 0.07253915f,
+				0.04152355f, 0.65219998f, 0.07974782f,
+				0.02862285f, 0.65219998f, 0.08510773f,
+				0.01472489f, 0.65219998f, 0.08844855f,
+				0.00000000f, 0.65219998f, 0.08960001f,
+				0.08370000f, 0.62452501f, 0.00000001f,
+				0.08262266f, 0.62452495f, 0.01374006f,
+				0.07949751f, 0.62452501f, 0.02671403f,
+				0.07448471f, 0.62452501f, 0.03876166f,
+				0.06774452f, 0.62452507f, 0.04972278f,
+				0.05943713f, 0.62452501f, 0.05943713f,
+				0.04972277f, 0.62452501f, 0.06774452f,
+				0.03876166f, 0.62452501f, 0.07448472f,
+				0.02671402f, 0.62452501f, 0.07949752f,
+				0.01374005f, 0.62452501f, 0.08262268f,
+				0.00000000f, 0.62452501f, 0.08370001f,
+				0.10000000f, 0.60000002f, 0.00000001f,
+				0.09871199f, 0.59999996f, 0.01640801f,
+				0.09497602f, 0.60000008f, 0.03190401f,
+				0.08898400f, 0.60000008f, 0.04629601f,
+				0.08092801f, 0.60000002f, 0.05939201f,
+				0.07100000f, 0.60000002f, 0.07100001f,
+				0.05939200f, 0.60000002f, 0.08092801f,
+				0.04629600f, 0.60000002f, 0.08898401f,
+				0.03190400f, 0.60000002f, 0.09497601f,
+				0.01640799f, 0.60000002f, 0.09871200f,
+				0.00000000f, 0.60000002f, 0.10000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500017f, 0.00000001f,
+				0.00000000f, 0.82500011f, 0.00000001f,
+				0.00000000f, 0.82500011f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82072502f, -0.09729999f,
+				0.01602403f, 0.82072490f, -0.09605332f,
+				0.03113590f, 0.82072508f, -0.09243497f,
+				0.04515317f, 0.82072502f, -0.08662734f,
+				0.05789339f, 0.82072508f, -0.07881292f,
+				0.06917413f, 0.82072502f, -0.06917411f,
+				0.07881293f, 0.82072502f, -0.05789338f,
+				0.08662736f, 0.82072508f, -0.04515316f,
+				0.09243498f, 0.82072502f, -0.03113589f,
+				0.09605334f, 0.82072502f, -0.01602402f,
+				0.09730000f, 0.82072502f, 0.00000001f,
+				0.00000000f, 0.80880016f, -0.15440002f,
+				0.02542727f, 0.80880004f, -0.15242170f,
+				0.04940724f, 0.80880022f, -0.14667983f,
+				0.07165038f, 0.80880022f, -0.13746388f,
+				0.09186715f, 0.80880028f, -0.12506345f,
+				0.10976800f, 0.80880016f, -0.10976801f,
+				0.12506345f, 0.80880016f, -0.09186714f,
+				0.13746388f, 0.80880022f, -0.07165038f,
+				0.14667982f, 0.80880022f, -0.04940723f,
+				0.15242171f, 0.80880016f, -0.02542725f,
+				0.15440002f, 0.80880016f, 0.00000001f,
+				0.00000000f, 0.79057503f, -0.17909999f,
+				0.02949389f, 0.79057491f, -0.17680508f,
+				0.05730940f, 0.79057509f, -0.17014435f,
+				0.08311061f, 0.79057509f, -0.15945368f,
+				0.10656158f, 0.79057503f, -0.14506905f,
+				0.12732637f, 0.79057503f, -0.12732637f,
+				0.14506905f, 0.79057503f, -0.10656157f,
+				0.15945369f, 0.79057503f, -0.08311061f,
+				0.17014435f, 0.79057503f, -0.05730939f,
+				0.17680509f, 0.79057503f, -0.02949387f,
+				0.17909999f, 0.79057503f, 0.00000001f,
+				0.00000000f, 0.76740009f, -0.17920001f,
+				0.02950811f, 0.76740003f, -0.17690356f,
+				0.05733786f, 0.76740015f, -0.17023848f,
+				0.08315294f, 0.76740009f, -0.15954097f,
+				0.10661709f, 0.76740015f, -0.14514740f,
+				0.12739401f, 0.76740009f, -0.12739399f,
+				0.14514741f, 0.76740009f, -0.10661709f,
+				0.15954098f, 0.76740009f, -0.08315295f,
+				0.17023847f, 0.76740015f, -0.05733785f,
+				0.17690358f, 0.76740009f, -0.02950809f,
+				0.17920001f, 0.76740009f, 0.00000001f,
+				0.00000000f, 0.74062496f, -0.16250001f,
+				0.02675412f, 0.74062496f, -0.16041712f,
+				0.05198800f, 0.74062502f, -0.15437202f,
+				0.07539637f, 0.74062496f, -0.14466989f,
+				0.09667401f, 0.74062496f, -0.13161601f,
+				0.11551563f, 0.74062496f, -0.11551563f,
+				0.13161600f, 0.74062496f, -0.09667400f,
+				0.14466989f, 0.74062502f, -0.07539637f,
+				0.15437201f, 0.74062502f, -0.05198799f,
+				0.16041712f, 0.74062496f, -0.02675411f,
+				0.16250001f, 0.74062496f, 0.00000001f,
+				0.00000000f, 0.71160007f, -0.13679999f,
+				0.02251613f, 0.71160001f, -0.13504578f,
+				0.04375527f, 0.71160012f, -0.12995481f,
+				0.06345994f, 0.71160007f, -0.12178454f,
+				0.08137268f, 0.71160012f, -0.11079244f,
+				0.09723600f, 0.71160007f, -0.09723599f,
+				0.11079245f, 0.71160007f, -0.08137266f,
+				0.12178455f, 0.71160007f, -0.06345993f,
+				0.12995481f, 0.71160007f, -0.04375526f,
+				0.13504580f, 0.71160007f, -0.02251611f,
+				0.13680001f, 0.71160007f, 0.00000001f,
+				0.00000000f, 0.68167502f, -0.10990000f,
+				0.01807832f, 0.68167496f, -0.10848958f,
+				0.03513507f, 0.68167502f, -0.10439678f,
+				0.05096266f, 0.68167496f, -0.09782913f,
+				0.06535346f, 0.68167502f, -0.08899431f,
+				0.07809988f, 0.68167502f, -0.07809987f,
+				0.08899431f, 0.68167502f, -0.06535345f,
+				0.09782915f, 0.68167502f, -0.05096265f,
+				0.10439678f, 0.68167502f, -0.03513506f,
+				0.10848960f, 0.68167502f, -0.01807830f,
+				0.10990001f, 0.68167502f, 0.00000001f,
+				0.00000000f, 0.65219998f, -0.08960000f,
+				0.01472490f, 0.65219992f, -0.08844854f,
+				0.02862285f, 0.65220004f, -0.08510771f,
+				0.04152355f, 0.65219998f, -0.07974780f,
+				0.05325671f, 0.65219998f, -0.07253914f,
+				0.06365199f, 0.65219998f, -0.06365199f,
+				0.07253914f, 0.65219998f, -0.05325670f,
+				0.07974781f, 0.65219998f, -0.04152355f,
+				0.08510771f, 0.65219998f, -0.02862284f,
+				0.08844854f, 0.65219998f, -0.01472488f,
+				0.08960000f, 0.65219998f, 0.00000001f,
+				0.00000000f, 0.62452501f, -0.08369999f,
+				0.01374006f, 0.62452495f, -0.08262266f,
+				0.02671402f, 0.62452501f, -0.07949750f,
+				0.03876166f, 0.62452501f, -0.07448471f,
+				0.04972277f, 0.62452507f, -0.06774451f,
+				0.05943713f, 0.62452501f, -0.05943712f,
+				0.06774452f, 0.62452501f, -0.04972276f,
+				0.07448471f, 0.62452501f, -0.03876166f,
+				0.07949751f, 0.62452501f, -0.02671401f,
+				0.08262268f, 0.62452501f, -0.01374004f,
+				0.08370000f, 0.62452501f, 0.00000001f,
+				0.00000000f, 0.60000002f, -0.09999999f,
+				0.01640800f, 0.59999996f, -0.09871199f,
+				0.03190400f, 0.60000008f, -0.09497599f,
+				0.04629600f, 0.60000008f, -0.08898398f,
+				0.05939200f, 0.60000002f, -0.08092800f,
+				0.07100000f, 0.60000002f, -0.07099999f,
+				0.08092801f, 0.60000002f, -0.05939199f,
+				0.08898400f, 0.60000002f, -0.04629600f,
+				0.09497601f, 0.60000002f, -0.03190399f,
+				0.09871200f, 0.60000002f, -0.01640799f,
+				0.10000000f, 0.60000002f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500017f, 0.00000001f,
+				0.00000000f, 0.82500011f, 0.00000001f,
+				0.00000000f, 0.82500011f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82072502f, 0.09730001f,
+				-0.01602403f, 0.82072490f, 0.09605334f,
+				-0.03113590f, 0.82072508f, 0.09243499f,
+				-0.04515317f, 0.82072502f, 0.08662736f,
+				-0.05789339f, 0.82072508f, 0.07881294f,
+				-0.06917413f, 0.82072502f, 0.06917413f,
+				-0.07881293f, 0.82072502f, 0.05789340f,
+				-0.08662736f, 0.82072508f, 0.04515318f,
+				-0.09243498f, 0.82072502f, 0.03113591f,
+				-0.09605334f, 0.82072502f, 0.01602404f,
+				-0.09730000f, 0.82072502f, 0.00000001f,
+				0.00000000f, 0.80880016f, 0.15440002f,
+				-0.02542727f, 0.80880004f, 0.15242170f,
+				-0.04940724f, 0.80880022f, 0.14667983f,
+				-0.07165038f, 0.80880022f, 0.13746390f,
+				-0.09186715f, 0.80880028f, 0.12506345f,
+				-0.10976800f, 0.80880016f, 0.10976802f,
+				-0.12506345f, 0.80880016f, 0.09186715f,
+				-0.13746388f, 0.80880022f, 0.07165039f,
+				-0.14667982f, 0.80880022f, 0.04940725f,
+				-0.15242171f, 0.80880016f, 0.02542727f,
+				-0.15440002f, 0.80880016f, 0.00000001f,
+				0.00000000f, 0.79057503f, 0.17909999f,
+				-0.02949389f, 0.79057491f, 0.17680508f,
+				-0.05730940f, 0.79057509f, 0.17014435f,
+				-0.08311061f, 0.79057509f, 0.15945368f,
+				-0.10656158f, 0.79057503f, 0.14506906f,
+				-0.12732637f, 0.79057503f, 0.12732637f,
+				-0.14506905f, 0.79057503f, 0.10656157f,
+				-0.15945369f, 0.79057503f, 0.08311062f,
+				-0.17014435f, 0.79057503f, 0.05730941f,
+				-0.17680509f, 0.79057503f, 0.02949389f,
+				-0.17909999f, 0.79057503f, 0.00000001f,
+				0.00000000f, 0.76740009f, 0.17920001f,
+				-0.02950811f, 0.76740003f, 0.17690358f,
+				-0.05733786f, 0.76740015f, 0.17023848f,
+				-0.08315294f, 0.76740009f, 0.15954098f,
+				-0.10661709f, 0.76740015f, 0.14514741f,
+				-0.12739401f, 0.76740009f, 0.12739401f,
+				-0.14514741f, 0.76740009f, 0.10661709f,
+				-0.15954098f, 0.76740009f, 0.08315295f,
+				-0.17023847f, 0.76740015f, 0.05733786f,
+				-0.17690358f, 0.76740009f, 0.02950811f,
+				-0.17920001f, 0.76740009f, 0.00000001f,
+				0.00000000f, 0.74062496f, 0.16250001f,
+				-0.02675412f, 0.74062496f, 0.16041712f,
+				-0.05198800f, 0.74062502f, 0.15437202f,
+				-0.07539637f, 0.74062496f, 0.14466989f,
+				-0.09667401f, 0.74062496f, 0.13161601f,
+				-0.11551563f, 0.74062496f, 0.11551563f,
+				-0.13161600f, 0.74062496f, 0.09667400f,
+				-0.14466989f, 0.74062502f, 0.07539638f,
+				-0.15437201f, 0.74062502f, 0.05198800f,
+				-0.16041712f, 0.74062496f, 0.02675412f,
+				-0.16250001f, 0.74062496f, 0.00000001f,
+				0.00000000f, 0.71160007f, 0.13680001f,
+				-0.02251613f, 0.71160001f, 0.13504578f,
+				-0.04375527f, 0.71160012f, 0.12995483f,
+				-0.06345994f, 0.71160007f, 0.12178455f,
+				-0.08137268f, 0.71160012f, 0.11079246f,
+				-0.09723600f, 0.71160007f, 0.09723601f,
+				-0.11079245f, 0.71160007f, 0.08137267f,
+				-0.12178455f, 0.71160007f, 0.06345994f,
+				-0.12995481f, 0.71160007f, 0.04375527f,
+				-0.13504580f, 0.71160007f, 0.02251613f,
+				-0.13680001f, 0.71160007f, 0.00000001f,
+				0.00000000f, 0.68167502f, 0.10990001f,
+				-0.01807832f, 0.68167496f, 0.10848960f,
+				-0.03513507f, 0.68167502f, 0.10439679f,
+				-0.05096266f, 0.68167496f, 0.09782915f,
+				-0.06535346f, 0.68167502f, 0.08899432f,
+				-0.07809988f, 0.68167502f, 0.07809988f,
+				-0.08899431f, 0.68167502f, 0.06535346f,
+				-0.09782915f, 0.68167502f, 0.05096267f,
+				-0.10439678f, 0.68167502f, 0.03513508f,
+				-0.10848960f, 0.68167502f, 0.01807832f,
+				-0.10990001f, 0.68167502f, 0.00000001f,
+				0.00000000f, 0.65219998f, 0.08960001f,
+				-0.01472490f, 0.65219992f, 0.08844855f,
+				-0.02862285f, 0.65220004f, 0.08510773f,
+				-0.04152355f, 0.65219998f, 0.07974782f,
+				-0.05325671f, 0.65219998f, 0.07253915f,
+				-0.06365199f, 0.65219998f, 0.06365201f,
+				-0.07253914f, 0.65219998f, 0.05325671f,
+				-0.07974781f, 0.65219998f, 0.04152356f,
+				-0.08510771f, 0.65219998f, 0.02862286f,
+				-0.08844854f, 0.65219998f, 0.01472490f,
+				-0.08960000f, 0.65219998f, 0.00000001f,
+				0.00000000f, 0.62452501f, 0.08370001f,
+				-0.01374006f, 0.62452495f, 0.08262267f,
+				-0.02671402f, 0.62452501f, 0.07949752f,
+				-0.03876166f, 0.62452501f, 0.07448472f,
+				-0.04972277f, 0.62452507f, 0.06774452f,
+				-0.05943713f, 0.62452501f, 0.05943713f,
+				-0.06774452f, 0.62452501f, 0.04972278f,
+				-0.07448471f, 0.62452501f, 0.03876167f,
+				-0.07949751f, 0.62452501f, 0.02671402f,
+				-0.08262268f, 0.62452501f, 0.01374006f,
+				-0.08370000f, 0.62452501f, 0.00000001f,
+				0.00000000f, 0.60000002f, 0.10000001f,
+				-0.01640800f, 0.59999996f, 0.09871200f,
+				-0.03190400f, 0.60000008f, 0.09497602f,
+				-0.04629600f, 0.60000008f, 0.08898401f,
+				-0.05939200f, 0.60000002f, 0.08092801f,
+				-0.07100000f, 0.60000002f, 0.07100001f,
+				-0.08092801f, 0.60000002f, 0.05939201f,
+				-0.08898400f, 0.60000002f, 0.04629601f,
+				-0.09497601f, 0.60000002f, 0.03190401f,
+				-0.09871200f, 0.60000002f, 0.01640800f,
+				-0.10000000f, 0.60000002f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500017f, 0.00000001f,
+				0.00000000f, 0.82500011f, 0.00000001f,
+				0.00000000f, 0.82500011f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				0.00000000f, 0.82500005f, 0.00000001f,
+				-0.09730000f, 0.82072502f, 0.00000001f,
+				-0.09605332f, 0.82072490f, -0.01602402f,
+				-0.09243499f, 0.82072508f, -0.03113589f,
+				-0.08662736f, 0.82072502f, -0.04515316f,
+				-0.07881293f, 0.82072508f, -0.05789338f,
+				-0.06917413f, 0.82072502f, -0.06917411f,
+				-0.05789339f, 0.82072502f, -0.07881292f,
+				-0.04515317f, 0.82072508f, -0.08662735f,
+				-0.03113590f, 0.82072502f, -0.09243497f,
+				-0.01602403f, 0.82072502f, -0.09605333f,
+				0.00000000f, 0.82072502f, -0.09729999f,
+				-0.15440002f, 0.80880016f, 0.00000001f,
+				-0.15242170f, 0.80880004f, -0.02542726f,
+				-0.14667983f, 0.80880022f, -0.04940723f,
+				-0.13746388f, 0.80880022f, -0.07165037f,
+				-0.12506345f, 0.80880028f, -0.09186714f,
+				-0.10976801f, 0.80880016f, -0.10976800f,
+				-0.09186715f, 0.80880016f, -0.12506345f,
+				-0.07165038f, 0.80880022f, -0.13746388f,
+				-0.04940724f, 0.80880022f, -0.14667982f,
+				-0.02542726f, 0.80880016f, -0.15242171f,
+				0.00000000f, 0.80880016f, -0.15440002f,
+				-0.17909999f, 0.79057503f, 0.00000001f,
+				-0.17680508f, 0.79057491f, -0.02949388f,
+				-0.17014435f, 0.79057509f, -0.05730940f,
+				-0.15945368f, 0.79057509f, -0.08311061f,
+				-0.14506906f, 0.79057503f, -0.10656158f,
+				-0.12732637f, 0.79057503f, -0.12732637f,
+				-0.10656157f, 0.79057503f, -0.14506905f,
+				-0.08311062f, 0.79057503f, -0.15945369f,
+				-0.05730940f, 0.79057503f, -0.17014435f,
+				-0.02949388f, 0.79057503f, -0.17680509f,
+				0.00000000f, 0.79057503f, -0.17909999f,
+				-0.17920001f, 0.76740009f, 0.00000001f,
+				-0.17690356f, 0.76740003f, -0.02950810f,
+				-0.17023848f, 0.76740015f, -0.05733785f,
+				-0.15954098f, 0.76740009f, -0.08315294f,
+				-0.14514741f, 0.76740015f, -0.10661709f,
+				-0.12739401f, 0.76740009f, -0.12739401f,
+				-0.10661709f, 0.76740009f, -0.14514740f,
+				-0.08315295f, 0.76740009f, -0.15954098f,
+				-0.05733785f, 0.76740015f, -0.17023847f,
+				-0.02950810f, 0.76740009f, -0.17690358f,
+				0.00000000f, 0.76740009f, -0.17920001f,
+				-0.16250001f, 0.74062496f, 0.00000001f,
+				-0.16041712f, 0.74062496f, -0.02675412f,
+				-0.15437202f, 0.74062502f, -0.05198800f,
+				-0.14466989f, 0.74062496f, -0.07539637f,
+				-0.13161601f, 0.74062496f, -0.09667400f,
+				-0.11551563f, 0.74062496f, -0.11551563f,
+				-0.09667400f, 0.74062496f, -0.13161600f,
+				-0.07539637f, 0.74062502f, -0.14466989f,
+				-0.05198800f, 0.74062502f, -0.15437201f,
+				-0.02675411f, 0.74062496f, -0.16041712f,
+				0.00000000f, 0.74062496f, -0.16250001f,
+				-0.13680001f, 0.71160007f, 0.00000001f,
+				-0.13504578f, 0.71160001f, -0.02251612f,
+				-0.12995483f, 0.71160012f, -0.04375526f,
+				-0.12178454f, 0.71160007f, -0.06345993f,
+				-0.11079245f, 0.71160012f, -0.08137267f,
+				-0.09723600f, 0.71160007f, -0.09723599f,
+				-0.08137267f, 0.71160007f, -0.11079245f,
+				-0.06345994f, 0.71160007f, -0.12178454f,
+				-0.04375526f, 0.71160007f, -0.12995481f,
+				-0.02251612f, 0.71160007f, -0.13504578f,
+				0.00000000f, 0.71160007f, -0.13679999f,
+				-0.10990001f, 0.68167502f, 0.00000001f,
+				-0.10848958f, 0.68167496f, -0.01807831f,
+				-0.10439678f, 0.68167502f, -0.03513507f,
+				-0.09782913f, 0.68167496f, -0.05096265f,
+				-0.08899431f, 0.68167502f, -0.06535345f,
+				-0.07809988f, 0.68167502f, -0.07809987f,
+				-0.06535345f, 0.68167502f, -0.08899430f,
+				-0.05096266f, 0.68167502f, -0.09782913f,
+				-0.03513507f, 0.68167502f, -0.10439677f,
+				-0.01807831f, 0.68167502f, -0.10848959f,
+				0.00000000f, 0.68167502f, -0.10990000f,
+				-0.08960000f, 0.65219998f, 0.00000001f,
+				-0.08844854f, 0.65219992f, -0.01472489f,
+				-0.08510771f, 0.65220004f, -0.02862284f,
+				-0.07974781f, 0.65219998f, -0.04152355f,
+				-0.07253914f, 0.65219998f, -0.05325670f,
+				-0.06365200f, 0.65219998f, -0.06365199f,
+				-0.05325670f, 0.65219998f, -0.07253914f,
+				-0.04152355f, 0.65219998f, -0.07974781f,
+				-0.02862285f, 0.65219998f, -0.08510771f,
+				-0.01472489f, 0.65219998f, -0.08844854f,
+				0.00000000f, 0.65219998f, -0.08960000f,
+				-0.08370000f, 0.62452501f, 0.00000001f,
+				-0.08262266f, 0.62452495f, -0.01374005f,
+				-0.07949751f, 0.62452501f, -0.02671401f,
+				-0.07448471f, 0.62452501f, -0.03876165f,
+				-0.06774452f, 0.62452507f, -0.04972276f,
+				-0.05943713f, 0.62452501f, -0.05943712f,
+				-0.04972277f, 0.62452501f, -0.06774451f,
+				-0.03876166f, 0.62452501f, -0.07448471f,
+				-0.02671402f, 0.62452501f, -0.07949750f,
+				-0.01374005f, 0.62452501f, -0.08262267f,
+				0.00000000f, 0.62452501f, -0.08369999f,
+				-0.10000000f, 0.60000002f, 0.00000001f,
+				-0.09871199f, 0.59999996f, -0.01640799f,
+				-0.09497602f, 0.60000008f, -0.03190399f,
+				-0.08898400f, 0.60000008f, -0.04629599f,
+				-0.08092801f, 0.60000002f, -0.05939199f,
+				-0.07100000f, 0.60000002f, -0.07099999f,
+				-0.05939200f, 0.60000002f, -0.08092800f,
+				-0.04629600f, 0.60000002f, -0.08898400f,
+				-0.03190400f, 0.60000002f, -0.09497599f,
+				-0.01640799f, 0.60000002f, -0.09871200f,
+				0.00000000f, 0.60000002f, -0.09999999f,
+				0.10000000f, 0.60000002f, 0.00000001f,
+				0.09871199f, 0.59999996f, 0.01640801f,
+				0.09497602f, 0.60000008f, 0.03190401f,
+				0.08898400f, 0.60000008f, 0.04629601f,
+				0.08092801f, 0.60000002f, 0.05939201f,
+				0.07100000f, 0.60000002f, 0.07100001f,
+				0.05939200f, 0.60000002f, 0.08092801f,
+				0.04629600f, 0.60000002f, 0.08898401f,
+				0.03190400f, 0.60000002f, 0.09497601f,
+				0.01640799f, 0.60000002f, 0.09871200f,
+				0.00000000f, 0.60000002f, 0.10000001f,
+				0.13969998f, 0.57959992f, 0.00000001f,
+				0.13790064f, 0.57959986f, 0.02292198f,
+				0.13268146f, 0.57959998f, 0.04456990f,
+				0.12431064f, 0.57959992f, 0.06467552f,
+				0.11305641f, 0.57959998f, 0.08297063f,
+				0.09918699f, 0.57959992f, 0.09918701f,
+				0.08297062f, 0.57959992f, 0.11305644f,
+				0.06467551f, 0.57959998f, 0.12431066f,
+				0.04456988f, 0.57959992f, 0.13268149f,
+				0.02292197f, 0.57959992f, 0.13790068f,
+				0.00000000f, 0.57959992f, 0.13970001f,
+				0.19560002f, 0.56280005f, 0.00000001f,
+				0.19308068f, 0.56279999f, 0.03209405f,
+				0.18577307f, 0.56280011f, 0.06240424f,
+				0.17405272f, 0.56280005f, 0.09055499f,
+				0.15829518f, 0.56280005f, 0.11617076f,
+				0.13887602f, 0.56280005f, 0.13887601f,
+				0.11617076f, 0.56280005f, 0.15829518f,
+				0.09055499f, 0.56280005f, 0.17405272f,
+				0.06240423f, 0.56280005f, 0.18577307f,
+				0.03209404f, 0.56280005f, 0.19308069f,
+				0.00000000f, 0.56280005f, 0.19560002f,
+				0.26289999f, 0.54869998f, 0.00000001f,
+				0.25951380f, 0.54869998f, 0.04313663f,
+				0.24969190f, 0.54869998f, 0.08387563f,
+				0.23393893f, 0.54870003f, 0.12171219f,
+				0.21275970f, 0.54869998f, 0.15614158f,
+				0.18665901f, 0.54869998f, 0.18665899f,
+				0.15614155f, 0.54869998f, 0.21275972f,
+				0.12171219f, 0.54869998f, 0.23393893f,
+				0.08387562f, 0.54869998f, 0.24969190f,
+				0.04313662f, 0.54869998f, 0.25951385f,
+				0.00000000f, 0.54869998f, 0.26289999f,
+				0.33680001f, 0.53640002f, 0.00000001f,
+				0.33246198f, 0.53639996f, 0.05526215f,
+				0.31987920f, 0.53640002f, 0.10745268f,
+				0.29969811f, 0.53640002f, 0.15592493f,
+				0.27256551f, 0.53640002f, 0.20003228f,
+				0.23912801f, 0.53640002f, 0.23912801f,
+				0.20003226f, 0.53640002f, 0.27256551f,
+				0.15592495f, 0.53640008f, 0.29969811f,
+				0.10745268f, 0.53640008f, 0.31987917f,
+				0.05526213f, 0.53640002f, 0.33246201f,
+				0.00000000f, 0.53640002f, 0.33680001f,
+				0.41250002f, 0.52499998f, 0.00000001f,
+				0.40718701f, 0.52499992f, 0.06768300f,
+				0.39177606f, 0.52499998f, 0.13160400f,
+				0.36705902f, 0.52499998f, 0.19097100f,
+				0.33382803f, 0.52499998f, 0.24499202f,
+				0.29287499f, 0.52499998f, 0.29287499f,
+				0.24499199f, 0.52499998f, 0.33382803f,
+				0.19097102f, 0.52499998f, 0.36705902f,
+				0.13160400f, 0.52499998f, 0.39177603f,
+				0.06768297f, 0.52499998f, 0.40718701f,
+				0.00000000f, 0.52499998f, 0.41250002f,
+				0.48519999f, 0.51359999f, 0.00000001f,
+				0.47895056f, 0.51359999f, 0.07961162f,
+				0.46082360f, 0.51359999f, 0.15479822f,
+				0.43175036f, 0.51359999f, 0.22462820f,
+				0.39266267f, 0.51360005f, 0.28817001f,
+				0.34449199f, 0.51359999f, 0.34449199f,
+				0.28816998f, 0.51359999f, 0.39266264f,
+				0.22462821f, 0.51359999f, 0.43175036f,
+				0.15479821f, 0.51359999f, 0.46082354f,
+				0.07961158f, 0.51359999f, 0.47895062f,
+				0.00000000f, 0.51359999f, 0.48519999f,
+				0.55009997f, 0.50129998f, 0.00000001f,
+				0.54301465f, 0.50129992f, 0.09026041f,
+				0.52246296f, 0.50129998f, 0.17550392f,
+				0.48950094f, 0.50129998f, 0.25467429f,
+				0.44518495f, 0.50129998f, 0.32671541f,
+				0.39057100f, 0.50129998f, 0.39057100f,
+				0.32671538f, 0.50129998f, 0.44518495f,
+				0.25467432f, 0.50129998f, 0.48950097f,
+				0.17550389f, 0.50129998f, 0.52246296f,
+				0.09026037f, 0.50129998f, 0.54301471f,
+				0.00000000f, 0.50129998f, 0.55009997f,
+				0.60240000f, 0.48720002f, 0.00000001f,
+				0.59464109f, 0.48719996f, 0.09884179f,
+				0.57213551f, 0.48720005f, 0.19218971f,
+				0.53603959f, 0.48719999f, 0.27888709f,
+				0.48751029f, 0.48720002f, 0.35777742f,
+				0.42770401f, 0.48720002f, 0.42770401f,
+				0.35777742f, 0.48720002f, 0.48751026f,
+				0.27888709f, 0.48720005f, 0.53603959f,
+				0.19218969f, 0.48720002f, 0.57213545f,
+				0.09884176f, 0.48720002f, 0.59464109f,
+				0.00000000f, 0.48720002f, 0.60240000f,
+				0.63730001f, 0.47040004f, 0.00000001f,
+				0.62909156f, 0.47039998f, 0.10456818f,
+				0.60528207f, 0.47040007f, 0.20332420f,
+				0.56709504f, 0.47040001f, 0.29504442f,
+				0.51575416f, 0.47040004f, 0.37850523f,
+				0.45248300f, 0.47040004f, 0.45248300f,
+				0.37850523f, 0.47040004f, 0.51575416f,
+				0.29504442f, 0.47040004f, 0.56709504f,
+				0.20332420f, 0.47040004f, 0.60528207f,
+				0.10456815f, 0.47040004f, 0.62909162f,
+				0.00000000f, 0.47040004f, 0.63730001f,
+				0.64999998f, 0.45000005f, 0.00000001f,
+				0.64162791f, 0.44999996f, 0.10665201f,
+				0.61734402f, 0.45000011f, 0.20737600f,
+				0.57839590f, 0.45000002f, 0.30092400f,
+				0.52603197f, 0.45000008f, 0.38604802f,
+				0.46149999f, 0.45000005f, 0.46149999f,
+				0.38604796f, 0.45000005f, 0.52603197f,
+				0.30092400f, 0.45000008f, 0.57839596f,
+				0.20737599f, 0.45000005f, 0.61734402f,
+				0.10665196f, 0.45000005f, 0.64162797f,
+				0.00000000f, 0.45000005f, 0.64999998f,
+				0.00000000f, 0.60000002f, -0.09999999f,
+				0.01640800f, 0.59999996f, -0.09871199f,
+				0.03190400f, 0.60000008f, -0.09497599f,
+				0.04629600f, 0.60000008f, -0.08898398f,
+				0.05939200f, 0.60000002f, -0.08092800f,
+				0.07100000f, 0.60000002f, -0.07099999f,
+				0.08092801f, 0.60000002f, -0.05939199f,
+				0.08898400f, 0.60000002f, -0.04629600f,
+				0.09497601f, 0.60000002f, -0.03190399f,
+				0.09871200f, 0.60000002f, -0.01640799f,
+				0.10000000f, 0.60000002f, 0.00000001f,
+				0.00000000f, 0.57959992f, -0.13969998f,
+				0.02292197f, 0.57959986f, -0.13790064f,
+				0.04456988f, 0.57959998f, -0.13268146f,
+				0.06467551f, 0.57959992f, -0.12431064f,
+				0.08297062f, 0.57959998f, -0.11305641f,
+				0.09918699f, 0.57959992f, -0.09918699f,
+				0.11305641f, 0.57959992f, -0.08297061f,
+				0.12431064f, 0.57959998f, -0.06467551f,
+				0.13268146f, 0.57959992f, -0.04456988f,
+				0.13790065f, 0.57959992f, -0.02292196f,
+				0.13969998f, 0.57959992f, 0.00000001f,
+				0.00000000f, 0.56280005f, -0.19560000f,
+				0.03209405f, 0.56279999f, -0.19308066f,
+				0.06240423f, 0.56280011f, -0.18577306f,
+				0.09055499f, 0.56280005f, -0.17405270f,
+				0.11617076f, 0.56280005f, -0.15829517f,
+				0.13887601f, 0.56280005f, -0.13887601f,
+				0.15829518f, 0.56280005f, -0.11617075f,
+				0.17405272f, 0.56280005f, -0.09055498f,
+				0.18577307f, 0.56280005f, -0.06240422f,
+				0.19308069f, 0.56280005f, -0.03209403f,
+				0.19560002f, 0.56280005f, 0.00000001f,
+				0.00000000f, 0.54869998f, -0.26289999f,
+				0.04313663f, 0.54869998f, -0.25951380f,
+				0.08387563f, 0.54869998f, -0.24969190f,
+				0.12171219f, 0.54870003f, -0.23393893f,
+				0.15614158f, 0.54869998f, -0.21275970f,
+				0.18665899f, 0.54869998f, -0.18665899f,
+				0.21275972f, 0.54869998f, -0.15614155f,
+				0.23393893f, 0.54869998f, -0.12171219f,
+				0.24969190f, 0.54869998f, -0.08387561f,
+				0.25951385f, 0.54869998f, -0.04313661f,
+				0.26289999f, 0.54869998f, 0.00000001f,
+				0.00000000f, 0.53640002f, -0.33680001f,
+				0.05526214f, 0.53639996f, -0.33246198f,
+				0.10745268f, 0.53640002f, -0.31987920f,
+				0.15592493f, 0.53640002f, -0.29969811f,
+				0.20003228f, 0.53640002f, -0.27256551f,
+				0.23912801f, 0.53640002f, -0.23912799f,
+				0.27256551f, 0.53640002f, -0.20003225f,
+				0.29969811f, 0.53640008f, -0.15592495f,
+				0.31987917f, 0.53640008f, -0.10745267f,
+				0.33246201f, 0.53640002f, -0.05526212f,
+				0.33680001f, 0.53640002f, 0.00000001f,
+				0.00000000f, 0.52499998f, -0.41250002f,
+				0.06768300f, 0.52499992f, -0.40718701f,
+				0.13160400f, 0.52499998f, -0.39177606f,
+				0.19097100f, 0.52499998f, -0.36705902f,
+				0.24499202f, 0.52499998f, -0.33382803f,
+				0.29287499f, 0.52499998f, -0.29287499f,
+				0.33382803f, 0.52499998f, -0.24499199f,
+				0.36705902f, 0.52499998f, -0.19097102f,
+				0.39177603f, 0.52499998f, -0.13160400f,
+				0.40718701f, 0.52499998f, -0.06768297f,
+				0.41250002f, 0.52499998f, 0.00000001f,
+				0.00000000f, 0.51359999f, -0.48519999f,
+				0.07961161f, 0.51359999f, -0.47895056f,
+				0.15479822f, 0.51359999f, -0.46082360f,
+				0.22462820f, 0.51359999f, -0.43175036f,
+				0.28817001f, 0.51360005f, -0.39266267f,
+				0.34449199f, 0.51359999f, -0.34449199f,
+				0.39266264f, 0.51359999f, -0.28816998f,
+				0.43175036f, 0.51359999f, -0.22462821f,
+				0.46082354f, 0.51359999f, -0.15479821f,
+				0.47895062f, 0.51359999f, -0.07961158f,
+				0.48519999f, 0.51359999f, 0.00000001f,
+				0.00000000f, 0.50129998f, -0.55009997f,
+				0.09026040f, 0.50129992f, -0.54301465f,
+				0.17550392f, 0.50129998f, -0.52246296f,
+				0.25467429f, 0.50129998f, -0.48950094f,
+				0.32671541f, 0.50129998f, -0.44518495f,
+				0.39057100f, 0.50129998f, -0.39057100f,
+				0.44518495f, 0.50129998f, -0.32671538f,
+				0.48950097f, 0.50129998f, -0.25467432f,
+				0.52246296f, 0.50129998f, -0.17550389f,
+				0.54301471f, 0.50129998f, -0.09026036f,
+				0.55009997f, 0.50129998f, 0.00000001f,
+				0.00000000f, 0.48720002f, -0.60240000f,
+				0.09884179f, 0.48719996f, -0.59464109f,
+				0.19218971f, 0.48720005f, -0.57213551f,
+				0.27888709f, 0.48719999f, -0.53603959f,
+				0.35777742f, 0.48720002f, -0.48751029f,
+				0.42770401f, 0.48720002f, -0.42770401f,
+				0.48751026f, 0.48720002f, -0.35777742f,
+				0.53603959f, 0.48720005f, -0.27888709f,
+				0.57213545f, 0.48720002f, -0.19218969f,
+				0.59464109f, 0.48720002f, -0.09884175f,
+				0.60240000f, 0.48720002f, 0.00000001f,
+				0.00000000f, 0.47040004f, -0.63730001f,
+				0.10456818f, 0.47039998f, -0.62909156f,
+				0.20332420f, 0.47040007f, -0.60528207f,
+				0.29504442f, 0.47040001f, -0.56709504f,
+				0.37850523f, 0.47040004f, -0.51575416f,
+				0.45248300f, 0.47040004f, -0.45248300f,
+				0.51575416f, 0.47040004f, -0.37850523f,
+				0.56709504f, 0.47040004f, -0.29504442f,
+				0.60528207f, 0.47040004f, -0.20332420f,
+				0.62909162f, 0.47040004f, -0.10456815f,
+				0.63730001f, 0.47040004f, 0.00000001f,
+				0.00000000f, 0.45000005f, -0.64999998f,
+				0.10665200f, 0.44999996f, -0.64162791f,
+				0.20737600f, 0.45000011f, -0.61734402f,
+				0.30092400f, 0.45000002f, -0.57839590f,
+				0.38604802f, 0.45000008f, -0.52603197f,
+				0.46149999f, 0.45000005f, -0.46149999f,
+				0.52603197f, 0.45000005f, -0.38604796f,
+				0.57839596f, 0.45000008f, -0.30092400f,
+				0.61734402f, 0.45000005f, -0.20737599f,
+				0.64162797f, 0.45000005f, -0.10665195f,
+				0.64999998f, 0.45000005f, 0.00000001f,
+				0.00000000f, 0.60000002f, 0.10000001f,
+				-0.01640800f, 0.59999996f, 0.09871200f,
+				-0.03190400f, 0.60000008f, 0.09497602f,
+				-0.04629600f, 0.60000008f, 0.08898401f,
+				-0.05939200f, 0.60000002f, 0.08092801f,
+				-0.07100000f, 0.60000002f, 0.07100001f,
+				-0.08092801f, 0.60000002f, 0.05939201f,
+				-0.08898400f, 0.60000002f, 0.04629601f,
+				-0.09497601f, 0.60000002f, 0.03190401f,
+				-0.09871200f, 0.60000002f, 0.01640800f,
+				-0.10000000f, 0.60000002f, 0.00000001f,
+				0.00000000f, 0.57959992f, 0.13970001f,
+				-0.02292197f, 0.57959986f, 0.13790067f,
+				-0.04456988f, 0.57959998f, 0.13268149f,
+				-0.06467551f, 0.57959992f, 0.12431065f,
+				-0.08297062f, 0.57959998f, 0.11305643f,
+				-0.09918699f, 0.57959992f, 0.09918701f,
+				-0.11305641f, 0.57959992f, 0.08297063f,
+				-0.12431064f, 0.57959998f, 0.06467552f,
+				-0.13268146f, 0.57959992f, 0.04456989f,
+				-0.13790065f, 0.57959992f, 0.02292197f,
+				-0.13969998f, 0.57959992f, 0.00000001f,
+				0.00000000f, 0.56280005f, 0.19560002f,
+				-0.03209405f, 0.56279999f, 0.19308068f,
+				-0.06240423f, 0.56280011f, 0.18577307f,
+				-0.09055499f, 0.56280005f, 0.17405272f,
+				-0.11617076f, 0.56280005f, 0.15829518f,
+				-0.13887601f, 0.56280005f, 0.13887602f,
+				-0.15829518f, 0.56280005f, 0.11617076f,
+				-0.17405272f, 0.56280005f, 0.09055499f,
+				-0.18577307f, 0.56280005f, 0.06240423f,
+				-0.19308069f, 0.56280005f, 0.03209404f,
+				-0.19560002f, 0.56280005f, 0.00000001f,
+				0.00000000f, 0.54869998f, 0.26289999f,
+				-0.04313663f, 0.54869998f, 0.25951380f,
+				-0.08387563f, 0.54869998f, 0.24969190f,
+				-0.12171219f, 0.54870003f, 0.23393893f,
+				-0.15614158f, 0.54869998f, 0.21275970f,
+				-0.18665899f, 0.54869998f, 0.18665901f,
+				-0.21275972f, 0.54869998f, 0.15614155f,
+				-0.23393893f, 0.54869998f, 0.12171219f,
+				-0.24969190f, 0.54869998f, 0.08387562f,
+				-0.25951385f, 0.54869998f, 0.04313662f,
+				-0.26289999f, 0.54869998f, 0.00000001f,
+				0.00000000f, 0.53640002f, 0.33680001f,
+				-0.05526214f, 0.53639996f, 0.33246198f,
+				-0.10745268f, 0.53640002f, 0.31987920f,
+				-0.15592493f, 0.53640002f, 0.29969811f,
+				-0.20003228f, 0.53640002f, 0.27256551f,
+				-0.23912801f, 0.53640002f, 0.23912801f,
+				-0.27256551f, 0.53640002f, 0.20003226f,
+				-0.29969811f, 0.53640008f, 0.15592495f,
+				-0.31987917f, 0.53640008f, 0.10745268f,
+				-0.33246201f, 0.53640002f, 0.05526213f,
+				-0.33680001f, 0.53640002f, 0.00000001f,
+				0.00000000f, 0.52499998f, 0.41250002f,
+				-0.06768300f, 0.52499992f, 0.40718701f,
+				-0.13160400f, 0.52499998f, 0.39177606f,
+				-0.19097100f, 0.52499998f, 0.36705902f,
+				-0.24499202f, 0.52499998f, 0.33382803f,
+				-0.29287499f, 0.52499998f, 0.29287499f,
+				-0.33382803f, 0.52499998f, 0.24499199f,
+				-0.36705902f, 0.52499998f, 0.19097102f,
+				-0.39177603f, 0.52499998f, 0.13160400f,
+				-0.40718701f, 0.52499998f, 0.06768298f,
+				-0.41250002f, 0.52499998f, 0.00000001f,
+				0.00000000f, 0.51359999f, 0.48519999f,
+				-0.07961161f, 0.51359999f, 0.47895056f,
+				-0.15479822f, 0.51359999f, 0.46082360f,
+				-0.22462820f, 0.51359999f, 0.43175036f,
+				-0.28817001f, 0.51360005f, 0.39266267f,
+				-0.34449199f, 0.51359999f, 0.34449199f,
+				-0.39266264f, 0.51359999f, 0.28816998f,
+				-0.43175036f, 0.51359999f, 0.22462821f,
+				-0.46082354f, 0.51359999f, 0.15479821f,
+				-0.47895062f, 0.51359999f, 0.07961159f,
+				-0.48519999f, 0.51359999f, 0.00000001f,
+				0.00000000f, 0.50129998f, 0.55009997f,
+				-0.09026040f, 0.50129992f, 0.54301465f,
+				-0.17550392f, 0.50129998f, 0.52246296f,
+				-0.25467429f, 0.50129998f, 0.48950094f,
+				-0.32671541f, 0.50129998f, 0.44518495f,
+				-0.39057100f, 0.50129998f, 0.39057100f,
+				-0.44518495f, 0.50129998f, 0.32671538f,
+				-0.48950097f, 0.50129998f, 0.25467432f,
+				-0.52246296f, 0.50129998f, 0.17550389f,
+				-0.54301471f, 0.50129998f, 0.09026038f,
+				-0.55009997f, 0.50129998f, 0.00000001f,
+				0.00000000f, 0.48720002f, 0.60240000f,
+				-0.09884179f, 0.48719996f, 0.59464109f,
+				-0.19218971f, 0.48720005f, 0.57213551f,
+				-0.27888709f, 0.48719999f, 0.53603959f,
+				-0.35777742f, 0.48720002f, 0.48751029f,
+				-0.42770401f, 0.48720002f, 0.42770401f,
+				-0.48751026f, 0.48720002f, 0.35777742f,
+				-0.53603959f, 0.48720005f, 0.27888709f,
+				-0.57213545f, 0.48720002f, 0.19218969f,
+				-0.59464109f, 0.48720002f, 0.09884176f,
+				-0.60240000f, 0.48720002f, 0.00000001f,
+				0.00000000f, 0.47040004f, 0.63730001f,
+				-0.10456818f, 0.47039998f, 0.62909156f,
+				-0.20332420f, 0.47040007f, 0.60528207f,
+				-0.29504442f, 0.47040001f, 0.56709504f,
+				-0.37850523f, 0.47040004f, 0.51575416f,
+				-0.45248300f, 0.47040004f, 0.45248300f,
+				-0.51575416f, 0.47040004f, 0.37850523f,
+				-0.56709504f, 0.47040004f, 0.29504442f,
+				-0.60528207f, 0.47040004f, 0.20332420f,
+				-0.62909162f, 0.47040004f, 0.10456816f,
+				-0.63730001f, 0.47040004f, 0.00000001f,
+				0.00000000f, 0.45000005f, 0.64999998f,
+				-0.10665200f, 0.44999996f, 0.64162791f,
+				-0.20737600f, 0.45000011f, 0.61734402f,
+				-0.30092400f, 0.45000002f, 0.57839590f,
+				-0.38604802f, 0.45000008f, 0.52603197f,
+				-0.46149999f, 0.45000005f, 0.46149999f,
+				-0.52603197f, 0.45000005f, 0.38604796f,
+				-0.57839596f, 0.45000008f, 0.30092400f,
+				-0.61734402f, 0.45000005f, 0.20737599f,
+				-0.64162797f, 0.45000005f, 0.10665197f,
+				-0.64999998f, 0.45000005f, 0.00000001f,
+				-0.10000000f, 0.60000002f, 0.00000001f,
+				-0.09871199f, 0.59999996f, -0.01640799f,
+				-0.09497602f, 0.60000008f, -0.03190399f,
+				-0.08898400f, 0.60000008f, -0.04629599f,
+				-0.08092801f, 0.60000002f, -0.05939199f,
+				-0.07100000f, 0.60000002f, -0.07099999f,
+				-0.05939200f, 0.60000002f, -0.08092800f,
+				-0.04629600f, 0.60000002f, -0.08898400f,
+				-0.03190400f, 0.60000002f, -0.09497599f,
+				-0.01640799f, 0.60000002f, -0.09871200f,
+				0.00000000f, 0.60000002f, -0.09999999f,
+				-0.13969998f, 0.57959992f, 0.00000001f,
+				-0.13790064f, 0.57959986f, -0.02292197f,
+				-0.13268146f, 0.57959998f, -0.04456988f,
+				-0.12431064f, 0.57959992f, -0.06467550f,
+				-0.11305641f, 0.57959998f, -0.08297062f,
+				-0.09918699f, 0.57959992f, -0.09918699f,
+				-0.08297062f, 0.57959992f, -0.11305641f,
+				-0.06467551f, 0.57959998f, -0.12431063f,
+				-0.04456988f, 0.57959992f, -0.13268146f,
+				-0.02292197f, 0.57959992f, -0.13790065f,
+				0.00000000f, 0.57959992f, -0.13969998f,
+				-0.19560002f, 0.56280005f, 0.00000001f,
+				-0.19308068f, 0.56279999f, -0.03209404f,
+				-0.18577307f, 0.56280011f, -0.06240422f,
+				-0.17405272f, 0.56280005f, -0.09055497f,
+				-0.15829518f, 0.56280005f, -0.11617076f,
+				-0.13887602f, 0.56280005f, -0.13887601f,
+				-0.11617076f, 0.56280005f, -0.15829517f,
+				-0.09055499f, 0.56280005f, -0.17405272f,
+				-0.06240423f, 0.56280005f, -0.18577306f,
+				-0.03209404f, 0.56280005f, -0.19308066f,
+				0.00000000f, 0.56280005f, -0.19560000f,
+				-0.26289999f, 0.54869998f, 0.00000001f,
+				-0.25951380f, 0.54869998f, -0.04313662f,
+				-0.24969190f, 0.54869998f, -0.08387562f,
+				-0.23393893f, 0.54870003f, -0.12171219f,
+				-0.21275970f, 0.54869998f, -0.15614156f,
+				-0.18665901f, 0.54869998f, -0.18665899f,
+				-0.15614155f, 0.54869998f, -0.21275972f,
+				-0.12171219f, 0.54869998f, -0.23393893f,
+				-0.08387562f, 0.54869998f, -0.24969190f,
+				-0.04313662f, 0.54869998f, -0.25951385f,
+				0.00000000f, 0.54869998f, -0.26289999f,
+				-0.33680001f, 0.53640002f, 0.00000001f,
+				-0.33246198f, 0.53639996f, -0.05526214f,
+				-0.31987920f, 0.53640002f, -0.10745268f,
+				-0.29969811f, 0.53640002f, -0.15592493f,
+				-0.27256551f, 0.53640002f, -0.20003226f,
+				-0.23912801f, 0.53640002f, -0.23912801f,
+				-0.20003226f, 0.53640002f, -0.27256551f,
+				-0.15592495f, 0.53640008f, -0.29969811f,
+				-0.10745268f, 0.53640008f, -0.31987917f,
+				-0.05526213f, 0.53640002f, -0.33246201f,
+				0.00000000f, 0.53640002f, -0.33680001f,
+				-0.41250002f, 0.52499998f, 0.00000001f,
+				-0.40718701f, 0.52499992f, -0.06768300f,
+				-0.39177606f, 0.52499998f, -0.13160400f,
+				-0.36705902f, 0.52499998f, -0.19097100f,
+				-0.33382803f, 0.52499998f, -0.24499202f,
+				-0.29287499f, 0.52499998f, -0.29287499f,
+				-0.24499199f, 0.52499998f, -0.33382803f,
+				-0.19097102f, 0.52499998f, -0.36705902f,
+				-0.13160400f, 0.52499998f, -0.39177603f,
+				-0.06768297f, 0.52499998f, -0.40718701f,
+				0.00000000f, 0.52499998f, -0.41250002f,
+				-0.48519999f, 0.51359999f, 0.00000001f,
+				-0.47895056f, 0.51359999f, -0.07961161f,
+				-0.46082360f, 0.51359999f, -0.15479822f,
+				-0.43175036f, 0.51359999f, -0.22462820f,
+				-0.39266267f, 0.51360005f, -0.28817001f,
+				-0.34449199f, 0.51359999f, -0.34449199f,
+				-0.28816998f, 0.51359999f, -0.39266264f,
+				-0.22462821f, 0.51359999f, -0.43175036f,
+				-0.15479821f, 0.51359999f, -0.46082354f,
+				-0.07961158f, 0.51359999f, -0.47895062f,
+				0.00000000f, 0.51359999f, -0.48519999f,
+				-0.55009997f, 0.50129998f, 0.00000001f,
+				-0.54301465f, 0.50129992f, -0.09026039f,
+				-0.52246296f, 0.50129998f, -0.17550392f,
+				-0.48950094f, 0.50129998f, -0.25467429f,
+				-0.44518495f, 0.50129998f, -0.32671541f,
+				-0.39057100f, 0.50129998f, -0.39057100f,
+				-0.32671538f, 0.50129998f, -0.44518495f,
+				-0.25467432f, 0.50129998f, -0.48950097f,
+				-0.17550389f, 0.50129998f, -0.52246296f,
+				-0.09026037f, 0.50129998f, -0.54301471f,
+				0.00000000f, 0.50129998f, -0.55009997f,
+				-0.60240000f, 0.48720002f, 0.00000001f,
+				-0.59464109f, 0.48719996f, -0.09884178f,
+				-0.57213551f, 0.48720005f, -0.19218971f,
+				-0.53603959f, 0.48719999f, -0.27888709f,
+				-0.48751029f, 0.48720002f, -0.35777742f,
+				-0.42770401f, 0.48720002f, -0.42770401f,
+				-0.35777742f, 0.48720002f, -0.48751026f,
+				-0.27888709f, 0.48720005f, -0.53603959f,
+				-0.19218969f, 0.48720002f, -0.57213545f,
+				-0.09884176f, 0.48720002f, -0.59464109f,
+				0.00000000f, 0.48720002f, -0.60240000f,
+				-0.63730001f, 0.47040004f, 0.00000001f,
+				-0.62909156f, 0.47039998f, -0.10456817f,
+				-0.60528207f, 0.47040007f, -0.20332420f,
+				-0.56709504f, 0.47040001f, -0.29504442f,
+				-0.51575416f, 0.47040004f, -0.37850523f,
+				-0.45248300f, 0.47040004f, -0.45248300f,
+				-0.37850523f, 0.47040004f, -0.51575416f,
+				-0.29504442f, 0.47040004f, -0.56709504f,
+				-0.20332420f, 0.47040004f, -0.60528207f,
+				-0.10456815f, 0.47040004f, -0.62909162f,
+				0.00000000f, 0.47040004f, -0.63730001f,
+				-0.64999998f, 0.45000005f, 0.00000001f,
+				-0.64162791f, 0.44999996f, -0.10665199f,
+				-0.61734402f, 0.45000011f, -0.20737600f,
+				-0.57839590f, 0.45000002f, -0.30092400f,
+				-0.52603197f, 0.45000008f, -0.38604802f,
+				-0.46149999f, 0.45000005f, -0.46149999f,
+				-0.38604796f, 0.45000005f, -0.52603197f,
+				-0.30092400f, 0.45000008f, -0.57839596f,
+				-0.20737599f, 0.45000005f, -0.61734402f,
+				-0.10665196f, 0.45000005f, -0.64162797f,
+				0.00000000f, 0.45000005f, -0.64999998f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000006f, -0.00000001f,
+				0.00000000f, -0.75000006f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000006f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.74891251f, 0.19413748f,
+				0.03185407f, -0.74891245f, 0.19163698f,
+				0.06193763f, -0.74891251f, 0.18438403f,
+				0.08987789f, -0.74891245f, 0.17275129f,
+				0.11530213f, -0.74891257f, 0.15711159f,
+				0.13783762f, -0.74891251f, 0.13783762f,
+				0.15711159f, -0.74891251f, 0.11530213f,
+				0.17275131f, -0.74891257f, 0.08987789f,
+				0.18438402f, -0.74891257f, 0.06193761f,
+				0.19163698f, -0.74891251f, 0.03185406f,
+				0.19413748f, -0.74891251f, -0.00000001f,
+				0.00000000f, -0.74580008f, 0.35160002f,
+				0.05769053f, -0.74579996f, 0.34707141f,
+				0.11217448f, -0.74580014f, 0.33393562f,
+				0.16277674f, -0.74580008f, 0.31286776f,
+				0.20882230f, -0.74580008f, 0.28454286f,
+				0.24963601f, -0.74580008f, 0.24963601f,
+				0.28454286f, -0.74580008f, 0.20882228f,
+				0.31286776f, -0.74580008f, 0.16277674f,
+				0.33393565f, -0.74580008f, 0.11217446f,
+				0.34707141f, -0.74580002f, 0.05769050f,
+				0.35160002f, -0.74580008f, -0.00000001f,
+				0.00000000f, -0.74088752f, 0.47621247f,
+				0.07813694f, -0.74088746f, 0.47007880f,
+				0.15193085f, -0.74088758f, 0.45228758f,
+				0.22046733f, -0.74088752f, 0.42375290f,
+				0.28283215f, -0.74088758f, 0.38538927f,
+				0.33811086f, -0.74088752f, 0.33811086f,
+				0.38538924f, -0.74088752f, 0.28283212f,
+				0.42375290f, -0.74088752f, 0.22046733f,
+				0.45228755f, -0.74088752f, 0.15193082f,
+				0.47007886f, -0.74088752f, 0.07813691f,
+				0.47621247f, -0.74088752f, -0.00000001f,
+				0.00000000f, -0.73440003f, 0.57179999f,
+				0.09382094f, -0.73439997f, 0.56443512f,
+				0.18242709f, -0.73440009f, 0.54307282f,
+				0.26472056f, -0.73440003f, 0.50881052f,
+				0.33960345f, -0.73440003f, 0.46274635f,
+				0.40597799f, -0.73440003f, 0.40597799f,
+				0.46274632f, -0.73440009f, 0.33960345f,
+				0.50881052f, -0.73440003f, 0.26472053f,
+				0.54307282f, -0.73440003f, 0.18242708f,
+				0.56443524f, -0.73440003f, 0.09382091f,
+				0.57179999f, -0.73440003f, -0.00000001f,
+				0.00000000f, -0.72656250f, 0.64218748f,
+				0.10537013f, -0.72656244f, 0.63391608f,
+				0.20488352f, -0.72656256f, 0.60992402f,
+				0.29730710f, -0.72656256f, 0.57144409f,
+				0.38140801f, -0.72656256f, 0.51970953f,
+				0.45595312f, -0.72656250f, 0.45595312f,
+				0.51970953f, -0.72656250f, 0.38140798f,
+				0.57144415f, -0.72656250f, 0.29730713f,
+				0.60992396f, -0.72656250f, 0.20488349f,
+				0.63391614f, -0.72656250f, 0.10537008f,
+				0.64218748f, -0.72656250f, -0.00000001f,
+				0.00000000f, -0.71759999f, 0.69119996f,
+				0.11341208f, -0.71759993f, 0.68229729f,
+				0.22052045f, -0.71760005f, 0.65647405f,
+				0.31999791f, -0.71759999f, 0.61505735f,
+				0.41051748f, -0.71759999f, 0.55937433f,
+				0.49075195f, -0.71759999f, 0.49075198f,
+				0.55937433f, -0.71759999f, 0.41051745f,
+				0.61505735f, -0.71759999f, 0.31999797f,
+				0.65647411f, -0.71759999f, 0.22052044f,
+				0.68229735f, -0.71759999f, 0.11341204f,
+				0.69119996f, -0.71759999f, -0.00000001f,
+				0.00000000f, -0.70773757f, 0.72266257f,
+				0.11857446f, -0.70773745f, 0.71335465f,
+				0.23055826f, -0.70773757f, 0.68635601f,
+				0.33456385f, -0.70773745f, 0.64305401f,
+				0.42920375f, -0.70773751f, 0.58483636f,
+				0.51309043f, -0.70773751f, 0.51309037f,
+				0.58483636f, -0.70773751f, 0.42920372f,
+				0.64305407f, -0.70773757f, 0.33456385f,
+				0.68635601f, -0.70773751f, 0.23055825f,
+				0.71335471f, -0.70773751f, 0.11857441f,
+				0.72266257f, -0.70773751f, -0.00000001f,
+				0.00000000f, -0.69720000f, 0.74039996f,
+				0.12148482f, -0.69719994f, 0.73086351f,
+				0.23621722f, -0.69720006f, 0.70320231f,
+				0.34277558f, -0.69720000f, 0.65883750f,
+				0.43973836f, -0.69720006f, 0.59919089f,
+				0.52568400f, -0.69719994f, 0.52568400f,
+				0.59919089f, -0.69720006f, 0.43973833f,
+				0.65883750f, -0.69720000f, 0.34277558f,
+				0.70320225f, -0.69720006f, 0.23621720f,
+				0.73086363f, -0.69720000f, 0.12148478f,
+				0.74039996f, -0.69720000f, -0.00000001f,
+				0.00000000f, -0.68621248f, 0.74823749f,
+				0.12277080f, -0.68621248f, 0.73860013f,
+				0.23871772f, -0.68621254f, 0.71064609f,
+				0.34640405f, -0.68621248f, 0.66581166f,
+				0.44439322f, -0.68621248f, 0.60553366f,
+				0.53124863f, -0.68621248f, 0.53124863f,
+				0.60553366f, -0.68621248f, 0.44439319f,
+				0.66581166f, -0.68621254f, 0.34640405f,
+				0.71064603f, -0.68621254f, 0.23871769f,
+				0.73860019f, -0.68621248f, 0.12277076f,
+				0.74823749f, -0.68621248f, -0.00000001f,
+				0.00000000f, -0.67500001f, 0.75000000f,
+				0.12305999f, -0.67499995f, 0.74033999f,
+				0.23928000f, -0.67500007f, 0.71232003f,
+				0.34721997f, -0.67500001f, 0.66737998f,
+				0.44544002f, -0.67500007f, 0.60696000f,
+				0.53250003f, -0.67500007f, 0.53250003f,
+				0.60696000f, -0.67500001f, 0.44543999f,
+				0.66738003f, -0.67500001f, 0.34722000f,
+				0.71231997f, -0.67500001f, 0.23927999f,
+				0.74033999f, -0.67500001f, 0.12305995f,
+				0.75000000f, -0.67500001f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000006f, -0.00000001f,
+				0.00000000f, -0.75000006f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000006f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.19413748f, -0.74891251f, -0.00000001f,
+				0.19163698f, -0.74891245f, -0.03185408f,
+				0.18438403f, -0.74891251f, -0.06193763f,
+				0.17275129f, -0.74891245f, -0.08987790f,
+				0.15711159f, -0.74891257f, -0.11530215f,
+				0.13783762f, -0.74891251f, -0.13783762f,
+				0.11530213f, -0.74891251f, -0.15711159f,
+				0.08987789f, -0.74891257f, -0.17275131f,
+				0.06193762f, -0.74891257f, -0.18438402f,
+				0.03185407f, -0.74891251f, -0.19163698f,
+				0.00000000f, -0.74891251f, -0.19413748f,
+				0.35160002f, -0.74580008f, -0.00000001f,
+				0.34707141f, -0.74579996f, -0.05769053f,
+				0.33393562f, -0.74580014f, -0.11217449f,
+				0.31286776f, -0.74580008f, -0.16277674f,
+				0.28454286f, -0.74580008f, -0.20882230f,
+				0.24963601f, -0.74580008f, -0.24963601f,
+				0.20882228f, -0.74580008f, -0.28454286f,
+				0.16277674f, -0.74580008f, -0.31286776f,
+				0.11217447f, -0.74580008f, -0.33393565f,
+				0.05769051f, -0.74580002f, -0.34707141f,
+				0.00000000f, -0.74580008f, -0.35160002f,
+				0.47621247f, -0.74088752f, -0.00000001f,
+				0.47007880f, -0.74088746f, -0.07813694f,
+				0.45228758f, -0.74088758f, -0.15193085f,
+				0.42375290f, -0.74088752f, -0.22046733f,
+				0.38538927f, -0.74088758f, -0.28283215f,
+				0.33811086f, -0.74088752f, -0.33811086f,
+				0.28283212f, -0.74088752f, -0.38538924f,
+				0.22046733f, -0.74088752f, -0.42375290f,
+				0.15193082f, -0.74088752f, -0.45228755f,
+				0.07813691f, -0.74088752f, -0.47007886f,
+				0.00000000f, -0.74088752f, -0.47621247f,
+				0.57179999f, -0.73440003f, -0.00000001f,
+				0.56443512f, -0.73439997f, -0.09382095f,
+				0.54307282f, -0.73440009f, -0.18242709f,
+				0.50881052f, -0.73440003f, -0.26472056f,
+				0.46274635f, -0.73440003f, -0.33960345f,
+				0.40597799f, -0.73440003f, -0.40597799f,
+				0.33960345f, -0.73440009f, -0.46274632f,
+				0.26472053f, -0.73440003f, -0.50881052f,
+				0.18242708f, -0.73440003f, -0.54307282f,
+				0.09382091f, -0.73440003f, -0.56443524f,
+				0.00000000f, -0.73440003f, -0.57179999f,
+				0.64218748f, -0.72656250f, -0.00000001f,
+				0.63391608f, -0.72656244f, -0.10537013f,
+				0.60992402f, -0.72656256f, -0.20488352f,
+				0.57144409f, -0.72656256f, -0.29730710f,
+				0.51970953f, -0.72656256f, -0.38140801f,
+				0.45595312f, -0.72656250f, -0.45595312f,
+				0.38140798f, -0.72656250f, -0.51970953f,
+				0.29730713f, -0.72656250f, -0.57144415f,
+				0.20488349f, -0.72656250f, -0.60992396f,
+				0.10537009f, -0.72656250f, -0.63391614f,
+				0.00000000f, -0.72656250f, -0.64218748f,
+				0.69119996f, -0.71759999f, -0.00000001f,
+				0.68229729f, -0.71759993f, -0.11341209f,
+				0.65647405f, -0.71760005f, -0.22052045f,
+				0.61505735f, -0.71759999f, -0.31999791f,
+				0.55937433f, -0.71759999f, -0.41051748f,
+				0.49075198f, -0.71759999f, -0.49075195f,
+				0.41051745f, -0.71759999f, -0.55937433f,
+				0.31999797f, -0.71759999f, -0.61505735f,
+				0.22052044f, -0.71759999f, -0.65647411f,
+				0.11341204f, -0.71759999f, -0.68229735f,
+				0.00000000f, -0.71759999f, -0.69119996f,
+				0.72266257f, -0.70773751f, -0.00000001f,
+				0.71335465f, -0.70773745f, -0.11857446f,
+				0.68635601f, -0.70773757f, -0.23055826f,
+				0.64305401f, -0.70773745f, -0.33456385f,
+				0.58483636f, -0.70773751f, -0.42920375f,
+				0.51309037f, -0.70773751f, -0.51309043f,
+				0.42920372f, -0.70773751f, -0.58483636f,
+				0.33456385f, -0.70773751f, -0.64305407f,
+				0.23055825f, -0.70773751f, -0.68635601f,
+				0.11857442f, -0.70773751f, -0.71335471f,
+				0.00000000f, -0.70773751f, -0.72266257f,
+				0.74039996f, -0.69720000f, -0.00000001f,
+				0.73086351f, -0.69719994f, -0.12148483f,
+				0.70320231f, -0.69720006f, -0.23621722f,
+				0.65883750f, -0.69720000f, -0.34277558f,
+				0.59919089f, -0.69720006f, -0.43973836f,
+				0.52568400f, -0.69719994f, -0.52568400f,
+				0.43973833f, -0.69720006f, -0.59919089f,
+				0.34277558f, -0.69720000f, -0.65883750f,
+				0.23621720f, -0.69720006f, -0.70320225f,
+				0.12148479f, -0.69720000f, -0.73086363f,
+				0.00000000f, -0.69720000f, -0.74039996f,
+				0.74823749f, -0.68621248f, -0.00000001f,
+				0.73860013f, -0.68621248f, -0.12277081f,
+				0.71064609f, -0.68621254f, -0.23871772f,
+				0.66581166f, -0.68621248f, -0.34640405f,
+				0.60553366f, -0.68621248f, -0.44439322f,
+				0.53124863f, -0.68621248f, -0.53124863f,
+				0.44439319f, -0.68621248f, -0.60553366f,
+				0.34640405f, -0.68621254f, -0.66581166f,
+				0.23871769f, -0.68621254f, -0.71064603f,
+				0.12277076f, -0.68621248f, -0.73860019f,
+				0.00000000f, -0.68621248f, -0.74823749f,
+				0.75000000f, -0.67500001f, -0.00000001f,
+				0.74033999f, -0.67499995f, -0.12306000f,
+				0.71232003f, -0.67500007f, -0.23928000f,
+				0.66737998f, -0.67500001f, -0.34721997f,
+				0.60696000f, -0.67500007f, -0.44544002f,
+				0.53250003f, -0.67500007f, -0.53250003f,
+				0.44543999f, -0.67500001f, -0.60696000f,
+				0.34722000f, -0.67500001f, -0.66738003f,
+				0.23927999f, -0.67500001f, -0.71231997f,
+				0.12305996f, -0.67500001f, -0.74033999f,
+				0.00000000f, -0.67500001f, -0.75000000f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000006f, -0.00000001f,
+				0.00000000f, -0.75000006f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000006f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				-0.19413748f, -0.74891251f, -0.00000001f,
+				-0.19163698f, -0.74891245f, 0.03185407f,
+				-0.18438403f, -0.74891251f, 0.06193762f,
+				-0.17275129f, -0.74891245f, 0.08987788f,
+				-0.15711159f, -0.74891257f, 0.11530213f,
+				-0.13783762f, -0.74891251f, 0.13783762f,
+				-0.11530213f, -0.74891251f, 0.15711159f,
+				-0.08987789f, -0.74891257f, 0.17275131f,
+				-0.06193762f, -0.74891257f, 0.18438402f,
+				-0.03185407f, -0.74891251f, 0.19163698f,
+				0.00000000f, -0.74891251f, 0.19413748f,
+				-0.35160002f, -0.74580008f, -0.00000001f,
+				-0.34707141f, -0.74579996f, 0.05769052f,
+				-0.33393562f, -0.74580014f, 0.11217447f,
+				-0.31286776f, -0.74580008f, 0.16277674f,
+				-0.28454286f, -0.74580008f, 0.20882230f,
+				-0.24963601f, -0.74580008f, 0.24963601f,
+				-0.20882228f, -0.74580008f, 0.28454286f,
+				-0.16277674f, -0.74580008f, 0.31286776f,
+				-0.11217447f, -0.74580008f, 0.33393565f,
+				-0.05769051f, -0.74580002f, 0.34707141f,
+				0.00000000f, -0.74580008f, 0.35160002f,
+				-0.47621247f, -0.74088752f, -0.00000001f,
+				-0.47007880f, -0.74088746f, 0.07813693f,
+				-0.45228758f, -0.74088758f, 0.15193084f,
+				-0.42375290f, -0.74088752f, 0.22046733f,
+				-0.38538927f, -0.74088758f, 0.28283215f,
+				-0.33811086f, -0.74088752f, 0.33811086f,
+				-0.28283212f, -0.74088752f, 0.38538924f,
+				-0.22046733f, -0.74088752f, 0.42375290f,
+				-0.15193082f, -0.74088752f, 0.45228755f,
+				-0.07813691f, -0.74088752f, 0.47007886f,
+				0.00000000f, -0.74088752f, 0.47621247f,
+				-0.57179999f, -0.73440003f, -0.00000001f,
+				-0.56443512f, -0.73439997f, 0.09382094f,
+				-0.54307282f, -0.73440009f, 0.18242709f,
+				-0.50881052f, -0.73440003f, 0.26472056f,
+				-0.46274635f, -0.73440003f, 0.33960345f,
+				-0.40597799f, -0.73440003f, 0.40597799f,
+				-0.33960345f, -0.73440009f, 0.46274632f,
+				-0.26472053f, -0.73440003f, 0.50881052f,
+				-0.18242708f, -0.73440003f, 0.54307282f,
+				-0.09382091f, -0.73440003f, 0.56443524f,
+				0.00000000f, -0.73440003f, 0.57179999f,
+				-0.64218748f, -0.72656250f, -0.00000001f,
+				-0.63391608f, -0.72656244f, 0.10537011f,
+				-0.60992402f, -0.72656256f, 0.20488352f,
+				-0.57144409f, -0.72656256f, 0.29730710f,
+				-0.51970953f, -0.72656256f, 0.38140801f,
+				-0.45595312f, -0.72656250f, 0.45595312f,
+				-0.38140798f, -0.72656250f, 0.51970953f,
+				-0.29730713f, -0.72656250f, 0.57144415f,
+				-0.20488349f, -0.72656250f, 0.60992396f,
+				-0.10537009f, -0.72656250f, 0.63391614f,
+				0.00000000f, -0.72656250f, 0.64218748f,
+				-0.69119996f, -0.71759999f, -0.00000001f,
+				-0.68229729f, -0.71759993f, 0.11341207f,
+				-0.65647405f, -0.71760005f, 0.22052045f,
+				-0.61505735f, -0.71759999f, 0.31999791f,
+				-0.55937433f, -0.71759999f, 0.41051748f,
+				-0.49075198f, -0.71759999f, 0.49075195f,
+				-0.41051745f, -0.71759999f, 0.55937433f,
+				-0.31999797f, -0.71759999f, 0.61505735f,
+				-0.22052044f, -0.71759999f, 0.65647411f,
+				-0.11341204f, -0.71759999f, 0.68229735f,
+				0.00000000f, -0.71759999f, 0.69119996f,
+				-0.72266257f, -0.70773751f, -0.00000001f,
+				-0.71335465f, -0.70773745f, 0.11857444f,
+				-0.68635601f, -0.70773757f, 0.23055826f,
+				-0.64305401f, -0.70773751f, 0.33456385f,
+				-0.58483636f, -0.70773751f, 0.42920375f,
+				-0.51309037f, -0.70773751f, 0.51309043f,
+				-0.42920372f, -0.70773751f, 0.58483636f,
+				-0.33456385f, -0.70773757f, 0.64305407f,
+				-0.23055825f, -0.70773757f, 0.68635601f,
+				-0.11857442f, -0.70773757f, 0.71335471f,
+				0.00000000f, -0.70773757f, 0.72266257f,
+				-0.74039996f, -0.69720000f, -0.00000001f,
+				-0.73086351f, -0.69719994f, 0.12148482f,
+				-0.70320231f, -0.69720006f, 0.23621722f,
+				-0.65883750f, -0.69720000f, 0.34277558f,
+				-0.59919089f, -0.69720006f, 0.43973836f,
+				-0.52568400f, -0.69719994f, 0.52568400f,
+				-0.43973833f, -0.69720006f, 0.59919089f,
+				-0.34277558f, -0.69720000f, 0.65883750f,
+				-0.23621720f, -0.69720006f, 0.70320225f,
+				-0.12148479f, -0.69720000f, 0.73086363f,
+				0.00000000f, -0.69720000f, 0.74039996f,
+				-0.74823749f, -0.68621248f, -0.00000001f,
+				-0.73860013f, -0.68621248f, 0.12277079f,
+				-0.71064609f, -0.68621254f, 0.23871772f,
+				-0.66581166f, -0.68621248f, 0.34640405f,
+				-0.60553366f, -0.68621248f, 0.44439322f,
+				-0.53124863f, -0.68621248f, 0.53124863f,
+				-0.44439319f, -0.68621248f, 0.60553366f,
+				-0.34640405f, -0.68621254f, 0.66581166f,
+				-0.23871769f, -0.68621254f, 0.71064603f,
+				-0.12277076f, -0.68621248f, 0.73860019f,
+				0.00000000f, -0.68621248f, 0.74823749f,
+				-0.75000000f, -0.67500001f, -0.00000001f,
+				-0.74033999f, -0.67499995f, 0.12305998f,
+				-0.71232003f, -0.67500007f, 0.23928000f,
+				-0.66737998f, -0.67500001f, 0.34721997f,
+				-0.60696000f, -0.67500007f, 0.44544002f,
+				-0.53250003f, -0.67500007f, 0.53250003f,
+				-0.44543999f, -0.67500001f, 0.60696000f,
+				-0.34722000f, -0.67500001f, 0.66738003f,
+				-0.23927999f, -0.67500001f, 0.71231997f,
+				-0.12305996f, -0.67500001f, 0.74033999f,
+				0.00000000f, -0.67500001f, 0.75000000f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000006f, -0.00000001f,
+				0.00000000f, -0.75000006f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000006f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.75000000f, -0.00000001f,
+				0.00000000f, -0.74891251f, -0.19413748f,
+				-0.03185407f, -0.74891245f, -0.19163698f,
+				-0.06193763f, -0.74891251f, -0.18438403f,
+				-0.08987789f, -0.74891245f, -0.17275129f,
+				-0.11530213f, -0.74891257f, -0.15711159f,
+				-0.13783762f, -0.74891251f, -0.13783762f,
+				-0.15711159f, -0.74891251f, -0.11530213f,
+				-0.17275131f, -0.74891257f, -0.08987790f,
+				-0.18438402f, -0.74891257f, -0.06193763f,
+				-0.19163698f, -0.74891251f, -0.03185407f,
+				-0.19413748f, -0.74891251f, -0.00000001f,
+				0.00000000f, -0.74580008f, -0.35160002f,
+				-0.05769053f, -0.74579996f, -0.34707141f,
+				-0.11217448f, -0.74580014f, -0.33393562f,
+				-0.16277674f, -0.74580008f, -0.31286776f,
+				-0.20882230f, -0.74580008f, -0.28454286f,
+				-0.24963601f, -0.74580008f, -0.24963601f,
+				-0.28454286f, -0.74580008f, -0.20882228f,
+				-0.31286776f, -0.74580008f, -0.16277674f,
+				-0.33393565f, -0.74580008f, -0.11217447f,
+				-0.34707141f, -0.74580002f, -0.05769052f,
+				-0.35160002f, -0.74580008f, -0.00000001f,
+				0.00000000f, -0.74088752f, -0.47621247f,
+				-0.07813694f, -0.74088746f, -0.47007880f,
+				-0.15193085f, -0.74088758f, -0.45228758f,
+				-0.22046733f, -0.74088752f, -0.42375290f,
+				-0.28283215f, -0.74088758f, -0.38538927f,
+				-0.33811086f, -0.74088752f, -0.33811086f,
+				-0.38538924f, -0.74088752f, -0.28283212f,
+				-0.42375290f, -0.74088752f, -0.22046733f,
+				-0.45228755f, -0.74088752f, -0.15193082f,
+				-0.47007886f, -0.74088752f, -0.07813692f,
+				-0.47621247f, -0.74088752f, -0.00000001f,
+				0.00000000f, -0.73440003f, -0.57179999f,
+				-0.09382094f, -0.73439997f, -0.56443512f,
+				-0.18242709f, -0.73440009f, -0.54307282f,
+				-0.26472056f, -0.73440003f, -0.50881052f,
+				-0.33960345f, -0.73440003f, -0.46274635f,
+				-0.40597799f, -0.73440003f, -0.40597799f,
+				-0.46274632f, -0.73440009f, -0.33960345f,
+				-0.50881052f, -0.73440003f, -0.26472053f,
+				-0.54307282f, -0.73440003f, -0.18242708f,
+				-0.56443524f, -0.73440003f, -0.09382092f,
+				-0.57179999f, -0.73440003f, -0.00000001f,
+				0.00000000f, -0.72656250f, -0.64218748f,
+				-0.10537013f, -0.72656244f, -0.63391608f,
+				-0.20488352f, -0.72656256f, -0.60992402f,
+				-0.29730710f, -0.72656256f, -0.57144409f,
+				-0.38140801f, -0.72656256f, -0.51970953f,
+				-0.45595312f, -0.72656250f, -0.45595312f,
+				-0.51970953f, -0.72656250f, -0.38140798f,
+				-0.57144415f, -0.72656250f, -0.29730713f,
+				-0.60992396f, -0.72656250f, -0.20488349f,
+				-0.63391614f, -0.72656250f, -0.10537010f,
+				-0.64218748f, -0.72656250f, -0.00000001f,
+				0.00000000f, -0.71759999f, -0.69119996f,
+				-0.11341208f, -0.71759993f, -0.68229729f,
+				-0.22052045f, -0.71760005f, -0.65647405f,
+				-0.31999791f, -0.71759999f, -0.61505735f,
+				-0.41051748f, -0.71759999f, -0.55937433f,
+				-0.49075195f, -0.71759999f, -0.49075198f,
+				-0.55937433f, -0.71759999f, -0.41051745f,
+				-0.61505735f, -0.71759999f, -0.31999797f,
+				-0.65647411f, -0.71759999f, -0.22052044f,
+				-0.68229735f, -0.71759999f, -0.11341205f,
+				-0.69119996f, -0.71759999f, -0.00000001f,
+				0.00000000f, -0.70773751f, -0.72266257f,
+				-0.11857446f, -0.70773745f, -0.71335465f,
+				-0.23055826f, -0.70773757f, -0.68635601f,
+				-0.33456385f, -0.70773745f, -0.64305401f,
+				-0.42920375f, -0.70773751f, -0.58483636f,
+				-0.51309043f, -0.70773751f, -0.51309037f,
+				-0.58483636f, -0.70773751f, -0.42920372f,
+				-0.64305407f, -0.70773751f, -0.33456385f,
+				-0.68635601f, -0.70773751f, -0.23055825f,
+				-0.71335471f, -0.70773751f, -0.11857443f,
+				-0.72266257f, -0.70773751f, -0.00000001f,
+				0.00000000f, -0.69720000f, -0.74039996f,
+				-0.12148482f, -0.69719994f, -0.73086351f,
+				-0.23621722f, -0.69720006f, -0.70320231f,
+				-0.34277558f, -0.69720000f, -0.65883750f,
+				-0.43973836f, -0.69720006f, -0.59919089f,
+				-0.52568400f, -0.69719994f, -0.52568400f,
+				-0.59919089f, -0.69720006f, -0.43973833f,
+				-0.65883750f, -0.69720000f, -0.34277558f,
+				-0.70320225f, -0.69720006f, -0.23621720f,
+				-0.73086363f, -0.69720000f, -0.12148479f,
+				-0.74039996f, -0.69720000f, -0.00000001f,
+				0.00000000f, -0.68621248f, -0.74823749f,
+				-0.12277080f, -0.68621248f, -0.73860013f,
+				-0.23871772f, -0.68621254f, -0.71064609f,
+				-0.34640405f, -0.68621248f, -0.66581166f,
+				-0.44439322f, -0.68621248f, -0.60553366f,
+				-0.53124863f, -0.68621248f, -0.53124863f,
+				-0.60553366f, -0.68621248f, -0.44439319f,
+				-0.66581166f, -0.68621254f, -0.34640405f,
+				-0.71064603f, -0.68621254f, -0.23871769f,
+				-0.73860019f, -0.68621248f, -0.12277077f,
+				-0.74823749f, -0.68621248f, -0.00000001f,
+				0.00000000f, -0.67500001f, -0.75000000f,
+				-0.12305999f, -0.67499995f, -0.74033999f,
+				-0.23928000f, -0.67500007f, -0.71232003f,
+				-0.34721997f, -0.67500001f, -0.66737998f,
+				-0.44544002f, -0.67500007f, -0.60696000f,
+				-0.53250003f, -0.67500007f, -0.53250003f,
+				-0.60696000f, -0.67500001f, -0.44543999f,
+				-0.66738003f, -0.67500001f, -0.34722000f,
+				-0.71231997f, -0.67500001f, -0.23927999f,
+				-0.74033999f, -0.67500001f, -0.12305997f,
+				-0.75000000f, -0.67500001f, -0.00000001f,
+				-0.80000001f, 0.26250005f, 0.00000000f,
+				-0.79859996f, 0.26565003f, 0.04050000f,
+				-0.79480010f, 0.27420005f, 0.07200000f,
+				-0.78920001f, 0.28680006f, 0.09450001f,
+				-0.78240001f, 0.30210006f, 0.10800001f,
+				-0.77499998f, 0.31875002f, 0.11250000f,
+				-0.76760000f, 0.33540002f, 0.10800000f,
+				-0.76080006f, 0.35070002f, 0.09450001f,
+				-0.75519997f, 0.36330000f, 0.07200000f,
+				-0.75139999f, 0.37185001f, 0.04049999f,
+				-0.75000000f, 0.37500000f, 0.00000000f,
+				-0.90044993f, 0.26238751f, 0.00000000f,
+				-0.90022725f, 0.26553434f, 0.04050000f,
+				-0.89962322f, 0.27407584f, 0.07200000f,
+				-0.89873272f, 0.28666320f, 0.09449999f,
+				-0.89765161f, 0.30194792f, 0.10800000f,
+				-0.89647496f, 0.31858125f, 0.11250000f,
+				-0.89529836f, 0.33521461f, 0.10800000f,
+				-0.89421713f, 0.35049930f, 0.09449999f,
+				-0.89332676f, 0.36308670f, 0.07200000f,
+				-0.89272249f, 0.37162814f, 0.04049999f,
+				-0.89249992f, 0.37477499f, 0.00000000f,
+				-0.99160004f, 0.26160005f, 0.00000000f,
+				-0.99239522f, 0.26472485f, 0.04050001f,
+				-0.99455369f, 0.27320647f, 0.07200002f,
+				-0.99773449f, 0.28570563f, 0.09450001f,
+				-1.00159693f, 0.30088326f, 0.10800002f,
+				-1.00580013f, 0.31740004f, 0.11250001f,
+				-1.01000333f, 0.33391684f, 0.10800001f,
+				-1.01386571f, 0.34909445f, 0.09450001f,
+				-1.01704645f, 0.36159366f, 0.07200001f,
+				-1.01920485f, 0.37007523f, 0.04050000f,
+				-1.02000010f, 0.37320003f, 0.00000000f,
+				-1.07315004f, 0.25946254f, 0.00000000f,
+				-1.07481182f, 0.26252747f, 0.04050001f,
+				-1.07932246f, 0.27084666f, 0.07200002f,
+				-1.08596969f, 0.28310645f, 0.09450001f,
+				-1.09404123f, 0.29799330f, 0.10800002f,
+				-1.10282505f, 0.31419379f, 0.11250001f,
+				-1.11160886f, 0.33039424f, 0.10800001f,
+				-1.11968040f, 0.34528112f, 0.09450001f,
+				-1.12632775f, 0.35754091f, 0.07200001f,
+				-1.13083827f, 0.36586004f, 0.04050000f,
+				-1.13250005f, 0.36892501f, 0.00000000f,
+				-1.14480007f, 0.25530005f, 0.00000000f,
+				-1.14718556f, 0.25824845f, 0.04050000f,
+				-1.15366089f, 0.26625127f, 0.07200000f,
+				-1.16320336f, 0.27804485f, 0.09450001f,
+				-1.17479050f, 0.29236567f, 0.10800001f,
+				-1.18740010f, 0.30795002f, 0.11250000f,
+				-1.20000958f, 0.32353443f, 0.10800000f,
+				-1.21159685f, 0.33785522f, 0.09450001f,
+				-1.22113919f, 0.34964880f, 0.07200000f,
+				-1.22761440f, 0.35765159f, 0.04049999f,
+				-1.23000002f, 0.36059999f, 0.00000000f,
+				-1.20625007f, 0.24843754f, 0.00000000f,
+				-1.20922494f, 0.25119376f, 0.04050000f,
+				-1.21730018f, 0.25867507f, 0.07200000f,
+				-1.22920001f, 0.26970002f, 0.09450001f,
+				-1.24365008f, 0.28308752f, 0.10800001f,
+				-1.25937510f, 0.29765627f, 0.11250000f,
+				-1.27509999f, 0.31222504f, 0.10800000f,
+				-1.28955007f, 0.32561252f, 0.09450001f,
+				-1.30145001f, 0.33663750f, 0.07200000f,
+				-1.30952501f, 0.34411874f, 0.04049999f,
+				-1.31250000f, 0.34687501f, 0.00000000f,
+				-1.25720000f, 0.23820004f, 0.00000000f,
+				-1.26063836f, 0.24066961f, 0.04050000f,
+				-1.26997125f, 0.24737285f, 0.07200000f,
+				-1.28372490f, 0.25725123f, 0.09450001f,
+				-1.30042577f, 0.26924643f, 0.10800001f,
+				-1.31860006f, 0.28230003f, 0.11250000f,
+				-1.33677435f, 0.29535359f, 0.10800000f,
+				-1.35347521f, 0.30734879f, 0.09450001f,
+				-1.36722875f, 0.31722721f, 0.07200000f,
+				-1.37656152f, 0.32393038f, 0.04049999f,
+				-1.38000000f, 0.32639998f, 0.00000000f,
+				-1.29735005f, 0.22391254f, 0.00000000f,
+				-1.30113411f, 0.22598207f, 0.04050000f,
+				-1.31140578f, 0.23159945f, 0.07200000f,
+				-1.32654238f, 0.23987763f, 0.09450001f,
+				-1.34492302f, 0.24992974f, 0.10800001f,
+				-1.36492503f, 0.26086879f, 0.11250000f,
+				-1.38492727f, 0.27180785f, 0.10800000f,
+				-1.40330780f, 0.28185993f, 0.09450001f,
+				-1.41844463f, 0.29013813f, 0.07200000f,
+				-1.42871594f, 0.29575545f, 0.04049999f,
+				-1.43250012f, 0.29782501f, 0.00000000f,
+				-1.32640004f, 0.20490001f, 0.00000000f,
+				-1.33042073f, 0.20643719f, 0.04050000f,
+				-1.34133446f, 0.21060961f, 0.07200000f,
+				-1.35741770f, 0.21675842f, 0.09450001f,
+				-1.37694728f, 0.22422481f, 0.10800001f,
+				-1.39820004f, 0.23234999f, 0.11250000f,
+				-1.41945279f, 0.24047519f, 0.10800000f,
+				-1.43898249f, 0.24794158f, 0.09450001f,
+				-1.45506573f, 0.25409040f, 0.07200000f,
+				-1.46597922f, 0.25826281f, 0.04049999f,
+				-1.47000003f, 0.25979999f, 0.00000000f,
+				-1.34404993f, 0.18048748f, 0.00000000f,
+				-1.34820640f, 0.18134111f, 0.04050000f,
+				-1.35948884f, 0.18365818f, 0.07200000f,
+				-1.37611520f, 0.18707278f, 0.09450001f,
+				-1.39630449f, 0.19121909f, 0.10800001f,
+				-1.41827500f, 0.19573122f, 0.11250000f,
+				-1.44024563f, 0.20024337f, 0.10800000f,
+				-1.46043491f, 0.20438966f, 0.09450001f,
+				-1.47706127f, 0.20780426f, 0.07200000f,
+				-1.48834348f, 0.21012132f, 0.04049999f,
+				-1.49250007f, 0.21097496f, 0.00000000f,
+				-1.35000002f, 0.14999998f, 0.00000000f,
+				-1.35419989f, 0.14999996f, 0.04050000f,
+				-1.36559999f, 0.14999999f, 0.07200000f,
+				-1.38239992f, 0.14999998f, 0.09450001f,
+				-1.40280008f, 0.14999999f, 0.10800001f,
+				-1.42499995f, 0.14999998f, 0.11250000f,
+				-1.44719994f, 0.14999998f, 0.10800000f,
+				-1.46760011f, 0.14999998f, 0.09450001f,
+				-1.48440003f, 0.14999998f, 0.07200000f,
+				-1.49580002f, 0.14999998f, 0.04049999f,
+				-1.50000000f, 0.14999998f, 0.00000000f,
+				-0.75000000f, 0.37500000f, 0.00000000f,
+				-0.75139999f, 0.37184998f, -0.04050000f,
+				-0.75520003f, 0.36330003f, -0.07200000f,
+				-0.76080000f, 0.35070002f, -0.09450001f,
+				-0.76760006f, 0.33540004f, -0.10800001f,
+				-0.77500004f, 0.31875002f, -0.11250000f,
+				-0.78240001f, 0.30210003f, -0.10800000f,
+				-0.78920001f, 0.28680006f, -0.09450001f,
+				-0.79480004f, 0.27420002f, -0.07200000f,
+				-0.79860002f, 0.26565003f, -0.04049999f,
+				-0.80000001f, 0.26250005f, 0.00000000f,
+				-0.89249992f, 0.37477499f, 0.00000000f,
+				-0.89272243f, 0.37162811f, -0.04049999f,
+				-0.89332676f, 0.36308670f, -0.07200000f,
+				-0.89421707f, 0.35049930f, -0.09449999f,
+				-0.89529842f, 0.33521461f, -0.10800000f,
+				-0.89647490f, 0.31858125f, -0.11250000f,
+				-0.89765155f, 0.30194789f, -0.10800000f,
+				-0.89873278f, 0.28666323f, -0.09449999f,
+				-0.89962316f, 0.27407581f, -0.07200000f,
+				-0.90022731f, 0.26553437f, -0.04049998f,
+				-0.90044993f, 0.26238751f, 0.00000000f,
+				-1.02000010f, 0.37320003f, 0.00000000f,
+				-1.01920474f, 0.37007520f, -0.04050000f,
+				-1.01704657f, 0.36159366f, -0.07200001f,
+				-1.01386571f, 0.34909445f, -0.09450001f,
+				-1.01000333f, 0.33391684f, -0.10800002f,
+				-1.00580001f, 0.31740004f, -0.11250001f,
+				-1.00159681f, 0.30088323f, -0.10800001f,
+				-0.99773443f, 0.28570566f, -0.09450001f,
+				-0.99455369f, 0.27320647f, -0.07200001f,
+				-0.99239522f, 0.26472485f, -0.04049999f,
+				-0.99160004f, 0.26160005f, 0.00000000f,
+				-1.13250005f, 0.36892501f, 0.00000000f,
+				-1.13083816f, 0.36586002f, -0.04050000f,
+				-1.12632775f, 0.35754091f, -0.07200001f,
+				-1.11968040f, 0.34528109f, -0.09450001f,
+				-1.11160886f, 0.33039424f, -0.10800002f,
+				-1.10282505f, 0.31419379f, -0.11250001f,
+				-1.09404123f, 0.29799333f, -0.10800001f,
+				-1.08596969f, 0.28310645f, -0.09450001f,
+				-1.07932246f, 0.27084661f, -0.07200001f,
+				-1.07481182f, 0.26252750f, -0.04049999f,
+				-1.07315004f, 0.25946254f, 0.00000000f,
+				-1.23000002f, 0.36059999f, 0.00000000f,
+				-1.22761440f, 0.35765156f, -0.04050000f,
+				-1.22113931f, 0.34964883f, -0.07200000f,
+				-1.21159685f, 0.33785522f, -0.09450001f,
+				-1.20000970f, 0.32353443f, -0.10800001f,
+				-1.18740010f, 0.30795002f, -0.11250000f,
+				-1.17479038f, 0.29236564f, -0.10800000f,
+				-1.16320324f, 0.27804482f, -0.09450001f,
+				-1.15366089f, 0.26625124f, -0.07200000f,
+				-1.14718568f, 0.25824845f, -0.04049999f,
+				-1.14480007f, 0.25530005f, 0.00000000f,
+				-1.31250000f, 0.34687501f, 0.00000000f,
+				-1.30952489f, 0.34411871f, -0.04050000f,
+				-1.30145013f, 0.33663750f, -0.07200000f,
+				-1.28955019f, 0.32561252f, -0.09450001f,
+				-1.27510011f, 0.31222504f, -0.10800001f,
+				-1.25937498f, 0.29765630f, -0.11250000f,
+				-1.24365008f, 0.28308752f, -0.10800000f,
+				-1.22920012f, 0.26970005f, -0.09450001f,
+				-1.21730018f, 0.25867504f, -0.07200000f,
+				-1.20922506f, 0.25119379f, -0.04049999f,
+				-1.20625007f, 0.24843754f, 0.00000000f,
+				-1.38000000f, 0.32639998f, 0.00000000f,
+				-1.37656140f, 0.32393035f, -0.04050000f,
+				-1.36722875f, 0.31722721f, -0.07200000f,
+				-1.35347521f, 0.30734879f, -0.09450001f,
+				-1.33677447f, 0.29535362f, -0.10800001f,
+				-1.31860006f, 0.28230000f, -0.11250000f,
+				-1.30042553f, 0.26924643f, -0.10800000f,
+				-1.28372478f, 0.25725123f, -0.09450001f,
+				-1.26997125f, 0.24737284f, -0.07200000f,
+				-1.26063836f, 0.24066964f, -0.04049999f,
+				-1.25720000f, 0.23820004f, 0.00000000f,
+				-1.43250012f, 0.29782501f, 0.00000000f,
+				-1.42871583f, 0.29575542f, -0.04050000f,
+				-1.41844463f, 0.29013813f, -0.07200000f,
+				-1.40330768f, 0.28185993f, -0.09450001f,
+				-1.38492751f, 0.27180785f, -0.10800001f,
+				-1.36492515f, 0.26086876f, -0.11250000f,
+				-1.34492302f, 0.24992973f, -0.10800000f,
+				-1.32654250f, 0.23987764f, -0.09450001f,
+				-1.31140566f, 0.23159944f, -0.07200000f,
+				-1.30113423f, 0.22598210f, -0.04049999f,
+				-1.29735005f, 0.22391254f, 0.00000000f,
+				-1.47000003f, 0.25979999f, 0.00000000f,
+				-1.46597922f, 0.25826275f, -0.04050000f,
+				-1.45506561f, 0.25409040f, -0.07200000f,
+				-1.43898249f, 0.24794158f, -0.09450001f,
+				-1.41945291f, 0.24047521f, -0.10800001f,
+				-1.39820004f, 0.23234999f, -0.11250000f,
+				-1.37694728f, 0.22422481f, -0.10800000f,
+				-1.35741770f, 0.21675842f, -0.09450001f,
+				-1.34133446f, 0.21060961f, -0.07200000f,
+				-1.33042085f, 0.20643722f, -0.04049999f,
+				-1.32640004f, 0.20490001f, 0.00000000f,
+				-1.49250007f, 0.21097496f, 0.00000000f,
+				-1.48834324f, 0.21012127f, -0.04050000f,
+				-1.47706139f, 0.20780428f, -0.07200000f,
+				-1.46043479f, 0.20438966f, -0.09450001f,
+				-1.44024563f, 0.20024338f, -0.10800001f,
+				-1.41827488f, 0.19573122f, -0.11250000f,
+				-1.39630437f, 0.19121908f, -0.10800000f,
+				-1.37611520f, 0.18707278f, -0.09450001f,
+				-1.35948884f, 0.18365818f, -0.07200000f,
+				-1.34820652f, 0.18134113f, -0.04049999f,
+				-1.34404993f, 0.18048748f, 0.00000000f,
+				-1.50000000f, 0.14999998f, 0.00000000f,
+				-1.49580002f, 0.14999996f, -0.04050000f,
+				-1.48440015f, 0.14999999f, -0.07200000f,
+				-1.46759999f, 0.14999998f, -0.09450001f,
+				-1.44720006f, 0.14999999f, -0.10800001f,
+				-1.42500007f, 0.14999998f, -0.11250000f,
+				-1.40280008f, 0.14999998f, -0.10800000f,
+				-1.38240004f, 0.14999998f, -0.09450001f,
+				-1.36559999f, 0.14999998f, -0.07200000f,
+				-1.35420001f, 0.14999998f, -0.04049999f,
+				-1.35000002f, 0.14999998f, 0.00000000f,
+				-1.35000002f, 0.14999998f, 0.00000000f,
+				-1.35419989f, 0.14999996f, 0.04050000f,
+				-1.36559999f, 0.14999999f, 0.07200000f,
+				-1.38239992f, 0.14999998f, 0.09450001f,
+				-1.40280008f, 0.14999999f, 0.10800001f,
+				-1.42499995f, 0.14999998f, 0.11250000f,
+				-1.44719994f, 0.14999998f, 0.10800000f,
+				-1.46760011f, 0.14999998f, 0.09450001f,
+				-1.48440003f, 0.14999998f, 0.07200000f,
+				-1.49580002f, 0.14999998f, 0.04049999f,
+				-1.50000000f, 0.14999998f, 0.00000000f,
+				-1.34694993f, 0.11309998f, 0.00000000f,
+				-1.35108757f, 0.11225944f, 0.04049999f,
+				-1.36231863f, 0.10997803f, 0.07200000f,
+				-1.37886941f, 0.10661592f, 0.09449999f,
+				-1.39896679f, 0.10253337f, 0.10800000f,
+				-1.42083752f, 0.09809060f, 0.11250000f,
+				-1.44270813f, 0.09364782f, 0.10800000f,
+				-1.46280551f, 0.08956527f, 0.09449999f,
+				-1.47935629f, 0.08620317f, 0.07200000f,
+				-1.49058723f, 0.08392174f, 0.04049999f,
+				-1.49472487f, 0.08308122f, 0.00000000f,
+				-1.33760011f, 0.07080000f, 0.00000000f,
+				-1.34155357f, 0.06930479f, 0.04050000f,
+				-1.35228503f, 0.06524640f, 0.07200002f,
+				-1.36809933f, 0.05926559f, 0.09450001f,
+				-1.38730252f, 0.05200320f, 0.10800002f,
+				-1.40820003f, 0.04410000f, 0.11250001f,
+				-1.42909765f, 0.03619679f, 0.10800001f,
+				-1.44830084f, 0.02893440f, 0.09450001f,
+				-1.46411526f, 0.02295359f, 0.07200001f,
+				-1.47484648f, 0.01889519f, 0.04049999f,
+				-1.47880006f, 0.01739999f, 0.00000000f,
+				-1.32164991f, 0.02445000f, 0.00000000f,
+				-1.32530177f, 0.02245132f, 0.04050000f,
+				-1.33521414f, 0.01702635f, 0.07200002f,
+				-1.34982169f, 0.00903165f, 0.09450001f,
+				-1.36755967f, -0.00067620f, 0.10800002f,
+				-1.38686240f, -0.01124063f, 0.11250001f,
+				-1.40616536f, -0.02180506f, 0.10800001f,
+				-1.42390323f, -0.03151290f, 0.09450001f,
+				-1.43851089f, -0.03950761f, 0.07200001f,
+				-1.44842303f, -0.04493258f, 0.04049999f,
+				-1.45207500f, -0.04693126f, -0.00000000f,
+				-1.29880011f, -0.02460000f, -0.00000000f,
+				-1.30203676f, -0.02698559f, 0.04050000f,
+				-1.31082261f, -0.03346080f, 0.07200000f,
+				-1.32376969f, -0.04300320f, 0.09450001f,
+				-1.33949125f, -0.05459040f, 0.10800001f,
+				-1.35660005f, -0.06720001f, 0.11250000f,
+				-1.37370884f, -0.07980961f, 0.10800000f,
+				-1.38943052f, -0.09139681f, 0.09450001f,
+				-1.40237761f, -0.10093921f, 0.07200000f,
+				-1.41116321f, -0.10741441f, 0.04049999f,
+				-1.41439998f, -0.10980001f, -0.00000000f,
+				-1.26874995f, -0.07500000f, -0.00000000f,
+				-1.27146244f, -0.07769062f, 0.04050000f,
+				-1.27882504f, -0.08499375f, 0.07200000f,
+				-1.28967500f, -0.09575625f, 0.09450001f,
+				-1.30285001f, -0.10882500f, 0.10800001f,
+				-1.31718755f, -0.12304687f, 0.11250000f,
+				-1.33152497f, -0.13726875f, 0.10800000f,
+				-1.34470010f, -0.15033749f, 0.09450001f,
+				-1.35555005f, -0.16110000f, 0.07200000f,
+				-1.36291242f, -0.16840312f, 0.04049999f,
+				-1.36562502f, -0.17109375f, -0.00000000f,
+				-1.23119998f, -0.12540001f, -0.00000000f,
+				-1.23328304f, -0.12834840f, 0.04050000f,
+				-1.23893762f, -0.13635120f, 0.07200000f,
+				-1.24727035f, -0.14814480f, 0.09450001f,
+				-1.25738895f, -0.16246562f, 0.10800001f,
+				-1.26839995f, -0.17805001f, 0.11250000f,
+				-1.27941120f, -0.19363439f, 0.10800000f,
+				-1.28952956f, -0.20795521f, 0.09450001f,
+				-1.29786241f, -0.21974881f, 0.07200000f,
+				-1.30351675f, -0.22775160f, 0.04049999f,
+				-1.30559993f, -0.23070000f, -0.00000000f,
+				-1.18585014f, -0.17445001f, -0.00000000f,
+				-1.18720317f, -0.17764355f, 0.04050000f,
+				-1.19087601f, -0.18631189f, 0.07200000f,
+				-1.19628823f, -0.19908616f, 0.09450001f,
+				-1.20286059f, -0.21459784f, 0.10800001f,
+				-1.21001256f, -0.23147814f, 0.11250000f,
+				-1.21716475f, -0.24835847f, 0.10800000f,
+				-1.22373688f, -0.26387012f, 0.09450001f,
+				-1.22914934f, -0.27664441f, 0.07200000f,
+				-1.23282194f, -0.28531271f, 0.04049999f,
+				-1.23417509f, -0.28850627f, -0.00000000f,
+				-1.13240004f, -0.22080001f, -0.00000000f,
+				-1.13292634f, -0.22426079f, 0.04050000f,
+				-1.13435543f, -0.23365441f, 0.07200000f,
+				-1.13646090f, -0.24749762f, 0.09450001f,
+				-1.13901782f, -0.26430723f, 0.10800001f,
+				-1.14180005f, -0.28260002f, 0.11250000f,
+				-1.14458251f, -0.30089283f, 0.10800000f,
+				-1.14713919f, -0.31770241f, 0.09450001f,
+				-1.14924490f, -0.33154562f, 0.07200000f,
+				-1.15067363f, -0.34093922f, 0.04049999f,
+				-1.15120006f, -0.34440002f, -0.00000000f,
+				-1.07054996f, -0.26310003f, -0.00000000f,
+				-1.07015729f, -0.26688471f, 0.04050000f,
+				-1.06909144f, -0.27715757f, 0.07200000f,
+				-1.06752050f, -0.29229647f, 0.09450001f,
+				-1.06561327f, -0.31067947f, 0.10800001f,
+				-1.06353748f, -0.33068439f, 0.11250000f,
+				-1.06146181f, -0.35068941f, 0.10800000f,
+				-1.05955434f, -0.36907232f, 0.09450001f,
+				-1.05798364f, -0.38421124f, 0.07200000f,
+				-1.05691767f, -0.39448404f, 0.04049999f,
+				-1.05652499f, -0.39826876f, -0.00000000f,
+				-1.00000000f, -0.30000001f, -0.00000000f,
+				-0.99859989f, -0.30419996f, 0.04050000f,
+				-0.99480003f, -0.31560001f, 0.07200000f,
+				-0.98920000f, -0.33240002f, 0.09450001f,
+				-0.98240000f, -0.35280001f, 0.10800001f,
+				-0.97499996f, -0.37500000f, 0.11250000f,
+				-0.96759999f, -0.39720002f, 0.10800000f,
+				-0.96080005f, -0.41759998f, 0.09450001f,
+				-0.95520002f, -0.43440002f, 0.07200000f,
+				-0.95139992f, -0.44580001f, 0.04049999f,
+				-0.94999999f, -0.44999999f, -0.00000001f,
+				-1.50000000f, 0.14999998f, 0.00000000f,
+				-1.49580002f, 0.14999996f, -0.04050000f,
+				-1.48440015f, 0.14999999f, -0.07200000f,
+				-1.46759999f, 0.14999998f, -0.09450001f,
+				-1.44720006f, 0.14999999f, -0.10800001f,
+				-1.42500007f, 0.14999998f, -0.11250000f,
+				-1.40280008f, 0.14999998f, -0.10800000f,
+				-1.38240004f, 0.14999998f, -0.09450001f,
+				-1.36559999f, 0.14999998f, -0.07200000f,
+				-1.35420001f, 0.14999998f, -0.04049999f,
+				-1.35000002f, 0.14999998f, 0.00000000f,
+				-1.49472487f, 0.08308122f, 0.00000000f,
+				-1.49058712f, 0.08392174f, -0.04049999f,
+				-1.47935617f, 0.08620317f, -0.07200000f,
+				-1.46280551f, 0.08956527f, -0.09449999f,
+				-1.44270813f, 0.09364782f, -0.10800000f,
+				-1.42083728f, 0.09809060f, -0.11250000f,
+				-1.39896679f, 0.10253337f, -0.10800000f,
+				-1.37886930f, 0.10661593f, -0.09449999f,
+				-1.36231852f, 0.10997803f, -0.07200000f,
+				-1.35108757f, 0.11225945f, -0.04049999f,
+				-1.34694993f, 0.11309998f, 0.00000000f,
+				-1.47880006f, 0.01739999f, 0.00000000f,
+				-1.47484636f, 0.01889519f, -0.04050000f,
+				-1.46411538f, 0.02295360f, -0.07200002f,
+				-1.44830084f, 0.02893439f, -0.09450001f,
+				-1.42909777f, 0.03619680f, -0.10800002f,
+				-1.40820003f, 0.04409999f, -0.11250001f,
+				-1.38730240f, 0.05200320f, -0.10800001f,
+				-1.36809933f, 0.05926560f, -0.09450001f,
+				-1.35228491f, 0.06524640f, -0.07200001f,
+				-1.34155369f, 0.06930479f, -0.04049999f,
+				-1.33760011f, 0.07080000f, 0.00000000f,
+				-1.45207500f, -0.04693126f, -0.00000000f,
+				-1.44842303f, -0.04493257f, -0.04050000f,
+				-1.43851078f, -0.03950761f, -0.07200002f,
+				-1.42390323f, -0.03151290f, -0.09450001f,
+				-1.40616548f, -0.02180506f, -0.10800002f,
+				-1.38686240f, -0.01124063f, -0.11250001f,
+				-1.36755955f, -0.00067620f, -0.10800001f,
+				-1.34982181f, 0.00903165f, -0.09450001f,
+				-1.33521414f, 0.01702635f, -0.07200001f,
+				-1.32530189f, 0.02245133f, -0.04049999f,
+				-1.32164991f, 0.02445000f, 0.00000000f,
+				-1.41439998f, -0.10980001f, -0.00000000f,
+				-1.41116297f, -0.10741439f, -0.04050000f,
+				-1.40237772f, -0.10093922f, -0.07200000f,
+				-1.38943040f, -0.09139680f, -0.09450001f,
+				-1.37370884f, -0.07980961f, -0.10800001f,
+				-1.35660017f, -0.06720000f, -0.11250000f,
+				-1.33949125f, -0.05459040f, -0.10800000f,
+				-1.32376981f, -0.04300320f, -0.09450001f,
+				-1.31082249f, -0.03346080f, -0.07200000f,
+				-1.30203688f, -0.02698559f, -0.04049999f,
+				-1.29880011f, -0.02460000f, -0.00000000f,
+				-1.36562502f, -0.17109375f, -0.00000000f,
+				-1.36291230f, -0.16840309f, -0.04050000f,
+				-1.35555005f, -0.16110000f, -0.07200000f,
+				-1.34469998f, -0.15033749f, -0.09450001f,
+				-1.33152509f, -0.13726877f, -0.10800001f,
+				-1.31718755f, -0.12304687f, -0.11250000f,
+				-1.30285001f, -0.10882499f, -0.10800000f,
+				-1.28967500f, -0.09575625f, -0.09450001f,
+				-1.27882504f, -0.08499375f, -0.07200000f,
+				-1.27146244f, -0.07769062f, -0.04049999f,
+				-1.26874995f, -0.07500000f, -0.00000000f,
+				-1.30559993f, -0.23070000f, -0.00000000f,
+				-1.30351651f, -0.22775158f, -0.04050000f,
+				-1.29786241f, -0.21974881f, -0.07200000f,
+				-1.28952956f, -0.20795520f, -0.09450001f,
+				-1.27941132f, -0.19363441f, -0.10800001f,
+				-1.26839995f, -0.17805001f, -0.11250000f,
+				-1.25738883f, -0.16246560f, -0.10800000f,
+				-1.24727035f, -0.14814481f, -0.09450001f,
+				-1.23893762f, -0.13635120f, -0.07200000f,
+				-1.23328316f, -0.12834841f, -0.04049999f,
+				-1.23119998f, -0.12540001f, -0.00000000f,
+				-1.23417509f, -0.28850627f, -0.00000000f,
+				-1.23282194f, -0.28531265f, -0.04050000f,
+				-1.22914934f, -0.27664444f, -0.07200000f,
+				-1.22373676f, -0.26387009f, -0.09450001f,
+				-1.21716475f, -0.24835847f, -0.10800001f,
+				-1.21001267f, -0.23147814f, -0.11250000f,
+				-1.20286047f, -0.21459781f, -0.10800000f,
+				-1.19628835f, -0.19908617f, -0.09450001f,
+				-1.19087601f, -0.18631187f, -0.07200000f,
+				-1.18720317f, -0.17764360f, -0.04049999f,
+				-1.18585014f, -0.17445001f, -0.00000000f,
+				-1.15120006f, -0.34440002f, -0.00000000f,
+				-1.15067351f, -0.34093919f, -0.04050000f,
+				-1.14924490f, -0.33154565f, -0.07200001f,
+				-1.14713919f, -0.31770241f, -0.09450001f,
+				-1.14458263f, -0.30089283f, -0.10800001f,
+				-1.14180017f, -0.28259999f, -0.11250000f,
+				-1.13901758f, -0.26430720f, -0.10800000f,
+				-1.13646090f, -0.24749762f, -0.09450001f,
+				-1.13435531f, -0.23365441f, -0.07200000f,
+				-1.13292646f, -0.22426081f, -0.04049999f,
+				-1.13240004f, -0.22080001f, -0.00000000f,
+				-1.05652499f, -0.39826876f, -0.00000000f,
+				-1.05691755f, -0.39448401f, -0.04050000f,
+				-1.05798364f, -0.38421121f, -0.07200001f,
+				-1.05955434f, -0.36907232f, -0.09450001f,
+				-1.06146181f, -0.35068938f, -0.10800001f,
+				-1.06353748f, -0.33068442f, -0.11250000f,
+				-1.06561315f, -0.31067941f, -0.10800000f,
+				-1.06752062f, -0.29229647f, -0.09450001f,
+				-1.06909132f, -0.27715760f, -0.07200000f,
+				-1.07015729f, -0.26688474f, -0.04049999f,
+				-1.07054996f, -0.26310003f, -0.00000000f,
+				-0.94999999f, -0.44999999f, -0.00000001f,
+				-0.95139986f, -0.44579995f, -0.04050000f,
+				-0.95520008f, -0.43440005f, -0.07200001f,
+				-0.96079999f, -0.41760001f, -0.09450001f,
+				-0.96759999f, -0.39719999f, -0.10800001f,
+				-0.97500002f, -0.37500000f, -0.11250000f,
+				-0.98240000f, -0.35280001f, -0.10800000f,
+				-0.98920006f, -0.33240002f, -0.09450001f,
+				-0.99480003f, -0.31560001f, -0.07200000f,
+				-0.99860001f, -0.30419999f, -0.04049999f,
+				-1.00000000f, -0.30000001f, -0.00000000f,
+				0.85000002f, -0.03750002f, -0.00000000f,
+				0.84999996f, -0.04905002f, 0.08910000f,
+				0.85000008f, -0.08040002f, 0.15840001f,
+				0.85000002f, -0.12660003f, 0.20790000f,
+				0.85000008f, -0.18270002f, 0.23760001f,
+				0.85000002f, -0.24375001f, 0.24750000f,
+				0.85000002f, -0.30480003f, 0.23760000f,
+				0.85000002f, -0.36089998f, 0.20790002f,
+				0.85000002f, -0.40710002f, 0.15840001f,
+				0.85000002f, -0.43845001f, 0.08909997f,
+				0.85000002f, -0.44999999f, -0.00000001f,
+				0.96794993f, -0.02790002f, -0.00000000f,
+				0.96969706f, -0.03838952f, 0.08755019f,
+				0.97443962f, -0.06686102f, 0.15564480f,
+				0.98142833f, -0.10881902f, 0.20428377f,
+				0.98991477f, -0.15976802f, 0.23346718f,
+				0.99914992f, -0.21521249f, 0.24319497f,
+				1.00838506f, -0.27065700f, 0.23346716f,
+				1.01687145f, -0.32160598f, 0.20428379f,
+				1.02386022f, -0.36356401f, 0.15564479f,
+				1.02860272f, -0.39203548f, 0.08755016f,
+				1.03034985f, -0.40252498f, -0.00000000f,
+				1.05560005f, -0.00120003f, -0.00000000f,
+				1.05848956f, -0.01044003f, 0.08334360f,
+				1.06633294f, -0.03552003f, 0.14816642f,
+				1.07789123f, -0.07248003f, 0.19446841f,
+				1.09192657f, -0.11736003f, 0.22224963f,
+				1.10720015f, -0.16620001f, 0.23151001f,
+				1.12247372f, -0.21504003f, 0.22224963f,
+				1.13650894f, -0.25992000f, 0.19446844f,
+				1.14806736f, -0.29688001f, 0.14816642f,
+				1.15591049f, -0.32196000f, 0.08334358f,
+				1.15880013f, -0.33120000f, -0.00000000f,
+				1.11864996f, 0.03944998f, 0.00000000f,
+				1.12222838f, 0.03158548f, 0.07714440f,
+				1.13194120f, 0.01023899f, 0.13714561f,
+				1.14625478f, -0.02121901f, 0.18000358f,
+				1.16363573f, -0.05941800f, 0.20571840f,
+				1.18255007f, -0.10098749f, 0.21428999f,
+				1.20146441f, -0.14255700f, 0.20571840f,
+				1.21884537f, -0.18075597f, 0.18000360f,
+				1.23315883f, -0.21221398f, 0.13714559f,
+				1.24287164f, -0.23356047f, 0.07714437f,
+				1.24645007f, -0.24142496f, -0.00000000f,
+				1.16280007f, 0.09089998f, 0.00000000f,
+				1.16676486f, 0.08447397f, 0.06961680f,
+				1.17752659f, 0.06703199f, 0.12376321f,
+				1.19338572f, 0.04132799f, 0.16243920f,
+				1.21264338f, 0.01011599f, 0.18564481f,
+				1.23360014f, -0.02385001f, 0.19338000f,
+				1.25455689f, -0.05781601f, 0.18564481f,
+				1.27381444f, -0.08902800f, 0.16243921f,
+				1.28967381f, -0.11473200f, 0.12376320f,
+				1.30043530f, -0.13217400f, 0.06961678f,
+				1.30440009f, -0.13859999f, -0.00000000f,
+				1.19375002f, 0.14999998f, 0.00000000f,
+				1.19794989f, 0.14501247f, 0.06142500f,
+				1.20935011f, 0.13147499f, 0.10920001f,
+				1.22614992f, 0.11152498f, 0.14332500f,
+				1.24654996f, 0.08730000f, 0.16380000f,
+				1.26874995f, 0.06093749f, 0.17062500f,
+				1.29094982f, 0.03457499f, 0.16380000f,
+				1.31134987f, 0.01035001f, 0.14332500f,
+				1.32814991f, -0.00959999f, 0.10920000f,
+				1.33954990f, -0.02313749f, 0.06142498f,
+				1.34374988f, -0.02812499f, -0.00000000f,
+				1.21719992f, 0.21360001f, 0.00000000f,
+				1.22163498f, 0.20998798f, 0.05323320f,
+				1.23367357f, 0.20018403f, 0.09463681f,
+				1.25141430f, 0.18573602f, 0.12421080f,
+				1.27295685f, 0.16819203f, 0.14195521f,
+				1.29639995f, 0.14910004f, 0.14787000f,
+				1.31984317f, 0.13000804f, 0.14195520f,
+				1.34138560f, 0.11246406f, 0.12421081f,
+				1.35912633f, 0.09801605f, 0.09463680f,
+				1.37116480f, 0.08821206f, 0.05323318f,
+				1.37559998f, 0.08460006f, 0.00000000f,
+				1.23885000f, 0.27855000f, 0.00000000f,
+				1.24367154f, 0.27618748f, 0.04570561f,
+				1.25675893f, 0.26977503f, 0.08125442f,
+				1.27604520f, 0.26032501f, 0.10664641f,
+				1.29946446f, 0.24885003f, 0.12188162f,
+				1.32495010f, 0.23636252f, 0.12696001f,
+				1.35043573f, 0.22387503f, 0.12188161f,
+				1.37385488f, 0.21240003f, 0.10664642f,
+				1.39314127f, 0.20295003f, 0.08125441f,
+				1.40622854f, 0.19653754f, 0.04570559f,
+				1.41105008f, 0.19417503f, 0.00000000f,
+				1.26440001f, 0.34170002f, 0.00000000f,
+				1.26991034f, 0.34039798f, 0.03950640f,
+				1.28486729f, 0.33686405f, 0.07023361f,
+				1.30690885f, 0.33165601f, 0.09218160f,
+				1.33367372f, 0.32533202f, 0.10535040f,
+				1.36280000f, 0.31845003f, 0.10974000f,
+				1.39192641f, 0.31156802f, 0.10535040f,
+				1.41869128f, 0.30524403f, 0.09218161f,
+				1.44073272f, 0.30003607f, 0.07023360f,
+				1.45568967f, 0.29650205f, 0.03950639f,
+				1.46120000f, 0.29520005f, 0.00000000f,
+				1.29955006f, 0.39990005f, 0.00000000f,
+				1.30620289f, 0.39940652f, 0.03529980f,
+				1.32426059f, 0.39806706f, 0.06275520f,
+				1.35087168f, 0.39609307f, 0.08236619f,
+				1.38318527f, 0.39369607f, 0.09413280f,
+				1.41835010f, 0.39108756f, 0.09805499f,
+				1.45351493f, 0.38847905f, 0.09413280f,
+				1.48582840f, 0.38608208f, 0.08236620f,
+				1.51243973f, 0.38410807f, 0.06275519f,
+				1.53049719f, 0.38276857f, 0.03529979f,
+				1.53715003f, 0.38227507f, 0.00000000f,
+				1.35000002f, 0.45000005f, 0.00000001f,
+				1.35839975f, 0.44999996f, 0.03375000f,
+				1.38120008f, 0.45000011f, 0.06000001f,
+				1.41480005f, 0.45000002f, 0.07875000f,
+				1.45560014f, 0.45000008f, 0.09000000f,
+				1.50000000f, 0.45000005f, 0.09375000f,
+				1.54439998f, 0.45000005f, 0.09000000f,
+				1.58520007f, 0.45000008f, 0.07875000f,
+				1.61880004f, 0.45000005f, 0.06000000f,
+				1.64159989f, 0.45000005f, 0.03374999f,
+				1.64999998f, 0.45000005f, 0.00000001f,
+				0.85000002f, -0.44999999f, -0.00000001f,
+				0.84999996f, -0.43844995f, -0.08910000f,
+				0.85000008f, -0.40710002f, -0.15840001f,
+				0.85000002f, -0.36089998f, -0.20790000f,
+				0.85000008f, -0.30480000f, -0.23760001f,
+				0.85000002f, -0.24375001f, -0.24750000f,
+				0.85000002f, -0.18269999f, -0.23760000f,
+				0.85000002f, -0.12660003f, -0.20790002f,
+				0.85000002f, -0.08040002f, -0.15840001f,
+				0.85000002f, -0.04905001f, -0.08909997f,
+				0.85000002f, -0.03750002f, -0.00000000f,
+				1.03034985f, -0.40252498f, -0.00000000f,
+				1.02860260f, -0.39203545f, -0.08755019f,
+				1.02386034f, -0.36356398f, -0.15564480f,
+				1.01687145f, -0.32160601f, -0.20428377f,
+				1.00838506f, -0.27065703f, -0.23346718f,
+				0.99914992f, -0.21521249f, -0.24319497f,
+				0.98991477f, -0.15976799f, -0.23346716f,
+				0.98142833f, -0.10881902f, -0.20428379f,
+				0.97443956f, -0.06686102f, -0.15564479f,
+				0.96969712f, -0.03838951f, -0.08755016f,
+				0.96794993f, -0.02790002f, -0.00000000f,
+				1.15880013f, -0.33120000f, -0.00000000f,
+				1.15591037f, -0.32195994f, -0.08334360f,
+				1.14806736f, -0.29688004f, -0.14816642f,
+				1.13650882f, -0.25992000f, -0.19446841f,
+				1.12247384f, -0.21504001f, -0.22224963f,
+				1.10720003f, -0.16620003f, -0.23151001f,
+				1.09192646f, -0.11736001f, -0.22224963f,
+				1.07789123f, -0.07248002f, -0.19446844f,
+				1.06633282f, -0.03552002f, -0.14816642f,
+				1.05848956f, -0.01044002f, -0.08334358f,
+				1.05560005f, -0.00120003f, -0.00000000f,
+				1.24645007f, -0.24142496f, -0.00000000f,
+				1.24287152f, -0.23356044f, -0.07714440f,
+				1.23315895f, -0.21221398f, -0.13714561f,
+				1.21884525f, -0.18075597f, -0.18000358f,
+				1.20146453f, -0.14255700f, -0.20571840f,
+				1.18254995f, -0.10098749f, -0.21428999f,
+				1.16363561f, -0.05941799f, -0.20571840f,
+				1.14625478f, -0.02121901f, -0.18000360f,
+				1.13194120f, 0.01023899f, -0.13714559f,
+				1.12222838f, 0.03158549f, -0.07714437f,
+				1.11864996f, 0.03944998f, 0.00000000f,
+				1.30440009f, -0.13859999f, -0.00000000f,
+				1.30043507f, -0.13217399f, -0.06961680f,
+				1.28967369f, -0.11473200f, -0.12376321f,
+				1.27381444f, -0.08902799f, -0.16243920f,
+				1.25455701f, -0.05781601f, -0.18564481f,
+				1.23360002f, -0.02385001f, -0.19338000f,
+				1.21264338f, 0.01011600f, -0.18564481f,
+				1.19338572f, 0.04132798f, -0.16243921f,
+				1.17752647f, 0.06703199f, -0.12376320f,
+				1.16676486f, 0.08447399f, -0.06961678f,
+				1.16280007f, 0.09089998f, 0.00000000f,
+				1.34374988f, -0.02812499f, -0.00000000f,
+				1.33954978f, -0.02313748f, -0.06142500f,
+				1.32814991f, -0.00959999f, -0.10920001f,
+				1.31134987f, 0.01035001f, -0.14332500f,
+				1.29095006f, 0.03457500f, -0.16380000f,
+				1.26874995f, 0.06093749f, -0.17062500f,
+				1.24654996f, 0.08730000f, -0.16380000f,
+				1.22615004f, 0.11152498f, -0.14332500f,
+				1.20935011f, 0.13147499f, -0.10920000f,
+				1.19795001f, 0.14501248f, -0.06142498f,
+				1.19375002f, 0.14999998f, 0.00000000f,
+				1.37559998f, 0.08460006f, 0.00000000f,
+				1.37116480f, 0.08821206f, -0.05323320f,
+				1.35912645f, 0.09801605f, -0.09463680f,
+				1.34138560f, 0.11246406f, -0.12421079f,
+				1.31984317f, 0.13000806f, -0.14195520f,
+				1.29639995f, 0.14910004f, -0.14786999f,
+				1.27295685f, 0.16819203f, -0.14195520f,
+				1.25141430f, 0.18573603f, -0.12421080f,
+				1.23367357f, 0.20018402f, -0.09463680f,
+				1.22163510f, 0.20998801f, -0.05323318f,
+				1.21719992f, 0.21360001f, 0.00000000f,
+				1.41105008f, 0.19417503f, 0.00000000f,
+				1.40622830f, 0.19653751f, -0.04570560f,
+				1.39314139f, 0.20295003f, -0.08125441f,
+				1.37385488f, 0.21240002f, -0.10664640f,
+				1.35043585f, 0.22387503f, -0.12188162f,
+				1.32494998f, 0.23636252f, -0.12696001f,
+				1.29946434f, 0.24885002f, -0.12188160f,
+				1.27604532f, 0.26032501f, -0.10664640f,
+				1.25675893f, 0.26977503f, -0.08125440f,
+				1.24367166f, 0.27618751f, -0.04570558f,
+				1.23885000f, 0.27855000f, 0.00000000f,
+				1.46120000f, 0.29520005f, 0.00000000f,
+				1.45568943f, 0.29650205f, -0.03950639f,
+				1.44073284f, 0.30003604f, -0.07023360f,
+				1.41869116f, 0.30524403f, -0.09218159f,
+				1.39192653f, 0.31156805f, -0.10535039f,
+				1.36280012f, 0.31845003f, -0.10973999f,
+				1.33367360f, 0.32533202f, -0.10535039f,
+				1.30690885f, 0.33165604f, -0.09218159f,
+				1.28486729f, 0.33686402f, -0.07023359f,
+				1.26991045f, 0.34039801f, -0.03950638f,
+				1.26440001f, 0.34170002f, 0.00000000f,
+				1.53715003f, 0.38227507f, 0.00000000f,
+				1.53049695f, 0.38276854f, -0.03529979f,
+				1.51243961f, 0.38410810f, -0.06275519f,
+				1.48582840f, 0.38608205f, -0.08236619f,
+				1.45351493f, 0.38847908f, -0.09413280f,
+				1.41835010f, 0.39108753f, -0.09805499f,
+				1.38318527f, 0.39369607f, -0.09413280f,
+				1.35087168f, 0.39609307f, -0.08236620f,
+				1.32426047f, 0.39806706f, -0.06275519f,
+				1.30620289f, 0.39940655f, -0.03529978f,
+				1.29955006f, 0.39990005f, 0.00000000f,
+				1.64999998f, 0.45000005f, 0.00000001f,
+				1.64159989f, 0.44999996f, -0.03374999f,
+				1.61880016f, 0.45000011f, -0.05999999f,
+				1.58519995f, 0.45000002f, -0.07874999f,
+				1.54440010f, 0.45000008f, -0.09000000f,
+				1.50000000f, 0.45000005f, -0.09374999f,
+				1.45560002f, 0.45000005f, -0.08999999f,
+				1.41480005f, 0.45000008f, -0.07875000f,
+				1.38120008f, 0.45000005f, -0.05999999f,
+				1.35839999f, 0.45000005f, -0.03374998f,
+				1.35000002f, 0.45000005f, 0.00000001f,
+				1.35000002f, 0.45000005f, 0.00000001f,
+				1.35839975f, 0.44999996f, 0.03375000f,
+				1.38120008f, 0.45000011f, 0.06000001f,
+				1.41480005f, 0.45000002f, 0.07875000f,
+				1.45560014f, 0.45000008f, 0.09000000f,
+				1.50000000f, 0.45000005f, 0.09375000f,
+				1.54439998f, 0.45000005f, 0.09000000f,
+				1.58520007f, 0.45000008f, 0.07875000f,
+				1.61880004f, 0.45000005f, 0.06000000f,
+				1.64159989f, 0.45000005f, 0.03374999f,
+				1.64999998f, 0.45000005f, 0.00000001f,
+				1.36489987f, 0.46012494f, 0.00000001f,
+				1.37370336f, 0.46020287f, 0.03337200f,
+				1.39759886f, 0.46041453f, 0.05932800f,
+				1.43281305f, 0.46072635f, 0.07786799f,
+				1.47557306f, 0.46110505f, 0.08899199f,
+				1.52210617f, 0.46151716f, 0.09269999f,
+				1.56863928f, 0.46192923f, 0.08899198f,
+				1.61139929f, 0.46230793f, 0.07786799f,
+				1.64661360f, 0.46261978f, 0.05932799f,
+				1.67050874f, 0.46283138f, 0.03337199f,
+				1.67931235f, 0.46290934f, 0.00000001f,
+				1.37919998f, 0.46800002f, 0.00000001f,
+				1.38818228f, 0.46815118f, 0.03234600f,
+				1.41256332f, 0.46856162f, 0.05750401f,
+				1.44849277f, 0.46916643f, 0.07547401f,
+				1.49212170f, 0.46990088f, 0.08625601f,
+				1.53960001f, 0.47070009f, 0.08985001f,
+				1.58707845f, 0.47149926f, 0.08625601f,
+				1.63070726f, 0.47223371f, 0.07547401f,
+				1.66663694f, 0.47283852f, 0.05750401f,
+				1.69101763f, 0.47324890f, 0.03234600f,
+				1.70000005f, 0.47340012f, 0.00000001f,
+				1.39230001f, 0.47362497f, 0.00000001f,
+				1.40126371f, 0.47383994f, 0.03083400f,
+				1.42559445f, 0.47442356f, 0.05481601f,
+				1.46144974f, 0.47528344f, 0.07194600f,
+				1.50498855f, 0.47632772f, 0.08222401f,
+				1.55236876f, 0.47746408f, 0.08565000f,
+				1.59974909f, 0.47860044f, 0.08222400f,
+				1.64328790f, 0.47964469f, 0.07194600f,
+				1.67914319f, 0.48050463f, 0.05481600f,
+				1.70347357f, 0.48108816f, 0.03083399f,
+				1.71243751f, 0.48130316f, 0.00000001f,
+				1.40359998f, 0.47700000f, 0.00000001f,
+				1.41237509f, 0.47726458f, 0.02899800f,
+				1.43619370f, 0.47798279f, 0.05155201f,
+				1.47129452f, 0.47904122f, 0.06766201f,
+				1.51391697f, 0.48032647f, 0.07732801f,
+				1.56030011f, 0.48172504f, 0.08055001f,
+				1.60668325f, 0.48312366f, 0.07732800f,
+				1.64930582f, 0.48440886f, 0.06766201f,
+				1.68440676f, 0.48546728f, 0.05155201f,
+				1.70822501f, 0.48618549f, 0.02899799f,
+				1.71700025f, 0.48645008f, 0.00000001f,
+				1.41249990f, 0.47812498f, 0.00000001f,
+				1.42094362f, 0.47842023f, 0.02700000f,
+				1.44386256f, 0.47922191f, 0.04800001f,
+				1.47763753f, 0.48040313f, 0.06300000f,
+				1.51865005f, 0.48183754f, 0.07200000f,
+				1.56328130f, 0.48339847f, 0.07500000f,
+				1.60791266f, 0.48495942f, 0.07200000f,
+				1.64892507f, 0.48639381f, 0.06300000f,
+				1.68270016f, 0.48757505f, 0.04800000f,
+				1.70561898f, 0.48837662f, 0.02700000f,
+				1.71406269f, 0.48867193f, 0.00000001f,
+				1.41839993f, 0.47700000f, 0.00000001f,
+				1.42639649f, 0.47730237f, 0.02500200f,
+				1.44810247f, 0.47812322f, 0.04444801f,
+				1.48008955f, 0.47933280f, 0.05833800f,
+				1.51893127f, 0.48080164f, 0.06667200f,
+				1.56120014f, 0.48240003f, 0.06945001f,
+				1.60346889f, 0.48399845f, 0.06667200f,
+				1.64231050f, 0.48546726f, 0.05833801f,
+				1.67429769f, 0.48667687f, 0.04444801f,
+				1.69600332f, 0.48749766f, 0.02500200f,
+				1.70400012f, 0.48780006f, 0.00000001f,
+				1.42070007f, 0.47362500f, 0.00000001f,
+				1.42816150f, 0.47390610f, 0.02316600f,
+				1.44841480f, 0.47466925f, 0.04118401f,
+				1.47826135f, 0.47579375f, 0.05405401f,
+				1.51450372f, 0.47715935f, 0.06177600f,
+				1.55394375f, 0.47864535f, 0.06435001f,
+				1.59338403f, 0.48013136f, 0.06177600f,
+				1.62962627f, 0.48149687f, 0.05405401f,
+				1.65947294f, 0.48262146f, 0.04118400f,
+				1.67972589f, 0.48338455f, 0.02316600f,
+				1.68718755f, 0.48366567f, 0.00000001f,
+				1.41880012f, 0.46799999f, 0.00000001f,
+				1.42566562f, 0.46822673f, 0.02165400f,
+				1.44430101f, 0.46884245f, 0.03849601f,
+				1.47176325f, 0.46974963f, 0.05052601f,
+				1.50511050f, 0.47085124f, 0.05774401f,
+				1.54139996f, 0.47205001f, 0.06015000f,
+				1.57768965f, 0.47324884f, 0.05774400f,
+				1.61103690f, 0.47435045f, 0.05052601f,
+				1.63849926f, 0.47525764f, 0.03849601f,
+				1.65713453f, 0.47587326f, 0.02165400f,
+				1.66400003f, 0.47610006f, 0.00000001f,
+				1.41209996f, 0.46012503f, 0.00000001f,
+				1.41833580f, 0.46025965f, 0.02062801f,
+				1.43526208f, 0.46062523f, 0.03667201f,
+				1.46020591f, 0.46116382f, 0.04813201f,
+				1.49049485f, 0.46181795f, 0.05500801f,
+				1.52345622f, 0.46252972f, 0.05730001f,
+				1.55641770f, 0.46324152f, 0.05500801f,
+				1.58670676f, 0.46389559f, 0.04813201f,
+				1.61165047f, 0.46443427f, 0.03667201f,
+				1.62857664f, 0.46479973f, 0.02062800f,
+				1.63481259f, 0.46493441f, 0.00000001f,
+				1.39999998f, 0.45000005f, 0.00000001f,
+				1.40559983f, 0.44999996f, 0.02025001f,
+				1.42079997f, 0.45000011f, 0.03600001f,
+				1.44319999f, 0.45000002f, 0.04725001f,
+				1.47040009f, 0.45000008f, 0.05400001f,
+				1.50000000f, 0.45000005f, 0.05625001f,
+				1.52960002f, 0.45000005f, 0.05400001f,
+				1.55680001f, 0.45000008f, 0.04725001f,
+				1.57920003f, 0.45000005f, 0.03600001f,
+				1.59440005f, 0.45000005f, 0.02025000f,
+				1.60000002f, 0.45000005f, 0.00000001f,
+				1.64999998f, 0.45000005f, 0.00000001f,
+				1.64159989f, 0.44999996f, -0.03374999f,
+				1.61880016f, 0.45000011f, -0.05999999f,
+				1.58519995f, 0.45000002f, -0.07874999f,
+				1.54440010f, 0.45000008f, -0.09000000f,
+				1.50000000f, 0.45000005f, -0.09374999f,
+				1.45560002f, 0.45000005f, -0.08999999f,
+				1.41480005f, 0.45000008f, -0.07875000f,
+				1.38120008f, 0.45000005f, -0.05999999f,
+				1.35839999f, 0.45000005f, -0.03374998f,
+				1.35000002f, 0.45000005f, 0.00000001f,
+				1.67931235f, 0.46290934f, 0.00000001f,
+				1.67050874f, 0.46283132f, -0.03337199f,
+				1.64661360f, 0.46261978f, -0.05932799f,
+				1.61139941f, 0.46230793f, -0.07786798f,
+				1.56863928f, 0.46192926f, -0.08899198f,
+				1.52210605f, 0.46151716f, -0.09269998f,
+				1.47557306f, 0.46110505f, -0.08899198f,
+				1.43281293f, 0.46072638f, -0.07786798f,
+				1.39759874f, 0.46041453f, -0.05932799f,
+				1.37370336f, 0.46020290f, -0.03337198f,
+				1.36489987f, 0.46012494f, 0.00000001f,
+				1.70000005f, 0.47340012f, 0.00000001f,
+				1.69101751f, 0.47324887f, -0.03234600f,
+				1.66663706f, 0.47283855f, -0.05750400f,
+				1.63070726f, 0.47223368f, -0.07547399f,
+				1.58707845f, 0.47149929f, -0.08625600f,
+				1.53960001f, 0.47070006f, -0.08985000f,
+				1.49212158f, 0.46990088f, -0.08625600f,
+				1.44849288f, 0.46916646f, -0.07547400f,
+				1.41256320f, 0.46856165f, -0.05750399f,
+				1.38818240f, 0.46815124f, -0.03234598f,
+				1.37919998f, 0.46800002f, 0.00000001f,
+				1.71243751f, 0.48130316f, 0.00000001f,
+				1.70347345f, 0.48108810f, -0.03083399f,
+				1.67914343f, 0.48050466f, -0.05481599f,
+				1.64328778f, 0.47964469f, -0.07194599f,
+				1.59974921f, 0.47860047f, -0.08222400f,
+				1.55236864f, 0.47746405f, -0.08564999f,
+				1.50498843f, 0.47632769f, -0.08222400f,
+				1.46144986f, 0.47528344f, -0.07194600f,
+				1.42559433f, 0.47442353f, -0.05481599f,
+				1.40126395f, 0.47383997f, -0.03083398f,
+				1.39230001f, 0.47362497f, 0.00000001f,
+				1.71700025f, 0.48645008f, 0.00000001f,
+				1.70822489f, 0.48618543f, -0.02899799f,
+				1.68440676f, 0.48546731f, -0.05155200f,
+				1.64930582f, 0.48440889f, -0.06766199f,
+				1.60668337f, 0.48312369f, -0.07732800f,
+				1.56030011f, 0.48172504f, -0.08055000f,
+				1.51391685f, 0.48032641f, -0.07732800f,
+				1.47129452f, 0.47904122f, -0.06766200f,
+				1.43619370f, 0.47798282f, -0.05155200f,
+				1.41237521f, 0.47726461f, -0.02899799f,
+				1.40359998f, 0.47700000f, 0.00000001f,
+				1.71406269f, 0.48867193f, 0.00000001f,
+				1.70561886f, 0.48837656f, -0.02699999f,
+				1.68270016f, 0.48757505f, -0.04800000f,
+				1.64892519f, 0.48639381f, -0.06299999f,
+				1.60791278f, 0.48495948f, -0.07200000f,
+				1.56328130f, 0.48339844f, -0.07500000f,
+				1.51865005f, 0.48183751f, -0.07200000f,
+				1.47763753f, 0.48040313f, -0.06299999f,
+				1.44386244f, 0.47922188f, -0.04799999f,
+				1.42094362f, 0.47842029f, -0.02699998f,
+				1.41249990f, 0.47812498f, 0.00000001f,
+				1.70400012f, 0.48780006f, 0.00000001f,
+				1.69600320f, 0.48749760f, -0.02500199f,
+				1.67429769f, 0.48667687f, -0.04444800f,
+				1.64231050f, 0.48546728f, -0.05833799f,
+				1.60346901f, 0.48399848f, -0.06667200f,
+				1.56120002f, 0.48240003f, -0.06944999f,
+				1.51893127f, 0.48080158f, -0.06667199f,
+				1.48008966f, 0.47933280f, -0.05833799f,
+				1.44810236f, 0.47812319f, -0.04444799f,
+				1.42639661f, 0.47730240f, -0.02500199f,
+				1.41839993f, 0.47700000f, 0.00000001f,
+				1.68718755f, 0.48366567f, 0.00000001f,
+				1.67972589f, 0.48338449f, -0.02316599f,
+				1.65947306f, 0.48262149f, -0.04118400f,
+				1.62962627f, 0.48149690f, -0.05405399f,
+				1.59338415f, 0.48013139f, -0.06177600f,
+				1.55394387f, 0.47864535f, -0.06434999f,
+				1.51450372f, 0.47715932f, -0.06177600f,
+				1.47826147f, 0.47579378f, -0.05405399f,
+				1.44841480f, 0.47466925f, -0.04118399f,
+				1.42816162f, 0.47390613f, -0.02316599f,
+				1.42070007f, 0.47362500f, 0.00000001f,
+				1.66400003f, 0.47610006f, 0.00000001f,
+				1.65713418f, 0.47587320f, -0.02165399f,
+				1.63849938f, 0.47525769f, -0.03849600f,
+				1.61103702f, 0.47435045f, -0.05052599f,
+				1.57768977f, 0.47324887f, -0.05774400f,
+				1.54140007f, 0.47205001f, -0.06015000f,
+				1.50511050f, 0.47085121f, -0.05774400f,
+				1.47176337f, 0.46974963f, -0.05052600f,
+				1.44430089f, 0.46884239f, -0.03849599f,
+				1.42566574f, 0.46822679f, -0.02165399f,
+				1.41880012f, 0.46799999f, 0.00000001f,
+				1.63481259f, 0.46493441f, 0.00000001f,
+				1.62857652f, 0.46479967f, -0.02062799f,
+				1.61165059f, 0.46443427f, -0.03667200f,
+				1.58670664f, 0.46389559f, -0.04813199f,
+				1.55641782f, 0.46324155f, -0.05500800f,
+				1.52345634f, 0.46252972f, -0.05730000f,
+				1.49049485f, 0.46181795f, -0.05500799f,
+				1.46020591f, 0.46116385f, -0.04813200f,
+				1.43526220f, 0.46062523f, -0.03667200f,
+				1.41833591f, 0.46025968f, -0.02062799f,
+				1.41209996f, 0.46012503f, 0.00000001f,
+				1.60000002f, 0.45000005f, 0.00000001f,
+				1.59439981f, 0.44999996f, -0.02024999f,
+				1.57920015f, 0.45000011f, -0.03600000f,
+				1.55680001f, 0.45000002f, -0.04725000f,
+				1.52960002f, 0.45000008f, -0.05400000f,
+				1.50000000f, 0.45000005f, -0.05625000f,
+				1.47039998f, 0.45000005f, -0.05400000f,
+				1.44320011f, 0.45000008f, -0.04725000f,
+				1.42079997f, 0.45000005f, -0.03599999f,
+				1.40559995f, 0.45000005f, -0.02024999f,
+				1.39999998f, 0.45000005f, 0.00000001f
+		};
+		
+		const std::array<float, (vertexCount * 3)> teapotNormals = {
+				-0.90286040f, -0.42993385f, 0.00000000f,
+				-0.89153194f, -0.43015867f, -0.14189552f,
+				-0.85786587f, -0.43059322f, -0.28045613f,
+				-0.80313200f, -0.43100095f, -0.41136023f,
+				-0.72916991f, -0.43127316f, -0.53133309f,
+				-0.63793534f, -0.43136632f, -0.63793534f,
+				-0.53133315f, -0.43127266f, -0.72917002f,
+				-0.41136023f, -0.43100119f, -0.80313188f,
+				-0.28045616f, -0.43059263f, -0.85786611f,
+				-0.14189562f, -0.43015778f, -0.89153230f,
+				-0.00000000f, -0.42993385f, -0.90286040f,
+				-0.95656341f, -0.29152444f, 0.00000000f,
+				-0.94462180f, -0.29169482f, -0.15034524f,
+				-0.90906340f, -0.29202676f, -0.29719374f,
+				-0.85116220f, -0.29233694f, -0.43596110f,
+				-0.77283686f, -0.29254490f, -0.56315243f,
+				-0.67615676f, -0.29261592f, -0.67615676f,
+				-0.56315249f, -0.29254425f, -0.77283710f,
+				-0.43596104f, -0.29233718f, -0.85116208f,
+				-0.29719374f, -0.29202670f, -0.90906346f,
+				-0.15034510f, -0.29169548f, -0.94462162f,
+				-0.00000000f, -0.29152444f, -0.95656341f,
+				-0.99756998f, -0.06967134f, 0.00000000f,
+				-0.98516703f, -0.06971460f, -0.15679841f,
+				-0.94817692f, -0.06980211f, -0.30998084f,
+				-0.88786739f, -0.06988241f, -0.45476130f,
+				-0.80621493f, -0.06993677f, -0.58747447f,
+				-0.70537448f, -0.06995525f, -0.70537448f,
+				-0.58747447f, -0.06993637f, -0.80621505f,
+				-0.45476136f, -0.06988230f, -0.88786751f,
+				-0.30998087f, -0.06980199f, -0.94817698f,
+				-0.15679830f, -0.06971515f, -0.98516703f,
+				-0.00000000f, -0.06967134f, -0.99756998f,
+				-0.95136631f, 0.30806199f, 0.00000000f,
+				-0.93948323f, 0.30824155f, -0.14952739f,
+				-0.90410745f, 0.30858687f, -0.29557356f,
+				-0.84651184f, 0.30891240f, -0.43357921f,
+				-0.76860887f, 0.30912822f, -0.56007159f,
+				-0.67245591f, 0.30920231f, -0.67245591f,
+				-0.56007153f, 0.30912805f, -0.76860899f,
+				-0.43357912f, 0.30891296f, -0.84651166f,
+				-0.29557377f, 0.30858609f, -0.90410781f,
+				-0.14952743f, 0.30823970f, -0.93948382f,
+				0.00000000f, 0.30806199f, -0.95136631f,
+				-0.59454966f, 0.80405891f, 0.00000000f,
+				-0.58691627f, 0.80424082f, -0.09341305f,
+				-0.56442964f, 0.80459291f, -0.18452507f,
+				-0.52813381f, 0.80492258f, -0.27050757f,
+				-0.47932491f, 0.80514228f, -0.34927556f,
+				-0.41930011f, 0.80521733f, -0.41930011f,
+				-0.34927583f, 0.80514193f, -0.47932535f,
+				-0.27050710f, 0.80492342f, -0.52813286f,
+				-0.18452539f, 0.80459219f, -0.56443053f,
+				-0.09341303f, 0.80424047f, -0.58691663f,
+				0.00000000f, 0.80405891f, -0.59454966f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				-0.00000000f, 1.00000000f, 0.00000002f,
+				0.00000000f, 1.00000000f, -0.00000001f,
+				-0.00000037f, 1.00000000f, -0.00000014f,
+				0.00000038f, 1.00000000f, 0.00000015f,
+				0.00000000f, 1.00000000f, -0.00000000f,
+				-0.00000004f, 1.00000000f, 0.00000003f,
+				0.00000018f, 1.00000000f, 0.00000035f,
+				0.00000005f, 0.99999994f, -0.00000002f,
+				0.00000000f, 1.00000000f, -0.00000000f,
+				0.00000000f, 1.00000000f, -0.00000000f,
+				0.39803210f, 0.91737145f, -0.00000000f,
+				0.39287356f, 0.91746408f, 0.06252954f,
+				0.37772986f, 0.91764414f, 0.12348856f,
+				0.35335949f, 0.91781265f, 0.18098903f,
+				0.32065529f, 0.91792440f, 0.23365574f,
+				0.28048533f, 0.91796297f, 0.28048533f,
+				0.23365568f, 0.91792440f, 0.32065514f,
+				0.18098891f, 0.91781276f, 0.35335934f,
+				0.12348875f, 0.91764396f, 0.37773019f,
+				0.06252949f, 0.91746432f, 0.39287323f,
+				0.00000000f, 0.91737145f, 0.39803210f,
+				0.61678272f, 0.78713351f, -0.00000000f,
+				0.60887474f, 0.78732491f, 0.09690809f,
+				0.58556628f, 0.78769588f, 0.19143508f,
+				0.54792851f, 0.78804308f, 0.28064632f,
+				0.49730095f, 0.78827441f, 0.36237431f,
+				0.43502763f, 0.78835386f, 0.43502763f,
+				0.36237440f, 0.78827435f, 0.49730113f,
+				0.28064600f, 0.78804362f, 0.54792786f,
+				0.19143537f, 0.78769529f, 0.58556694f,
+				0.09690812f, 0.78732455f, 0.60887510f,
+				0.00000000f, 0.78713351f, 0.61678272f,
+				0.74976826f, 0.66170049f, -0.00000000f,
+				0.74024051f, 0.66193908f, 0.11781615f,
+				0.71206385f, 0.66240013f, 0.23279002f,
+				0.66643608f, 0.66283208f, 0.34134525f,
+				0.60494393f, 0.66311979f, 0.44081178f,
+				0.52921647f, 0.66321933f, 0.52921647f,
+				0.44081184f, 0.66311955f, 0.60494399f,
+				0.34134495f, 0.66283280f, 0.66643548f,
+				0.23279032f, 0.66239935f, 0.71206450f,
+				0.11781613f, 0.66193885f, 0.74024075f,
+				0.00000000f, 0.66170049f, 0.74976826f,
+				0.83935553f, 0.54358286f, -0.00000000f,
+				0.82876492f, 0.54382867f, 0.13190563f,
+				0.79735982f, 0.54430288f, 0.26067528f,
+				0.74638999f, 0.54474837f, 0.38229731f,
+				0.67759502f, 0.54504555f, 0.49375138f,
+				0.59279579f, 0.54514796f, 0.59279579f,
+				0.49375141f, 0.54504526f, 0.67759514f,
+				0.38229698f, 0.54474950f, 0.74638933f,
+				0.26067549f, 0.54430223f, 0.79736024f,
+				0.13190556f, 0.54382777f, 0.82876539f,
+				0.00000000f, 0.54358286f, 0.83935553f,
+				0.90286040f, 0.42993385f, -0.00000000f,
+				0.89153200f, 0.43015870f, 0.14189546f,
+				0.85786629f, 0.43059230f, 0.28045624f,
+				0.80313194f, 0.43100107f, 0.41136029f,
+				0.72917026f, 0.43127215f, 0.53133333f,
+				0.63793516f, 0.43136698f, 0.63793516f,
+				0.53133321f, 0.43127269f, 0.72917008f,
+				0.41136014f, 0.43100169f, 0.80313170f,
+				0.28045627f, 0.43059269f, 0.85786611f,
+				0.14189564f, 0.43015781f, 0.89153230f,
+				0.00000000f, 0.42993385f, 0.90286040f,
+				0.00000000f, -0.42993385f, 0.90286040f,
+				-0.14189552f, -0.43015867f, 0.89153194f,
+				-0.28045613f, -0.43059322f, 0.85786587f,
+				-0.41136023f, -0.43100095f, 0.80313200f,
+				-0.53133309f, -0.43127316f, 0.72916991f,
+				-0.63793534f, -0.43136632f, 0.63793534f,
+				-0.72917002f, -0.43127266f, 0.53133315f,
+				-0.80313188f, -0.43100119f, 0.41136023f,
+				-0.85786611f, -0.43059263f, 0.28045616f,
+				-0.89153230f, -0.43015778f, 0.14189562f,
+				-0.90286040f, -0.42993385f, 0.00000000f,
+				0.00000000f, -0.29152444f, 0.95656341f,
+				-0.15034524f, -0.29169479f, 0.94462180f,
+				-0.29719374f, -0.29202676f, 0.90906340f,
+				-0.43596110f, -0.29233694f, 0.85116220f,
+				-0.56315243f, -0.29254490f, 0.77283686f,
+				-0.67615676f, -0.29261592f, 0.67615676f,
+				-0.77283710f, -0.29254425f, 0.56315249f,
+				-0.85116208f, -0.29233718f, 0.43596104f,
+				-0.90906346f, -0.29202670f, 0.29719374f,
+				-0.94462162f, -0.29169548f, 0.15034510f,
+				-0.95656341f, -0.29152444f, 0.00000000f,
+				0.00000000f, -0.06967134f, 0.99756998f,
+				-0.15679841f, -0.06971460f, 0.98516703f,
+				-0.30998084f, -0.06980211f, 0.94817692f,
+				-0.45476130f, -0.06988241f, 0.88786739f,
+				-0.58747447f, -0.06993677f, 0.80621493f,
+				-0.70537448f, -0.06995525f, 0.70537448f,
+				-0.80621505f, -0.06993637f, 0.58747447f,
+				-0.88786751f, -0.06988230f, 0.45476136f,
+				-0.94817698f, -0.06980199f, 0.30998087f,
+				-0.98516703f, -0.06971514f, 0.15679830f,
+				-0.99756998f, -0.06967134f, 0.00000000f,
+				-0.00000000f, 0.30806199f, 0.95136631f,
+				-0.14952739f, 0.30824158f, 0.93948323f,
+				-0.29557356f, 0.30858687f, 0.90410745f,
+				-0.43357921f, 0.30891240f, 0.84651184f,
+				-0.56007159f, 0.30912822f, 0.76860887f,
+				-0.67245591f, 0.30920231f, 0.67245591f,
+				-0.76860899f, 0.30912805f, 0.56007153f,
+				-0.84651166f, 0.30891296f, 0.43357912f,
+				-0.90410781f, 0.30858609f, 0.29557377f,
+				-0.93948382f, 0.30823973f, 0.14952743f,
+				-0.95136631f, 0.30806199f, 0.00000000f,
+				-0.00000000f, 0.80405891f, 0.59454966f,
+				-0.09341305f, 0.80424082f, 0.58691627f,
+				-0.18452507f, 0.80459291f, 0.56442964f,
+				-0.27050757f, 0.80492258f, 0.52813381f,
+				-0.34927556f, 0.80514228f, 0.47932491f,
+				-0.41930011f, 0.80521733f, 0.41930011f,
+				-0.47932535f, 0.80514193f, 0.34927583f,
+				-0.52813286f, 0.80492342f, 0.27050710f,
+				-0.56443053f, 0.80459219f, 0.18452539f,
+				-0.58691663f, 0.80424052f, 0.09341303f,
+				-0.59454966f, 0.80405891f, 0.00000000f,
+				-0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000002f, 1.00000000f, 0.00000000f,
+				-0.00000001f, 1.00000000f, -0.00000000f,
+				-0.00000014f, 1.00000000f, 0.00000037f,
+				0.00000015f, 1.00000000f, -0.00000038f,
+				-0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000003f, 1.00000000f, 0.00000004f,
+				0.00000035f, 1.00000000f, -0.00000018f,
+				-0.00000002f, 0.99999994f, -0.00000005f,
+				-0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 0.91737145f, -0.39803210f,
+				0.06252954f, 0.91746408f, -0.39287356f,
+				0.12348856f, 0.91764414f, -0.37772986f,
+				0.18098900f, 0.91781259f, -0.35335946f,
+				0.23365574f, 0.91792440f, -0.32065529f,
+				0.28048533f, 0.91796297f, -0.28048533f,
+				0.32065514f, 0.91792440f, -0.23365568f,
+				0.35335934f, 0.91781276f, -0.18098891f,
+				0.37773019f, 0.91764396f, -0.12348875f,
+				0.39287323f, 0.91746432f, -0.06252949f,
+				0.39803210f, 0.91737145f, -0.00000000f,
+				0.00000000f, 0.78713351f, -0.61678272f,
+				0.09690809f, 0.78732491f, -0.60887474f,
+				0.19143508f, 0.78769588f, -0.58556628f,
+				0.28064632f, 0.78804308f, -0.54792851f,
+				0.36237431f, 0.78827441f, -0.49730095f,
+				0.43502763f, 0.78835386f, -0.43502763f,
+				0.49730113f, 0.78827435f, -0.36237440f,
+				0.54792786f, 0.78804362f, -0.28064600f,
+				0.58556694f, 0.78769529f, -0.19143537f,
+				0.60887510f, 0.78732455f, -0.09690812f,
+				0.61678272f, 0.78713351f, -0.00000000f,
+				0.00000000f, 0.66170049f, -0.74976826f,
+				0.11781615f, 0.66193908f, -0.74024051f,
+				0.23279002f, 0.66240013f, -0.71206385f,
+				0.34134525f, 0.66283208f, -0.66643608f,
+				0.44081178f, 0.66311979f, -0.60494393f,
+				0.52921647f, 0.66321933f, -0.52921647f,
+				0.60494399f, 0.66311955f, -0.44081184f,
+				0.66643548f, 0.66283280f, -0.34134495f,
+				0.71206450f, 0.66239935f, -0.23279032f,
+				0.74024075f, 0.66193885f, -0.11781613f,
+				0.74976826f, 0.66170049f, -0.00000000f,
+				0.00000000f, 0.54358286f, -0.83935553f,
+				0.13190563f, 0.54382867f, -0.82876492f,
+				0.26067528f, 0.54430288f, -0.79735982f,
+				0.38229731f, 0.54474837f, -0.74638999f,
+				0.49375138f, 0.54504555f, -0.67759502f,
+				0.59279579f, 0.54514796f, -0.59279579f,
+				0.67759514f, 0.54504526f, -0.49375141f,
+				0.74638933f, 0.54474950f, -0.38229698f,
+				0.79736024f, 0.54430223f, -0.26067549f,
+				0.82876539f, 0.54382777f, -0.13190556f,
+				0.83935553f, 0.54358286f, -0.00000000f,
+				0.00000000f, 0.42993385f, -0.90286040f,
+				0.14189546f, 0.43015870f, -0.89153200f,
+				0.28045624f, 0.43059230f, -0.85786629f,
+				0.41136029f, 0.43100107f, -0.80313194f,
+				0.53133333f, 0.43127215f, -0.72917026f,
+				0.63793516f, 0.43136698f, -0.63793516f,
+				0.72917008f, 0.43127269f, -0.53133321f,
+				0.80313170f, 0.43100169f, -0.41136014f,
+				0.85786611f, 0.43059269f, -0.28045627f,
+				0.89153230f, 0.43015778f, -0.14189564f,
+				0.90286040f, 0.42993385f, -0.00000000f,
+				-0.00000000f, -0.42993385f, -0.90286040f,
+				0.14189552f, -0.43015867f, -0.89153194f,
+				0.28045613f, -0.43059322f, -0.85786587f,
+				0.41136023f, -0.43100095f, -0.80313200f,
+				0.53133309f, -0.43127316f, -0.72916991f,
+				0.63793534f, -0.43136632f, -0.63793534f,
+				0.72917002f, -0.43127266f, -0.53133315f,
+				0.80313188f, -0.43100119f, -0.41136023f,
+				0.85786611f, -0.43059263f, -0.28045616f,
+				0.89153230f, -0.43015775f, -0.14189562f,
+				0.90286040f, -0.42993385f, 0.00000000f,
+				-0.00000000f, -0.29152444f, -0.95656341f,
+				0.15034524f, -0.29169479f, -0.94462180f,
+				0.29719374f, -0.29202676f, -0.90906340f,
+				0.43596110f, -0.29233694f, -0.85116220f,
+				0.56315243f, -0.29254490f, -0.77283686f,
+				0.67615676f, -0.29261592f, -0.67615676f,
+				0.77283710f, -0.29254425f, -0.56315249f,
+				0.85116208f, -0.29233718f, -0.43596104f,
+				0.90906346f, -0.29202670f, -0.29719374f,
+				0.94462162f, -0.29169548f, -0.15034510f,
+				0.95656341f, -0.29152444f, 0.00000000f,
+				-0.00000000f, -0.06967134f, -0.99756998f,
+				0.15679841f, -0.06971460f, -0.98516703f,
+				0.30998084f, -0.06980211f, -0.94817692f,
+				0.45476130f, -0.06988241f, -0.88786739f,
+				0.58747447f, -0.06993677f, -0.80621493f,
+				0.70537448f, -0.06995525f, -0.70537448f,
+				0.80621505f, -0.06993637f, -0.58747447f,
+				0.88786751f, -0.06988230f, -0.45476136f,
+				0.94817698f, -0.06980199f, -0.30998087f,
+				0.98516703f, -0.06971515f, -0.15679830f,
+				0.99756998f, -0.06967134f, 0.00000000f,
+				0.00000000f, 0.30806199f, -0.95136631f,
+				0.14952739f, 0.30824158f, -0.93948323f,
+				0.29557356f, 0.30858687f, -0.90410745f,
+				0.43357921f, 0.30891240f, -0.84651184f,
+				0.56007159f, 0.30912822f, -0.76860887f,
+				0.67245591f, 0.30920231f, -0.67245591f,
+				0.76860899f, 0.30912805f, -0.56007153f,
+				0.84651166f, 0.30891296f, -0.43357912f,
+				0.90410781f, 0.30858609f, -0.29557377f,
+				0.93948382f, 0.30823976f, -0.14952743f,
+				0.95136631f, 0.30806199f, 0.00000000f,
+				0.00000000f, 0.80405891f, -0.59454966f,
+				0.09341305f, 0.80424082f, -0.58691627f,
+				0.18452507f, 0.80459291f, -0.56442964f,
+				0.27050757f, 0.80492258f, -0.52813381f,
+				0.34927556f, 0.80514228f, -0.47932491f,
+				0.41930011f, 0.80521733f, -0.41930011f,
+				0.47932535f, 0.80514193f, -0.34927583f,
+				0.52813286f, 0.80492342f, -0.27050710f,
+				0.56443053f, 0.80459219f, -0.18452539f,
+				0.58691663f, 0.80424052f, -0.09341303f,
+				0.59454966f, 0.80405891f, 0.00000000f,
+				0.00000000f, 1.00000000f, -0.00000000f,
+				-0.00000002f, 1.00000000f, -0.00000000f,
+				0.00000001f, 1.00000000f, 0.00000000f,
+				0.00000014f, 1.00000000f, -0.00000037f,
+				-0.00000015f, 1.00000000f, 0.00000038f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				-0.00000003f, 1.00000000f, -0.00000004f,
+				-0.00000035f, 1.00000000f, 0.00000018f,
+				0.00000002f, 0.99999994f, 0.00000005f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 0.91737145f, 0.39803210f,
+				-0.06252954f, 0.91746408f, 0.39287356f,
+				-0.12348856f, 0.91764414f, 0.37772986f,
+				-0.18098900f, 0.91781259f, 0.35335946f,
+				-0.23365574f, 0.91792440f, 0.32065529f,
+				-0.28048533f, 0.91796297f, 0.28048533f,
+				-0.32065514f, 0.91792440f, 0.23365568f,
+				-0.35335934f, 0.91781276f, 0.18098891f,
+				-0.37773019f, 0.91764396f, 0.12348875f,
+				-0.39287323f, 0.91746432f, 0.06252949f,
+				-0.39803210f, 0.91737145f, 0.00000000f,
+				0.00000000f, 0.78713351f, 0.61678272f,
+				-0.09690809f, 0.78732491f, 0.60887474f,
+				-0.19143508f, 0.78769588f, 0.58556628f,
+				-0.28064632f, 0.78804308f, 0.54792851f,
+				-0.36237431f, 0.78827441f, 0.49730095f,
+				-0.43502763f, 0.78835386f, 0.43502763f,
+				-0.49730113f, 0.78827435f, 0.36237440f,
+				-0.54792786f, 0.78804362f, 0.28064600f,
+				-0.58556694f, 0.78769529f, 0.19143537f,
+				-0.60887510f, 0.78732455f, 0.09690812f,
+				-0.61678272f, 0.78713351f, 0.00000000f,
+				0.00000000f, 0.66170049f, 0.74976826f,
+				-0.11781615f, 0.66193908f, 0.74024051f,
+				-0.23279002f, 0.66240013f, 0.71206385f,
+				-0.34134525f, 0.66283208f, 0.66643608f,
+				-0.44081178f, 0.66311979f, 0.60494393f,
+				-0.52921647f, 0.66321933f, 0.52921647f,
+				-0.60494399f, 0.66311955f, 0.44081184f,
+				-0.66643548f, 0.66283280f, 0.34134495f,
+				-0.71206450f, 0.66239935f, 0.23279032f,
+				-0.74024075f, 0.66193885f, 0.11781613f,
+				-0.74976826f, 0.66170049f, 0.00000000f,
+				0.00000000f, 0.54358286f, 0.83935553f,
+				-0.13190563f, 0.54382867f, 0.82876492f,
+				-0.26067528f, 0.54430288f, 0.79735982f,
+				-0.38229731f, 0.54474837f, 0.74638999f,
+				-0.49375138f, 0.54504555f, 0.67759502f,
+				-0.59279579f, 0.54514796f, 0.59279579f,
+				-0.67759514f, 0.54504526f, 0.49375141f,
+				-0.74638933f, 0.54474950f, 0.38229698f,
+				-0.79736024f, 0.54430223f, 0.26067549f,
+				-0.82876539f, 0.54382777f, 0.13190556f,
+				-0.83935553f, 0.54358286f, 0.00000000f,
+				0.00000000f, 0.42993385f, 0.90286040f,
+				-0.14189546f, 0.43015870f, 0.89153200f,
+				-0.28045624f, 0.43059230f, 0.85786629f,
+				-0.41136029f, 0.43100107f, 0.80313194f,
+				-0.53133333f, 0.43127215f, 0.72917026f,
+				-0.63793516f, 0.43136698f, 0.63793516f,
+				-0.72917008f, 0.43127269f, 0.53133321f,
+				-0.80313170f, 0.43100169f, 0.41136014f,
+				-0.85786611f, 0.43059269f, 0.28045627f,
+				-0.89153230f, 0.43015781f, 0.14189564f,
+				-0.90286040f, 0.42993385f, 0.00000000f,
+				0.90286040f, -0.42993385f, 0.00000000f,
+				0.89153194f, -0.43015867f, 0.14189552f,
+				0.85786587f, -0.43059322f, 0.28045613f,
+				0.80313200f, -0.43100095f, 0.41136023f,
+				0.72916991f, -0.43127316f, 0.53133309f,
+				0.63793534f, -0.43136632f, 0.63793534f,
+				0.53133315f, -0.43127266f, 0.72917002f,
+				0.41136023f, -0.43100119f, 0.80313188f,
+				0.28045616f, -0.43059263f, 0.85786611f,
+				0.14189562f, -0.43015778f, 0.89153230f,
+				0.00000000f, -0.42993385f, 0.90286040f,
+				0.95656341f, -0.29152444f, 0.00000000f,
+				0.94462180f, -0.29169479f, 0.15034524f,
+				0.90906340f, -0.29202676f, 0.29719374f,
+				0.85116220f, -0.29233694f, 0.43596110f,
+				0.77283686f, -0.29254490f, 0.56315243f,
+				0.67615676f, -0.29261592f, 0.67615676f,
+				0.56315249f, -0.29254425f, 0.77283710f,
+				0.43596104f, -0.29233718f, 0.85116208f,
+				0.29719374f, -0.29202670f, 0.90906346f,
+				0.15034510f, -0.29169548f, 0.94462162f,
+				0.00000000f, -0.29152444f, 0.95656341f,
+				0.99756998f, -0.06967134f, 0.00000000f,
+				0.98516703f, -0.06971459f, 0.15679841f,
+				0.94817692f, -0.06980211f, 0.30998084f,
+				0.88786739f, -0.06988241f, 0.45476130f,
+				0.80621493f, -0.06993677f, 0.58747447f,
+				0.70537448f, -0.06995525f, 0.70537448f,
+				0.58747447f, -0.06993637f, 0.80621505f,
+				0.45476136f, -0.06988230f, 0.88786751f,
+				0.30998087f, -0.06980199f, 0.94817698f,
+				0.15679830f, -0.06971515f, 0.98516703f,
+				0.00000000f, -0.06967134f, 0.99756998f,
+				0.95136631f, 0.30806199f, 0.00000000f,
+				0.93948323f, 0.30824158f, 0.14952739f,
+				0.90410745f, 0.30858687f, 0.29557356f,
+				0.84651184f, 0.30891240f, 0.43357921f,
+				0.76860887f, 0.30912822f, 0.56007159f,
+				0.67245591f, 0.30920231f, 0.67245591f,
+				0.56007153f, 0.30912805f, 0.76860899f,
+				0.43357912f, 0.30891296f, 0.84651166f,
+				0.29557377f, 0.30858609f, 0.90410781f,
+				0.14952743f, 0.30823970f, 0.93948382f,
+				-0.00000000f, 0.30806199f, 0.95136631f,
+				0.59454966f, 0.80405891f, 0.00000000f,
+				0.58691627f, 0.80424082f, 0.09341305f,
+				0.56442964f, 0.80459291f, 0.18452507f,
+				0.52813381f, 0.80492258f, 0.27050757f,
+				0.47932491f, 0.80514228f, 0.34927556f,
+				0.41930011f, 0.80521733f, 0.41930011f,
+				0.34927583f, 0.80514193f, 0.47932535f,
+				0.27050710f, 0.80492342f, 0.52813286f,
+				0.18452539f, 0.80459219f, 0.56443053f,
+				0.09341303f, 0.80424047f, 0.58691663f,
+				-0.00000000f, 0.80405891f, 0.59454966f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, -0.00000002f,
+				-0.00000000f, 1.00000000f, 0.00000001f,
+				0.00000037f, 1.00000000f, 0.00000014f,
+				-0.00000038f, 1.00000000f, -0.00000015f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000004f, 1.00000000f, -0.00000003f,
+				-0.00000018f, 1.00000000f, -0.00000035f,
+				-0.00000005f, 0.99999994f, 0.00000002f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				-0.00000000f, 1.00000000f, 0.00000000f,
+				-0.39803210f, 0.91737145f, 0.00000000f,
+				-0.39287356f, 0.91746408f, -0.06252954f,
+				-0.37772986f, 0.91764414f, -0.12348856f,
+				-0.35335946f, 0.91781259f, -0.18098900f,
+				-0.32065529f, 0.91792440f, -0.23365574f,
+				-0.28048533f, 0.91796297f, -0.28048533f,
+				-0.23365568f, 0.91792440f, -0.32065514f,
+				-0.18098891f, 0.91781276f, -0.35335934f,
+				-0.12348875f, 0.91764396f, -0.37773019f,
+				-0.06252949f, 0.91746432f, -0.39287323f,
+				0.00000000f, 0.91737145f, -0.39803210f,
+				-0.61678272f, 0.78713351f, 0.00000000f,
+				-0.60887474f, 0.78732485f, -0.09690809f,
+				-0.58556628f, 0.78769588f, -0.19143508f,
+				-0.54792851f, 0.78804308f, -0.28064632f,
+				-0.49730095f, 0.78827441f, -0.36237431f,
+				-0.43502763f, 0.78835386f, -0.43502763f,
+				-0.36237440f, 0.78827435f, -0.49730113f,
+				-0.28064600f, 0.78804362f, -0.54792786f,
+				-0.19143537f, 0.78769529f, -0.58556694f,
+				-0.09690812f, 0.78732455f, -0.60887510f,
+				0.00000000f, 0.78713351f, -0.61678272f,
+				-0.74976826f, 0.66170049f, 0.00000000f,
+				-0.74024051f, 0.66193908f, -0.11781615f,
+				-0.71206385f, 0.66240013f, -0.23279002f,
+				-0.66643608f, 0.66283208f, -0.34134522f,
+				-0.60494393f, 0.66311979f, -0.44081178f,
+				-0.52921647f, 0.66321933f, -0.52921647f,
+				-0.44081184f, 0.66311955f, -0.60494399f,
+				-0.34134495f, 0.66283280f, -0.66643548f,
+				-0.23279032f, 0.66239935f, -0.71206450f,
+				-0.11781613f, 0.66193885f, -0.74024075f,
+				0.00000000f, 0.66170049f, -0.74976826f,
+				-0.83935553f, 0.54358286f, 0.00000000f,
+				-0.82876492f, 0.54382867f, -0.13190563f,
+				-0.79735982f, 0.54430288f, -0.26067528f,
+				-0.74638999f, 0.54474837f, -0.38229731f,
+				-0.67759502f, 0.54504555f, -0.49375138f,
+				-0.59279579f, 0.54514796f, -0.59279579f,
+				-0.49375141f, 0.54504526f, -0.67759514f,
+				-0.38229698f, 0.54474950f, -0.74638933f,
+				-0.26067549f, 0.54430223f, -0.79736024f,
+				-0.13190556f, 0.54382777f, -0.82876539f,
+				0.00000000f, 0.54358286f, -0.83935553f,
+				-0.90286040f, 0.42993385f, 0.00000000f,
+				-0.89153200f, 0.43015870f, -0.14189546f,
+				-0.85786629f, 0.43059230f, -0.28045624f,
+				-0.80313194f, 0.43100107f, -0.41136029f,
+				-0.72917026f, 0.43127215f, -0.53133333f,
+				-0.63793516f, 0.43136698f, -0.63793516f,
+				-0.53133321f, 0.43127269f, -0.72917008f,
+				-0.41136014f, 0.43100169f, -0.80313170f,
+				-0.28045627f, 0.43059269f, -0.85786611f,
+				-0.14189564f, 0.43015781f, -0.89153230f,
+				0.00000000f, 0.42993385f, -0.90286040f,
+				0.90286052f, 0.42993352f, -0.00000000f,
+				0.89153242f, 0.43015760f, 0.14189552f,
+				0.85786629f, 0.43059236f, 0.28045624f,
+				0.80313188f, 0.43100101f, 0.41136026f,
+				0.72917014f, 0.43127251f, 0.53133327f,
+				0.63793558f, 0.43136591f, 0.63793558f,
+				0.53133327f, 0.43127245f, 0.72917008f,
+				0.41136032f, 0.43100089f, 0.80313200f,
+				0.28045630f, 0.43059239f, 0.85786623f,
+				0.14189564f, 0.43015793f, 0.89153224f,
+				0.00000000f, 0.42993346f, 0.90286052f,
+				0.90429026f, 0.42691806f, -0.00000000f,
+				0.89294571f, 0.42714140f, 0.14212063f,
+				0.85922909f, 0.42757416f, 0.28090176f,
+				0.80441010f, 0.42798156f, 0.41201493f,
+				0.73033202f, 0.42825174f, 0.53217989f,
+				0.63895255f, 0.42834479f, 0.63895255f,
+				0.53217989f, 0.42825180f, 0.73033208f,
+				0.41201496f, 0.42798129f, 0.80441022f,
+				0.28090179f, 0.42757428f, 0.85922897f,
+				0.14212044f, 0.42714158f, 0.89294565f,
+				0.00000000f, 0.42691806f, 0.90429026f,
+				0.90856975f, 0.41773322f, -0.00000000f,
+				0.89717585f, 0.41795388f, 0.14279392f,
+				0.86330783f, 0.41838130f, 0.28223523f,
+				0.80823600f, 0.41878363f, 0.41397455f,
+				0.73381007f, 0.41905072f, 0.53471422f,
+				0.64199662f, 0.41914272f, 0.64199662f,
+				0.53471422f, 0.41905066f, 0.73381001f,
+				0.41397452f, 0.41878343f, 0.80823600f,
+				0.28223526f, 0.41838151f, 0.86330777f,
+				0.14279366f, 0.41795388f, 0.89717585f,
+				0.00000000f, 0.41773322f, 0.90856981f,
+				0.91565990f, 0.40195388f, -0.00000000f,
+				0.90418458f, 0.40216953f, 0.14390936f,
+				0.87006593f, 0.40258732f, 0.28444463f,
+				0.81457525f, 0.40298060f, 0.41722149f,
+				0.73957306f, 0.40324163f, 0.53891361f,
+				0.64704084f, 0.40333158f, 0.64704084f,
+				0.53891361f, 0.40324160f, 0.73957300f,
+				0.41722152f, 0.40298060f, 0.81457531f,
+				0.28444469f, 0.40258750f, 0.87006587f,
+				0.14390932f, 0.40216964f, 0.90418446f,
+				0.00000000f, 0.40195388f, 0.91565990f,
+				0.92546070f, 0.37884355f, -0.00000000f,
+				0.91387308f, 0.37905136f, 0.14545137f,
+				0.87940860f, 0.37945345f, 0.28749895f,
+				0.82333946f, 0.37983215f, 0.42171046f,
+				0.74754065f, 0.38008353f, 0.54471958f,
+				0.65401483f, 0.38016999f, 0.65401483f,
+				0.54471952f, 0.38008350f, 0.74754065f,
+				0.42171052f, 0.37983203f, 0.82333946f,
+				0.28749895f, 0.37945345f, 0.87940860f,
+				0.14545140f, 0.37905133f, 0.91387314f,
+				0.00000000f, 0.37884352f, 0.92546076f,
+				0.93774879f, 0.34731436f, 0.00000000f,
+				0.92602080f, 0.34750980f, 0.14738478f,
+				0.89112353f, 0.34788847f, 0.29132879f,
+				0.83432990f, 0.34824491f, 0.42733964f,
+				0.75753278f, 0.34848174f, 0.55200058f,
+				0.66276079f, 0.34856305f, 0.66276079f,
+				0.55200052f, 0.34848171f, 0.75753278f,
+				0.42733967f, 0.34824488f, 0.83432984f,
+				0.29132882f, 0.34788850f, 0.89112341f,
+				0.14738482f, 0.34750980f, 0.92602086f,
+				0.00000000f, 0.34731436f, 0.93774879f,
+				0.95206839f, 0.30588529f, -0.00000000f,
+				0.94017762f, 0.30606282f, 0.14963792f,
+				0.90477723f, 0.30640665f, 0.29579252f,
+				0.84714013f, 0.30673033f, 0.43390101f,
+				0.76918012f, 0.30694523f, 0.56048775f,
+				0.67295587f, 0.30701905f, 0.67295587f,
+				0.56048775f, 0.30694523f, 0.76918018f,
+				0.43390101f, 0.30673033f, 0.84714013f,
+				0.29579255f, 0.30640674f, 0.90477717f,
+				0.14963797f, 0.30606288f, 0.94017768f,
+				0.00000000f, 0.30588529f, 0.95206839f,
+				0.96755409f, 0.25266385f, -0.00000000f,
+				0.95548815f, 0.25281537f, 0.15207484f,
+				0.91954517f, 0.25310859f, 0.30062059f,
+				0.86099726f, 0.25338471f, 0.44099870f,
+				0.78178006f, 0.25356814f, 0.56966931f,
+				0.68398511f, 0.25363129f, 0.68398517f,
+				0.56966925f, 0.25356814f, 0.78178018f,
+				0.44099870f, 0.25338474f, 0.86099732f,
+				0.30062056f, 0.25310862f, 0.91954517f,
+				0.15207487f, 0.25281525f, 0.95548820f,
+				0.00000000f, 0.25266385f, 0.96755409f,
+				0.98266166f, 0.18540785f, -0.00000000f,
+				0.97042567f, 0.18552248f, 0.15445223f,
+				0.93395483f, 0.18574443f, 0.30533135f,
+				0.87451965f, 0.18595363f, 0.44792461f,
+				0.79407656f, 0.18609245f, 0.57862937f,
+				0.69474876f, 0.18614022f, 0.69474876f,
+				0.57862937f, 0.18609245f, 0.79407656f,
+				0.44792467f, 0.18595368f, 0.87451971f,
+				0.30533132f, 0.18574448f, 0.93395483f,
+				0.15445219f, 0.18552248f, 0.97042572f,
+				0.00000000f, 0.18540785f, 0.98266166f,
+				0.99480653f, 0.10178412f, -0.00000000f,
+				0.98243433f, 0.10184865f, 0.15636347f,
+				0.94554055f, 0.10197352f, 0.30911899f,
+				0.88539290f, 0.10209139f, 0.45349392f,
+				0.80396467f, 0.10216936f, 0.58583462f,
+				0.70340455f, 0.10219638f, 0.70340455f,
+				0.58583462f, 0.10216936f, 0.80396467f,
+				0.45349392f, 0.10209133f, 0.88539290f,
+				0.30911896f, 0.10197353f, 0.94554049f,
+				0.15636343f, 0.10184866f, 0.98243433f,
+				0.00000000f, 0.10178412f, 0.99480653f,
+				1.00000000f, 0.00000000f, -0.00000000f,
+				0.98756981f, 0.00000000f, 0.15718083f,
+				0.95049530f, 0.00000000f, 0.31073883f,
+				0.89004338f, 0.00000000f, 0.45587584f,
+				0.80819386f, 0.00000000f, 0.58891642f,
+				0.70710677f, 0.00000000f, 0.70710677f,
+				0.58891648f, 0.00000000f, 0.80819392f,
+				0.45587584f, 0.00000000f, 0.89004332f,
+				0.31073880f, 0.00000000f, 0.95049530f,
+				0.15718080f, 0.00000000f, 0.98756981f,
+				0.00000000f, 0.00000000f, 1.00000000f,
+				0.00000000f, 0.42993355f, -0.90286052f,
+				0.14189552f, 0.43015760f, -0.89153242f,
+				0.28045624f, 0.43059236f, -0.85786629f,
+				0.41136029f, 0.43100107f, -0.80313194f,
+				0.53133321f, 0.43127251f, -0.72917008f,
+				0.63793558f, 0.43136591f, -0.63793558f,
+				0.72917008f, 0.43127251f, -0.53133327f,
+				0.80313194f, 0.43100089f, -0.41136026f,
+				0.85786623f, 0.43059239f, -0.28045630f,
+				0.89153218f, 0.43015793f, -0.14189562f,
+				0.90286052f, 0.42993352f, -0.00000000f,
+				0.00000000f, 0.42691806f, -0.90429026f,
+				0.14212063f, 0.42714140f, -0.89294571f,
+				0.28090176f, 0.42757416f, -0.85922909f,
+				0.41201493f, 0.42798156f, -0.80441010f,
+				0.53217989f, 0.42825174f, -0.73033202f,
+				0.63895255f, 0.42834479f, -0.63895255f,
+				0.73033208f, 0.42825180f, -0.53217989f,
+				0.80441022f, 0.42798129f, -0.41201496f,
+				0.85922897f, 0.42757428f, -0.28090179f,
+				0.89294565f, 0.42714158f, -0.14212044f,
+				0.90429026f, 0.42691806f, -0.00000000f,
+				0.00000000f, 0.41773325f, -0.90856969f,
+				0.14279392f, 0.41795388f, -0.89717585f,
+				0.28223523f, 0.41838136f, -0.86330783f,
+				0.41397452f, 0.41878363f, -0.80823594f,
+				0.53471416f, 0.41905075f, -0.73381007f,
+				0.64199662f, 0.41914275f, -0.64199662f,
+				0.73381001f, 0.41905072f, -0.53471422f,
+				0.80823600f, 0.41878349f, -0.41397452f,
+				0.86330777f, 0.41838151f, -0.28223526f,
+				0.89717585f, 0.41795391f, -0.14279366f,
+				0.90856975f, 0.41773322f, -0.00000000f,
+				0.00000000f, 0.40195388f, -0.91565990f,
+				0.14390936f, 0.40216953f, -0.90418458f,
+				0.28444463f, 0.40258732f, -0.87006593f,
+				0.41722149f, 0.40298060f, -0.81457525f,
+				0.53891361f, 0.40324163f, -0.73957306f,
+				0.64704084f, 0.40333158f, -0.64704084f,
+				0.73957300f, 0.40324160f, -0.53891361f,
+				0.81457531f, 0.40298060f, -0.41722152f,
+				0.87006587f, 0.40258750f, -0.28444469f,
+				0.90418446f, 0.40216964f, -0.14390932f,
+				0.91565990f, 0.40195388f, -0.00000000f,
+				0.00000000f, 0.37884355f, -0.92546070f,
+				0.14545137f, 0.37905136f, -0.91387308f,
+				0.28749895f, 0.37945345f, -0.87940860f,
+				0.42171046f, 0.37983215f, -0.82333946f,
+				0.54471958f, 0.38008353f, -0.74754065f,
+				0.65401483f, 0.38017002f, -0.65401483f,
+				0.74754071f, 0.38008356f, -0.54471958f,
+				0.82333952f, 0.37983206f, -0.42171052f,
+				0.87940860f, 0.37945351f, -0.28749895f,
+				0.91387314f, 0.37905133f, -0.14545140f,
+				0.92546070f, 0.37884355f, -0.00000000f,
+				0.00000000f, 0.34731436f, -0.93774879f,
+				0.14738478f, 0.34750980f, -0.92602080f,
+				0.29132879f, 0.34788847f, -0.89112353f,
+				0.42733964f, 0.34824491f, -0.83432990f,
+				0.55200058f, 0.34848174f, -0.75753278f,
+				0.66276079f, 0.34856305f, -0.66276079f,
+				0.75753278f, 0.34848171f, -0.55200052f,
+				0.83432984f, 0.34824488f, -0.42733967f,
+				0.89112341f, 0.34788850f, -0.29132882f,
+				0.92602086f, 0.34750980f, -0.14738482f,
+				0.93774879f, 0.34731436f, 0.00000000f,
+				0.00000000f, 0.30588529f, -0.95206845f,
+				0.14963792f, 0.30606282f, -0.94017762f,
+				0.29579252f, 0.30640665f, -0.90477723f,
+				0.43390101f, 0.30673030f, -0.84714013f,
+				0.56048775f, 0.30694523f, -0.76918012f,
+				0.67295587f, 0.30701905f, -0.67295587f,
+				0.76918018f, 0.30694523f, -0.56048775f,
+				0.84714013f, 0.30673033f, -0.43390101f,
+				0.90477717f, 0.30640674f, -0.29579255f,
+				0.94017768f, 0.30606288f, -0.14963797f,
+				0.95206839f, 0.30588529f, 0.00000000f,
+				0.00000000f, 0.25266385f, -0.96755409f,
+				0.15207484f, 0.25281537f, -0.95548815f,
+				0.30062059f, 0.25310859f, -0.91954517f,
+				0.44099876f, 0.25338471f, -0.86099732f,
+				0.56966931f, 0.25356814f, -0.78178006f,
+				0.68398511f, 0.25363129f, -0.68398511f,
+				0.78178018f, 0.25356814f, -0.56966925f,
+				0.86099732f, 0.25338474f, -0.44099864f,
+				0.91954517f, 0.25310862f, -0.30062056f,
+				0.95548820f, 0.25281525f, -0.15207487f,
+				0.96755409f, 0.25266385f, 0.00000000f,
+				0.00000000f, 0.18540785f, -0.98266166f,
+				0.15445223f, 0.18552248f, -0.97042567f,
+				0.30533135f, 0.18574443f, -0.93395483f,
+				0.44792461f, 0.18595363f, -0.87451965f,
+				0.57862937f, 0.18609245f, -0.79407662f,
+				0.69474876f, 0.18614022f, -0.69474876f,
+				0.79407656f, 0.18609245f, -0.57862937f,
+				0.87451965f, 0.18595366f, -0.44792467f,
+				0.93395483f, 0.18574448f, -0.30533132f,
+				0.97042572f, 0.18552248f, -0.15445219f,
+				0.98266166f, 0.18540785f, 0.00000000f,
+				0.00000000f, 0.10178412f, -0.99480653f,
+				0.15636347f, 0.10184865f, -0.98243433f,
+				0.30911899f, 0.10197351f, -0.94554049f,
+				0.45349398f, 0.10209139f, -0.88539296f,
+				0.58583462f, 0.10216935f, -0.80396461f,
+				0.70340455f, 0.10219637f, -0.70340455f,
+				0.80396461f, 0.10216935f, -0.58583462f,
+				0.88539290f, 0.10209133f, -0.45349395f,
+				0.94554055f, 0.10197353f, -0.30911899f,
+				0.98243439f, 0.10184865f, -0.15636343f,
+				0.99480653f, 0.10178412f, -0.00000000f,
+				0.00000000f, 0.00000000f, -1.00000000f,
+				0.15718083f, 0.00000000f, -0.98756981f,
+				0.31073883f, 0.00000000f, -0.95049530f,
+				0.45587587f, 0.00000000f, -0.89004344f,
+				0.58891642f, 0.00000000f, -0.80819386f,
+				0.70710677f, 0.00000000f, -0.70710677f,
+				0.80819386f, 0.00000000f, -0.58891642f,
+				0.89004338f, 0.00000000f, -0.45587584f,
+				0.95049530f, 0.00000000f, -0.31073883f,
+				0.98756981f, 0.00000000f, -0.15718079f,
+				1.00000000f, 0.00000000f, -0.00000000f,
+				0.00000000f, 0.42993346f, 0.90286052f,
+				-0.14189553f, 0.43015754f, 0.89153242f,
+				-0.28045624f, 0.43059230f, 0.85786629f,
+				-0.41136029f, 0.43100101f, 0.80313194f,
+				-0.53133327f, 0.43127251f, 0.72917014f,
+				-0.63793558f, 0.43136591f, 0.63793558f,
+				-0.72917008f, 0.43127245f, 0.53133327f,
+				-0.80313200f, 0.43100089f, 0.41136032f,
+				-0.85786623f, 0.43059239f, 0.28045630f,
+				-0.89153230f, 0.43015793f, 0.14189564f,
+				-0.90286052f, 0.42993352f, 0.00000000f,
+				0.00000000f, 0.42691806f, 0.90429026f,
+				-0.14212061f, 0.42714134f, 0.89294565f,
+				-0.28090173f, 0.42757410f, 0.85922903f,
+				-0.41201493f, 0.42798156f, 0.80441010f,
+				-0.53217989f, 0.42825174f, 0.73033202f,
+				-0.63895255f, 0.42834479f, 0.63895255f,
+				-0.73033208f, 0.42825180f, 0.53217989f,
+				-0.80441022f, 0.42798129f, 0.41201496f,
+				-0.85922897f, 0.42757428f, 0.28090179f,
+				-0.89294565f, 0.42714158f, 0.14212044f,
+				-0.90429026f, 0.42691806f, 0.00000000f,
+				0.00000000f, 0.41773322f, 0.90856981f,
+				-0.14279392f, 0.41795379f, 0.89717585f,
+				-0.28223521f, 0.41838124f, 0.86330777f,
+				-0.41397455f, 0.41878363f, 0.80823600f,
+				-0.53471422f, 0.41905072f, 0.73381007f,
+				-0.64199662f, 0.41914272f, 0.64199662f,
+				-0.73381001f, 0.41905066f, 0.53471422f,
+				-0.80823600f, 0.41878343f, 0.41397452f,
+				-0.86330777f, 0.41838151f, 0.28223526f,
+				-0.89717585f, 0.41795388f, 0.14279366f,
+				-0.90856975f, 0.41773322f, 0.00000000f,
+				0.00000000f, 0.40195388f, 0.91565990f,
+				-0.14390936f, 0.40216953f, 0.90418458f,
+				-0.28444460f, 0.40258726f, 0.87006587f,
+				-0.41722149f, 0.40298060f, 0.81457525f,
+				-0.53891361f, 0.40324163f, 0.73957306f,
+				-0.64704084f, 0.40333158f, 0.64704084f,
+				-0.73957300f, 0.40324160f, 0.53891361f,
+				-0.81457531f, 0.40298060f, 0.41722152f,
+				-0.87006587f, 0.40258750f, 0.28444469f,
+				-0.90418446f, 0.40216964f, 0.14390932f,
+				-0.91565990f, 0.40195388f, 0.00000000f,
+				0.00000000f, 0.37884352f, 0.92546076f,
+				-0.14545135f, 0.37905130f, 0.91387308f,
+				-0.28749895f, 0.37945345f, 0.87940860f,
+				-0.42171046f, 0.37983215f, 0.82333946f,
+				-0.54471958f, 0.38008353f, 0.74754065f,
+				-0.65401483f, 0.38016999f, 0.65401483f,
+				-0.74754065f, 0.38008350f, 0.54471958f,
+				-0.82333946f, 0.37983203f, 0.42171055f,
+				-0.87940860f, 0.37945351f, 0.28749898f,
+				-0.91387314f, 0.37905133f, 0.14545140f,
+				-0.92546070f, 0.37884355f, 0.00000000f,
+				0.00000000f, 0.34731436f, 0.93774879f,
+				-0.14738478f, 0.34750980f, 0.92602080f,
+				-0.29132879f, 0.34788847f, 0.89112353f,
+				-0.42733964f, 0.34824497f, 0.83432990f,
+				-0.55200058f, 0.34848174f, 0.75753278f,
+				-0.66276079f, 0.34856305f, 0.66276079f,
+				-0.75753278f, 0.34848171f, 0.55200052f,
+				-0.83432984f, 0.34824488f, 0.42733967f,
+				-0.89112341f, 0.34788850f, 0.29132882f,
+				-0.92602086f, 0.34750980f, 0.14738482f,
+				-0.93774879f, 0.34731436f, 0.00000000f,
+				0.00000000f, 0.30588529f, 0.95206839f,
+				-0.14963792f, 0.30606285f, 0.94017768f,
+				-0.29579252f, 0.30640665f, 0.90477723f,
+				-0.43390101f, 0.30673033f, 0.84714013f,
+				-0.56048775f, 0.30694523f, 0.76918012f,
+				-0.67295587f, 0.30701905f, 0.67295587f,
+				-0.76918018f, 0.30694523f, 0.56048775f,
+				-0.84714013f, 0.30673033f, 0.43390101f,
+				-0.90477717f, 0.30640674f, 0.29579255f,
+				-0.94017768f, 0.30606288f, 0.14963797f,
+				-0.95206839f, 0.30588529f, 0.00000000f,
+				0.00000000f, 0.25266385f, 0.96755409f,
+				-0.15207484f, 0.25281540f, 0.95548815f,
+				-0.30062059f, 0.25310859f, 0.91954517f,
+				-0.44099870f, 0.25338471f, 0.86099726f,
+				-0.56966931f, 0.25356814f, 0.78178006f,
+				-0.68398511f, 0.25363129f, 0.68398517f,
+				-0.78178018f, 0.25356814f, 0.56966925f,
+				-0.86099732f, 0.25338474f, 0.44099870f,
+				-0.91954517f, 0.25310862f, 0.30062056f,
+				-0.95548820f, 0.25281525f, 0.15207487f,
+				-0.96755409f, 0.25266385f, 0.00000000f,
+				0.00000000f, 0.18540785f, 0.98266166f,
+				-0.15445222f, 0.18552248f, 0.97042567f,
+				-0.30533135f, 0.18574443f, 0.93395483f,
+				-0.44792467f, 0.18595368f, 0.87451977f,
+				-0.57862937f, 0.18609245f, 0.79407656f,
+				-0.69474876f, 0.18614022f, 0.69474876f,
+				-0.79407656f, 0.18609245f, 0.57862937f,
+				-0.87451971f, 0.18595368f, 0.44792467f,
+				-0.93395483f, 0.18574448f, 0.30533132f,
+				-0.97042572f, 0.18552248f, 0.15445219f,
+				-0.98266166f, 0.18540785f, 0.00000000f,
+				0.00000000f, 0.10178412f, 0.99480653f,
+				-0.15636347f, 0.10184866f, 0.98243439f,
+				-0.30911899f, 0.10197352f, 0.94554055f,
+				-0.45349392f, 0.10209139f, 0.88539290f,
+				-0.58583462f, 0.10216936f, 0.80396467f,
+				-0.70340455f, 0.10219638f, 0.70340455f,
+				-0.80396467f, 0.10216936f, 0.58583462f,
+				-0.88539290f, 0.10209133f, 0.45349392f,
+				-0.94554055f, 0.10197353f, 0.30911899f,
+				-0.98243439f, 0.10184865f, 0.15636343f,
+				-0.99480653f, 0.10178412f, 0.00000000f,
+				0.00000000f, 0.00000000f, 1.00000000f,
+				-0.15718085f, 0.00000000f, 0.98756987f,
+				-0.31073883f, 0.00000000f, 0.95049530f,
+				-0.45587584f, 0.00000000f, 0.89004338f,
+				-0.58891648f, 0.00000000f, 0.80819386f,
+				-0.70710677f, 0.00000000f, 0.70710677f,
+				-0.80819386f, 0.00000000f, 0.58891642f,
+				-0.89004338f, 0.00000000f, 0.45587584f,
+				-0.95049530f, 0.00000000f, 0.31073883f,
+				-0.98756981f, 0.00000000f, 0.15718079f,
+				-1.00000000f, 0.00000000f, -0.00000000f,
+				-0.90286052f, 0.42993352f, 0.00000000f,
+				-0.89153242f, 0.43015760f, -0.14189552f,
+				-0.85786629f, 0.43059236f, -0.28045624f,
+				-0.80313194f, 0.43100107f, -0.41136029f,
+				-0.72917014f, 0.43127251f, -0.53133327f,
+				-0.63793558f, 0.43136591f, -0.63793558f,
+				-0.53133327f, 0.43127257f, -0.72917008f,
+				-0.41136026f, 0.43100089f, -0.80313194f,
+				-0.28045627f, 0.43059239f, -0.85786617f,
+				-0.14189562f, 0.43015796f, -0.89153224f,
+				0.00000000f, 0.42993355f, -0.90286052f,
+				-0.90429026f, 0.42691806f, 0.00000000f,
+				-0.89294571f, 0.42714143f, -0.14212063f,
+				-0.85922909f, 0.42757416f, -0.28090176f,
+				-0.80441010f, 0.42798156f, -0.41201493f,
+				-0.73033202f, 0.42825174f, -0.53217989f,
+				-0.63895255f, 0.42834479f, -0.63895255f,
+				-0.53217995f, 0.42825186f, -0.73033214f,
+				-0.41201496f, 0.42798129f, -0.80441022f,
+				-0.28090179f, 0.42757428f, -0.85922897f,
+				-0.14212044f, 0.42714158f, -0.89294565f,
+				0.00000000f, 0.42691806f, -0.90429026f,
+				-0.90856975f, 0.41773322f, 0.00000000f,
+				-0.89717585f, 0.41795388f, -0.14279392f,
+				-0.86330783f, 0.41838130f, -0.28223523f,
+				-0.80823594f, 0.41878363f, -0.41397452f,
+				-0.73381007f, 0.41905075f, -0.53471416f,
+				-0.64199662f, 0.41914275f, -0.64199662f,
+				-0.53471422f, 0.41905072f, -0.73381001f,
+				-0.41397452f, 0.41878349f, -0.80823600f,
+				-0.28223523f, 0.41838151f, -0.86330771f,
+				-0.14279366f, 0.41795391f, -0.89717579f,
+				0.00000000f, 0.41773325f, -0.90856969f,
+				-0.91565990f, 0.40195388f, 0.00000000f,
+				-0.90418458f, 0.40216953f, -0.14390936f,
+				-0.87006593f, 0.40258732f, -0.28444463f,
+				-0.81457525f, 0.40298060f, -0.41722149f,
+				-0.73957306f, 0.40324163f, -0.53891361f,
+				-0.64704084f, 0.40333158f, -0.64704084f,
+				-0.53891361f, 0.40324160f, -0.73957300f,
+				-0.41722152f, 0.40298060f, -0.81457531f,
+				-0.28444469f, 0.40258750f, -0.87006587f,
+				-0.14390932f, 0.40216964f, -0.90418446f,
+				0.00000000f, 0.40195388f, -0.91565990f,
+				-0.92546070f, 0.37884355f, 0.00000000f,
+				-0.91387308f, 0.37905136f, -0.14545137f,
+				-0.87940860f, 0.37945345f, -0.28749895f,
+				-0.82333946f, 0.37983215f, -0.42171046f,
+				-0.74754065f, 0.38008353f, -0.54471952f,
+				-0.65401483f, 0.38017002f, -0.65401483f,
+				-0.54471958f, 0.38008356f, -0.74754071f,
+				-0.42171055f, 0.37983206f, -0.82333952f,
+				-0.28749898f, 0.37945351f, -0.87940860f,
+				-0.14545140f, 0.37905133f, -0.91387314f,
+				0.00000000f, 0.37884355f, -0.92546070f,
+				-0.93774879f, 0.34731436f, 0.00000000f,
+				-0.92602080f, 0.34750980f, -0.14738476f,
+				-0.89112353f, 0.34788847f, -0.29132879f,
+				-0.83432990f, 0.34824491f, -0.42733961f,
+				-0.75753278f, 0.34848174f, -0.55200058f,
+				-0.66276079f, 0.34856305f, -0.66276079f,
+				-0.55200058f, 0.34848171f, -0.75753284f,
+				-0.42733967f, 0.34824488f, -0.83432984f,
+				-0.29132882f, 0.34788850f, -0.89112341f,
+				-0.14738482f, 0.34750980f, -0.92602086f,
+				0.00000000f, 0.34731436f, -0.93774879f,
+				-0.95206839f, 0.30588529f, 0.00000000f,
+				-0.94017762f, 0.30606282f, -0.14963792f,
+				-0.90477723f, 0.30640665f, -0.29579252f,
+				-0.84714019f, 0.30673030f, -0.43390101f,
+				-0.76918012f, 0.30694523f, -0.56048775f,
+				-0.67295587f, 0.30701905f, -0.67295587f,
+				-0.56048775f, 0.30694520f, -0.76918018f,
+				-0.43390101f, 0.30673033f, -0.84714013f,
+				-0.29579252f, 0.30640671f, -0.90477717f,
+				-0.14963795f, 0.30606285f, -0.94017762f,
+				0.00000000f, 0.30588529f, -0.95206845f,
+				-0.96755409f, 0.25266385f, 0.00000000f,
+				-0.95548815f, 0.25281537f, -0.15207484f,
+				-0.91954517f, 0.25310859f, -0.30062059f,
+				-0.86099732f, 0.25338471f, -0.44099876f,
+				-0.78178006f, 0.25356814f, -0.56966931f,
+				-0.68398511f, 0.25363129f, -0.68398511f,
+				-0.56966925f, 0.25356814f, -0.78178018f,
+				-0.44099870f, 0.25338474f, -0.86099732f,
+				-0.30062056f, 0.25310862f, -0.91954517f,
+				-0.15207487f, 0.25281525f, -0.95548820f,
+				0.00000000f, 0.25266385f, -0.96755409f,
+				-0.98266166f, 0.18540785f, 0.00000000f,
+				-0.97042567f, 0.18552248f, -0.15445223f,
+				-0.93395483f, 0.18574443f, -0.30533135f,
+				-0.87451965f, 0.18595363f, -0.44792461f,
+				-0.79407662f, 0.18609245f, -0.57862937f,
+				-0.69474876f, 0.18614022f, -0.69474876f,
+				-0.57862937f, 0.18609245f, -0.79407662f,
+				-0.44792467f, 0.18595366f, -0.87451965f,
+				-0.30533135f, 0.18574448f, -0.93395483f,
+				-0.15445219f, 0.18552245f, -0.97042567f,
+				0.00000000f, 0.18540785f, -0.98266166f,
+				-0.99480653f, 0.10178412f, 0.00000000f,
+				-0.98243433f, 0.10184865f, -0.15636347f,
+				-0.94554049f, 0.10197351f, -0.30911899f,
+				-0.88539296f, 0.10209139f, -0.45349398f,
+				-0.80396461f, 0.10216935f, -0.58583462f,
+				-0.70340455f, 0.10219637f, -0.70340455f,
+				-0.58583462f, 0.10216935f, -0.80396461f,
+				-0.45349395f, 0.10209133f, -0.88539290f,
+				-0.30911899f, 0.10197353f, -0.94554055f,
+				-0.15636343f, 0.10184865f, -0.98243439f,
+				0.00000000f, 0.10178412f, -0.99480653f,
+				-1.00000000f, 0.00000000f, -0.00000000f,
+				-0.98756981f, -0.00000000f, -0.15718083f,
+				-0.95049530f, -0.00000000f, -0.31073886f,
+				-0.89004344f, -0.00000000f, -0.45587587f,
+				-0.80819386f, -0.00000000f, -0.58891642f,
+				-0.70710677f, -0.00000000f, -0.70710677f,
+				-0.58891648f, -0.00000000f, -0.80819392f,
+				-0.45587584f, -0.00000000f, -0.89004338f,
+				-0.31073880f, -0.00000000f, -0.95049530f,
+				-0.15718079f, -0.00000000f, -0.98756987f,
+				0.00000000f, 0.00000000f, -1.00000000f,
+				1.00000000f, 0.00000000f, -0.00000000f,
+				0.98756981f, 0.00000000f, 0.15718083f,
+				0.95049524f, 0.00000000f, 0.31073883f,
+				0.89004338f, 0.00000000f, 0.45587587f,
+				0.80819386f, 0.00000000f, 0.58891642f,
+				0.70710677f, 0.00000000f, 0.70710677f,
+				0.58891648f, 0.00000000f, 0.80819392f,
+				0.45587584f, 0.00000000f, 0.89004332f,
+				0.31073880f, 0.00000000f, 0.95049530f,
+				0.15718080f, 0.00000000f, 0.98756981f,
+				0.00000000f, 0.00000000f, 1.00000000f,
+				0.97627187f, -0.21654844f, 0.00000000f,
+				0.96410769f, -0.21668050f, 0.15344663f,
+				0.92785990f, -0.21693638f, 0.30333877f,
+				0.86879992f, -0.21717757f, 0.44499502f,
+				0.78887528f, -0.21733762f, 0.57483923f,
+				0.69019580f, -0.21739276f, 0.69019580f,
+				0.57483923f, -0.21733767f, 0.78887522f,
+				0.44499508f, -0.21717753f, 0.86879987f,
+				0.30333874f, -0.21693641f, 0.92786002f,
+				0.15344663f, -0.21668044f, 0.96410781f,
+				0.00000000f, -0.21654841f, 0.97627187f,
+				0.91505456f, -0.40332985f, -0.00000003f,
+				0.90358627f, -0.40354598f, 0.14381410f,
+				0.86948907f, -0.40396458f, 0.28425604f,
+				0.81403410f, -0.40435863f, 0.41694435f,
+				0.73908108f, -0.40462026f, 0.53855520f,
+				0.64661026f, -0.40471029f, 0.64661026f,
+				0.53855515f, -0.40462023f, 0.73908103f,
+				0.41694435f, -0.40435848f, 0.81403404f,
+				0.28425604f, -0.40396464f, 0.86948895f,
+				0.14381401f, -0.40354609f, 0.90358627f,
+				0.00000000f, -0.40332985f, 0.91505468f,
+				0.83741641f, -0.54656547f, -0.00000004f,
+				0.82684904f, -0.54681069f, 0.13160066f,
+				0.79551321f, -0.54728562f, 0.26007158f,
+				0.74465787f, -0.54773253f, 0.38141018f,
+				0.67602134f, -0.54802895f, 0.49260470f,
+				0.59141880f, -0.54813099f, 0.59141880f,
+				0.49260470f, -0.54802889f, 0.67602134f,
+				0.38141015f, -0.54773235f, 0.74465799f,
+				0.26007164f, -0.54728562f, 0.79551315f,
+				0.13160071f, -0.54681093f, 0.82684886f,
+				0.00000000f, -0.54656547f, 0.83741641f,
+				0.76153940f, -0.64811856f, -0.00000005f,
+				0.75187135f, -0.64835888f, 0.11966719f,
+				0.72326809f, -0.64882445f, 0.23645295f,
+				0.67693549f, -0.64926237f, 0.34672299f,
+				0.61448330f, -0.64955258f, 0.44776294f,
+				0.53756469f, -0.64965260f, 0.53756469f,
+				0.44776297f, -0.64955264f, 0.61448330f,
+				0.34672314f, -0.64926219f, 0.67693549f,
+				0.23645304f, -0.64882457f, 0.72326809f,
+				0.11966727f, -0.64835912f, 0.75187117f,
+				0.00000000f, -0.64811856f, 0.76153940f,
+				0.69810015f, -0.71600008f, 0.00000000f,
+				0.68919653f, -0.71622342f, 0.10969204f,
+				0.66290152f, -0.71665543f, 0.21671773f,
+				0.62036926f, -0.71706140f, 0.31775001f,
+				0.56309497f, -0.71733099f, 0.41031727f,
+				0.49259701f, -0.71742338f, 0.49259701f,
+				0.41031730f, -0.71733087f, 0.56309515f,
+				0.31775004f, -0.71706140f, 0.62036926f,
+				0.21671782f, -0.71665543f, 0.66290152f,
+				0.10969196f, -0.71622348f, 0.68919641f,
+				0.00000000f, -0.71600014f, 0.69810015f,
+				0.65312678f, -0.75724858f, 0.00000000f,
+				0.64477170f, -0.75745511f, 0.10262139f,
+				0.62012511f, -0.75785500f, 0.20273319f,
+				0.58029598f, -0.75823092f, 0.29722479f,
+				0.52669704f, -0.75848007f, 0.38379461f,
+				0.46074849f, -0.75856555f, 0.46074849f,
+				0.38379470f, -0.75847995f, 0.52669698f,
+				0.29722482f, -0.75823075f, 0.58029616f,
+				0.20273310f, -0.75785506f, 0.62012488f,
+				0.10262127f, -0.75745541f, 0.64477140f,
+				0.00000000f, -0.75724858f, 0.65312666f,
+				0.63308728f, -0.77408046f, 0.00000000f,
+				0.62497813f, -0.77427888f, 0.09947111f,
+				0.60106885f, -0.77466297f, 0.19650327f,
+				0.56244695f, -0.77502376f, 0.28808260f,
+				0.51048630f, -0.77526313f, 0.37198231f,
+				0.44656453f, -0.77534527f, 0.44656453f,
+				0.37198231f, -0.77526301f, 0.51048636f,
+				0.28808266f, -0.77502370f, 0.56244695f,
+				0.19650330f, -0.77466315f, 0.60106856f,
+				0.09947103f, -0.77427906f, 0.62497783f,
+				0.00000000f, -0.77408046f, 0.63308716f,
+				0.65312707f, -0.75724828f, 0.00000000f,
+				0.64477211f, -0.75745481f, 0.10262145f,
+				0.62012541f, -0.75785458f, 0.20273332f,
+				0.58029628f, -0.75823045f, 0.29722497f,
+				0.52669716f, -0.75847977f, 0.38379481f,
+				0.46074894f, -0.75856501f, 0.46074894f,
+				0.38379484f, -0.75847983f, 0.52669722f,
+				0.29722500f, -0.75823039f, 0.58029646f,
+				0.20273322f, -0.75785488f, 0.62012511f,
+				0.10262135f, -0.75745499f, 0.64477187f,
+				0.00000000f, -0.75724828f, 0.65312707f,
+				0.76153988f, -0.64811796f, 0.00000000f,
+				0.75187159f, -0.64835846f, 0.11966733f,
+				0.72326809f, -0.64882451f, 0.23645300f,
+				0.67693573f, -0.64926189f, 0.34672317f,
+				0.61448365f, -0.64955217f, 0.44776317f,
+				0.53756529f, -0.64965147f, 0.53756529f,
+				0.44776306f, -0.64955246f, 0.61448342f,
+				0.34672317f, -0.64926195f, 0.67693573f,
+				0.23645295f, -0.64882445f, 0.72326815f,
+				0.11966728f, -0.64835876f, 0.75187135f,
+				0.00000000f, -0.64811832f, 0.76153970f,
+				1.00000000f, 0.00000000f, -0.00000000f,
+				0.98756987f, 0.00000000f, 0.15718076f,
+				0.95049542f, 0.00000000f, 0.31073883f,
+				0.89004332f, 0.00000000f, 0.45587590f,
+				0.80819392f, 0.00000000f, 0.58891642f,
+				0.70710683f, 0.00000000f, 0.70710683f,
+				0.58891648f, 0.00000000f, 0.80819392f,
+				0.45587584f, 0.00000000f, 0.89004332f,
+				0.31073889f, 0.00000000f, 0.95049536f,
+				0.15718086f, 0.00000000f, 0.98756987f,
+				0.00000000f, 0.00000000f, 1.00000000f,
+				0.00000000f, 0.00000000f, -1.00000000f,
+				0.15718083f, 0.00000000f, -0.98756981f,
+				0.31073886f, 0.00000000f, -0.95049542f,
+				0.45587587f, 0.00000000f, -0.89004338f,
+				0.58891648f, 0.00000000f, -0.80819392f,
+				0.70710677f, 0.00000000f, -0.70710677f,
+				0.80819392f, 0.00000000f, -0.58891642f,
+				0.89004332f, 0.00000000f, -0.45587584f,
+				0.95049536f, 0.00000000f, -0.31073883f,
+				0.98756981f, 0.00000000f, -0.15718080f,
+				1.00000000f, 0.00000000f, -0.00000000f,
+				0.00000000f, -0.21654844f, -0.97627187f,
+				0.15344663f, -0.21668050f, -0.96410769f,
+				0.30333880f, -0.21693645f, -0.92786008f,
+				0.44499505f, -0.21717761f, -0.86879992f,
+				0.57483923f, -0.21733770f, -0.78887528f,
+				0.69019580f, -0.21739276f, -0.69019580f,
+				0.78887522f, -0.21733771f, -0.57483923f,
+				0.86879992f, -0.21717757f, -0.44499508f,
+				0.92785990f, -0.21693642f, -0.30333874f,
+				0.96410769f, -0.21668047f, -0.15344661f,
+				0.97627187f, -0.21654844f, 0.00000000f,
+				0.00000000f, -0.40332985f, -0.91505456f,
+				0.14381412f, -0.40354598f, -0.90358627f,
+				0.28425601f, -0.40396458f, -0.86948901f,
+				0.41694438f, -0.40435869f, -0.81403404f,
+				0.53855515f, -0.40462032f, -0.73908097f,
+				0.64661026f, -0.40471029f, -0.64661026f,
+				0.73908108f, -0.40462029f, -0.53855515f,
+				0.81403416f, -0.40435857f, -0.41694435f,
+				0.86948901f, -0.40396467f, -0.28425604f,
+				0.90358621f, -0.40354609f, -0.14381403f,
+				0.91505456f, -0.40332985f, 0.00000000f,
+				0.00000000f, -0.54656547f, -0.83741641f,
+				0.13160068f, -0.54681069f, -0.82684904f,
+				0.26007161f, -0.54728562f, -0.79551315f,
+				0.38141018f, -0.54773253f, -0.74465787f,
+				0.49260476f, -0.54802901f, -0.67602134f,
+				0.59141880f, -0.54813099f, -0.59141880f,
+				0.67602134f, -0.54802889f, -0.49260470f,
+				0.74465799f, -0.54773235f, -0.38141015f,
+				0.79551315f, -0.54728562f, -0.26007164f,
+				0.82684886f, -0.54681093f, -0.13160068f,
+				0.83741641f, -0.54656547f, 0.00000000f,
+				0.00000000f, -0.64811856f, -0.76153940f,
+				0.11966727f, -0.64835888f, -0.75187135f,
+				0.23645303f, -0.64882445f, -0.72326821f,
+				0.34672299f, -0.64926225f, -0.67693543f,
+				0.44776300f, -0.64955264f, -0.61448330f,
+				0.53756469f, -0.64965254f, -0.53756469f,
+				0.61448336f, -0.64955264f, -0.44776294f,
+				0.67693555f, -0.64926219f, -0.34672308f,
+				0.72326809f, -0.64882457f, -0.23645304f,
+				0.75187117f, -0.64835912f, -0.11966727f,
+				0.76153940f, -0.64811856f, 0.00000000f,
+				0.00000000f, -0.71600008f, -0.69810015f,
+				0.10969204f, -0.71622342f, -0.68919653f,
+				0.21671776f, -0.71665537f, -0.66290158f,
+				0.31775004f, -0.71706134f, -0.62036926f,
+				0.41031733f, -0.71733087f, -0.56309509f,
+				0.49259701f, -0.71742338f, -0.49259701f,
+				0.56309521f, -0.71733081f, -0.41031736f,
+				0.62036926f, -0.71706134f, -0.31775004f,
+				0.66290152f, -0.71665537f, -0.21671782f,
+				0.68919653f, -0.71622342f, -0.10969198f,
+				0.69810015f, -0.71600008f, 0.00000000f,
+				0.00000000f, -0.75724858f, -0.65312678f,
+				0.10262139f, -0.75745511f, -0.64477170f,
+				0.20273322f, -0.75785494f, -0.62012511f,
+				0.29722479f, -0.75823075f, -0.58029604f,
+				0.38379470f, -0.75847995f, -0.52669716f,
+				0.46074849f, -0.75856555f, -0.46074849f,
+				0.52669710f, -0.75847995f, -0.38379475f,
+				0.58029622f, -0.75823069f, -0.29722485f,
+				0.62012500f, -0.75785506f, -0.20273314f,
+				0.64477146f, -0.75745541f, -0.10262129f,
+				0.65312678f, -0.75724858f, 0.00000000f,
+				0.00000000f, -0.77408046f, -0.63308728f,
+				0.09947111f, -0.77427888f, -0.62497813f,
+				0.19650328f, -0.77466291f, -0.60106891f,
+				0.28808263f, -0.77502370f, -0.56244701f,
+				0.37198243f, -0.77526301f, -0.51048648f,
+				0.44656453f, -0.77534527f, -0.44656453f,
+				0.51048642f, -0.77526295f, -0.37198234f,
+				0.56244701f, -0.77502364f, -0.28808269f,
+				0.60106862f, -0.77466303f, -0.19650333f,
+				0.62497795f, -0.77427900f, -0.09947104f,
+				0.63308728f, -0.77408046f, 0.00000000f,
+				0.00000000f, -0.75724828f, -0.65312707f,
+				0.10262145f, -0.75745481f, -0.64477211f,
+				0.20273340f, -0.75785440f, -0.62012565f,
+				0.29722497f, -0.75823045f, -0.58029628f,
+				0.38379493f, -0.75847960f, -0.52669734f,
+				0.46074894f, -0.75856501f, -0.46074894f,
+				0.52669722f, -0.75847983f, -0.38379484f,
+				0.58029646f, -0.75823039f, -0.29722500f,
+				0.62012535f, -0.75785476f, -0.20273331f,
+				0.64477187f, -0.75745499f, -0.10262135f,
+				0.65312707f, -0.75724828f, 0.00000000f,
+				0.00000000f, -0.64811796f, -0.76153988f,
+				0.11966733f, -0.64835846f, -0.75187159f,
+				0.23645303f, -0.64882457f, -0.72326809f,
+				0.34672326f, -0.64926159f, -0.67693597f,
+				0.44776332f, -0.64955187f, -0.61448377f,
+				0.53756529f, -0.64965147f, -0.53756529f,
+				0.61448365f, -0.64955217f, -0.44776317f,
+				0.67693591f, -0.64926165f, -0.34672329f,
+				0.72326815f, -0.64882445f, -0.23645295f,
+				0.75187165f, -0.64835852f, -0.11966733f,
+				0.76153988f, -0.64811796f, 0.00000000f,
+				0.00000000f, 0.00000000f, -1.00000000f,
+				0.15718076f, 0.00000000f, -0.98756987f,
+				0.31073883f, 0.00000000f, -0.95049542f,
+				0.45587590f, 0.00000000f, -0.89004332f,
+				0.58891642f, 0.00000000f, -0.80819392f,
+				0.70710683f, 0.00000000f, -0.70710683f,
+				0.80819392f, 0.00000000f, -0.58891648f,
+				0.89004332f, 0.00000000f, -0.45587584f,
+				0.95049536f, 0.00000000f, -0.31073889f,
+				0.98756987f, 0.00000000f, -0.15718086f,
+				1.00000000f, 0.00000000f, -0.00000000f,
+				0.00000000f, 0.00000000f, 1.00000000f,
+				-0.15718085f, 0.00000000f, 0.98756987f,
+				-0.31073883f, 0.00000000f, 0.95049524f,
+				-0.45587587f, 0.00000000f, 0.89004338f,
+				-0.58891648f, 0.00000000f, 0.80819386f,
+				-0.70710677f, 0.00000000f, 0.70710677f,
+				-0.80819392f, 0.00000000f, 0.58891648f,
+				-0.89004332f, 0.00000000f, 0.45587584f,
+				-0.95049536f, 0.00000000f, 0.31073883f,
+				-0.98756981f, 0.00000000f, 0.15718080f,
+				-1.00000000f, 0.00000000f, -0.00000000f,
+				0.00000000f, -0.21654841f, 0.97627187f,
+				-0.15344664f, -0.21668047f, 0.96410775f,
+				-0.30333877f, -0.21693638f, 0.92785990f,
+				-0.44499502f, -0.21717757f, 0.86879992f,
+				-0.57483923f, -0.21733765f, 0.78887528f,
+				-0.69019574f, -0.21739271f, 0.69019574f,
+				-0.78887522f, -0.21733767f, 0.57483923f,
+				-0.86879987f, -0.21717753f, 0.44499508f,
+				-0.92785990f, -0.21693642f, 0.30333874f,
+				-0.96410769f, -0.21668047f, 0.15344661f,
+				-0.97627187f, -0.21654844f, -0.00000000f,
+				0.00000000f, -0.40332985f, 0.91505468f,
+				-0.14381412f, -0.40354595f, 0.90358633f,
+				-0.28425604f, -0.40396458f, 0.86948907f,
+				-0.41694441f, -0.40435863f, 0.81403410f,
+				-0.53855515f, -0.40462026f, 0.73908097f,
+				-0.64661026f, -0.40471026f, 0.64661026f,
+				-0.73908103f, -0.40462023f, 0.53855515f,
+				-0.81403416f, -0.40435854f, 0.41694435f,
+				-0.86948901f, -0.40396467f, 0.28425601f,
+				-0.90358621f, -0.40354609f, 0.14381400f,
+				-0.91505456f, -0.40332985f, -0.00000003f,
+				0.00000000f, -0.54656547f, 0.83741641f,
+				-0.13160071f, -0.54681069f, 0.82684904f,
+				-0.26007161f, -0.54728562f, 0.79551321f,
+				-0.38141018f, -0.54773253f, 0.74465787f,
+				-0.49260470f, -0.54802895f, 0.67602134f,
+				-0.59141886f, -0.54813093f, 0.59141880f,
+				-0.67602134f, -0.54802889f, 0.49260467f,
+				-0.74465799f, -0.54773235f, 0.38141012f,
+				-0.79551315f, -0.54728562f, 0.26007164f,
+				-0.82684886f, -0.54681093f, 0.13160068f,
+				-0.83741641f, -0.54656547f, -0.00000004f,
+				0.00000000f, -0.64811856f, 0.76153940f,
+				-0.11966725f, -0.64835888f, 0.75187135f,
+				-0.23645300f, -0.64882445f, 0.72326809f,
+				-0.34672302f, -0.64926237f, 0.67693537f,
+				-0.44776300f, -0.64955264f, 0.61448330f,
+				-0.53756469f, -0.64965260f, 0.53756469f,
+				-0.61448336f, -0.64955264f, 0.44776294f,
+				-0.67693555f, -0.64926219f, 0.34672308f,
+				-0.72326815f, -0.64882451f, 0.23645297f,
+				-0.75187117f, -0.64835912f, 0.11966722f,
+				-0.76153940f, -0.64811856f, -0.00000005f,
+				0.00000000f, -0.71600014f, 0.69810015f,
+				-0.10969204f, -0.71622342f, 0.68919653f,
+				-0.21671773f, -0.71665543f, 0.66290152f,
+				-0.31775001f, -0.71706140f, 0.62036926f,
+				-0.41031730f, -0.71733093f, 0.56309503f,
+				-0.49259698f, -0.71742344f, 0.49259698f,
+				-0.56309515f, -0.71733087f, 0.41031730f,
+				-0.62036926f, -0.71706140f, 0.31775004f,
+				-0.66290152f, -0.71665537f, 0.21671782f,
+				-0.68919653f, -0.71622342f, 0.10969198f,
+				-0.69810015f, -0.71600008f, -0.00000000f,
+				0.00000000f, -0.75724858f, 0.65312666f,
+				-0.10262137f, -0.75745529f, 0.64477158f,
+				-0.20273319f, -0.75785500f, 0.62012511f,
+				-0.29722479f, -0.75823092f, 0.58029598f,
+				-0.38379467f, -0.75848001f, 0.52669710f,
+				-0.46074843f, -0.75856560f, 0.46074843f,
+				-0.52669698f, -0.75847995f, 0.38379470f,
+				-0.58029616f, -0.75823075f, 0.29722482f,
+				-0.62012500f, -0.75785506f, 0.20273314f,
+				-0.64477146f, -0.75745541f, 0.10262129f,
+				-0.65312678f, -0.75724858f, -0.00000000f,
+				0.00000000f, -0.77408046f, 0.63308716f,
+				-0.09947111f, -0.77427888f, 0.62497813f,
+				-0.19650327f, -0.77466297f, 0.60106885f,
+				-0.28808260f, -0.77502376f, 0.56244695f,
+				-0.37198234f, -0.77526301f, 0.51048636f,
+				-0.44656447f, -0.77534533f, 0.44656447f,
+				-0.51048636f, -0.77526301f, 0.37198231f,
+				-0.56244695f, -0.77502370f, 0.28808266f,
+				-0.60106862f, -0.77466303f, 0.19650333f,
+				-0.62497795f, -0.77427900f, 0.09947104f,
+				-0.63308728f, -0.77408046f, -0.00000000f,
+				0.00000000f, -0.75724828f, 0.65312707f,
+				-0.10262142f, -0.75745505f, 0.64477187f,
+				-0.20273332f, -0.75785458f, 0.62012541f,
+				-0.29722497f, -0.75823045f, 0.58029628f,
+				-0.38379493f, -0.75847960f, 0.52669734f,
+				-0.46074894f, -0.75856501f, 0.46074894f,
+				-0.52669722f, -0.75847983f, 0.38379484f,
+				-0.58029646f, -0.75823039f, 0.29722500f,
+				-0.62012535f, -0.75785476f, 0.20273331f,
+				-0.64477187f, -0.75745499f, 0.10262135f,
+				-0.65312707f, -0.75724828f, -0.00000000f,
+				0.00000000f, -0.64811832f, 0.76153970f,
+				-0.11966730f, -0.64835882f, 0.75187141f,
+				-0.23645303f, -0.64882457f, 0.72326809f,
+				-0.34672317f, -0.64926189f, 0.67693573f,
+				-0.44776317f, -0.64955217f, 0.61448365f,
+				-0.53756529f, -0.64965147f, 0.53756529f,
+				-0.61448342f, -0.64955246f, 0.44776306f,
+				-0.67693573f, -0.64926195f, 0.34672317f,
+				-0.72326815f, -0.64882445f, 0.23645295f,
+				-0.75187165f, -0.64835852f, 0.11966733f,
+				-0.76153988f, -0.64811796f, -0.00000000f,
+				0.00000000f, 0.00000000f, 1.00000000f,
+				-0.15718076f, 0.00000000f, 0.98756987f,
+				-0.31073883f, 0.00000000f, 0.95049542f,
+				-0.45587590f, 0.00000000f, 0.89004332f,
+				-0.58891642f, 0.00000000f, 0.80819392f,
+				-0.70710683f, 0.00000000f, 0.70710683f,
+				-0.80819392f, 0.00000000f, 0.58891648f,
+				-0.89004332f, 0.00000000f, 0.45587584f,
+				-0.95049536f, 0.00000000f, 0.31073889f,
+				-0.98756987f, 0.00000000f, 0.15718086f,
+				-1.00000000f, 0.00000000f, -0.00000000f,
+				-1.00000000f, 0.00000000f, -0.00000000f,
+				-0.98756981f, -0.00000000f, -0.15718083f,
+				-0.95049542f, -0.00000000f, -0.31073886f,
+				-0.89004338f, -0.00000000f, -0.45587587f,
+				-0.80819392f, -0.00000000f, -0.58891648f,
+				-0.70710677f, -0.00000000f, -0.70710677f,
+				-0.58891642f, -0.00000000f, -0.80819392f,
+				-0.45587584f, -0.00000000f, -0.89004332f,
+				-0.31073883f, -0.00000000f, -0.95049536f,
+				-0.15718080f, -0.00000000f, -0.98756981f,
+				0.00000000f, 0.00000000f, -1.00000000f,
+				-0.97627187f, -0.21654844f, -0.00000000f,
+				-0.96410769f, -0.21668050f, -0.15344663f,
+				-0.92786008f, -0.21693645f, -0.30333880f,
+				-0.86879992f, -0.21717761f, -0.44499505f,
+				-0.78887528f, -0.21733770f, -0.57483923f,
+				-0.69019580f, -0.21739276f, -0.69019580f,
+				-0.57483923f, -0.21733771f, -0.78887522f,
+				-0.44499508f, -0.21717757f, -0.86879992f,
+				-0.30333874f, -0.21693642f, -0.92785990f,
+				-0.15344661f, -0.21668047f, -0.96410769f,
+				0.00000000f, -0.21654844f, -0.97627187f,
+				-0.91505456f, -0.40332985f, -0.00000000f,
+				-0.90358627f, -0.40354595f, -0.14381412f,
+				-0.86948901f, -0.40396458f, -0.28425601f,
+				-0.81403404f, -0.40435869f, -0.41694438f,
+				-0.73908097f, -0.40462032f, -0.53855515f,
+				-0.64661026f, -0.40471029f, -0.64661026f,
+				-0.53855515f, -0.40462029f, -0.73908108f,
+				-0.41694435f, -0.40435857f, -0.81403416f,
+				-0.28425604f, -0.40396467f, -0.86948901f,
+				-0.14381403f, -0.40354609f, -0.90358621f,
+				0.00000000f, -0.40332985f, -0.91505456f,
+				-0.83741641f, -0.54656547f, -0.00000000f,
+				-0.82684904f, -0.54681069f, -0.13160068f,
+				-0.79551315f, -0.54728562f, -0.26007161f,
+				-0.74465787f, -0.54773253f, -0.38141018f,
+				-0.67602134f, -0.54802901f, -0.49260476f,
+				-0.59141880f, -0.54813099f, -0.59141880f,
+				-0.49260470f, -0.54802889f, -0.67602134f,
+				-0.38141015f, -0.54773235f, -0.74465799f,
+				-0.26007164f, -0.54728562f, -0.79551315f,
+				-0.13160068f, -0.54681093f, -0.82684886f,
+				0.00000000f, -0.54656547f, -0.83741641f,
+				-0.76153940f, -0.64811856f, -0.00000000f,
+				-0.75187135f, -0.64835888f, -0.11966727f,
+				-0.72326821f, -0.64882445f, -0.23645303f,
+				-0.67693543f, -0.64926225f, -0.34672299f,
+				-0.61448330f, -0.64955264f, -0.44776300f,
+				-0.53756469f, -0.64965254f, -0.53756469f,
+				-0.44776294f, -0.64955264f, -0.61448336f,
+				-0.34672308f, -0.64926219f, -0.67693555f,
+				-0.23645304f, -0.64882457f, -0.72326809f,
+				-0.11966727f, -0.64835912f, -0.75187117f,
+				0.00000000f, -0.64811856f, -0.76153940f,
+				-0.69810015f, -0.71600008f, -0.00000000f,
+				-0.68919653f, -0.71622342f, -0.10969204f,
+				-0.66290158f, -0.71665537f, -0.21671776f,
+				-0.62036926f, -0.71706134f, -0.31775004f,
+				-0.56309509f, -0.71733087f, -0.41031733f,
+				-0.49259701f, -0.71742338f, -0.49259701f,
+				-0.41031736f, -0.71733081f, -0.56309521f,
+				-0.31775004f, -0.71706134f, -0.62036926f,
+				-0.21671782f, -0.71665537f, -0.66290152f,
+				-0.10969198f, -0.71622342f, -0.68919653f,
+				0.00000000f, -0.71600008f, -0.69810015f,
+				-0.65312678f, -0.75724858f, -0.00000000f,
+				-0.64477170f, -0.75745511f, -0.10262139f,
+				-0.62012511f, -0.75785494f, -0.20273322f,
+				-0.58029604f, -0.75823075f, -0.29722479f,
+				-0.52669716f, -0.75847995f, -0.38379470f,
+				-0.46074849f, -0.75856555f, -0.46074849f,
+				-0.38379475f, -0.75847995f, -0.52669710f,
+				-0.29722485f, -0.75823069f, -0.58029622f,
+				-0.20273314f, -0.75785506f, -0.62012500f,
+				-0.10262129f, -0.75745541f, -0.64477146f,
+				0.00000000f, -0.75724858f, -0.65312678f,
+				-0.63308728f, -0.77408046f, -0.00000000f,
+				-0.62497813f, -0.77427888f, -0.09947111f,
+				-0.60106891f, -0.77466291f, -0.19650328f,
+				-0.56244701f, -0.77502370f, -0.28808263f,
+				-0.51048648f, -0.77526301f, -0.37198243f,
+				-0.44656453f, -0.77534527f, -0.44656453f,
+				-0.37198234f, -0.77526295f, -0.51048642f,
+				-0.28808269f, -0.77502364f, -0.56244701f,
+				-0.19650333f, -0.77466303f, -0.60106862f,
+				-0.09947104f, -0.77427900f, -0.62497795f,
+				0.00000000f, -0.77408046f, -0.63308728f,
+				-0.65312707f, -0.75724828f, -0.00000000f,
+				-0.64477211f, -0.75745481f, -0.10262145f,
+				-0.62012565f, -0.75785440f, -0.20273340f,
+				-0.58029628f, -0.75823045f, -0.29722497f,
+				-0.52669734f, -0.75847960f, -0.38379493f,
+				-0.46074894f, -0.75856501f, -0.46074894f,
+				-0.38379484f, -0.75847983f, -0.52669722f,
+				-0.29722500f, -0.75823039f, -0.58029646f,
+				-0.20273331f, -0.75785476f, -0.62012535f,
+				-0.10262135f, -0.75745499f, -0.64477187f,
+				0.00000000f, -0.75724828f, -0.65312707f,
+				-0.76153988f, -0.64811796f, -0.00000000f,
+				-0.75187159f, -0.64835846f, -0.11966733f,
+				-0.72326809f, -0.64882457f, -0.23645303f,
+				-0.67693597f, -0.64926159f, -0.34672326f,
+				-0.61448377f, -0.64955187f, -0.44776332f,
+				-0.53756529f, -0.64965147f, -0.53756529f,
+				-0.44776317f, -0.64955217f, -0.61448365f,
+				-0.34672329f, -0.64926165f, -0.67693591f,
+				-0.23645295f, -0.64882445f, -0.72326815f,
+				-0.11966733f, -0.64835852f, -0.75187165f,
+				0.00000000f, -0.64811796f, -0.76153988f,
+				-1.00000000f, 0.00000000f, -0.00000000f,
+				-0.98756987f, -0.00000000f, -0.15718076f,
+				-0.95049536f, -0.00000000f, -0.31073886f,
+				-0.89004332f, -0.00000000f, -0.45587590f,
+				-0.80819392f, -0.00000000f, -0.58891642f,
+				-0.70710683f, -0.00000000f, -0.70710683f,
+				-0.58891648f, -0.00000000f, -0.80819392f,
+				-0.45587584f, -0.00000000f, -0.89004332f,
+				-0.31073889f, -0.00000000f, -0.95049536f,
+				-0.15718086f, -0.00000000f, -0.98756987f,
+				0.00000000f, 0.00000000f, -1.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.10903012f, 0.99403834f, -0.00000000f,
+				0.10761157f, 0.99404776f, 0.01699242f,
+				0.10344726f, 0.99406654f, 0.03362026f,
+				0.09674154f, 0.99408495f, 0.04935813f,
+				0.08773998f, 0.99409753f, 0.06381103f,
+				0.07668582f, 0.99410182f, 0.07668582f,
+				0.06381113f, 0.99409747f, 0.08774014f,
+				0.04935821f, 0.99408495f, 0.09674170f,
+				0.03362055f, 0.99406654f, 0.10344723f,
+				0.01699220f, 0.99404776f, 0.10761159f,
+				0.00000000f, 0.99403834f, 0.10903012f,
+				0.36039943f, 0.93279809f, -0.00000000f,
+				0.35574505f, 0.93289334f, 0.05617307f,
+				0.34204447f, 0.93308479f, 0.11116764f,
+				0.31993434f, 0.93327194f, 0.16323482f,
+				0.29020390f, 0.93340009f, 0.21105911f,
+				0.25365368f, 0.93344504f, 0.25365371f,
+				0.21105954f, 0.93340015f, 0.29020363f,
+				0.16323458f, 0.93327194f, 0.31993434f,
+				0.11116784f, 0.93308485f, 0.34204453f,
+				0.05617263f, 0.93289340f, 0.35574511f,
+				0.00000000f, 0.93279803f, 0.36039948f,
+				0.88340259f, 0.46861488f, -0.00000000f,
+				0.87244546f, 0.46889016f, 0.13777141f,
+				0.83972138f, 0.46944135f, 0.27293366f,
+				0.78624606f, 0.46997890f, 0.40116957f,
+				0.71368778f, 0.47034633f, 0.51906073f,
+				0.62396044f, 0.47047496f, 0.62396044f,
+				0.51906067f, 0.47034639f, 0.71368772f,
+				0.40116948f, 0.46997908f, 0.78624594f,
+				0.27293396f, 0.46944159f, 0.83972114f,
+				0.13777111f, 0.46889019f, 0.87244546f,
+				0.00000000f, 0.46861485f, 0.88340265f,
+				0.93448776f, -0.35599536f, 0.00000000f,
+				0.92294377f, -0.35626677f, 0.14576957f,
+				0.88841265f, -0.35681954f, 0.28879514f,
+				0.83191514f, -0.35736835f, 0.42450577f,
+				0.75519395f, -0.35774899f, 0.54927009f,
+				0.66027242f, -0.35788339f, 0.66027254f,
+				0.54926991f, -0.35774904f, 0.75519413f,
+				0.42450562f, -0.35736838f, 0.83191508f,
+				0.28879505f, -0.35681972f, 0.88841271f,
+				0.14576939f, -0.35626683f, 0.92294371f,
+				0.00000000f, -0.35599536f, 0.93448776f,
+				0.78086889f, -0.62469494f, 0.00000000f,
+				0.77104694f, -0.62501729f, 0.12181899f,
+				0.74185717f, -0.62567103f, 0.24121319f,
+				0.69436729f, -0.62631625f, 0.35437542f,
+				0.63014323f, -0.62676215f, 0.45835435f,
+				0.55089575f, -0.62691921f, 0.55089581f,
+				0.45835426f, -0.62676227f, 0.63014323f,
+				0.35437530f, -0.62631637f, 0.69436723f,
+				0.24121325f, -0.62567103f, 0.74185717f,
+				0.12181915f, -0.62501746f, 0.77104694f,
+				0.00000000f, -0.62469494f, 0.78086889f,
+				0.73252982f, -0.68073493f, 0.00000000f,
+				0.72325647f, -0.68104768f, 0.11434216f,
+				0.69576049f, -0.68168211f, 0.22633344f,
+				0.65112102f, -0.68230885f, 0.33240935f,
+				0.59084833f, -0.68274176f, 0.42983946f,
+				0.51655358f, -0.68289435f, 0.51655358f,
+				0.42983946f, -0.68274188f, 0.59084815f,
+				0.33240965f, -0.68230897f, 0.65112090f,
+				0.22633316f, -0.68168223f, 0.69576037f,
+				0.11434201f, -0.68104780f, 0.72325629f,
+				0.00000000f, -0.68073487f, 0.73252988f,
+				0.76869494f, -0.63961560f, 0.00000000f,
+				0.75896567f, -0.63995206f, 0.12013541f,
+				0.73011720f, -0.64063656f, 0.23772609f,
+				0.68329000f, -0.64131474f, 0.34904197f,
+				0.62007785f, -0.64178431f, 0.45123872f,
+				0.54217160f, -0.64195007f, 0.54217166f,
+				0.45123863f, -0.64178431f, 0.62007785f,
+				0.34904230f, -0.64131451f, 0.68328995f,
+				0.23772585f, -0.64063650f, 0.73011738f,
+				0.12013473f, -0.63995212f, 0.75896567f,
+				0.00000000f, -0.63961560f, 0.76869500f,
+				0.89442712f, -0.44721353f, 0.00000000f,
+				0.88320464f, -0.44757605f, 0.14009038f,
+				0.84982812f, -0.44832191f, 0.27712727f,
+				0.79552603f, -0.44907007f, 0.40678552f,
+				0.72211319f, -0.44959316f, 0.52575517f,
+				0.63154542f, -0.44977871f, 0.63154531f,
+				0.52575505f, -0.44959310f, 0.72211331f,
+				0.40678558f, -0.44906983f, 0.79552603f,
+				0.27712739f, -0.44832191f, 0.84982806f,
+				0.14009029f, -0.44757628f, 0.88320440f,
+				0.00000000f, -0.44721353f, 0.89442724f,
+				0.98920345f, 0.14654903f, -0.00000000f,
+				0.97693658f, 0.14651901f, 0.15532872f,
+				0.94031984f, 0.14642376f, 0.30717853f,
+				0.88055933f, 0.14629376f, 0.45079187f,
+				0.79958105f, 0.14618376f, 0.58249503f,
+				0.69951504f, 0.14614125f, 0.69951504f,
+				0.58249503f, 0.14618391f, 0.79958105f,
+				0.45079190f, 0.14629373f, 0.88055933f,
+				0.30717853f, 0.14642377f, 0.94031984f,
+				0.15532865f, 0.14651905f, 0.97693658f,
+				0.00000000f, 0.14654903f, 0.98920339f,
+				0.59999961f, 0.80000031f, -0.00000000f,
+				0.59229910f, 0.80018437f, 0.09426992f,
+				0.56961125f, 0.80054069f, 0.18621883f,
+				0.53298515f, 0.80087584f, 0.27299231f,
+				0.48373166f, 0.80109745f, 0.35248643f,
+				0.42315489f, 0.80117404f, 0.42315492f,
+				0.35248655f, 0.80109763f, 0.48373106f,
+				0.27299243f, 0.80087572f, 0.53298527f,
+				0.18621899f, 0.80054086f, 0.56961107f,
+				0.09426976f, 0.80018455f, 0.59229887f,
+				0.00000000f, 0.80000025f, 0.59999967f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 0.99403834f, -0.10903012f,
+				0.01699242f, 0.99404776f, -0.10761157f,
+				0.03362026f, 0.99406654f, -0.10344726f,
+				0.04935814f, 0.99408495f, -0.09674154f,
+				0.06381103f, 0.99409747f, -0.08773997f,
+				0.07668582f, 0.99410182f, -0.07668582f,
+				0.08774014f, 0.99409753f, -0.06381113f,
+				0.09674169f, 0.99408489f, -0.04935820f,
+				0.10344723f, 0.99406660f, -0.03362057f,
+				0.10761159f, 0.99404776f, -0.01699222f,
+				0.10903012f, 0.99403834f, -0.00000000f,
+				0.00000000f, 0.93279809f, -0.36039945f,
+				0.05617307f, 0.93289340f, -0.35574505f,
+				0.11116765f, 0.93308485f, -0.34204447f,
+				0.16323480f, 0.93327194f, -0.31993431f,
+				0.21105908f, 0.93340009f, -0.29020390f,
+				0.25365368f, 0.93344510f, -0.25365368f,
+				0.29020360f, 0.93340009f, -0.21105953f,
+				0.31993431f, 0.93327194f, -0.16323458f,
+				0.34204447f, 0.93308485f, -0.11116787f,
+				0.35574511f, 0.93289340f, -0.05617262f,
+				0.36039943f, 0.93279809f, -0.00000000f,
+				0.00000000f, 0.46861491f, -0.88340265f,
+				0.13777143f, 0.46889022f, -0.87244546f,
+				0.27293366f, 0.46944135f, -0.83972138f,
+				0.40116957f, 0.46997890f, -0.78624600f,
+				0.51906073f, 0.47034633f, -0.71368778f,
+				0.62396044f, 0.47047499f, -0.62396038f,
+				0.71368778f, 0.47034642f, -0.51906067f,
+				0.78624600f, 0.46997911f, -0.40116948f,
+				0.83972114f, 0.46944162f, -0.27293390f,
+				0.87244540f, 0.46889022f, -0.13777119f,
+				0.88340265f, 0.46861491f, -0.00000000f,
+				0.00000000f, -0.35599536f, -0.93448770f,
+				0.14576958f, -0.35626677f, -0.92294377f,
+				0.28879517f, -0.35681960f, -0.88841271f,
+				0.42450577f, -0.35736835f, -0.83191514f,
+				0.54927015f, -0.35774902f, -0.75519395f,
+				0.66027242f, -0.35788339f, -0.66027242f,
+				0.75519413f, -0.35774907f, -0.54926997f,
+				0.83191514f, -0.35736847f, -0.42450568f,
+				0.88841271f, -0.35681972f, -0.28879502f,
+				0.92294371f, -0.35626686f, -0.14576937f,
+				0.93448776f, -0.35599536f, 0.00000000f,
+				0.00000000f, -0.62469494f, -0.78086889f,
+				0.12181899f, -0.62501729f, -0.77104694f,
+				0.24121317f, -0.62567103f, -0.74185711f,
+				0.35437539f, -0.62631625f, -0.69436729f,
+				0.45835432f, -0.62676215f, -0.63014323f,
+				0.55089581f, -0.62691921f, -0.55089581f,
+				0.63014317f, -0.62676215f, -0.45835423f,
+				0.69436723f, -0.62631643f, -0.35437530f,
+				0.74185711f, -0.62567097f, -0.24121323f,
+				0.77104694f, -0.62501740f, -0.12181912f,
+				0.78086889f, -0.62469494f, 0.00000000f,
+				0.00000000f, -0.68073487f, -0.73252988f,
+				0.11434216f, -0.68104768f, -0.72325647f,
+				0.22633334f, -0.68168211f, -0.69576049f,
+				0.33240932f, -0.68230891f, -0.65112108f,
+				0.42983937f, -0.68274176f, -0.59084839f,
+				0.51655358f, -0.68289435f, -0.51655364f,
+				0.59084809f, -0.68274182f, -0.42983949f,
+				0.65112084f, -0.68230897f, -0.33240962f,
+				0.69576043f, -0.68168229f, -0.22633317f,
+				0.72325629f, -0.68104786f, -0.11434212f,
+				0.73252994f, -0.68073493f, 0.00000000f,
+				0.00000000f, -0.63961560f, -0.76869488f,
+				0.12013536f, -0.63995206f, -0.75896567f,
+				0.23772606f, -0.64063662f, -0.73011720f,
+				0.34904191f, -0.64131474f, -0.68329000f,
+				0.45123872f, -0.64178437f, -0.62007785f,
+				0.54217160f, -0.64195001f, -0.54217160f,
+				0.62007791f, -0.64178431f, -0.45123860f,
+				0.68328995f, -0.64131457f, -0.34904230f,
+				0.73011726f, -0.64063644f, -0.23772582f,
+				0.75896567f, -0.63995212f, -0.12013467f,
+				0.76869494f, -0.63961560f, 0.00000000f,
+				0.00000000f, -0.44721362f, -0.89442724f,
+				0.14009042f, -0.44757605f, -0.88320464f,
+				0.27712736f, -0.44832203f, -0.84982812f,
+				0.40678561f, -0.44907010f, -0.79552591f,
+				0.52575535f, -0.44959316f, -0.72211301f,
+				0.63154542f, -0.44977871f, -0.63154531f,
+				0.72211337f, -0.44959310f, -0.52575493f,
+				0.79552609f, -0.44906986f, -0.40678555f,
+				0.84982818f, -0.44832194f, -0.27712733f,
+				0.88320452f, -0.44757628f, -0.14009032f,
+				0.89442724f, -0.44721356f, 0.00000000f,
+				0.00000000f, 0.14654903f, -0.98920339f,
+				0.15532865f, 0.14651905f, -0.97693658f,
+				0.30717847f, 0.14642361f, -0.94031984f,
+				0.45079190f, 0.14629373f, -0.88055927f,
+				0.58249491f, 0.14618376f, -0.79958099f,
+				0.69951510f, 0.14614128f, -0.69951516f,
+				0.79958105f, 0.14618389f, -0.58249503f,
+				0.88055938f, 0.14629366f, -0.45079195f,
+				0.94031984f, 0.14642382f, -0.30717853f,
+				0.97693658f, 0.14651911f, -0.15532877f,
+				0.98920339f, 0.14654903f, -0.00000000f,
+				0.00000000f, 0.80000031f, -0.59999961f,
+				0.09426987f, 0.80018437f, -0.59229904f,
+				0.18621887f, 0.80054075f, -0.56961137f,
+				0.27299228f, 0.80087584f, -0.53298515f,
+				0.35248643f, 0.80109745f, -0.48373166f,
+				0.42315489f, 0.80117404f, -0.42315489f,
+				0.48373106f, 0.80109769f, -0.35248649f,
+				0.53298527f, 0.80087572f, -0.27299249f,
+				0.56961101f, 0.80054092f, -0.18621904f,
+				0.59229881f, 0.80018449f, -0.09426975f,
+				0.59999961f, 0.80000025f, -0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 0.99403834f, 0.10903012f,
+				-0.01699241f, 0.99404776f, 0.10761157f,
+				-0.03362026f, 0.99406654f, 0.10344726f,
+				-0.04935814f, 0.99408495f, 0.09674154f,
+				-0.06381103f, 0.99409753f, 0.08773999f,
+				-0.07668582f, 0.99410182f, 0.07668582f,
+				-0.08774015f, 0.99409753f, 0.06381113f,
+				-0.09674170f, 0.99408495f, 0.04935821f,
+				-0.10344724f, 0.99406660f, 0.03362056f,
+				-0.10761159f, 0.99404776f, 0.01699222f,
+				-0.10903012f, 0.99403834f, 0.00000000f,
+				0.00000000f, 0.93279803f, 0.36039948f,
+				-0.05617307f, 0.93289334f, 0.35574508f,
+				-0.11116763f, 0.93308485f, 0.34204450f,
+				-0.16323477f, 0.93327188f, 0.31993434f,
+				-0.21105908f, 0.93340015f, 0.29020393f,
+				-0.25365368f, 0.93344504f, 0.25365368f,
+				-0.29020357f, 0.93340009f, 0.21105954f,
+				-0.31993434f, 0.93327194f, 0.16323459f,
+				-0.34204450f, 0.93308485f, 0.11116788f,
+				-0.35574511f, 0.93289346f, 0.05617263f,
+				-0.36039943f, 0.93279809f, 0.00000000f,
+				0.00000000f, 0.46861485f, 0.88340265f,
+				-0.13777140f, 0.46889010f, 0.87244546f,
+				-0.27293369f, 0.46944135f, 0.83972144f,
+				-0.40116957f, 0.46997890f, 0.78624612f,
+				-0.51906067f, 0.47034630f, 0.71368784f,
+				-0.62396044f, 0.47047496f, 0.62396044f,
+				-0.71368766f, 0.47034639f, 0.51906073f,
+				-0.78624600f, 0.46997911f, 0.40116954f,
+				-0.83972114f, 0.46944162f, 0.27293396f,
+				-0.87244540f, 0.46889028f, 0.13777120f,
+				-0.88340259f, 0.46861488f, 0.00000000f,
+				0.00000000f, -0.35599536f, 0.93448776f,
+				-0.14576957f, -0.35626677f, 0.92294377f,
+				-0.28879514f, -0.35681957f, 0.88841271f,
+				-0.42450571f, -0.35736832f, 0.83191514f,
+				-0.54927009f, -0.35774896f, 0.75519395f,
+				-0.66027248f, -0.35788342f, 0.66027254f,
+				-0.75519407f, -0.35774904f, 0.54926997f,
+				-0.83191502f, -0.35736841f, 0.42450568f,
+				-0.88841271f, -0.35681972f, 0.28879508f,
+				-0.92294371f, -0.35626683f, 0.14576939f,
+				-0.93448776f, -0.35599536f, -0.00000000f,
+				0.00000000f, -0.62469494f, 0.78086889f,
+				-0.12181900f, -0.62501729f, 0.77104706f,
+				-0.24121317f, -0.62567103f, 0.74185717f,
+				-0.35437536f, -0.62631625f, 0.69436735f,
+				-0.45835429f, -0.62676215f, 0.63014328f,
+				-0.55089581f, -0.62691921f, 0.55089581f,
+				-0.63014323f, -0.62676221f, 0.45835429f,
+				-0.69436723f, -0.62631643f, 0.35437533f,
+				-0.74185717f, -0.62567103f, 0.24121325f,
+				-0.77104694f, -0.62501740f, 0.12181915f,
+				-0.78086889f, -0.62469494f, -0.00000000f,
+				0.00000000f, -0.68073487f, 0.73252988f,
+				-0.11434214f, -0.68104768f, 0.72325647f,
+				-0.22633335f, -0.68168211f, 0.69576049f,
+				-0.33240938f, -0.68230891f, 0.65112108f,
+				-0.42983943f, -0.68274170f, 0.59084833f,
+				-0.51655358f, -0.68289441f, 0.51655358f,
+				-0.59084809f, -0.68274182f, 0.42983949f,
+				-0.65112084f, -0.68230897f, 0.33240965f,
+				-0.69576037f, -0.68168223f, 0.22633319f,
+				-0.72325629f, -0.68104780f, 0.11434212f,
+				-0.73252982f, -0.68073493f, -0.00000000f,
+				0.00000000f, -0.63961560f, 0.76869500f,
+				-0.12013534f, -0.63995206f, 0.75896573f,
+				-0.23772603f, -0.64063656f, 0.73011720f,
+				-0.34904197f, -0.64131474f, 0.68329000f,
+				-0.45123866f, -0.64178431f, 0.62007785f,
+				-0.54217160f, -0.64195001f, 0.54217160f,
+				-0.62007785f, -0.64178431f, 0.45123863f,
+				-0.68328989f, -0.64131457f, 0.34904236f,
+				-0.73011738f, -0.64063650f, 0.23772585f,
+				-0.75896567f, -0.63995212f, 0.12013467f,
+				-0.76869494f, -0.63961560f, -0.00000000f,
+				0.00000000f, -0.44721353f, 0.89442724f,
+				-0.14009035f, -0.44757602f, 0.88320464f,
+				-0.27712739f, -0.44832197f, 0.84982818f,
+				-0.40678561f, -0.44907001f, 0.79552597f,
+				-0.52575523f, -0.44959310f, 0.72211307f,
+				-0.63154542f, -0.44977871f, 0.63154531f,
+				-0.72211337f, -0.44959313f, 0.52575499f,
+				-0.79552603f, -0.44906992f, 0.40678558f,
+				-0.84982818f, -0.44832194f, 0.27712733f,
+				-0.88320440f, -0.44757628f, 0.14009029f,
+				-0.89442712f, -0.44721353f, -0.00000000f,
+				0.00000000f, 0.14654903f, 0.98920339f,
+				-0.15532869f, 0.14651905f, 0.97693658f,
+				-0.30717850f, 0.14642371f, 0.94031984f,
+				-0.45079190f, 0.14629376f, 0.88055927f,
+				-0.58249503f, 0.14618376f, 0.79958099f,
+				-0.69951504f, 0.14614125f, 0.69951504f,
+				-0.79958105f, 0.14618388f, 0.58249503f,
+				-0.88055938f, 0.14629366f, 0.45079190f,
+				-0.94031984f, 0.14642382f, 0.30717853f,
+				-0.97693658f, 0.14651911f, 0.15532877f,
+				-0.98920345f, 0.14654903f, 0.00000000f,
+				0.00000000f, 0.80000025f, 0.59999967f,
+				-0.09426992f, 0.80018437f, 0.59229910f,
+				-0.18621887f, 0.80054075f, 0.56961131f,
+				-0.27299228f, 0.80087584f, 0.53298515f,
+				-0.35248643f, 0.80109739f, 0.48373166f,
+				-0.42315486f, 0.80117404f, 0.42315492f,
+				-0.48373103f, 0.80109763f, 0.35248649f,
+				-0.53298527f, 0.80087566f, 0.27299249f,
+				-0.56961095f, 0.80054086f, 0.18621902f,
+				-0.59229881f, 0.80018449f, 0.09426975f,
+				-0.59999961f, 0.80000031f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				-0.10903012f, 0.99403834f, 0.00000000f,
+				-0.10761159f, 0.99404788f, -0.01699242f,
+				-0.10344726f, 0.99406654f, -0.03362026f,
+				-0.09674155f, 0.99408489f, -0.04935813f,
+				-0.08773997f, 0.99409747f, -0.06381102f,
+				-0.07668582f, 0.99410182f, -0.07668582f,
+				-0.06381113f, 0.99409747f, -0.08774014f,
+				-0.04935822f, 0.99408489f, -0.09674168f,
+				-0.03362055f, 0.99406654f, -0.10344723f,
+				-0.01699222f, 0.99404776f, -0.10761159f,
+				0.00000000f, 0.99403834f, -0.10903012f,
+				-0.36039943f, 0.93279809f, 0.00000000f,
+				-0.35574505f, 0.93289340f, -0.05617307f,
+				-0.34204450f, 0.93308485f, -0.11116764f,
+				-0.31993437f, 0.93327194f, -0.16323480f,
+				-0.29020390f, 0.93340009f, -0.21105908f,
+				-0.25365368f, 0.93344510f, -0.25365368f,
+				-0.21105953f, 0.93340009f, -0.29020354f,
+				-0.16323458f, 0.93327194f, -0.31993428f,
+				-0.11116786f, 0.93308479f, -0.34204444f,
+				-0.05617262f, 0.93289334f, -0.35574505f,
+				0.00000000f, 0.93279809f, -0.36039945f,
+				-0.88340265f, 0.46861491f, 0.00000000f,
+				-0.87244540f, 0.46889016f, -0.13777140f,
+				-0.83972138f, 0.46944138f, -0.27293366f,
+				-0.78624606f, 0.46997890f, -0.40116951f,
+				-0.71368784f, 0.47034633f, -0.51906067f,
+				-0.62396044f, 0.47047499f, -0.62396038f,
+				-0.51906073f, 0.47034639f, -0.71368766f,
+				-0.40116948f, 0.46997911f, -0.78624594f,
+				-0.27293396f, 0.46944165f, -0.83972114f,
+				-0.13777120f, 0.46889028f, -0.87244540f,
+				0.00000000f, 0.46861491f, -0.88340265f,
+				-0.93448776f, -0.35599536f, -0.00000000f,
+				-0.92294377f, -0.35626677f, -0.14576957f,
+				-0.88841271f, -0.35681954f, -0.28879514f,
+				-0.83191514f, -0.35736835f, -0.42450577f,
+				-0.75519401f, -0.35774902f, -0.54927009f,
+				-0.66027242f, -0.35788339f, -0.66027242f,
+				-0.54926997f, -0.35774904f, -0.75519407f,
+				-0.42450568f, -0.35736844f, -0.83191508f,
+				-0.28879514f, -0.35681972f, -0.88841265f,
+				-0.14576939f, -0.35626683f, -0.92294371f,
+				0.00000000f, -0.35599536f, -0.93448770f,
+				-0.78086889f, -0.62469494f, -0.00000000f,
+				-0.77104706f, -0.62501734f, -0.12181900f,
+				-0.74185711f, -0.62567097f, -0.24121314f,
+				-0.69436729f, -0.62631625f, -0.35437539f,
+				-0.63014323f, -0.62676215f, -0.45835432f,
+				-0.55089581f, -0.62691921f, -0.55089581f,
+				-0.45835429f, -0.62676221f, -0.63014317f,
+				-0.35437530f, -0.62631643f, -0.69436723f,
+				-0.24121323f, -0.62567097f, -0.74185711f,
+				-0.12181915f, -0.62501746f, -0.77104694f,
+				0.00000000f, -0.62469494f, -0.78086889f,
+				-0.73252994f, -0.68073493f, -0.00000000f,
+				-0.72325647f, -0.68104768f, -0.11434216f,
+				-0.69576049f, -0.68168211f, -0.22633339f,
+				-0.65112102f, -0.68230885f, -0.33240935f,
+				-0.59084833f, -0.68274176f, -0.42983946f,
+				-0.51655358f, -0.68289435f, -0.51655364f,
+				-0.42983946f, -0.68274188f, -0.59084809f,
+				-0.33240956f, -0.68230897f, -0.65112090f,
+				-0.22633308f, -0.68168223f, -0.69576037f,
+				-0.11434201f, -0.68104780f, -0.72325629f,
+				0.00000000f, -0.68073487f, -0.73252988f,
+				-0.76869494f, -0.63961560f, -0.00000000f,
+				-0.75896567f, -0.63995206f, -0.12013541f,
+				-0.73011720f, -0.64063656f, -0.23772609f,
+				-0.68329000f, -0.64131469f, -0.34904197f,
+				-0.62007785f, -0.64178431f, -0.45123869f,
+				-0.54217160f, -0.64195007f, -0.54217166f,
+				-0.45123863f, -0.64178437f, -0.62007785f,
+				-0.34904227f, -0.64131457f, -0.68328995f,
+				-0.23772582f, -0.64063644f, -0.73011726f,
+				-0.12013479f, -0.63995218f, -0.75896567f,
+				0.00000000f, -0.63961560f, -0.76869488f,
+				-0.89442724f, -0.44721356f, -0.00000000f,
+				-0.88320464f, -0.44757605f, -0.14009038f,
+				-0.84982818f, -0.44832191f, -0.27712730f,
+				-0.79552603f, -0.44907001f, -0.40678552f,
+				-0.72211319f, -0.44959310f, -0.52575517f,
+				-0.63154542f, -0.44977871f, -0.63154531f,
+				-0.52575505f, -0.44959310f, -0.72211331f,
+				-0.40678564f, -0.44906986f, -0.79552597f,
+				-0.27712736f, -0.44832200f, -0.84982800f,
+				-0.14009029f, -0.44757628f, -0.88320440f,
+				0.00000000f, -0.44721362f, -0.89442724f,
+				-0.98920339f, 0.14654903f, 0.00000000f,
+				-0.97693658f, 0.14651901f, -0.15532872f,
+				-0.94031984f, 0.14642377f, -0.30717847f,
+				-0.88055927f, 0.14629376f, -0.45079187f,
+				-0.79958099f, 0.14618376f, -0.58249503f,
+				-0.69951510f, 0.14614128f, -0.69951516f,
+				-0.58249503f, 0.14618391f, -0.79958105f,
+				-0.45079190f, 0.14629370f, -0.88055933f,
+				-0.30717865f, 0.14642373f, -0.94031984f,
+				-0.15532877f, 0.14651911f, -0.97693658f,
+				0.00000000f, 0.14654903f, -0.98920339f,
+				-0.59999961f, 0.80000025f, 0.00000000f,
+				-0.59229910f, 0.80018437f, -0.09426993f,
+				-0.56961125f, 0.80054069f, -0.18621883f,
+				-0.53298515f, 0.80087584f, -0.27299231f,
+				-0.48373166f, 0.80109745f, -0.35248643f,
+				-0.42315486f, 0.80117404f, -0.42315489f,
+				-0.35248655f, 0.80109769f, -0.48373106f,
+				-0.27299246f, 0.80087566f, -0.53298521f,
+				-0.18621899f, 0.80054086f, -0.56961107f,
+				-0.09426975f, 0.80018449f, -0.59229881f,
+				0.00000000f, 0.80000031f, -0.59999961f,
+				0.60000014f, 0.79999983f, -0.00000000f,
+				0.59229946f, 0.80018395f, 0.09426998f,
+				0.56961179f, 0.80054033f, 0.18621901f,
+				0.53298604f, 0.80087507f, 0.27299276f,
+				0.48373187f, 0.80109727f, 0.35248655f,
+				0.42315534f, 0.80117357f, 0.42315537f,
+				0.35248691f, 0.80109721f, 0.48373157f,
+				0.27299273f, 0.80087519f, 0.53298587f,
+				0.18621919f, 0.80054045f, 0.56961161f,
+				0.09426985f, 0.80018401f, 0.59229940f,
+				0.00000000f, 0.79999983f, 0.60000014f,
+				0.35491538f, 0.93489850f, -0.00000000f,
+				0.35030773f, 0.93497366f, 0.05575488f,
+				0.33679214f, 0.93511921f, 0.11010502f,
+				0.31505084f, 0.93525589f, 0.16136751f,
+				0.28588474f, 0.93534654f, 0.20831876f,
+				0.25006858f, 0.93537772f, 0.25006849f,
+				0.20831899f, 0.93534654f, 0.28588459f,
+				0.16136734f, 0.93525594f, 0.31505048f,
+				0.11010502f, 0.93511939f, 0.33679175f,
+				0.05575469f, 0.93497372f, 0.35030770f,
+				0.00000000f, 0.93489844f, 0.35491538f,
+				0.23813862f, 0.97123122f, -0.00000000f,
+				0.23503657f, 0.97126639f, 0.03740831f,
+				0.22594899f, 0.97133446f, 0.07386778f,
+				0.21134609f, 0.97139829f, 0.10825043f,
+				0.19177027f, 0.97144073f, 0.13973957f,
+				0.16774194f, 0.97145522f, 0.16774194f,
+				0.13973957f, 0.97144073f, 0.19177030f,
+				0.10825033f, 0.97139841f, 0.21134590f,
+				0.07386787f, 0.97133446f, 0.22594878f,
+				0.03740822f, 0.97126633f, 0.23503660f,
+				0.00000000f, 0.97123122f, 0.23813862f,
+				0.17979470f, 0.98370415f, -0.00000000f,
+				0.17744993f, 0.98372447f, 0.02824289f,
+				0.17058375f, 0.98376375f, 0.05576761f,
+				0.15955459f, 0.98380059f, 0.08172295f,
+				0.14477314f, 0.98382503f, 0.10549361f,
+				0.12663263f, 0.98383349f, 0.12663263f,
+				0.10549360f, 0.98382503f, 0.14477316f,
+				0.08172285f, 0.98380071f, 0.15955447f,
+				0.05576771f, 0.98376375f, 0.17058358f,
+				0.02824305f, 0.98372453f, 0.17744984f,
+				0.00000000f, 0.98370415f, 0.17979471f,
+				0.15294123f, 0.98823535f, -0.00000000f,
+				0.15094571f, 0.98825008f, 0.02402467f,
+				0.14510347f, 0.98827863f, 0.04743762f,
+				0.13572034f, 0.98830545f, 0.06951533f,
+				0.12314621f, 0.98832321f, 0.08973438f,
+				0.10771532f, 0.98832929f, 0.10771532f,
+				0.08973443f, 0.98832321f, 0.12314621f,
+				0.06951523f, 0.98830539f, 0.13572021f,
+				0.04743744f, 0.98827863f, 0.14510347f,
+				0.02402461f, 0.98825002f, 0.15094574f,
+				0.00000000f, 0.98823535f, 0.15294123f,
+				0.14834042f, 0.98893636f, -0.00000000f,
+				0.14640494f, 0.98895019f, 0.02330164f,
+				0.14073798f, 0.98897713f, 0.04601044f,
+				0.13163701f, 0.98900241f, 0.06742381f,
+				0.11944108f, 0.98901922f, 0.08703452f,
+				0.10447443f, 0.98902482f, 0.10447443f,
+				0.08703458f, 0.98901910f, 0.11944108f,
+				0.06742378f, 0.98900241f, 0.13163690f,
+				0.04601069f, 0.98897713f, 0.14073797f,
+				0.02330185f, 0.98895031f, 0.14640486f,
+				0.00000000f, 0.98893636f, 0.14834042f,
+				0.16577734f, 0.98616314f, -0.00000000f,
+				0.16361488f, 0.98618054f, 0.02604098f,
+				0.15728295f, 0.98621398f, 0.05141935f,
+				0.14711300f, 0.98624545f, 0.07535055f,
+				0.13348369f, 0.98626626f, 0.09726719f,
+				0.11675765f, 0.98627347f, 0.11675765f,
+				0.09726722f, 0.98626631f, 0.13348375f,
+				0.07535047f, 0.98624545f, 0.14711283f,
+				0.05141941f, 0.98621410f, 0.15728293f,
+				0.02604095f, 0.98618054f, 0.16361482f,
+				0.00000000f, 0.98616314f, 0.16577734f,
+				0.21457930f, 0.97670650f, -0.00000000f,
+				0.21178281f, 0.97673523f, 0.03370722f,
+				0.20359138f, 0.97679090f, 0.06655866f,
+				0.19043125f, 0.97684300f, 0.09753795f,
+				0.17279114f, 0.97687763f, 0.12590984f,
+				0.15114051f, 0.97688949f, 0.15114051f,
+				0.12590988f, 0.97687763f, 0.17279126f,
+				0.09753782f, 0.97684312f, 0.19043098f,
+				0.06655872f, 0.97679090f, 0.20359136f,
+				0.03370715f, 0.97673535f, 0.21178272f,
+				0.00000000f, 0.97670650f, 0.21457930f,
+				0.32579350f, 0.94544101f, -0.00000000f,
+				0.32155997f, 0.94550508f, 0.05117927f,
+				0.30914524f, 0.94562930f, 0.10106669f,
+				0.28918239f, 0.94574553f, 0.14811778f,
+				0.26240703f, 0.94582283f, 0.19121127f,
+				0.22953111f, 0.94584930f, 0.22953111f,
+				0.19121137f, 0.94582278f, 0.26240712f,
+				0.14811763f, 0.94574559f, 0.28918216f,
+				0.10106679f, 0.94562918f, 0.30914542f,
+				0.05117931f, 0.94550508f, 0.32155982f,
+				0.00000000f, 0.94544101f, 0.32579350f,
+				0.59999937f, 0.80000049f, -0.00000000f,
+				0.59229898f, 0.80018443f, 0.09426988f,
+				0.56961066f, 0.80054110f, 0.18621883f,
+				0.53298545f, 0.80087554f, 0.27299255f,
+				0.48373094f, 0.80109799f, 0.35248604f,
+				0.42315483f, 0.80117410f, 0.42315483f,
+				0.35248625f, 0.80109781f, 0.48373121f,
+				0.27299234f, 0.80087590f, 0.53298503f,
+				0.18621901f, 0.80054098f, 0.56961095f,
+				0.09426989f, 0.80018461f, 0.59229875f,
+				0.00000000f, 0.80000049f, 0.59999937f,
+				0.99999994f, 0.00000000f, -0.00000000f,
+				0.98756981f, 0.00000000f, 0.15718086f,
+				0.95049530f, 0.00000000f, 0.31073883f,
+				0.89004338f, 0.00000000f, 0.45587584f,
+				0.80819392f, 0.00000000f, 0.58891648f,
+				0.70710683f, 0.00000000f, 0.70710683f,
+				0.58891642f, 0.00000000f, 0.80819392f,
+				0.45587584f, 0.00000000f, 0.89004338f,
+				0.31073889f, 0.00000000f, 0.95049530f,
+				0.15718089f, 0.00000000f, 0.98756981f,
+				0.00000000f, 0.00000000f, 0.99999994f,
+				0.00000000f, 0.79999989f, -0.60000014f,
+				0.09426994f, 0.80018401f, -0.59229946f,
+				0.18621901f, 0.80054039f, -0.56961179f,
+				0.27299276f, 0.80087507f, -0.53298604f,
+				0.35248658f, 0.80109727f, -0.48373187f,
+				0.42315534f, 0.80117357f, -0.42315534f,
+				0.48373160f, 0.80109727f, -0.35248691f,
+				0.53298575f, 0.80087519f, -0.27299273f,
+				0.56961155f, 0.80054045f, -0.18621923f,
+				0.59229940f, 0.80018401f, -0.09426985f,
+				0.60000026f, 0.79999977f, -0.00000000f,
+				0.00000000f, 0.93489850f, -0.35491538f,
+				0.05575490f, 0.93497372f, -0.35030776f,
+				0.11010505f, 0.93511921f, -0.33679211f,
+				0.16136754f, 0.93525589f, -0.31505081f,
+				0.20831878f, 0.93534660f, -0.28588474f,
+				0.25006852f, 0.93537766f, -0.25006849f,
+				0.28588459f, 0.93534654f, -0.20831895f,
+				0.31505051f, 0.93525606f, -0.16136728f,
+				0.33679172f, 0.93511939f, -0.11010498f,
+				0.35030773f, 0.93497372f, -0.05575464f,
+				0.35491535f, 0.93489844f, -0.00000000f,
+				0.00000000f, 0.97123122f, -0.23813862f,
+				0.03740832f, 0.97126639f, -0.23503657f,
+				0.07386777f, 0.97133446f, -0.22594899f,
+				0.10825042f, 0.97139835f, -0.21134609f,
+				0.13973957f, 0.97144073f, -0.19177027f,
+				0.16774191f, 0.97145522f, -0.16774194f,
+				0.19177030f, 0.97144073f, -0.13973957f,
+				0.21134590f, 0.97139835f, -0.10825033f,
+				0.22594878f, 0.97133446f, -0.07386787f,
+				0.23503660f, 0.97126633f, -0.03740822f,
+				0.23813862f, 0.97123122f, -0.00000000f,
+				0.00000000f, 0.98370415f, -0.17979471f,
+				0.02824289f, 0.98372447f, -0.17744993f,
+				0.05576760f, 0.98376375f, -0.17058372f,
+				0.08172296f, 0.98380059f, -0.15955459f,
+				0.10549361f, 0.98382509f, -0.14477314f,
+				0.12663263f, 0.98383349f, -0.12663262f,
+				0.14477316f, 0.98382503f, -0.10549360f,
+				0.15955447f, 0.98380071f, -0.08172285f,
+				0.17058358f, 0.98376375f, -0.05576771f,
+				0.17744984f, 0.98372453f, -0.02824305f,
+				0.17979470f, 0.98370415f, -0.00000000f,
+				0.00000000f, 0.98823535f, -0.15294123f,
+				0.02402467f, 0.98825008f, -0.15094571f,
+				0.04743762f, 0.98827863f, -0.14510345f,
+				0.06951532f, 0.98830533f, -0.13572033f,
+				0.08973438f, 0.98832327f, -0.12314621f,
+				0.10771532f, 0.98832929f, -0.10771532f,
+				0.12314620f, 0.98832321f, -0.08973442f,
+				0.13572021f, 0.98830539f, -0.06951523f,
+				0.14510347f, 0.98827863f, -0.04743744f,
+				0.15094574f, 0.98825008f, -0.02402462f,
+				0.15294123f, 0.98823529f, -0.00000000f,
+				0.00000000f, 0.98893636f, -0.14834042f,
+				0.02330164f, 0.98895019f, -0.14640494f,
+				0.04601044f, 0.98897713f, -0.14073797f,
+				0.06742381f, 0.98900241f, -0.13163701f,
+				0.08703452f, 0.98901922f, -0.11944108f,
+				0.10447443f, 0.98902482f, -0.10447443f,
+				0.11944108f, 0.98901910f, -0.08703458f,
+				0.13163690f, 0.98900241f, -0.06742378f,
+				0.14073797f, 0.98897713f, -0.04601069f,
+				0.14640486f, 0.98895031f, -0.02330185f,
+				0.14834042f, 0.98893636f, -0.00000000f,
+				0.00000000f, 0.98616314f, -0.16577734f,
+				0.02604098f, 0.98618054f, -0.16361488f,
+				0.05141935f, 0.98621410f, -0.15728295f,
+				0.07535054f, 0.98624545f, -0.14711298f,
+				0.09726719f, 0.98626626f, -0.13348369f,
+				0.11675765f, 0.98627347f, -0.11675765f,
+				0.13348375f, 0.98626631f, -0.09726722f,
+				0.14711283f, 0.98624545f, -0.07535047f,
+				0.15728293f, 0.98621410f, -0.05141941f,
+				0.16361482f, 0.98618054f, -0.02604095f,
+				0.16577734f, 0.98616314f, -0.00000000f,
+				0.00000000f, 0.97670650f, -0.21457930f,
+				0.03370722f, 0.97673523f, -0.21178281f,
+				0.06655866f, 0.97679090f, -0.20359138f,
+				0.09753795f, 0.97684300f, -0.19043125f,
+				0.12590984f, 0.97687763f, -0.17279114f,
+				0.15114051f, 0.97688949f, -0.15114051f,
+				0.17279126f, 0.97687763f, -0.12590988f,
+				0.19043098f, 0.97684312f, -0.09753782f,
+				0.20359136f, 0.97679090f, -0.06655872f,
+				0.21178272f, 0.97673535f, -0.03370715f,
+				0.21457930f, 0.97670650f, -0.00000000f,
+				0.00000000f, 0.94544101f, -0.32579350f,
+				0.05117927f, 0.94550508f, -0.32155997f,
+				0.10106669f, 0.94562936f, -0.30914521f,
+				0.14811778f, 0.94574553f, -0.28918239f,
+				0.19121125f, 0.94582278f, -0.26240700f,
+				0.22953111f, 0.94584930f, -0.22953111f,
+				0.26240712f, 0.94582278f, -0.19121137f,
+				0.28918216f, 0.94574559f, -0.14811763f,
+				0.30914542f, 0.94562918f, -0.10106679f,
+				0.32155982f, 0.94550508f, -0.05117931f,
+				0.32579350f, 0.94544101f, -0.00000000f,
+				0.00000000f, 0.80000049f, -0.59999937f,
+				0.09426988f, 0.80018443f, -0.59229898f,
+				0.18621883f, 0.80054110f, -0.56961066f,
+				0.27299255f, 0.80087554f, -0.53298545f,
+				0.35248604f, 0.80109799f, -0.48373094f,
+				0.42315483f, 0.80117410f, -0.42315483f,
+				0.48373121f, 0.80109781f, -0.35248625f,
+				0.53298503f, 0.80087590f, -0.27299234f,
+				0.56961095f, 0.80054098f, -0.18621901f,
+				0.59229875f, 0.80018461f, -0.09426989f,
+				0.59999937f, 0.80000049f, -0.00000000f,
+				0.00000000f, 0.00000000f, -0.99999994f,
+				0.15718086f, 0.00000000f, -0.98756981f,
+				0.31073883f, 0.00000000f, -0.95049530f,
+				0.45587584f, 0.00000000f, -0.89004338f,
+				0.58891648f, 0.00000000f, -0.80819392f,
+				0.70710683f, 0.00000000f, -0.70710683f,
+				0.80819392f, 0.00000000f, -0.58891642f,
+				0.89004338f, 0.00000000f, -0.45587584f,
+				0.95049530f, 0.00000000f, -0.31073889f,
+				0.98756981f, 0.00000000f, -0.15718089f,
+				0.99999994f, 0.00000000f, -0.00000000f,
+				0.00000000f, 0.79999983f, 0.60000014f,
+				-0.09426998f, 0.80018401f, 0.59229952f,
+				-0.18621902f, 0.80054033f, 0.56961179f,
+				-0.27299276f, 0.80087507f, 0.53298604f,
+				-0.35248658f, 0.80109727f, 0.48373187f,
+				-0.42315531f, 0.80117357f, 0.42315537f,
+				-0.48373151f, 0.80109727f, 0.35248685f,
+				-0.53298575f, 0.80087519f, 0.27299273f,
+				-0.56961155f, 0.80054045f, 0.18621922f,
+				-0.59229940f, 0.80018401f, 0.09426985f,
+				-0.60000014f, 0.79999983f, 0.00000000f,
+				0.00000000f, 0.93489844f, 0.35491538f,
+				-0.05575493f, 0.93497372f, 0.35030776f,
+				-0.11010505f, 0.93511921f, 0.33679211f,
+				-0.16136758f, 0.93525589f, 0.31505081f,
+				-0.20831881f, 0.93534654f, 0.28588471f,
+				-0.25006858f, 0.93537772f, 0.25006849f,
+				-0.28588459f, 0.93534654f, 0.20831895f,
+				-0.31505051f, 0.93525594f, 0.16136727f,
+				-0.33679172f, 0.93511927f, 0.11010496f,
+				-0.35030773f, 0.93497372f, 0.05575464f,
+				-0.35491538f, 0.93489850f, 0.00000000f,
+				0.00000000f, 0.97123122f, 0.23813862f,
+				-0.03740831f, 0.97126639f, 0.23503657f,
+				-0.07386778f, 0.97133446f, 0.22594899f,
+				-0.10825043f, 0.97139829f, 0.21134609f,
+				-0.13973956f, 0.97144073f, 0.19177027f,
+				-0.16774194f, 0.97145522f, 0.16774194f,
+				-0.19177030f, 0.97144073f, 0.13973957f,
+				-0.21134590f, 0.97139841f, 0.10825033f,
+				-0.22594878f, 0.97133440f, 0.07386787f,
+				-0.23503658f, 0.97126639f, 0.03740823f,
+				-0.23813862f, 0.97123122f, 0.00000000f,
+				0.00000000f, 0.98370415f, 0.17979471f,
+				-0.02824289f, 0.98372447f, 0.17744993f,
+				-0.05576761f, 0.98376375f, 0.17058375f,
+				-0.08172295f, 0.98380059f, 0.15955459f,
+				-0.10549361f, 0.98382503f, 0.14477316f,
+				-0.12663263f, 0.98383349f, 0.12663263f,
+				-0.14477316f, 0.98382503f, 0.10549360f,
+				-0.15955447f, 0.98380071f, 0.08172285f,
+				-0.17058358f, 0.98376375f, 0.05576771f,
+				-0.17744984f, 0.98372447f, 0.02824305f,
+				-0.17979470f, 0.98370415f, 0.00000000f,
+				0.00000000f, 0.98823535f, 0.15294123f,
+				-0.02402467f, 0.98825002f, 0.15094571f,
+				-0.04743762f, 0.98827863f, 0.14510347f,
+				-0.06951531f, 0.98830533f, 0.13572033f,
+				-0.08973438f, 0.98832321f, 0.12314621f,
+				-0.10771532f, 0.98832929f, 0.10771532f,
+				-0.12314621f, 0.98832321f, 0.08973443f,
+				-0.13572021f, 0.98830539f, 0.06951523f,
+				-0.14510347f, 0.98827863f, 0.04743744f,
+				-0.15094574f, 0.98825002f, 0.02402461f,
+				-0.15294123f, 0.98823535f, 0.00000000f,
+				0.00000000f, 0.98893636f, 0.14834042f,
+				-0.02330164f, 0.98895019f, 0.14640494f,
+				-0.04601044f, 0.98897719f, 0.14073798f,
+				-0.06742381f, 0.98900241f, 0.13163702f,
+				-0.08703452f, 0.98901922f, 0.11944108f,
+				-0.10447443f, 0.98902482f, 0.10447443f,
+				-0.11944108f, 0.98901910f, 0.08703458f,
+				-0.13163692f, 0.98900247f, 0.06742378f,
+				-0.14073797f, 0.98897713f, 0.04601069f,
+				-0.14640486f, 0.98895031f, 0.02330185f,
+				-0.14834042f, 0.98893636f, 0.00000000f,
+				0.00000000f, 0.98616314f, 0.16577734f,
+				-0.02604098f, 0.98618054f, 0.16361488f,
+				-0.05141935f, 0.98621398f, 0.15728295f,
+				-0.07535055f, 0.98624545f, 0.14711300f,
+				-0.09726719f, 0.98626626f, 0.13348369f,
+				-0.11675765f, 0.98627347f, 0.11675765f,
+				-0.13348375f, 0.98626631f, 0.09726722f,
+				-0.14711285f, 0.98624545f, 0.07535048f,
+				-0.15728293f, 0.98621410f, 0.05141941f,
+				-0.16361482f, 0.98618054f, 0.02604095f,
+				-0.16577734f, 0.98616314f, 0.00000000f,
+				0.00000000f, 0.97670650f, 0.21457930f,
+				-0.03370723f, 0.97673535f, 0.21178287f,
+				-0.06655866f, 0.97679090f, 0.20359138f,
+				-0.09753795f, 0.97684300f, 0.19043125f,
+				-0.12590984f, 0.97687763f, 0.17279114f,
+				-0.15114051f, 0.97688949f, 0.15114051f,
+				-0.17279126f, 0.97687763f, 0.12590988f,
+				-0.19043098f, 0.97684312f, 0.09753782f,
+				-0.20359136f, 0.97679090f, 0.06655872f,
+				-0.21178272f, 0.97673535f, 0.03370715f,
+				-0.21457930f, 0.97670650f, 0.00000000f,
+				0.00000000f, 0.94544101f, 0.32579350f,
+				-0.05117927f, 0.94550508f, 0.32155997f,
+				-0.10106669f, 0.94562930f, 0.30914524f,
+				-0.14811778f, 0.94574553f, 0.28918239f,
+				-0.19121127f, 0.94582283f, 0.26240703f,
+				-0.22953111f, 0.94584930f, 0.22953111f,
+				-0.26240712f, 0.94582278f, 0.19121137f,
+				-0.28918216f, 0.94574559f, 0.14811763f,
+				-0.30914542f, 0.94562918f, 0.10106679f,
+				-0.32155982f, 0.94550508f, 0.05117931f,
+				-0.32579350f, 0.94544101f, 0.00000000f,
+				0.00000000f, 0.80000049f, 0.59999937f,
+				-0.09426988f, 0.80018443f, 0.59229898f,
+				-0.18621883f, 0.80054110f, 0.56961066f,
+				-0.27299255f, 0.80087554f, 0.53298545f,
+				-0.35248604f, 0.80109799f, 0.48373094f,
+				-0.42315483f, 0.80117410f, 0.42315483f,
+				-0.48373121f, 0.80109781f, 0.35248625f,
+				-0.53298503f, 0.80087590f, 0.27299234f,
+				-0.56961095f, 0.80054098f, 0.18621901f,
+				-0.59229875f, 0.80018461f, 0.09426989f,
+				-0.59999937f, 0.80000049f, 0.00000000f,
+				0.00000000f, 0.00000000f, 0.99999994f,
+				-0.15718086f, 0.00000000f, 0.98756981f,
+				-0.31073883f, 0.00000000f, 0.95049530f,
+				-0.45587584f, 0.00000000f, 0.89004338f,
+				-0.58891648f, 0.00000000f, 0.80819392f,
+				-0.70710683f, 0.00000000f, 0.70710683f,
+				-0.80819392f, 0.00000000f, 0.58891642f,
+				-0.89004338f, 0.00000000f, 0.45587584f,
+				-0.95049530f, 0.00000000f, 0.31073889f,
+				-0.98756981f, 0.00000000f, 0.15718089f,
+				-0.99999994f, 0.00000000f, -0.00000000f,
+				-0.60000026f, 0.79999977f, 0.00000000f,
+				-0.59229946f, 0.80018401f, -0.09426999f,
+				-0.56961179f, 0.80054033f, -0.18621901f,
+				-0.53298604f, 0.80087507f, -0.27299276f,
+				-0.48373181f, 0.80109727f, -0.35248649f,
+				-0.42315531f, 0.80117357f, -0.42315534f,
+				-0.35248691f, 0.80109721f, -0.48373157f,
+				-0.27299273f, 0.80087519f, -0.53298575f,
+				-0.18621917f, 0.80054057f, -0.56961155f,
+				-0.09426985f, 0.80018407f, -0.59229940f,
+				0.00000000f, 0.79999989f, -0.60000014f,
+				-0.35491535f, 0.93489844f, 0.00000000f,
+				-0.35030776f, 0.93497372f, -0.05575490f,
+				-0.33679211f, 0.93511921f, -0.11010504f,
+				-0.31505084f, 0.93525589f, -0.16136752f,
+				-0.28588474f, 0.93534660f, -0.20831876f,
+				-0.25006852f, 0.93537766f, -0.25006849f,
+				-0.20831898f, 0.93534660f, -0.28588462f,
+				-0.16136733f, 0.93525600f, -0.31505051f,
+				-0.11010498f, 0.93511939f, -0.33679172f,
+				-0.05575464f, 0.93497372f, -0.35030770f,
+				0.00000000f, 0.93489850f, -0.35491538f,
+				-0.23813862f, 0.97123122f, 0.00000000f,
+				-0.23503657f, 0.97126639f, -0.03740831f,
+				-0.22594899f, 0.97133446f, -0.07386778f,
+				-0.21134609f, 0.97139835f, -0.10825043f,
+				-0.19177027f, 0.97144073f, -0.13973957f,
+				-0.16774194f, 0.97145522f, -0.16774194f,
+				-0.13973959f, 0.97144073f, -0.19177033f,
+				-0.10825032f, 0.97139841f, -0.21134591f,
+				-0.07386787f, 0.97133446f, -0.22594878f,
+				-0.03740822f, 0.97126633f, -0.23503660f,
+				0.00000000f, 0.97123122f, -0.23813862f,
+				-0.17979470f, 0.98370415f, 0.00000000f,
+				-0.17744993f, 0.98372447f, -0.02824289f,
+				-0.17058375f, 0.98376375f, -0.05576761f,
+				-0.15955459f, 0.98380065f, -0.08172295f,
+				-0.14477316f, 0.98382503f, -0.10549361f,
+				-0.12663263f, 0.98383349f, -0.12663262f,
+				-0.10549361f, 0.98382503f, -0.14477316f,
+				-0.08172286f, 0.98380071f, -0.15955447f,
+				-0.05576771f, 0.98376375f, -0.17058356f,
+				-0.02824305f, 0.98372453f, -0.17744984f,
+				0.00000000f, 0.98370415f, -0.17979471f,
+				-0.15294123f, 0.98823529f, 0.00000000f,
+				-0.15094571f, 0.98825008f, -0.02402467f,
+				-0.14510347f, 0.98827863f, -0.04743762f,
+				-0.13572033f, 0.98830533f, -0.06951531f,
+				-0.12314621f, 0.98832321f, -0.08973438f,
+				-0.10771532f, 0.98832929f, -0.10771532f,
+				-0.08973443f, 0.98832321f, -0.12314620f,
+				-0.06951524f, 0.98830539f, -0.13572021f,
+				-0.04743744f, 0.98827863f, -0.14510347f,
+				-0.02402461f, 0.98825002f, -0.15094574f,
+				0.00000000f, 0.98823535f, -0.15294123f,
+				-0.14834042f, 0.98893636f, 0.00000000f,
+				-0.14640494f, 0.98895019f, -0.02330164f,
+				-0.14073798f, 0.98897713f, -0.04601044f,
+				-0.13163701f, 0.98900241f, -0.06742381f,
+				-0.11944108f, 0.98901922f, -0.08703452f,
+				-0.10447443f, 0.98902482f, -0.10447443f,
+				-0.08703458f, 0.98901910f, -0.11944108f,
+				-0.06742378f, 0.98900241f, -0.13163690f,
+				-0.04601069f, 0.98897713f, -0.14073797f,
+				-0.02330185f, 0.98895031f, -0.14640486f,
+				0.00000000f, 0.98893636f, -0.14834042f,
+				-0.16577734f, 0.98616314f, 0.00000000f,
+				-0.16361488f, 0.98618054f, -0.02604098f,
+				-0.15728295f, 0.98621398f, -0.05141935f,
+				-0.14711300f, 0.98624545f, -0.07535055f,
+				-0.13348369f, 0.98626626f, -0.09726719f,
+				-0.11675765f, 0.98627347f, -0.11675765f,
+				-0.09726722f, 0.98626631f, -0.13348375f,
+				-0.07535047f, 0.98624545f, -0.14711283f,
+				-0.05141941f, 0.98621410f, -0.15728293f,
+				-0.02604095f, 0.98618054f, -0.16361482f,
+				0.00000000f, 0.98616314f, -0.16577734f,
+				-0.21457930f, 0.97670650f, 0.00000000f,
+				-0.21178281f, 0.97673523f, -0.03370722f,
+				-0.20359138f, 0.97679090f, -0.06655866f,
+				-0.19043125f, 0.97684300f, -0.09753795f,
+				-0.17279114f, 0.97687763f, -0.12590984f,
+				-0.15114051f, 0.97688949f, -0.15114051f,
+				-0.12590988f, 0.97687763f, -0.17279126f,
+				-0.09753782f, 0.97684312f, -0.19043098f,
+				-0.06655872f, 0.97679090f, -0.20359136f,
+				-0.03370715f, 0.97673535f, -0.21178272f,
+				0.00000000f, 0.97670650f, -0.21457930f,
+				-0.32579350f, 0.94544101f, 0.00000000f,
+				-0.32155997f, 0.94550508f, -0.05117927f,
+				-0.30914524f, 0.94562930f, -0.10106669f,
+				-0.28918239f, 0.94574553f, -0.14811778f,
+				-0.26240703f, 0.94582283f, -0.19121127f,
+				-0.22953111f, 0.94584930f, -0.22953111f,
+				-0.19121137f, 0.94582283f, -0.26240712f,
+				-0.14811763f, 0.94574559f, -0.28918216f,
+				-0.10106679f, 0.94562918f, -0.30914542f,
+				-0.05117931f, 0.94550508f, -0.32155982f,
+				0.00000000f, 0.94544101f, -0.32579350f,
+				-0.59999937f, 0.80000049f, 0.00000000f,
+				-0.59229898f, 0.80018443f, -0.09426988f,
+				-0.56961066f, 0.80054110f, -0.18621883f,
+				-0.53298545f, 0.80087554f, -0.27299255f,
+				-0.48373094f, 0.80109799f, -0.35248604f,
+				-0.42315483f, 0.80117410f, -0.42315483f,
+				-0.35248625f, 0.80109781f, -0.48373121f,
+				-0.27299234f, 0.80087590f, -0.53298503f,
+				-0.18621901f, 0.80054098f, -0.56961095f,
+				-0.09426989f, 0.80018461f, -0.59229875f,
+				0.00000000f, 0.80000049f, -0.59999937f,
+				-0.99999994f, 0.00000000f, -0.00000000f,
+				-0.98756981f, -0.00000000f, -0.15718086f,
+				-0.95049530f, -0.00000000f, -0.31073883f,
+				-0.89004338f, -0.00000000f, -0.45587584f,
+				-0.80819392f, -0.00000000f, -0.58891648f,
+				-0.70710683f, -0.00000000f, -0.70710683f,
+				-0.58891642f, -0.00000000f, -0.80819392f,
+				-0.45587584f, -0.00000000f, -0.89004338f,
+				-0.31073889f, -0.00000000f, -0.95049530f,
+				-0.15718089f, -0.00000000f, -0.98756981f,
+				0.00000000f, 0.00000000f, -0.99999994f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -0.99992561f, 0.01220202f,
+				0.00191707f, -0.99992567f, 0.01204260f,
+				0.00378433f, -0.99992579f, 0.01157632f,
+				0.00554587f, -0.99992591f, 0.01082732f,
+				0.00715852f, -0.99992603f, 0.00982409f,
+				0.00859298f, -0.99992615f, 0.00859298f,
+				0.00982405f, -0.99992603f, 0.00715861f,
+				0.01082739f, -0.99992591f, 0.00554574f,
+				0.01157622f, -0.99992579f, 0.00378453f,
+				0.01204260f, -0.99992561f, 0.00191707f,
+				0.01220204f, -0.99992549f, 0.00000000f,
+				0.00000000f, -0.99958426f, 0.02883408f,
+				0.00452943f, -0.99958473f, 0.02845749f,
+				0.00894296f, -0.99958575f, 0.02735529f,
+				0.01310474f, -0.99958670f, 0.02558575f,
+				0.01691613f, -0.99958730f, 0.02321488f,
+				0.02030576f, -0.99958766f, 0.02030576f,
+				0.02321492f, -0.99958742f, 0.01691616f,
+				0.02558579f, -0.99958670f, 0.01310467f,
+				0.02735530f, -0.99958575f, 0.00894308f,
+				0.02845747f, -0.99958473f, 0.00452948f,
+				0.02883412f, -0.99958426f, 0.00000000f,
+				0.00000000f, -0.99862915f, 0.05234338f,
+				0.00822229f, -0.99863088f, 0.05165981f,
+				0.01623459f, -0.99863428f, 0.04965891f,
+				0.02378965f, -0.99863744f, 0.04644668f,
+				0.03070865f, -0.99863952f, 0.04214293f,
+				0.03686193f, -0.99864024f, 0.03686193f,
+				0.04214287f, -0.99863952f, 0.03070885f,
+				0.04644670f, -0.99863750f, 0.02378960f,
+				0.04965906f, -0.99863428f, 0.01623469f,
+				0.05165980f, -0.99863088f, 0.00822228f,
+				0.05234344f, -0.99862915f, 0.00000000f,
+				0.00000000f, -0.99619961f, 0.08709927f,
+				0.01368168f, -0.99620444f, 0.08596209f,
+				0.02701467f, -0.99621379f, 0.08263328f,
+				0.03958682f, -0.99622256f, 0.07728840f,
+				0.05110020f, -0.99622840f, 0.07012693f,
+				0.06133937f, -0.99623030f, 0.06133937f,
+				0.07012701f, -0.99622846f, 0.05110029f,
+				0.07728842f, -0.99622256f, 0.03958675f,
+				0.08263330f, -0.99621379f, 0.02701474f,
+				0.08596208f, -0.99620444f, 0.01368166f,
+				0.08709935f, -0.99619961f, 0.00000000f,
+				0.00000000f, -0.98994946f, 0.14142129f,
+				0.02221482f, -0.98996216f, 0.13957602f,
+				0.04386425f, -0.98998660f, 0.13417313f,
+				0.06427860f, -0.99000955f, 0.12549633f,
+				0.08297431f, -0.99002475f, 0.11386906f,
+				0.09960055f, -0.99002999f, 0.09960055f,
+				0.11386913f, -0.99002481f, 0.08297440f,
+				0.12549631f, -0.99000955f, 0.06427859f,
+				0.13417305f, -0.98998660f, 0.04386439f,
+				0.13957596f, -0.98996210f, 0.02221479f,
+				0.14142138f, -0.98994946f, 0.00000000f,
+				0.00000000f, -0.97268748f, 0.23211849f,
+				0.03646255f, -0.97272098f, 0.22909468f,
+				0.07200014f, -0.97278571f, 0.22023587f,
+				0.10551301f, -0.97284645f, 0.20600158f,
+				0.13620538f, -0.97288692f, 0.18692032f,
+				0.16349944f, -0.97290081f, 0.16349944f,
+				0.18692048f, -0.97288686f, 0.13620566f,
+				0.20600158f, -0.97284657f, 0.10551302f,
+				0.22023584f, -0.97278577f, 0.07200018f,
+				0.22909461f, -0.97272098f, 0.03646262f,
+				0.23211864f, -0.97268748f, 0.00000000f,
+				0.00000000f, -0.91959101f, 0.39287689f,
+				0.06171954f, -0.91968167f, 0.38778374f,
+				0.12188835f, -0.91985726f, 0.37283489f,
+				0.17864260f, -0.92002207f, 0.34877822f,
+				0.23062557f, -0.92013133f, 0.31649676f,
+				0.27684769f, -0.92016888f, 0.27684754f,
+				0.31649706f, -0.92013121f, 0.23062554f,
+				0.34877831f, -0.92002207f, 0.17864247f,
+				0.37283483f, -0.91985726f, 0.12188824f,
+				0.38778353f, -0.91968179f, 0.06171941f,
+				0.39287689f, -0.91959101f, 0.00000000f,
+				0.00000000f, -0.74740958f, 0.66436350f,
+				0.10438798f, -0.74762046f, 0.65587103f,
+				0.20622680f, -0.74802911f, 0.63081133f,
+				0.30235204f, -0.74841273f, 0.59030640f,
+				0.39041957f, -0.74866760f, 0.53578854f,
+				0.46870315f, -0.74875546f, 0.46870315f,
+				0.53578901f, -0.74866718f, 0.39041984f,
+				0.59030616f, -0.74841291f, 0.30235201f,
+				0.63081133f, -0.74802911f, 0.20622671f,
+				0.65587062f, -0.74762082f, 0.10438793f,
+				0.66436350f, -0.74740958f, 0.00000000f,
+				0.00000000f, -0.35008657f, 0.93671733f,
+				0.14722249f, -0.35028356f, 0.92500114f,
+				0.29100731f, -0.35066363f, 0.89014024f,
+				0.42686713f, -0.35102227f, 0.83340722f,
+				0.55138916f, -0.35126165f, 0.75669360f,
+				0.66202652f, -0.35134289f, 0.66202652f,
+				0.75669384f, -0.35126114f, 0.55138922f,
+				0.83340722f, -0.35102227f, 0.42686713f,
+				0.89013988f, -0.35066500f, 0.29100722f,
+				0.92500126f, -0.35028300f, 0.14722241f,
+				0.93671727f, -0.35008690f, 0.00000000f,
+				0.00000000f, 0.00000000f, 1.00000000f,
+				0.15718076f, -0.00000000f, 0.98756981f,
+				0.31073880f, -0.00000000f, 0.95049536f,
+				0.45587587f, -0.00000000f, 0.89004326f,
+				0.58891648f, -0.00000000f, 0.80819386f,
+				0.70710683f, -0.00000000f, 0.70710683f,
+				0.80819392f, -0.00000000f, 0.58891648f,
+				0.89004338f, -0.00000000f, 0.45587590f,
+				0.95049524f, -0.00000000f, 0.31073886f,
+				0.98756975f, -0.00000000f, 0.15718083f,
+				1.00000000f, -0.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.01220204f, -0.99992561f, 0.00000000f,
+				0.01204262f, -0.99992567f, -0.00191707f,
+				0.01157635f, -0.99992579f, -0.00378435f,
+				0.01082734f, -0.99992597f, -0.00554588f,
+				0.00982409f, -0.99992603f, -0.00715852f,
+				0.00859299f, -0.99992621f, -0.00859299f,
+				0.00715861f, -0.99992603f, -0.00982405f,
+				0.00554574f, -0.99992591f, -0.01082739f,
+				0.00378453f, -0.99992585f, -0.01157622f,
+				0.00191707f, -0.99992567f, -0.01204260f,
+				-0.00000000f, -0.99992561f, -0.01220204f,
+				0.02883412f, -0.99958426f, 0.00000000f,
+				0.02845753f, -0.99958473f, -0.00452944f,
+				0.02735537f, -0.99958575f, -0.00894299f,
+				0.02558579f, -0.99958676f, -0.01310476f,
+				0.02321488f, -0.99958736f, -0.01691613f,
+				0.02030579f, -0.99958766f, -0.02030579f,
+				0.01691616f, -0.99958742f, -0.02321492f,
+				0.01310467f, -0.99958670f, -0.02558579f,
+				0.00894308f, -0.99958575f, -0.02735530f,
+				0.00452948f, -0.99958473f, -0.02845747f,
+				-0.00000000f, -0.99958426f, -0.02883412f,
+				0.05234344f, -0.99862915f, 0.00000000f,
+				0.05165986f, -0.99863088f, -0.00822230f,
+				0.04965902f, -0.99863428f, -0.01623463f,
+				0.04644673f, -0.99863744f, -0.02378968f,
+				0.04214293f, -0.99863952f, -0.03070865f,
+				0.03686197f, -0.99864024f, -0.03686197f,
+				0.03070885f, -0.99863952f, -0.04214287f,
+				0.02378960f, -0.99863750f, -0.04644670f,
+				0.01623469f, -0.99863428f, -0.04965906f,
+				0.00822228f, -0.99863094f, -0.05165981f,
+				-0.00000000f, -0.99862915f, -0.05234344f,
+				0.08709935f, -0.99619961f, 0.00000000f,
+				0.08596216f, -0.99620444f, -0.01368170f,
+				0.08263341f, -0.99621379f, -0.02701472f,
+				0.07728846f, -0.99622256f, -0.03958685f,
+				0.07012693f, -0.99622840f, -0.05110020f,
+				0.06133943f, -0.99623030f, -0.06133943f,
+				0.05110029f, -0.99622846f, -0.07012701f,
+				0.03958676f, -0.99622262f, -0.07728843f,
+				0.02701474f, -0.99621379f, -0.08263331f,
+				0.01368166f, -0.99620444f, -0.08596208f,
+				-0.00000000f, -0.99619961f, -0.08709935f,
+				0.14142138f, -0.98994946f, 0.00000000f,
+				0.13957602f, -0.98996216f, -0.02221482f,
+				0.13417330f, -0.98998660f, -0.04386431f,
+				0.12549642f, -0.99000955f, -0.06427864f,
+				0.11386906f, -0.99002475f, -0.08297431f,
+				0.09960062f, -0.99002999f, -0.09960062f,
+				0.08297440f, -0.99002481f, -0.11386913f,
+				0.06427859f, -0.99000955f, -0.12549631f,
+				0.04386439f, -0.98998660f, -0.13417305f,
+				0.02221479f, -0.98996210f, -0.13957596f,
+				-0.00000000f, -0.98994946f, -0.14142138f,
+				0.23211864f, -0.97268748f, 0.00000000f,
+				0.22909468f, -0.97272098f, -0.03646255f,
+				0.22023600f, -0.97278571f, -0.07200018f,
+				0.20600171f, -0.97284645f, -0.10551308f,
+				0.18692029f, -0.97288686f, -0.13620538f,
+				0.16349953f, -0.97290069f, -0.16349953f,
+				0.13620566f, -0.97288686f, -0.18692048f,
+				0.10551302f, -0.97284657f, -0.20600158f,
+				0.07200018f, -0.97278577f, -0.22023584f,
+				0.03646262f, -0.97272098f, -0.22909461f,
+				-0.00000000f, -0.97268748f, -0.23211864f,
+				0.39287689f, -0.91959101f, 0.00000000f,
+				0.38778380f, -0.91968179f, -0.06171946f,
+				0.37283456f, -0.91985744f, -0.12188818f,
+				0.34877828f, -0.92002207f, -0.17864251f,
+				0.31649682f, -0.92013133f, -0.23062550f,
+				0.27684763f, -0.92016888f, -0.27684763f,
+				0.23062561f, -0.92013121f, -0.31649697f,
+				0.17864254f, -0.92002207f, -0.34877825f,
+				0.12188834f, -0.91985726f, -0.37283480f,
+				0.06171951f, -0.91968179f, -0.38778350f,
+				-0.00000000f, -0.91959101f, -0.39287689f,
+				0.66436350f, -0.74740958f, 0.00000000f,
+				0.65587103f, -0.74762046f, -0.10438798f,
+				0.63081098f, -0.74802947f, -0.20622666f,
+				0.59030640f, -0.74841273f, -0.30235204f,
+				0.53578854f, -0.74866760f, -0.39041957f,
+				0.46870315f, -0.74875546f, -0.46870315f,
+				0.39041984f, -0.74866718f, -0.53578901f,
+				0.30235201f, -0.74841291f, -0.59030616f,
+				0.20622671f, -0.74802911f, -0.63081133f,
+				0.10438793f, -0.74762082f, -0.65587062f,
+				-0.00000000f, -0.74740958f, -0.66436350f,
+				0.93671727f, -0.35008690f, 0.00000000f,
+				0.92500097f, -0.35028383f, -0.14722244f,
+				0.89014006f, -0.35066432f, -0.29100725f,
+				0.83340710f, -0.35102299f, -0.42686704f,
+				0.75669360f, -0.35126165f, -0.55138916f,
+				0.66202641f, -0.35134318f, -0.66202641f,
+				0.55138922f, -0.35126114f, -0.75669384f,
+				0.42686713f, -0.35102227f, -0.83340722f,
+				0.29100722f, -0.35066500f, -0.89013988f,
+				0.14722241f, -0.35028300f, -0.92500132f,
+				-0.00000000f, -0.35008690f, -0.93671727f,
+				1.00000000f, -0.00000000f, 0.00000000f,
+				0.98756981f, 0.00000000f, -0.15718076f,
+				0.95049536f, 0.00000000f, -0.31073886f,
+				0.89004332f, 0.00000000f, -0.45587593f,
+				0.80819386f, 0.00000000f, -0.58891648f,
+				0.70710677f, 0.00000000f, -0.70710677f,
+				0.58891648f, 0.00000000f, -0.80819392f,
+				0.45587590f, 0.00000000f, -0.89004338f,
+				0.31073886f, 0.00000000f, -0.95049524f,
+				0.15718083f, 0.00000000f, -0.98756975f,
+				0.00000000f, 0.00000000f, -1.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				-0.01220204f, -0.99992549f, 0.00000000f,
+				-0.01204262f, -0.99992567f, 0.00191707f,
+				-0.01157633f, -0.99992579f, 0.00378434f,
+				-0.01082734f, -0.99992591f, 0.00554588f,
+				-0.00982409f, -0.99992603f, 0.00715852f,
+				-0.00859298f, -0.99992615f, 0.00859298f,
+				-0.00715860f, -0.99992603f, 0.00982403f,
+				-0.00554574f, -0.99992591f, 0.01082739f,
+				-0.00378453f, -0.99992585f, 0.01157620f,
+				-0.00191707f, -0.99992567f, 0.01204258f,
+				0.00000000f, -0.99992561f, 0.01220202f,
+				-0.02883412f, -0.99958426f, 0.00000000f,
+				-0.02845753f, -0.99958473f, 0.00452944f,
+				-0.02735532f, -0.99958575f, 0.00894297f,
+				-0.02558579f, -0.99958670f, 0.01310476f,
+				-0.02321488f, -0.99958730f, 0.01691613f,
+				-0.02030576f, -0.99958766f, 0.02030576f,
+				-0.01691614f, -0.99958742f, 0.02321489f,
+				-0.01310467f, -0.99958670f, 0.02558579f,
+				-0.00894307f, -0.99958575f, 0.02735526f,
+				-0.00452947f, -0.99958473f, 0.02845743f,
+				0.00000000f, -0.99958426f, 0.02883408f,
+				-0.05234344f, -0.99862915f, 0.00000000f,
+				-0.05165986f, -0.99863088f, 0.00822230f,
+				-0.04965896f, -0.99863428f, 0.01623461f,
+				-0.04644673f, -0.99863744f, 0.02378967f,
+				-0.04214293f, -0.99863946f, 0.03070865f,
+				-0.03686193f, -0.99864024f, 0.03686193f,
+				-0.03070882f, -0.99863952f, 0.04214283f,
+				-0.02378960f, -0.99863750f, 0.04644670f,
+				-0.01623467f, -0.99863428f, 0.04965901f,
+				-0.00822227f, -0.99863088f, 0.05165974f,
+				0.00000000f, -0.99862915f, 0.05234338f,
+				-0.08709935f, -0.99619961f, 0.00000000f,
+				-0.08596216f, -0.99620444f, 0.01368170f,
+				-0.08263335f, -0.99621379f, 0.02701470f,
+				-0.07728847f, -0.99622256f, 0.03958685f,
+				-0.07012694f, -0.99622846f, 0.05110020f,
+				-0.06133937f, -0.99623030f, 0.06133937f,
+				-0.05110025f, -0.99622846f, 0.07012695f,
+				-0.03958676f, -0.99622262f, 0.07728843f,
+				-0.02701471f, -0.99621379f, 0.08263323f,
+				-0.01368164f, -0.99620444f, 0.08596201f,
+				0.00000000f, -0.99619961f, 0.08709927f,
+				-0.14142138f, -0.98994946f, 0.00000000f,
+				-0.13957602f, -0.98996216f, 0.02221482f,
+				-0.13417321f, -0.98998666f, 0.04386427f,
+				-0.12549642f, -0.99000955f, 0.06427864f,
+				-0.11386906f, -0.99002475f, 0.08297431f,
+				-0.09960055f, -0.99002999f, 0.09960055f,
+				-0.08297434f, -0.99002481f, 0.11386906f,
+				-0.06427859f, -0.99000955f, 0.12549631f,
+				-0.04386435f, -0.98998660f, 0.13417295f,
+				-0.02221478f, -0.98996210f, 0.13957585f,
+				0.00000000f, -0.98994946f, 0.14142129f,
+				-0.23211864f, -0.97268748f, 0.00000000f,
+				-0.22909468f, -0.97272098f, 0.03646255f,
+				-0.22023587f, -0.97278571f, 0.07200014f,
+				-0.20600173f, -0.97284651f, 0.10551308f,
+				-0.18692032f, -0.97288692f, 0.13620538f,
+				-0.16349944f, -0.97290081f, 0.16349944f,
+				-0.13620557f, -0.97288686f, 0.18692036f,
+				-0.10551302f, -0.97284657f, 0.20600158f,
+				-0.07200012f, -0.97278577f, 0.22023571f,
+				-0.03646260f, -0.97272110f, 0.22909448f,
+				0.00000000f, -0.97268748f, 0.23211849f,
+				-0.39287689f, -0.91959101f, 0.00000000f,
+				-0.38778380f, -0.91968179f, 0.06171943f,
+				-0.37283459f, -0.91985744f, 0.12188814f,
+				-0.34877831f, -0.92002207f, 0.17864247f,
+				-0.31649688f, -0.92013133f, 0.23062544f,
+				-0.27684769f, -0.92016888f, 0.27684754f,
+				-0.23062566f, -0.92013121f, 0.31649694f,
+				-0.17864263f, -0.92002207f, 0.34877819f,
+				-0.12188834f, -0.91985726f, 0.37283480f,
+				-0.06171941f, -0.91968179f, 0.38778353f,
+				0.00000000f, -0.91959101f, 0.39287689f,
+				-0.66436350f, -0.74740958f, 0.00000000f,
+				-0.65587103f, -0.74762046f, 0.10438798f,
+				-0.63081098f, -0.74802947f, 0.20622666f,
+				-0.59030640f, -0.74841273f, 0.30235204f,
+				-0.53578854f, -0.74866760f, 0.39041957f,
+				-0.46870315f, -0.74875546f, 0.46870315f,
+				-0.39041984f, -0.74866718f, 0.53578901f,
+				-0.30235201f, -0.74841291f, 0.59030616f,
+				-0.20622671f, -0.74802911f, 0.63081133f,
+				-0.10438793f, -0.74762082f, 0.65587062f,
+				0.00000000f, -0.74740958f, 0.66436350f,
+				-0.93671727f, -0.35008690f, 0.00000000f,
+				-0.92500097f, -0.35028383f, 0.14722244f,
+				-0.89014024f, -0.35066363f, 0.29100731f,
+				-0.83340710f, -0.35102299f, 0.42686704f,
+				-0.75669360f, -0.35126165f, 0.55138916f,
+				-0.66202652f, -0.35134289f, 0.66202652f,
+				-0.55138934f, -0.35126081f, 0.75669390f,
+				-0.42686713f, -0.35102227f, 0.83340722f,
+				-0.29100725f, -0.35066465f, 0.89013994f,
+				-0.14722243f, -0.35028264f, 0.92500138f,
+				0.00000000f, -0.35008657f, 0.93671733f,
+				-1.00000000f, 0.00000000f, 0.00000000f,
+				-0.98756981f, 0.00000000f, 0.15718076f,
+				-0.95049542f, 0.00000000f, 0.31073883f,
+				-0.89004332f, 0.00000000f, 0.45587593f,
+				-0.80819386f, 0.00000000f, 0.58891648f,
+				-0.70710683f, 0.00000000f, 0.70710683f,
+				-0.58891654f, 0.00000000f, 0.80819386f,
+				-0.45587590f, 0.00000000f, 0.89004338f,
+				-0.31073889f, 0.00000000f, 0.95049536f,
+				-0.15718086f, 0.00000000f, 0.98756987f,
+				0.00000000f, 0.00000000f, 1.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				-0.00000000f, -0.99992561f, -0.01220204f,
+				-0.00191707f, -0.99992567f, -0.01204262f,
+				-0.00378434f, -0.99992579f, -0.01157635f,
+				-0.00554588f, -0.99992591f, -0.01082734f,
+				-0.00715852f, -0.99992603f, -0.00982409f,
+				-0.00859299f, -0.99992621f, -0.00859299f,
+				-0.00982405f, -0.99992603f, -0.00715861f,
+				-0.01082739f, -0.99992591f, -0.00554574f,
+				-0.01157622f, -0.99992585f, -0.00378453f,
+				-0.01204260f, -0.99992567f, -0.00191707f,
+				-0.01220204f, -0.99992561f, 0.00000000f,
+				-0.00000000f, -0.99958426f, -0.02883412f,
+				-0.00452944f, -0.99958473f, -0.02845753f,
+				-0.00894299f, -0.99958575f, -0.02735537f,
+				-0.01310476f, -0.99958670f, -0.02558579f,
+				-0.01691613f, -0.99958730f, -0.02321488f,
+				-0.02030579f, -0.99958766f, -0.02030579f,
+				-0.02321492f, -0.99958742f, -0.01691616f,
+				-0.02558579f, -0.99958670f, -0.01310467f,
+				-0.02735530f, -0.99958575f, -0.00894308f,
+				-0.02845747f, -0.99958473f, -0.00452948f,
+				-0.02883412f, -0.99958426f, 0.00000000f,
+				-0.00000000f, -0.99862915f, -0.05234344f,
+				-0.00822230f, -0.99863088f, -0.05165986f,
+				-0.01623463f, -0.99863428f, -0.04965902f,
+				-0.02378967f, -0.99863744f, -0.04644673f,
+				-0.03070865f, -0.99863952f, -0.04214294f,
+				-0.03686197f, -0.99864024f, -0.03686197f,
+				-0.04214287f, -0.99863952f, -0.03070885f,
+				-0.04644670f, -0.99863750f, -0.02378960f,
+				-0.04965907f, -0.99863434f, -0.01623469f,
+				-0.05165980f, -0.99863088f, -0.00822228f,
+				-0.05234344f, -0.99862915f, 0.00000000f,
+				-0.00000000f, -0.99619961f, -0.08709935f,
+				-0.01368170f, -0.99620444f, -0.08596216f,
+				-0.02701472f, -0.99621379f, -0.08263342f,
+				-0.03958685f, -0.99622256f, -0.07728846f,
+				-0.05110020f, -0.99622840f, -0.07012693f,
+				-0.06133943f, -0.99623030f, -0.06133943f,
+				-0.07012701f, -0.99622840f, -0.05110029f,
+				-0.07728843f, -0.99622262f, -0.03958676f,
+				-0.08263330f, -0.99621379f, -0.02701474f,
+				-0.08596208f, -0.99620444f, -0.01368166f,
+				-0.08709935f, -0.99619961f, 0.00000000f,
+				-0.00000000f, -0.98994946f, -0.14142138f,
+				-0.02221482f, -0.98996216f, -0.13957602f,
+				-0.04386431f, -0.98998660f, -0.13417332f,
+				-0.06427864f, -0.99000955f, -0.12549642f,
+				-0.08297431f, -0.99002475f, -0.11386906f,
+				-0.09960062f, -0.99002999f, -0.09960062f,
+				-0.11386913f, -0.99002481f, -0.08297440f,
+				-0.12549631f, -0.99000955f, -0.06427859f,
+				-0.13417305f, -0.98998660f, -0.04386439f,
+				-0.13957594f, -0.98996210f, -0.02221479f,
+				-0.14142138f, -0.98994946f, 0.00000000f,
+				-0.00000000f, -0.97268748f, -0.23211864f,
+				-0.03646255f, -0.97272098f, -0.22909468f,
+				-0.07200018f, -0.97278571f, -0.22023600f,
+				-0.10551308f, -0.97284645f, -0.20600171f,
+				-0.13620538f, -0.97288692f, -0.18692032f,
+				-0.16349953f, -0.97290069f, -0.16349953f,
+				-0.18692048f, -0.97288686f, -0.13620566f,
+				-0.20600158f, -0.97284657f, -0.10551302f,
+				-0.22023584f, -0.97278577f, -0.07200018f,
+				-0.22909461f, -0.97272098f, -0.03646262f,
+				-0.23211864f, -0.97268748f, 0.00000000f,
+				-0.00000000f, -0.91959101f, -0.39287689f,
+				-0.06171946f, -0.91968179f, -0.38778380f,
+				-0.12188818f, -0.91985744f, -0.37283456f,
+				-0.17864251f, -0.92002207f, -0.34877828f,
+				-0.23062550f, -0.92013133f, -0.31649682f,
+				-0.27684763f, -0.92016888f, -0.27684763f,
+				-0.31649697f, -0.92013121f, -0.23062561f,
+				-0.34877825f, -0.92002207f, -0.17864254f,
+				-0.37283480f, -0.91985726f, -0.12188834f,
+				-0.38778350f, -0.91968179f, -0.06171951f,
+				-0.39287689f, -0.91959101f, 0.00000000f,
+				-0.00000000f, -0.74740958f, -0.66436350f,
+				-0.10438798f, -0.74762046f, -0.65587103f,
+				-0.20622666f, -0.74802947f, -0.63081098f,
+				-0.30235204f, -0.74841273f, -0.59030640f,
+				-0.39041957f, -0.74866760f, -0.53578854f,
+				-0.46870315f, -0.74875546f, -0.46870315f,
+				-0.53578901f, -0.74866718f, -0.39041984f,
+				-0.59030616f, -0.74841291f, -0.30235201f,
+				-0.63081133f, -0.74802911f, -0.20622671f,
+				-0.65587062f, -0.74762082f, -0.10438793f,
+				-0.66436350f, -0.74740958f, 0.00000000f,
+				-0.00000000f, -0.35008690f, -0.93671727f,
+				-0.14722244f, -0.35028386f, -0.92500097f,
+				-0.29100725f, -0.35066432f, -0.89014006f,
+				-0.42686704f, -0.35102299f, -0.83340710f,
+				-0.55138916f, -0.35126165f, -0.75669360f,
+				-0.66202641f, -0.35134318f, -0.66202641f,
+				-0.75669384f, -0.35126114f, -0.55138922f,
+				-0.83340722f, -0.35102227f, -0.42686713f,
+				-0.89013988f, -0.35066500f, -0.29100722f,
+				-0.92500126f, -0.35028300f, -0.14722241f,
+				-0.93671727f, -0.35008690f, 0.00000000f,
+				0.00000000f, 0.00000000f, -1.00000000f,
+				-0.15718076f, 0.00000000f, -0.98756981f,
+				-0.31073880f, 0.00000000f, -0.95049536f,
+				-0.45587593f, 0.00000000f, -0.89004332f,
+				-0.58891648f, 0.00000000f, -0.80819386f,
+				-0.70710677f, 0.00000000f, -0.70710677f,
+				-0.80819392f, 0.00000000f, -0.58891648f,
+				-0.89004338f, 0.00000000f, -0.45587590f,
+				-0.95049524f, 0.00000000f, -0.31073886f,
+				-0.98756975f, 0.00000000f, -0.15718083f,
+				-1.00000000f, 0.00000000f, 0.00000000f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				0.00000000f, -0.98605883f, 0.16639723f,
+				0.00000000f, -0.92847669f, 0.37139052f,
+				0.00000000f, -0.78563637f, 0.61868852f,
+				0.00000000f, -0.48564312f, 0.87415707f,
+				0.00000000f, -0.00000002f, 0.99999994f,
+				0.00000000f, 0.48564315f, 0.87415707f,
+				0.00000000f, 0.78563654f, 0.61868834f,
+				0.00000000f, 0.92847681f, 0.37139040f,
+				0.00000000f, 0.98605877f, 0.16639741f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00352115f, -0.99999374f, 0.00000000f,
+				0.00352907f, -0.98608685f, 0.16619359f,
+				0.00346285f, -0.92863286f, 0.37098381f,
+				0.00309265f, -0.78601688f, 0.61819726f,
+				0.00202332f, -0.48612255f, 0.87388837f,
+				0.00000001f, -0.00000002f, 1.00000000f,
+				-0.00223059f, 0.48613369f, 0.87388152f,
+				-0.00373999f, 0.78603256f, 0.61817366f,
+				-0.00453865f, 0.92863941f, 0.37095594f,
+				-0.00490123f, 0.98608381f, 0.16617684f,
+				-0.00499993f, 0.99998748f, 0.00000000f,
+				0.01562307f, -0.99987787f, 0.00000000f,
+				0.01566775f, -0.98604649f, 0.16573150f,
+				0.01539990f, -0.92885453f, 0.37012452f,
+				0.01378791f, -0.78663695f, 0.61726177f,
+				0.00904643f, -0.48685381f, 0.87343663f,
+				0.00000000f, -0.00000002f, 0.99999994f,
+				-0.01000922f, 0.48666272f, 0.87353265f,
+				-0.01679711f, 0.78630960f, 0.61760414f,
+				-0.02040106f, 0.92859751f, 0.37052733f,
+				-0.02204463f, 0.98588336f, 0.16597590f,
+				-0.02249430f, 0.99974698f, 0.00000000f,
+				0.03959709f, -0.99921572f, 0.00000000f,
+				0.03973298f, -0.98543602f, 0.16533947f,
+				0.03911157f, -0.92837644f, 0.36957741f,
+				0.03508222f, -0.78619260f, 0.61698496f,
+				0.02305110f, -0.48625365f, 0.87351370f,
+				-0.00000000f, -0.00000002f, 1.00000000f,
+				-0.02556038f, 0.48519081f, 0.87403470f,
+				-0.04298534f, 0.78433579f, 0.61884552f,
+				-0.05230516f, 0.92685103f, 0.37176794f,
+				-0.05658430f, 0.98438811f, 0.16666794f,
+				-0.05776057f, 0.99833041f, 0.00000000f,
+				0.08081594f, -0.99672896f, 0.00000000f,
+				0.08112378f, -0.98284930f, 0.16560850f,
+				0.07990950f, -0.92532861f, 0.37065011f,
+				0.07166215f, -0.78199679f, 0.61914897f,
+				0.04697014f, -0.48146683f, 0.87520474f,
+				0.00000001f, -0.00000001f, 0.99999994f,
+				-0.05205173f, 0.47823566f, 0.87668771f,
+				-0.08801019f, 0.77604234f, 0.62450969f,
+				-0.10756110f, 0.91994244f, 0.37701002f,
+				-0.11663799f, 0.97860771f, 0.16947733f,
+				-0.11914521f, 0.99287677f, 0.00000000f,
+				0.14834052f, -0.98893636f, 0.00000000f,
+				0.14888510f, -0.97454345f, 0.16762571f,
+				0.14646719f, -0.91512442f, 0.37562558f,
+				0.13072294f, -0.76828307f, 0.62662005f,
+				0.08480435f, -0.46723226f, 0.88005811f,
+				0.00000000f, -0.00000002f, 1.00000000f,
+				-0.09347267f, 0.45980066f, 0.88308901f,
+				-0.15982038f, 0.75331390f, 0.63794643f,
+				-0.19704285f, 0.89976454f, 0.38935578f,
+				-0.21463276f, 0.96069551f, 0.17605987f,
+				-0.21951221f, 0.97560972f, 0.00000000f,
+				0.25746500f, -0.96628761f, 0.00000000f,
+				0.25805786f, -0.95046312f, 0.17328028f,
+				0.25243360f, -0.88633966f, 0.38817427f,
+				0.22221342f, -0.73262465f, 0.64333671f,
+				0.14082101f, -0.43436509f, 0.88966089f,
+				-0.00000000f, -0.00000003f, 1.00000000f,
+				-0.15323463f, 0.42038015f, 0.89431524f,
+				-0.26688996f, 0.70047694f, 0.66189259f,
+				-0.33389512f, 0.84794778f, 0.41170225f,
+				-0.36636090f, 0.91127318f, 0.18804511f,
+				-0.37538239f, 0.92687011f, 0.00000000f,
+				0.43072972f, -0.90248102f, 0.00000000f,
+				0.42996117f, -0.88366210f, 0.18513416f,
+				0.41455880f, -0.81121510f, 0.41239676f,
+				0.35453796f, -0.65030318f, 0.67186946f,
+				0.21581109f, -0.36961418f, 0.90377587f,
+				0.00000001f, -0.00000001f, 1.00000000f,
+				-0.22965637f, 0.34849611f, 0.90874004f,
+				-0.40901679f, 0.59286922f, 0.69369388f,
+				-0.52110124f, 0.72998118f, 0.44224542f,
+				-0.57683110f, 0.79082137f, 0.20461525f,
+				-0.59223664f, 0.80576408f, 0.00000000f,
+				0.67827994f, -0.73480362f, 0.00000000f,
+				0.67117316f, -0.71288520f, 0.20327650f,
+				0.62980002f, -0.63613772f, 0.44573602f,
+				0.51530230f, -0.48705083f, 0.70515603f,
+				0.29868835f, -0.26309496f, 0.91736919f,
+				0.00000001f, -0.00000001f, 1.00000000f,
+				-0.30877924f, 0.24007824f, 0.92033571f,
+				-0.55776328f, 0.41359809f, 0.71960878f,
+				-0.71884412f, 0.51453108f, 0.46746215f,
+				-0.79948735f, 0.55961394f, 0.21829374f,
+				-0.82137012f, 0.57039577f, 0.00000000f,
+				0.91750979f, -0.39771309f, 0.00000000f,
+				0.89836329f, -0.38162711f, 0.21749499f,
+				0.81937253f, -0.33061025f, 0.46832207f,
+				0.64609301f, -0.24353826f, 0.72336227f,
+				0.36258951f, -0.12712903f, 0.92323726f,
+				0.00000001f, -0.00000000f, 1.00000000f,
+				-0.36578882f, 0.11278567f, 0.92383873f,
+				-0.65935183f, 0.19359557f, 0.72648203f,
+				-0.84755963f, 0.23992470f, 0.47336963f,
+				-0.93993521f, 0.25999326f, 0.22119047f,
+				-0.96436578f, 0.26457235f, 0.00000000f,
+				1.00000000f, 0.00000000f, -0.00000000f,
+				0.97560978f, -0.00000001f, 0.21951191f,
+				0.88235295f, 0.00000000f, 0.47058815f,
+				0.68965501f, 0.00000000f, 0.72413802f,
+				0.38461545f, 0.00000000f, 0.92307693f,
+				0.00000001f, 0.00000000f, 1.00000000f,
+				-0.38461548f, 0.00000000f, 0.92307681f,
+				-0.68965536f, 0.00000000f, 0.72413772f,
+				-0.88235283f, 0.00000000f, 0.47058827f,
+				-0.97560972f, -0.00000000f, 0.21951239f,
+				-0.99999994f, 0.00000000f, -0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				0.00000000f, 0.98605877f, -0.16639729f,
+				0.00000000f, 0.92847687f, -0.37139040f,
+				0.00000000f, 0.78563643f, -0.61868840f,
+				0.00000000f, 0.48564321f, -0.87415713f,
+				-0.00000000f, -0.00000001f, -1.00000000f,
+				-0.00000000f, -0.48564330f, -0.87415713f,
+				-0.00000000f, -0.78563637f, -0.61868852f,
+				-0.00000000f, -0.92847681f, -0.37139040f,
+				-0.00000000f, -0.98605883f, -0.16639724f,
+				0.00000000f, -1.00000000f, 0.00000000f,
+				-0.00499993f, 0.99998748f, 0.00000000f,
+				-0.00490124f, 0.98608375f, -0.16617680f,
+				-0.00453864f, 0.92863935f, -0.37095582f,
+				-0.00373999f, 0.78603268f, -0.61817366f,
+				-0.00223057f, 0.48613375f, -0.87388164f,
+				0.00000001f, -0.00000001f, -1.00000000f,
+				0.00202331f, -0.48612273f, -0.87388825f,
+				0.00309265f, -0.78601700f, -0.61819714f,
+				0.00346281f, -0.92863292f, -0.37098360f,
+				0.00352907f, -0.98608685f, -0.16619354f,
+				0.00352115f, -0.99999374f, 0.00000000f,
+				-0.02249430f, 0.99974698f, 0.00000000f,
+				-0.02204463f, 0.98588336f, -0.16597593f,
+				-0.02040106f, 0.92859745f, -0.37052733f,
+				-0.01679711f, 0.78630960f, -0.61760414f,
+				-0.01000922f, 0.48666272f, -0.87353259f,
+				0.00000000f, -0.00000001f, -1.00000000f,
+				0.00904643f, -0.48685390f, -0.87343657f,
+				0.01378791f, -0.78663707f, -0.61726171f,
+				0.01539990f, -0.92885453f, -0.37012431f,
+				0.01566775f, -0.98604643f, -0.16573155f,
+				0.01562307f, -0.99987787f, 0.00000000f,
+				-0.05776057f, 0.99833041f, 0.00000000f,
+				-0.05658428f, 0.98438817f, -0.16666785f,
+				-0.05230515f, 0.92685103f, -0.37176776f,
+				-0.04298534f, 0.78433579f, -0.61884540f,
+				-0.02556037f, 0.48519066f, -0.87403476f,
+				-0.00000000f, -0.00000001f, -1.00000000f,
+				0.02305111f, -0.48625365f, -0.87351352f,
+				0.03508221f, -0.78619260f, -0.61698502f,
+				0.03911154f, -0.92837650f, -0.36957735f,
+				0.03973297f, -0.98543602f, -0.16533944f,
+				0.03959709f, -0.99921572f, 0.00000000f,
+				-0.11914521f, 0.99287677f, 0.00000000f,
+				-0.11663795f, 0.97860771f, -0.16947742f,
+				-0.10756104f, 0.91994238f, -0.37700999f,
+				-0.08801019f, 0.77604234f, -0.62450969f,
+				-0.05205172f, 0.47823566f, -0.87668771f,
+				0.00000001f, -0.00000001f, -1.00000000f,
+				0.04697014f, -0.48146695f, -0.87520474f,
+				0.07166213f, -0.78199679f, -0.61914903f,
+				0.07990947f, -0.92532867f, -0.37064984f,
+				0.08112375f, -0.98284930f, -0.16560827f,
+				0.08081594f, -0.99672896f, 0.00000000f,
+				-0.21951221f, 0.97560972f, 0.00000000f,
+				-0.21463269f, 0.96069551f, -0.17605998f,
+				-0.19704288f, 0.89976460f, -0.38935572f,
+				-0.15982041f, 0.75331384f, -0.63794643f,
+				-0.09347264f, 0.45980066f, -0.88308901f,
+				0.00000000f, -0.00000001f, -1.00000000f,
+				0.08480434f, -0.46723235f, -0.88005805f,
+				0.13072288f, -0.76828313f, -0.62661994f,
+				0.14646715f, -0.91512454f, -0.37562546f,
+				0.14888506f, -0.97454339f, -0.16762568f,
+				0.14834052f, -0.98893636f, 0.00000000f,
+				-0.37538239f, 0.92687011f, 0.00000000f,
+				-0.36636072f, 0.91127330f, -0.18804498f,
+				-0.33389512f, 0.84794778f, -0.41170213f,
+				-0.26688999f, 0.70047688f, -0.66189259f,
+				-0.15323462f, 0.42038018f, -0.89431512f,
+				-0.00000001f, -0.00000002f, -1.00000000f,
+				0.14082104f, -0.43436518f, -0.88966078f,
+				0.22221328f, -0.73262471f, -0.64333671f,
+				0.25243348f, -0.88633984f, -0.38817394f,
+				0.25805777f, -0.95046312f, -0.17328016f,
+				0.25746500f, -0.96628761f, 0.00000000f,
+				-0.59223664f, 0.80576408f, 0.00000000f,
+				-0.57683092f, 0.79082137f, -0.20461576f,
+				-0.52110112f, 0.72998136f, -0.44224519f,
+				-0.40901700f, 0.59286916f, -0.69369400f,
+				-0.22965620f, 0.34849602f, -0.90874004f,
+				0.00000001f, -0.00000001f, -0.99999994f,
+				0.21581109f, -0.36961427f, -0.90377587f,
+				0.35453799f, -0.65030324f, -0.67186940f,
+				0.41455874f, -0.81121504f, -0.41239697f,
+				0.42996103f, -0.88366216f, -0.18513407f,
+				0.43072972f, -0.90248102f, 0.00000000f,
+				-0.82137012f, 0.57039577f, 0.00000000f,
+				-0.79948711f, 0.55961424f, -0.21829398f,
+				-0.71884418f, 0.51453096f, -0.46746221f,
+				-0.55776322f, 0.41359806f, -0.71960890f,
+				-0.30877942f, 0.24007839f, -0.92033559f,
+				0.00000001f, -0.00000001f, -1.00000000f,
+				0.29868859f, -0.26309517f, -0.91736907f,
+				0.51530242f, -0.48705092f, -0.70515585f,
+				0.62979978f, -0.63613826f, -0.44573584f,
+				0.67117304f, -0.71288520f, -0.20327665f,
+				0.67827994f, -0.73480362f, 0.00000000f,
+				-0.96436578f, 0.26457235f, 0.00000000f,
+				-0.93993515f, 0.25999367f, -0.22119042f,
+				-0.84756005f, 0.23992476f, -0.47336900f,
+				-0.65935189f, 0.19359526f, -0.72648191f,
+				-0.36578867f, 0.11278601f, -0.92383868f,
+				0.00000001f, -0.00000000f, -1.00000000f,
+				0.36258978f, -0.12712936f, -0.92323720f,
+				0.64609277f, -0.24353866f, -0.72336233f,
+				0.81937248f, -0.33061025f, -0.46832204f,
+				0.89836329f, -0.38162714f, -0.21749526f,
+				0.91750979f, -0.39771309f, 0.00000000f,
+				-0.99999994f, 0.00000000f, -0.00000000f,
+				-0.97560984f, 0.00000000f, -0.21951208f,
+				-0.88235319f, -0.00000000f, -0.47058785f,
+				-0.68965513f, -0.00000000f, -0.72413802f,
+				-0.38461575f, -0.00000000f, -0.92307681f,
+				0.00000001f, 0.00000000f, -1.00000000f,
+				0.38461554f, 0.00000000f, -0.92307687f,
+				0.68965495f, 0.00000000f, -0.72413814f,
+				0.88235325f, 0.00000000f, -0.47058773f,
+				0.97560984f, 0.00000001f, -0.21951178f,
+				1.00000000f, 0.00000000f, -0.00000000f,
+				1.00000000f, 0.00000000f, -0.00000000f,
+				0.97560984f, 0.00000000f, 0.21951193f,
+				0.88235295f, 0.00000000f, 0.47058818f,
+				0.68965501f, 0.00000000f, 0.72413808f,
+				0.38461548f, 0.00000000f, 0.92307693f,
+				0.00000001f, 0.00000000f, 1.00000000f,
+				-0.38461551f, 0.00000000f, 0.92307681f,
+				-0.68965536f, 0.00000000f, 0.72413766f,
+				-0.88235295f, 0.00000000f, 0.47058836f,
+				-0.97560972f, 0.00000000f, 0.21951239f,
+				-1.00000000f, 0.00000000f, -0.00000000f,
+				0.98828548f, 0.15261687f, -0.00000000f,
+				0.96395302f, 0.14908172f, 0.22038466f,
+				0.87109631f, 0.13522971f, 0.47212726f,
+				0.67989933f, 0.10607001f, 0.72559357f,
+				0.37859201f, 0.05936911f, 0.92365760f,
+				-0.00000000f, 0.00000002f, 1.00000000f,
+				-0.37849104f, -0.05988481f, 0.92366570f,
+				-0.67957181f, -0.10787752f, 0.72563398f,
+				-0.87054414f, -0.13852927f, 0.47218913f,
+				-0.96324706f, -0.15351437f, 0.22042809f,
+				-0.98752415f, -0.15746772f, -0.00000000f,
+				0.96225405f, 0.27215251f, -0.00000000f,
+				0.93821144f, 0.26704645f, 0.22010332f,
+				0.84699523f, 0.24504340f, 0.47175488f,
+				0.66009367f, 0.19516250f, 0.72538817f,
+				0.36684823f, 0.11101300f, 0.92363340f,
+				-0.00000000f, 0.00000001f, 1.00000000f,
+				-0.36529851f, -0.11522681f, 0.92373145f,
+				-0.65501243f, -0.20990686f, 0.72587734f,
+				-0.83835191f, -0.27185294f, 0.47250620f,
+				-0.92713392f, -0.30289504f, 0.22062922f,
+				-0.95031464f, -0.31129095f, -0.00000000f,
+				0.92727089f, 0.37439111f, -0.00000000f,
+				0.90412927f, 0.36844388f, 0.21633159f,
+				0.81684649f, 0.34092909f, 0.46532696f,
+				0.63770211f, 0.27489299f, 0.71956229f,
+				0.35483336f, 0.15842862f, 0.92140847f,
+				-0.00000000f, -0.00000000f, 1.00000000f,
+				-0.35035580f, -0.16681494f, 0.92164183f,
+				-0.62295628f, -0.30413818f, 0.72071177f,
+				-0.79164261f, -0.39387864f, 0.46707779f,
+				-0.87175226f, -0.43899769f, 0.21755241f,
+				-0.89236629f, -0.45131177f, -0.00000000f,
+				0.88411051f, 0.46727774f, -0.00000000f,
+				0.86261570f, 0.46090138f, 0.20848046f,
+				0.78192133f, 0.42974684f, 0.45157146f,
+				0.61448407f, 0.35097304f, 0.70656025f,
+				0.34419492f, 0.20510472f, 0.91622162f,
+				-0.00000001f, 0.00000001f, 1.00000000f,
+				-0.33593270f, -0.21662095f, 0.91663766f,
+				-0.58751225f, -0.39089262f, 0.70854241f,
+				-0.73610902f, -0.50157106f, 0.45449966f,
+				-0.80393779f, -0.55621833f, 0.21048787f,
+				-0.82090527f, -0.57106435f, -0.00000000f,
+				0.83205020f, 0.55470026f, -0.00000000f,
+				0.81281066f, 0.54816157f, 0.19712368f,
+				0.74092257f, 0.51492828f, 0.43114099f,
+				0.58900535f, 0.42658585f, 0.68636519f,
+				0.33426523f, 0.25343350f, 0.90776551f,
+				-0.00000001f, -0.00000000f, 1.00000000f,
+				-0.32207918f, -0.26620632f, 0.90851486f,
+				-0.55016369f, -0.47074062f, 0.68972695f,
+				-0.67611545f, -0.59405363f, 0.43585345f,
+				-0.73050374f, -0.65288639f, 0.20025899f,
+				-0.74358070f, -0.66864622f, -0.00000000f,
+				0.76992410f, 0.63813549f, -0.00000000f,
+				0.75327897f, 0.63149405f, 0.18381010f,
+				0.69146866f, 0.59718293f, 0.40650177f,
+				0.55801696f, 0.50208545f, 0.66070211f,
+				0.32275081f, 0.30397698f, 0.89634252f,
+				0.00000000f, -0.00000001f, 1.00000000f,
+				-0.30733797f, -0.31568038f, 0.89771330f,
+				-0.51069558f, -0.54324114f, 0.66639268f,
+				-0.61454564f, -0.67152673f, 0.41398731f,
+				-0.65671909f, -0.73016632f, 0.18861942f,
+				-0.66642159f, -0.74557513f, -0.00000000f,
+				0.69667339f, 0.71738845f, -0.00000000f,
+				0.68270814f, 0.71039432f, 0.17102490f,
+				0.63117641f, 0.67495537f, 0.38216704f,
+				0.51756680f, 0.57469726f, 0.63391453f,
+				0.30609715f, 0.35447037f, 0.88354695f,
+				-0.00000001f, -0.00000001f, 1.00000000f,
+				-0.28927997f, -0.36283797f, 0.88581359f,
+				-0.46819973f, -0.60650212f, 0.64260727f,
+				-0.55317837f, -0.73456883f, 0.39294046f,
+				-0.58594245f, -0.79062068f, 0.17773652f,
+				-0.59321886f, -0.80504113f, -0.00000000f,
+				0.61179918f, 0.79101306f, -0.00000000f,
+				0.60046834f, 0.78304410f, 0.16210969f,
+				0.55861497f, 0.74493736f, 0.36471584f,
+				0.46437371f, 0.63860297f, 0.61363119f,
+				0.28028923f, 0.39887190f, 0.87312043f,
+				-0.00000001f, -0.00000002f, 1.00000000f,
+				-0.26522434f, -0.40266120f, 0.87608224f,
+				-0.42191574f, -0.65742224f, 0.62432611f,
+				-0.49311385f, -0.78382802f, 0.37742877f,
+				-0.51994032f, -0.83714205f, 0.16986848f,
+				-0.52585983f, -0.85057127f, -0.00000000f,
+				0.51578081f, 0.85672063f, -0.00000000f,
+				0.50704503f, 0.84674674f, 0.16101368f,
+				0.47396615f, 0.80263937f, 0.36211339f,
+				0.39728773f, 0.68575180f, 0.60984170f,
+				0.24266504f, 0.42777768f, 0.87070084f,
+				-0.00000001f, -0.00000002f, 0.99999994f,
+				-0.23340400f, -0.42794749f, 0.87314582f,
+				-0.37172547f, -0.69227660f, 0.61852515f,
+				-0.43502232f, -0.81983733f, 0.37232029f,
+				-0.45938149f, -0.87235868f, 0.16720937f,
+				-0.46495038f, -0.88533664f, -0.00000000f,
+				0.41036469f, 0.91192144f, -0.00000000f,
+				0.40426248f, 0.89836109f, 0.17181143f,
+				0.37923580f, 0.84274662f, 0.38204512f,
+				0.31827313f, 0.70727378f, 0.63124174f,
+				0.19429605f, 0.43176913f, 0.88080895f,
+				-0.00000001f, -0.00000002f, 1.00000000f,
+				-0.19429621f, -0.43176931f, 0.88080889f,
+				-0.31827319f, -0.70727384f, 0.63124150f,
+				-0.37923595f, -0.84274650f, 0.38204503f,
+				-0.40426233f, -0.89836109f, 0.17181182f,
+				-0.41036457f, -0.91192156f, -0.00000000f,
+				-1.00000000f, 0.00000000f, -0.00000000f,
+				-0.97560984f, -0.00000000f, -0.21951209f,
+				-0.88235319f, -0.00000000f, -0.47058782f,
+				-0.68965513f, -0.00000000f, -0.72413796f,
+				-0.38461569f, -0.00000000f, -0.92307675f,
+				0.00000001f, 0.00000000f, -1.00000000f,
+				0.38461548f, 0.00000000f, -0.92307687f,
+				0.68965495f, 0.00000000f, -0.72413814f,
+				0.88235319f, 0.00000000f, -0.47058773f,
+				0.97560990f, 0.00000000f, -0.21951178f,
+				1.00000000f, 0.00000000f, -0.00000000f,
+				-0.98752415f, -0.15746772f, -0.00000000f,
+				-0.96324700f, -0.15351437f, -0.22042856f,
+				-0.87054449f, -0.13852932f, -0.47218847f,
+				-0.67957199f, -0.10787728f, -0.72563392f,
+				-0.37849066f, -0.05988473f, -0.92366582f,
+				0.00000000f, 0.00000002f, -1.00000000f,
+				0.37859198f, 0.05936907f, -0.92365766f,
+				0.67989922f, 0.10607000f, -0.72559375f,
+				0.87109667f, 0.13522977f, -0.47212654f,
+				0.96395284f, 0.14908154f, -0.22038496f,
+				0.98828548f, 0.15261687f, -0.00000000f,
+				-0.95031464f, -0.31129095f, -0.00000000f,
+				-0.92713398f, -0.30289510f, -0.22062880f,
+				-0.83835226f, -0.27185315f, -0.47250551f,
+				-0.65501243f, -0.20990673f, -0.72587729f,
+				-0.36529854f, -0.11522687f, -0.92373139f,
+				0.00000000f, 0.00000001f, -1.00000000f,
+				0.36684859f, 0.11101311f, -0.92363316f,
+				0.66009378f, 0.19516268f, -0.72538793f,
+				0.84699517f, 0.24504340f, -0.47175524f,
+				0.93821150f, 0.26704639f, -0.22010335f,
+				0.96225405f, 0.27215251f, -0.00000000f,
+				-0.89236629f, -0.45131177f, -0.00000000f,
+				-0.87175226f, -0.43899778f, -0.21755199f,
+				-0.79164267f, -0.39387897f, -0.46707743f,
+				-0.62295640f, -0.30413797f, -0.72071171f,
+				-0.35035577f, -0.16681507f, -0.92164183f,
+				0.00000000f, -0.00000000f, -1.00000000f,
+				0.35483366f, 0.15842879f, -0.92140841f,
+				0.63770241f, 0.27489290f, -0.71956205f,
+				0.81684643f, 0.34092909f, -0.46532708f,
+				0.90412921f, 0.36844400f, -0.21633165f,
+				0.92727089f, 0.37439111f, -0.00000000f,
+				-0.82090527f, -0.57106435f, -0.00000000f,
+				-0.80393773f, -0.55621839f, -0.21048813f,
+				-0.73610926f, -0.50157106f, -0.45449942f,
+				-0.58751225f, -0.39089242f, -0.70854253f,
+				-0.33593246f, -0.21662092f, -0.91663772f,
+				-0.00000001f, 0.00000001f, -1.00000000f,
+				0.34419492f, 0.20510472f, -0.91622150f,
+				0.61448371f, 0.35097295f, -0.70656049f,
+				0.78192151f, 0.42974693f, -0.45157096f,
+				0.86261582f, 0.46090129f, -0.20848002f,
+				0.88411051f, 0.46727774f, -0.00000000f,
+				-0.74358070f, -0.66864622f, -0.00000000f,
+				-0.73050368f, -0.65288651f, -0.20025879f,
+				-0.67611581f, -0.59405386f, -0.43585256f,
+				-0.55016387f, -0.47074053f, -0.68972677f,
+				-0.32207900f, -0.26620629f, -0.90851492f,
+				-0.00000000f, -0.00000000f, -1.00000000f,
+				0.33426523f, 0.25343353f, -0.90776551f,
+				0.58900535f, 0.42658606f, -0.68636519f,
+				0.74092275f, 0.51492846f, -0.43114042f,
+				0.81281078f, 0.54816157f, -0.19712304f,
+				0.83205020f, 0.55470026f, -0.00000000f,
+				-0.66642159f, -0.74557513f, -0.00000000f,
+				-0.65671885f, -0.73016638f, -0.18861963f,
+				-0.61454582f, -0.67152685f, -0.41398701f,
+				-0.51069564f, -0.54324096f, -0.66639268f,
+				-0.30733815f, -0.31568071f, -0.89771318f,
+				0.00000000f, -0.00000001f, -1.00000000f,
+				0.32275072f, 0.30397689f, -0.89634252f,
+				0.55801678f, 0.50208527f, -0.66070235f,
+				0.69146866f, 0.59718293f, -0.40650174f,
+				0.75327885f, 0.63149405f, -0.18381011f,
+				0.76992410f, 0.63813549f, -0.00000000f,
+				-0.59321886f, -0.80504113f, -0.00000000f,
+				-0.58594233f, -0.79062068f, -0.17773685f,
+				-0.55317843f, -0.73456901f, -0.39294016f,
+				-0.46819982f, -0.60650206f, -0.64260739f,
+				-0.28927994f, -0.36283809f, -0.88581365f,
+				-0.00000001f, -0.00000001f, -1.00000000f,
+				0.30609706f, 0.35447022f, -0.88354713f,
+				0.51756680f, 0.57469732f, -0.63391447f,
+				0.63117641f, 0.67495537f, -0.38216686f,
+				0.68270808f, 0.71039438f, -0.17102489f,
+				0.69667339f, 0.71738845f, -0.00000000f,
+				-0.52585983f, -0.85057127f, -0.00000000f,
+				-0.51994026f, -0.83714211f, -0.16986860f,
+				-0.49311391f, -0.78382814f, -0.37742817f,
+				-0.42191574f, -0.65742230f, -0.62432611f,
+				-0.26522428f, -0.40266114f, -0.87608230f,
+				-0.00000001f, -0.00000001f, -1.00000000f,
+				0.28028926f, 0.39887193f, -0.87312031f,
+				0.46437368f, 0.63860303f, -0.61363125f,
+				0.55861503f, 0.74493748f, -0.36471558f,
+				0.60046846f, 0.78304422f, -0.16210929f,
+				0.61179918f, 0.79101306f, -0.00000000f,
+				-0.46495038f, -0.88533664f, -0.00000000f,
+				-0.45938149f, -0.87235868f, -0.16720954f,
+				-0.43502232f, -0.81983721f, -0.37232047f,
+				-0.37172547f, -0.69227666f, -0.61852509f,
+				-0.23340388f, -0.42794740f, -0.87314582f,
+				-0.00000001f, -0.00000001f, -0.99999994f,
+				0.24266508f, 0.42777771f, -0.87070072f,
+				0.39728773f, 0.68575174f, -0.60984188f,
+				0.47396612f, 0.80263925f, -0.36211371f,
+				0.50704491f, 0.84674692f, -0.16101298f,
+				0.51578081f, 0.85672063f, -0.00000000f,
+				-0.41036457f, -0.91192156f, -0.00000000f,
+				-0.40426230f, -0.89836115f, -0.17181180f,
+				-0.37923595f, -0.84274662f, -0.38204485f,
+				-0.31827325f, -0.70727384f, -0.63124156f,
+				-0.19429615f, -0.43176928f, -0.88080883f,
+				0.00000001f, 0.00000002f, -0.99999994f,
+				0.19429608f, 0.43176919f, -0.88080895f,
+				0.31827322f, 0.70727378f, -0.63124168f,
+				0.37923586f, 0.84274662f, -0.38204494f,
+				0.40426245f, 0.89836115f, -0.17181125f,
+				0.41036469f, 0.91192144f, -0.00000000f,
+				-0.00000000f, 1.00000000f, 0.00000002f,
+				-0.00663520f, 0.96262980f, 0.27073959f,
+				-0.02044736f, 0.83187640f, 0.55458421f,
+				-0.02920749f, 0.60578465f, 0.79509228f,
+				-0.02326994f, 0.31614211f, 0.94842637f,
+				0.00000000f, -0.00000001f, 1.00000000f,
+				0.03764164f, -0.31600380f, 0.94801098f,
+				0.08246279f, -0.60397911f, 0.79272252f,
+				0.12348490f, -0.82568222f, 0.55045468f,
+				0.15017621f, -0.95173377f, 0.26767510f,
+				0.15867807f, -0.98733038f, 0.00000000f,
+				-0.18035127f, 0.98360229f, 0.00000002f,
+				-0.17532882f, 0.95096803f, 0.25479335f,
+				-0.15287721f, 0.83426702f, 0.52974254f,
+				-0.10462277f, 0.62215620f, 0.77587104f,
+				-0.03044831f, 0.33461118f, 0.94186425f,
+				0.06193968f, 0.01031707f, 0.99802655f,
+				0.16082527f, -0.31103027f, 0.93669385f,
+				0.25167316f, -0.59030336f, 0.76694363f,
+				0.31965575f, -0.79168862f, 0.52062392f,
+				0.35713619f, -0.90012705f, 0.24944942f,
+				0.36760372f, -0.92998248f, 0.00000000f,
+				-0.41766369f, 0.90860170f, 0.00000001f,
+				-0.39988622f, 0.88385296f, 0.24268301f,
+				-0.33610013f, 0.79113972f, 0.51101321f,
+				-0.21738812f, 0.61049283f, 0.76160419f,
+				-0.05202692f, 0.34848279f, 0.93587017f,
+				0.13481762f, 0.04216119f, 0.98997307f,
+				0.31265706f, -0.25846288f, 0.91402549f,
+				0.45580176f, -0.50834626f, 0.73063594f,
+				0.55002815f, -0.67951775f, 0.48551479f,
+				0.59663087f, -0.76890105f, 0.22983235f,
+				0.60857272f, -0.79349810f, 0.00000000f,
+				-0.66257387f, 0.74899656f, 0.00000002f,
+				-0.63599384f, 0.73751098f, 0.22713293f,
+				-0.54030949f, 0.68535352f, 0.48821744f,
+				-0.35592762f, 0.56165427f, 0.74690026f,
+				-0.09161030f, 0.35298181f, 0.93113446f,
+				0.19997165f, 0.09098855f, 0.97556776f,
+				0.45451909f, -0.16425833f, 0.87546074f,
+				0.63705850f, -0.36688966f, 0.67790008f,
+				0.74545228f, -0.50049365f, 0.44023511f,
+				0.79541844f, -0.56993616f, 0.20611253f,
+				0.80771065f, -0.58957899f, 0.00000000f,
+				-0.83617663f, 0.54846013f, 0.00000000f,
+				-0.80830199f, 0.55003119f, 0.21003231f,
+				-0.70330739f, 0.54000026f, 0.46234021f,
+				-0.48353508f, 0.48285684f, 0.73009795f,
+				-0.14467996f, 0.34488079f, 0.92742926f,
+				0.23135546f, 0.14274487f, 0.96234012f,
+				0.53892589f, -0.06078010f, 0.84015751f,
+				0.74133462f, -0.22075030f, 0.63379204f,
+				0.85413134f, -0.32620990f, 0.40502673f,
+				0.90458620f, -0.38241822f, 0.18836212f,
+				0.91694802f, -0.39900666f, 0.00000000f,
+				-0.92058182f, 0.39054993f, 0.00000000f,
+				-0.89363056f, 0.40022916f, 0.20307857f,
+				-0.78890914f, 0.41686431f, 0.45149362f,
+				-0.56006563f, 0.40645269f, 0.72188824f,
+				-0.19519633f, 0.32757819f, 0.92444086f,
+				0.21408427f, 0.18028127f, 0.96003473f,
+				0.54745990f, 0.01576568f, 0.83668333f,
+				0.76624936f, -0.12207591f, 0.63084018f,
+				0.88872969f, -0.21771559f, 0.40343457f,
+				0.94407701f, -0.27101600f, 0.18780012f,
+				0.95782626f, -0.28734791f, 0.00000000f,
+				-0.94868332f, 0.31622770f, 0.00000000f,
+				-0.91918433f, 0.33012095f, 0.21475680f,
+				-0.80660689f, 0.35781714f, 0.47048092f,
+				-0.57353723f, 0.36397618f, 0.73387766f,
+				-0.22571620f, 0.31080776f, 0.92328256f,
+				0.15842876f, 0.19453593f, 0.96801656f,
+				0.49079683f, 0.04867331f, 0.86991346f,
+				0.73050225f, -0.08864024f, 0.67713326f,
+				0.87579465f, -0.19275309f, 0.44252673f,
+				0.94458473f, -0.25386029f, 0.20812173f,
+				0.96200842f, -0.27301973f, 0.00000000f,
+				-0.94363600f, 0.33098498f, 0.00000000f,
+				-0.90570927f, 0.34520733f, 0.24601370f,
+				-0.77261388f, 0.36835617f, 0.51708955f,
+				-0.53611439f, 0.36250114f, 0.76234782f,
+				-0.23092552f, 0.30191389f, 0.92494398f,
+				0.09193577f, 0.18763106f, 0.97792768f,
+				0.39456257f, 0.03777903f, 0.91809207f,
+				0.64655590f, -0.11995956f, 0.75337595f,
+				0.82085574f, -0.25226805f, 0.51240283f,
+				0.90997124f, -0.33401650f, 0.24573438f,
+				0.93303967f, -0.35977367f, 0.00000000f,
+				-0.90138495f, 0.43301868f, 0.00000000f,
+				-0.85063618f, 0.43930891f, 0.28883508f,
+				-0.69515461f, 0.43491554f, 0.57237101f,
+				-0.46838579f, 0.39142096f, 0.79208869f,
+				-0.21674377f, 0.30048704f, 0.92883235f,
+				0.03872212f, 0.16388227f, 0.98571962f,
+				0.29301688f, -0.01331866f, 0.95601457f,
+				0.53064740f, -0.21285287f, 0.82043099f,
+				0.71452874f, -0.39256069f, 0.57908958f,
+				0.81452185f, -0.50658178f, 0.28271735f,
+				0.84053087f, -0.54176366f, 0.00000000f,
+				-0.79407930f, 0.60781413f, 0.00000000f,
+				-0.73794198f, 0.59429532f, 0.31977278f,
+				-0.58513945f, 0.53897822f, 0.60589951f,
+				-0.39107439f, 0.43975663f, 0.80850166f,
+				-0.19196139f, 0.30015025f, 0.93437707f,
+				0.00855905f, 0.11538364f, 0.99328417f,
+				0.21362855f, -0.11758450f, 0.96981269f,
+				0.40814662f, -0.37572223f, 0.83201510f,
+				0.55414897f, -0.59705591f, 0.58003724f,
+				0.62806821f, -0.72647786f, 0.27885503f,
+				0.64588672f, -0.76343334f, 0.00000000f,
+				-0.60000002f, 0.80000001f, 0.00000000f,
+				-0.56169033f, 0.76974368f, 0.30331263f,
+				-0.45668069f, 0.67062843f, 0.58455133f,
+				-0.31731656f, 0.50979352f, 0.79963785f,
+				-0.16375455f, 0.28897864f, 0.94322628f,
+				0.00000001f, -0.00000001f, 1.00000000f,
+				0.16118340f, -0.33475828f, 0.92841625f,
+				0.28603175f, -0.63137418f, 0.72079998f,
+				0.35418314f, -0.81793916f, 0.45335400f,
+				0.37968126f, -0.90211189f, 0.20502774f,
+				0.38461548f, -0.92307693f, 0.00000000f,
+				0.15867807f, -0.98733038f, 0.00000000f,
+				0.15017621f, -0.95173377f, -0.26767510f,
+				0.12348484f, -0.82568216f, -0.55045474f,
+				0.08246280f, -0.60397911f, -0.79272252f,
+				0.03764160f, -0.31600368f, -0.94801098f,
+				0.00000000f, -0.00000000f, -1.00000000f,
+				-0.02326995f, 0.31614220f, -0.94842637f,
+				-0.02920748f, 0.60578465f, -0.79509240f,
+				-0.02044738f, 0.83187640f, -0.55458409f,
+				-0.00663519f, 0.96262980f, -0.27073947f,
+				-0.00000000f, 1.00000000f, 0.00000002f,
+				0.36760372f, -0.92998248f, 0.00000000f,
+				0.35713634f, -0.90012699f, -0.24944946f,
+				0.31965572f, -0.79168868f, -0.52062398f,
+				0.25167322f, -0.59030336f, -0.76694363f,
+				0.16082519f, -0.31103012f, -0.93669391f,
+				0.06193968f, 0.01031707f, -0.99802655f,
+				-0.03044830f, 0.33461127f, -0.94186413f,
+				-0.10462277f, 0.62215620f, -0.77587098f,
+				-0.15287724f, 0.83426702f, -0.52974242f,
+				-0.17532876f, 0.95096809f, -0.25479320f,
+				-0.18035127f, 0.98360229f, 0.00000001f,
+				0.60857272f, -0.79349810f, 0.00000000f,
+				0.59663087f, -0.76890081f, -0.22983252f,
+				0.55002815f, -0.67951781f, -0.48551467f,
+				0.45580178f, -0.50834626f, -0.73063594f,
+				0.31265700f, -0.25846279f, -0.91402549f,
+				0.13481762f, 0.04216120f, -0.98997307f,
+				-0.05202700f, 0.34848288f, -0.93587005f,
+				-0.21738812f, 0.61049283f, -0.76160419f,
+				-0.33610022f, 0.79113984f, -0.51101309f,
+				-0.39988619f, 0.88385296f, -0.24268289f,
+				-0.41766369f, 0.90860170f, 0.00000001f,
+				0.80771065f, -0.58957899f, 0.00000000f,
+				0.79541844f, -0.56993616f, -0.20611253f,
+				0.74545234f, -0.50049359f, -0.44023511f,
+				0.63705850f, -0.36688969f, -0.67790002f,
+				0.45451906f, -0.16425829f, -0.87546074f,
+				0.19997162f, 0.09098855f, -0.97556776f,
+				-0.09161033f, 0.35298187f, -0.93113440f,
+				-0.35592768f, 0.56165421f, -0.74690026f,
+				-0.54030943f, 0.68535358f, -0.48821732f,
+				-0.63599378f, 0.73751110f, -0.22713269f,
+				-0.66257387f, 0.74899656f, 0.00000000f,
+				0.91694802f, -0.39900666f, 0.00000000f,
+				0.90458608f, -0.38241825f, -0.18836221f,
+				0.85413140f, -0.32620987f, -0.40502664f,
+				0.74133456f, -0.22075026f, -0.63379204f,
+				0.53892601f, -0.06078019f, -0.84015751f,
+				0.23135549f, 0.14274485f, -0.96234012f,
+				-0.14468013f, 0.34488085f, -0.92742920f,
+				-0.48353511f, 0.48285678f, -0.73009801f,
+				-0.70330739f, 0.54000038f, -0.46234021f,
+				-0.80830193f, 0.55003130f, -0.21003252f,
+				-0.83617663f, 0.54846013f, 0.00000000f,
+				0.95782626f, -0.28734791f, 0.00000000f,
+				0.94407701f, -0.27101621f, -0.18779969f,
+				0.88872993f, -0.21771561f, -0.40343419f,
+				0.76624936f, -0.12207589f, -0.63084036f,
+				0.54745996f, 0.01576565f, -0.83668339f,
+				0.21408427f, 0.18028127f, -0.96003473f,
+				-0.19519642f, 0.32757822f, -0.92444074f,
+				-0.56006563f, 0.40645254f, -0.72188836f,
+				-0.78890932f, 0.41686437f, -0.45149341f,
+				-0.89363045f, 0.40022945f, -0.20307872f,
+				-0.92058182f, 0.39054993f, 0.00000000f,
+				0.96200842f, -0.27301973f, 0.00000000f,
+				0.94458461f, -0.25386047f, -0.20812206f,
+				0.87579477f, -0.19275305f, -0.44252670f,
+				0.73050219f, -0.08864018f, -0.67713314f,
+				0.49079677f, 0.04867329f, -0.86991346f,
+				0.15842876f, 0.19453594f, -0.96801656f,
+				-0.22571620f, 0.31080776f, -0.92328250f,
+				-0.57353723f, 0.36397609f, -0.73387772f,
+				-0.80660677f, 0.35781729f, -0.47048086f,
+				-0.91918421f, 0.33012089f, -0.21475697f,
+				-0.94868326f, 0.31622767f, 0.00000000f,
+				0.93303967f, -0.35977367f, 0.00000000f,
+				0.90997100f, -0.33401665f, -0.24573487f,
+				0.82085598f, -0.25226820f, -0.51240242f,
+				0.64655590f, -0.11995946f, -0.75337583f,
+				0.39456260f, 0.03777899f, -0.91809213f,
+				0.09193578f, 0.18763106f, -0.97792768f,
+				-0.23092572f, 0.30191398f, -0.92494386f,
+				-0.53611463f, 0.36250117f, -0.76234764f,
+				-0.77261376f, 0.36835647f, -0.51708949f,
+				-0.90570915f, 0.34520757f, -0.24601352f,
+				-0.94363600f, 0.33098498f, 0.00000000f,
+				0.84053087f, -0.54176372f, 0.00000000f,
+				0.81452167f, -0.50658214f, -0.28271732f,
+				0.71452862f, -0.39256078f, -0.57908970f,
+				0.53064746f, -0.21285276f, -0.82043099f,
+				0.29301694f, -0.01331866f, -0.95601451f,
+				0.03872212f, 0.16388229f, -0.98571962f,
+				-0.21674389f, 0.30048716f, -0.92883229f,
+				-0.46838561f, 0.39142081f, -0.79208875f,
+				-0.69515425f, 0.43491599f, -0.57237113f,
+				-0.85063595f, 0.43930909f, -0.28883564f,
+				-0.90138495f, 0.43301874f, 0.00000000f,
+				0.64588672f, -0.76343334f, 0.00000000f,
+				0.62806827f, -0.72647810f, -0.27885461f,
+				0.55414909f, -0.59705591f, -0.58003718f,
+				0.40814668f, -0.37572223f, -0.83201504f,
+				0.21362852f, -0.11758441f, -0.96981281f,
+				0.00855905f, 0.11538365f, -0.99328417f,
+				-0.19196151f, 0.30015031f, -0.93437707f,
+				-0.39107460f, 0.43975663f, -0.80850154f,
+				-0.58513945f, 0.53897864f, -0.60589927f,
+				-0.73794204f, 0.59429544f, -0.31977260f,
+				-0.79407930f, 0.60781413f, 0.00000000f,
+				0.38461545f, -0.92307687f, 0.00000000f,
+				0.37968108f, -0.90211183f, -0.20502785f,
+				0.35418308f, -0.81793898f, -0.45335433f,
+				0.28603172f, -0.63137424f, -0.72079992f,
+				0.16118342f, -0.33475828f, -0.92841625f,
+				0.00000001f, 0.00000004f, -1.00000000f,
+				-0.16375461f, 0.28897879f, -0.94322622f,
+				-0.31731653f, 0.50979352f, -0.79963791f,
+				-0.45668048f, 0.67062896f, -0.58455086f,
+				-0.56169003f, 0.76974392f, -0.30331281f,
+				-0.59999996f, 0.79999995f, 0.00000000f,
+				-0.60000002f, 0.80000001f, 0.00000000f,
+				-0.56168801f, 0.76974595f, 0.30331135f,
+				-0.45668051f, 0.67062873f, 0.58455110f,
+				-0.31731650f, 0.50979382f, 0.79963768f,
+				-0.16375455f, 0.28897852f, 0.94322622f,
+				0.00000001f, -0.00000001f, 1.00000000f,
+				0.16118334f, -0.33475927f, 0.92841595f,
+				0.28603160f, -0.63137466f, 0.72079957f,
+				0.35418302f, -0.81793934f, 0.45335382f,
+				0.37968105f, -0.90211189f, 0.20502764f,
+				0.38461483f, -0.92307717f, 0.00000000f,
+				-0.52215356f, 0.85285151f, 0.00000000f,
+				-0.48819607f, 0.82820129f, 0.27522215f,
+				-0.39745197f, 0.74893141f, 0.53022045f,
+				-0.27929682f, 0.62227607f, 0.73127675f,
+				-0.14856830f, 0.44534677f, 0.88294607f,
+				-0.00172636f, 0.19493943f, 0.98081374f,
+				0.16121377f, -0.14311403f, 0.97648787f,
+				0.30696428f, -0.50290024f, 0.80800015f,
+				0.39156401f, -0.75766802f, 0.52212727f,
+				0.42092448f, -0.87572849f, 0.23647870f,
+				0.42558387f, -0.90491897f, 0.00000000f,
+				-0.43938431f, 0.89829928f, 0.00000000f,
+				-0.41034442f, 0.88024288f, 0.23830649f,
+				-0.33475637f, 0.82335114f, 0.45829135f,
+				-0.23836468f, 0.73443186f, 0.63544637f,
+				-0.13188016f, 0.60968041f, 0.78159934f,
+				-0.00710458f, 0.42205760f, 0.90654117f,
+				0.15046835f, 0.12560046f, 0.98060381f,
+				0.32566464f, -0.28249556f, 0.90229636f,
+				0.44518387f, -0.64581746f, 0.62026703f,
+				0.48392394f, -0.82802969f, 0.28316852f,
+				0.48799869f, -0.87284440f, 0.00000000f,
+				-0.34358078f, 0.93912309f, 0.00000000f,
+				-0.32073438f, 0.92827159f, 0.18825857f,
+				-0.26279277f, 0.89484864f, 0.36081272f,
+				-0.19049235f, 0.84415460f, 0.50111455f,
+				-0.11119042f, 0.77414727f, 0.62316346f,
+				-0.01599536f, 0.66692048f, 0.74495709f,
+				0.11782130f, 0.47883904f, 0.86996061f,
+				0.31610468f, 0.13072994f, 0.93967414f,
+				0.52400970f, -0.36312634f, 0.77042389f,
+				0.60744083f, -0.70159787f, 0.37252650f,
+				0.61339337f, -0.78977746f, 0.00000000f,
+				-0.21540894f, 0.97652394f, 0.00000000f,
+				-0.20210963f, 0.97277087f, 0.11343981f,
+				-0.16888496f, 0.96128762f, 0.21772476f,
+				-0.12775169f, 0.94399923f, 0.30421191f,
+				-0.08228281f, 0.92055047f, 0.38185909f,
+				-0.02672647f, 0.88635093f, 0.46224198f,
+				0.05417515f, 0.83025962f, 0.55473769f,
+				0.19413483f, 0.72417361f, 0.66172826f,
+				0.45968795f, 0.49317458f, 0.73855662f,
+				0.83387059f, 0.06007503f, 0.54868102f,
+				0.98019689f, -0.19802566f, 0.00000000f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				-0.00798650f, 0.99980050f, -0.01830650f,
+				-0.02329356f, 0.99955261f, -0.01876155f,
+				-0.03473718f, 0.99939632f, -0.00068433f,
+				-0.03873326f, 0.99887133f, 0.02749065f,
+				-0.03489117f, 0.99762177f, 0.05944324f,
+				-0.02229466f, 0.99563199f, 0.09066366f,
+				0.00150407f, 0.99339056f, 0.11477303f,
+				0.03894940f, 0.99219519f, 0.11845525f,
+				0.08366866f, 0.99324965f, 0.08034155f,
+				0.10653293f, 0.99430919f, 0.00000000f,
+				0.47222057f, 0.88148040f, -0.00000000f,
+				0.38292861f, 0.87748671f, -0.28876084f,
+				0.22452858f, 0.87743115f, -0.42391223f,
+				0.10304770f, 0.89068371f, -0.44279075f,
+				0.02195636f, 0.90869540f, -0.41688216f,
+				-0.03502908f, 0.92632145f, -0.37510204f,
+				-0.07944816f, 0.94263619f, -0.32422990f,
+				-0.11721789f, 0.95783943f, -0.26230437f,
+				-0.14933059f, 0.97128469f, -0.18522017f,
+				-0.17214298f, 0.98060298f, -0.09372614f,
+				-0.18032812f, 0.98360652f, 0.00000000f,
+				0.99778539f, 0.06651436f, -0.00000000f,
+				0.80641705f, 0.16489866f, -0.56789082f,
+				0.47624663f, 0.34165922f, -0.81022102f,
+				0.23846878f, 0.49079743f, -0.83800387f,
+				0.08315616f, 0.60699552f, -0.79034263f,
+				-0.02645415f, 0.70211625f, -0.71157062f,
+				-0.11138277f, 0.78460753f, -0.60990572f,
+				-0.18041402f, 0.85678113f, -0.48309112f,
+				-0.23395304f, 0.91435987f, -0.33047256f,
+				-0.26772311f, 0.94976443f, -0.16208562f,
+				-0.27868998f, 0.96038115f, 0.00000000f,
+				0.84905565f, -0.52830338f, 0.00000000f,
+				0.74757648f, -0.43204904f, -0.50444335f,
+				0.51660007f, -0.20501730f, -0.83131957f,
+				0.29630959f, 0.03158545f, -0.95456952f,
+				0.12317742f, 0.24186306f, -0.96246016f,
+				-0.01423063f, 0.43078220f, -0.90234375f,
+				-0.12728645f, 0.60243106f, -0.78795618f,
+				-0.21896675f, 0.75068080f, -0.62332320f,
+				-0.28577635f, 0.86156183f, -0.41957495f,
+				-0.32389721f, 0.92431885f, -0.20180508f,
+				-0.33514237f, 0.94216758f, 0.00000000f,
+				0.69542080f, -0.71860266f, 0.00000000f,
+				0.63358843f, -0.65843248f, -0.40624171f,
+				0.47831276f, -0.49247903f, -0.72710478f,
+				0.30356473f, -0.28035074f, -0.91063273f,
+				0.14240842f, -0.05222204f, -0.98842943f,
+				-0.00406526f, 0.18825392f, -0.98211193f,
+				-0.13672033f, 0.43257806f, -0.89116985f,
+				-0.24777175f, 0.65353578f, -0.71519232f,
+				-0.32531679f, 0.81566668f, -0.47838998f,
+				-0.36555430f, 0.90269053f, -0.22697979f,
+				-0.37628973f, 0.92650205f, 0.00000000f,
+				0.59999752f, -0.80000180f, 0.00000000f,
+				0.55578601f, -0.76151079f, -0.33347145f,
+				0.43982491f, -0.64441258f, -0.62552899f,
+				0.29645419f, -0.47233042f, -0.83007169f,
+				0.14913522f, -0.25836608f, -0.95446616f,
+				0.00000000f, -0.00000000f, -1.00000000f,
+				-0.14759630f, 0.29311213f, -0.94461662f,
+				-0.27540490f, 0.57402539f, -0.77113360f,
+				-0.36081311f, 0.77877104f, -0.51315647f,
+				-0.40083086f, 0.88402218f, -0.24049798f,
+				-0.41036457f, 0.91192162f, 0.00000000f,
+				0.38461483f, -0.92307711f, 0.00000000f,
+				0.37968156f, -0.90211177f, -0.20502809f,
+				0.35418290f, -0.81793922f, -0.45335409f,
+				0.28603169f, -0.63137424f, -0.72079986f,
+				0.16118337f, -0.33475906f, -0.92841601f,
+				0.00000001f, -0.00000001f, -1.00000000f,
+				-0.16375463f, 0.28897840f, -0.94322634f,
+				-0.31731617f, 0.50979501f, -0.79963702f,
+				-0.45668083f, 0.67062843f, -0.58455133f,
+				-0.56169003f, 0.76974392f, -0.30331278f,
+				-0.60000002f, 0.80000001f, 0.00000000f,
+				0.42558384f, -0.90491897f, 0.00000000f,
+				0.42092523f, -0.87572807f, -0.23647898f,
+				0.39156422f, -0.75766778f, -0.52212739f,
+				0.30696440f, -0.50289935f, -0.80800068f,
+				0.16121379f, -0.14311399f, -0.97648787f,
+				-0.00172636f, 0.19493946f, -0.98081374f,
+				-0.14856836f, 0.44534636f, -0.88294625f,
+				-0.27929646f, 0.62227708f, -0.73127621f,
+				-0.39745176f, 0.74893188f, -0.53022003f,
+				-0.48819649f, 0.82820088f, -0.27522251f,
+				-0.52215350f, 0.85285145f, 0.00000000f,
+				0.48799869f, -0.87284434f, 0.00000000f,
+				0.48392478f, -0.82802933f, -0.28316849f,
+				0.44518381f, -0.64581764f, -0.62026691f,
+				0.32566467f, -0.28249523f, -0.90229648f,
+				0.15046827f, 0.12560080f, -0.98060375f,
+				-0.00710459f, 0.42205763f, -0.90654117f,
+				-0.13188021f, 0.60968047f, -0.78159928f,
+				-0.23836447f, 0.73443234f, -0.63544589f,
+				-0.33475649f, 0.82335120f, -0.45829135f,
+				-0.41034439f, 0.88024282f, -0.23830633f,
+				-0.43938428f, 0.89829916f, 0.00000000f,
+				0.61339337f, -0.78977746f, 0.00000000f,
+				0.60744220f, -0.70159632f, -0.37252715f,
+				0.52400988f, -0.36312595f, -0.77042401f,
+				0.31610486f, 0.13072906f, -0.93967414f,
+				0.11782125f, 0.47883931f, -0.86996049f,
+				-0.01599536f, 0.66692042f, -0.74495715f,
+				-0.11119034f, 0.77414769f, -0.62316298f,
+				-0.19049208f, 0.84415501f, -0.50111377f,
+				-0.26279294f, 0.89484841f, -0.36081317f,
+				-0.32073432f, 0.92827165f, -0.18825847f,
+				-0.34358078f, 0.93912309f, 0.00000000f,
+				0.98019677f, -0.19802561f, 0.00000000f,
+				0.83387059f, 0.06008269f, -0.54868007f,
+				0.45968765f, 0.49317545f, -0.73855609f,
+				0.19413501f, 0.72417301f, -0.66172880f,
+				0.05417512f, 0.83025974f, -0.55473757f,
+				-0.02672647f, 0.88635093f, -0.46224201f,
+				-0.08228284f, 0.92055041f, -0.38185903f,
+				-0.12775156f, 0.94399947f, -0.30421141f,
+				-0.16888507f, 0.96128756f, -0.21772504f,
+				-0.20210969f, 0.97277093f, -0.11343937f,
+				-0.21540894f, 0.97652400f, 0.00000000f,
+				0.10653293f, 0.99430919f, 0.00000000f,
+				0.08366887f, 0.99324965f, -0.08034138f,
+				0.03894951f, 0.99219519f, -0.11845496f,
+				0.00150435f, 0.99339044f, -0.11477400f,
+				-0.02229443f, 0.99563181f, -0.09066474f,
+				-0.03489117f, 0.99762177f, -0.05944332f,
+				-0.03873324f, 0.99887133f, -0.02749083f,
+				-0.03473703f, 0.99939620f, 0.00068491f,
+				-0.02329374f, 0.99955261f, 0.01876094f,
+				-0.00798648f, 0.99980056f, 0.01830641f,
+				0.00000000f, 1.00000000f, 0.00000000f,
+				-0.18032812f, 0.98360652f, 0.00000000f,
+				-0.17214245f, 0.98060316f, 0.09372532f,
+				-0.14933097f, 0.97128439f, 0.18522131f,
+				-0.11721768f, 0.95783967f, 0.26230362f,
+				-0.07944822f, 0.94263613f, 0.32423037f,
+				-0.03502908f, 0.92632139f, 0.37510198f,
+				0.02195640f, 0.90869552f, 0.41688174f,
+				0.10304778f, 0.89068371f, 0.44279081f,
+				0.22452924f, 0.87743056f, 0.42391288f,
+				0.38292828f, 0.87748682f, 0.28876066f,
+				0.47222057f, 0.88148040f, -0.00000000f,
+				-0.27868998f, 0.96038115f, 0.00000000f,
+				-0.26772285f, 0.94976461f, 0.16208540f,
+				-0.23395303f, 0.91435981f, 0.33047244f,
+				-0.18041429f, 0.85678071f, 0.48309162f,
+				-0.11138276f, 0.78460753f, 0.60990566f,
+				-0.02645415f, 0.70211625f, 0.71157062f,
+				0.08315627f, 0.60699481f, 0.79034311f,
+				0.23846854f, 0.49079826f, 0.83800346f,
+				0.47624737f, 0.34165865f, 0.81022084f,
+				0.80641758f, 0.16489473f, 0.56789118f,
+				0.99778539f, 0.06651436f, -0.00000000f,
+				-0.33514237f, 0.94216758f, 0.00000000f,
+				-0.32389730f, 0.92431867f, 0.20180558f,
+				-0.28577620f, 0.86156183f, 0.41957507f,
+				-0.21896696f, 0.75067997f, 0.62332416f,
+				-0.12728633f, 0.60243118f, 0.78795624f,
+				-0.01423062f, 0.43078214f, 0.90234381f,
+				0.12317742f, 0.24186318f, 0.96246016f,
+				0.29630962f, 0.03158538f, 0.95456952f,
+				0.51659995f, -0.20501706f, 0.83131969f,
+				0.74757755f, -0.43204758f, 0.50444305f,
+				0.84905565f, -0.52830338f, 0.00000000f,
+				-0.37628970f, 0.92650205f, 0.00000000f,
+				-0.36555433f, 0.90269059f, 0.22697939f,
+				-0.32531694f, 0.81566656f, 0.47838989f,
+				-0.24777193f, 0.65353495f, 0.71519297f,
+				-0.13672033f, 0.43257833f, 0.89116967f,
+				-0.00406526f, 0.18825392f, 0.98211193f,
+				0.14240852f, -0.05222183f, 0.98842937f,
+				0.30356464f, -0.28035024f, 0.91063285f,
+				0.47831330f, -0.49247789f, 0.72710514f,
+				0.63358814f, -0.65843308f, 0.40624121f,
+				0.69542086f, -0.71860272f, 0.00000000f,
+				-0.41036457f, 0.91192162f, 0.00000000f,
+				-0.40083098f, 0.88402182f, 0.24049932f,
+				-0.36081249f, 0.77877188f, 0.51315570f,
+				-0.27540508f, 0.57402408f, 0.77113444f,
+				-0.14759634f, 0.29311273f, 0.94461650f,
+				0.00000000f, -0.00000000f, 1.00000000f,
+				0.14913538f, -0.25836638f, 0.95446599f,
+				0.29645407f, -0.47232965f, 0.83007205f,
+				0.43982542f, -0.64441097f, 0.62553024f,
+				0.55578405f, -0.76151282f, 0.33347026f,
+				0.59999752f, -0.80000180f, 0.00000000f
+		};
+		
+		const std::array<float, (vertexCount * 2)> teapotUVCoords = {
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f,
+				1.00000000f, 0.00000000f,
+				0.89999998f, 0.00000000f,
+				0.80000001f, 0.00000000f,
+				0.69999999f, 0.00000000f,
+				0.60000002f, 0.00000000f,
+				0.50000000f, 0.00000000f,
+				0.39999998f, 0.00000000f,
+				0.30000001f, 0.00000000f,
+				0.19999999f, 0.00000000f,
+				0.09999996f, 0.00000000f,
+				0.00000000f, 0.00000000f,
+				1.00000000f, 0.10000000f,
+				0.89999992f, 0.10000000f,
+				0.79999995f, 0.10000000f,
+				0.69999999f, 0.10000000f,
+				0.60000002f, 0.10000000f,
+				0.50000000f, 0.10000000f,
+				0.39999995f, 0.10000000f,
+				0.30000001f, 0.10000000f,
+				0.19999997f, 0.10000000f,
+				0.09999996f, 0.10000000f,
+				0.00000000f, 0.10000000f,
+				1.00000000f, 0.20000000f,
+				0.89999998f, 0.20000000f,
+				0.80000007f, 0.20000000f,
+				0.69999999f, 0.20000000f,
+				0.60000002f, 0.20000000f,
+				0.50000000f, 0.20000000f,
+				0.39999998f, 0.20000000f,
+				0.30000001f, 0.20000000f,
+				0.19999999f, 0.20000000f,
+				0.09999997f, 0.20000000f,
+				0.00000000f, 0.20000000f,
+				1.00000000f, 0.30000001f,
+				0.89999998f, 0.30000001f,
+				0.80000001f, 0.30000001f,
+				0.69999999f, 0.30000001f,
+				0.60000002f, 0.30000001f,
+				0.50000000f, 0.30000001f,
+				0.39999998f, 0.30000001f,
+				0.30000001f, 0.30000001f,
+				0.19999999f, 0.30000001f,
+				0.09999996f, 0.30000001f,
+				0.00000000f, 0.30000001f,
+				1.00000000f, 0.40000001f,
+				0.89999998f, 0.40000001f,
+				0.80000007f, 0.40000001f,
+				0.70000005f, 0.40000001f,
+				0.60000002f, 0.40000001f,
+				0.50000000f, 0.40000001f,
+				0.39999998f, 0.40000001f,
+				0.30000001f, 0.40000001f,
+				0.19999999f, 0.40000001f,
+				0.09999996f, 0.40000001f,
+				0.00000000f, 0.40000001f,
+				1.00000000f, 0.50000000f,
+				0.89999998f, 0.50000000f,
+				0.80000001f, 0.50000000f,
+				0.69999999f, 0.50000000f,
+				0.60000002f, 0.50000000f,
+				0.50000000f, 0.50000000f,
+				0.39999998f, 0.50000000f,
+				0.30000001f, 0.50000000f,
+				0.19999999f, 0.50000000f,
+				0.09999996f, 0.50000000f,
+				0.00000000f, 0.50000000f,
+				1.00000000f, 0.60000002f,
+				0.89999998f, 0.60000002f,
+				0.80000001f, 0.60000002f,
+				0.69999999f, 0.60000002f,
+				0.60000002f, 0.60000002f,
+				0.50000000f, 0.60000002f,
+				0.39999998f, 0.60000002f,
+				0.30000001f, 0.60000002f,
+				0.19999999f, 0.60000002f,
+				0.09999996f, 0.60000002f,
+				0.00000000f, 0.60000002f,
+				1.00000000f, 0.69999999f,
+				0.89999998f, 0.69999999f,
+				0.80000001f, 0.69999999f,
+				0.69999999f, 0.69999999f,
+				0.60000002f, 0.69999999f,
+				0.50000000f, 0.69999999f,
+				0.39999998f, 0.69999999f,
+				0.30000001f, 0.69999999f,
+				0.19999999f, 0.69999999f,
+				0.09999996f, 0.69999999f,
+				0.00000000f, 0.69999999f,
+				1.00000000f, 0.80000001f,
+				0.89999998f, 0.80000001f,
+				0.80000007f, 0.80000001f,
+				0.69999999f, 0.80000001f,
+				0.60000002f, 0.80000001f,
+				0.50000000f, 0.80000001f,
+				0.39999998f, 0.80000001f,
+				0.30000001f, 0.80000001f,
+				0.19999999f, 0.80000001f,
+				0.09999996f, 0.80000001f,
+				0.00000000f, 0.80000001f,
+				1.00000000f, 0.90000004f,
+				0.89999998f, 0.90000004f,
+				0.80000001f, 0.90000004f,
+				0.69999999f, 0.90000004f,
+				0.60000002f, 0.90000004f,
+				0.50000000f, 0.90000004f,
+				0.39999998f, 0.90000004f,
+				0.30000001f, 0.90000004f,
+				0.19999999f, 0.90000004f,
+				0.09999996f, 0.90000004f,
+				0.00000000f, 0.90000004f,
+				1.00000000f, 1.00000000f,
+				0.89999998f, 1.00000000f,
+				0.80000001f, 1.00000000f,
+				0.69999999f, 1.00000000f,
+				0.60000002f, 1.00000000f,
+				0.50000000f, 1.00000000f,
+				0.39999998f, 1.00000000f,
+				0.30000001f, 1.00000000f,
+				0.19999999f, 1.00000000f,
+				0.09999996f, 1.00000000f,
+				0.00000000f, 1.00000000f
+		};
+		
+		const std::array<uint16_t, indexCount> teapotIndices = {
+				0, 1, 11, 11, 1, 12,
+				1, 2, 12, 12, 2, 13,
+				2, 3, 13, 13, 3, 14,
+				3, 4, 14, 14, 4, 15,
+				4, 5, 15, 15, 5, 16,
+				5, 6, 16, 16, 6, 17,
+				6, 7, 17, 17, 7, 18,
+				7, 8, 18, 18, 8, 19,
+				8, 9, 19, 19, 9, 20,
+				9, 10, 20, 20, 10, 21,
+				11, 12, 22, 22, 12, 23,
+				12, 13, 23, 23, 13, 24,
+				13, 14, 24, 24, 14, 25,
+				14, 15, 25, 25, 15, 26,
+				15, 16, 26, 26, 16, 27,
+				16, 17, 27, 27, 17, 28,
+				17, 18, 28, 28, 18, 29,
+				18, 19, 29, 29, 19, 30,
+				19, 20, 30, 30, 20, 31,
+				20, 21, 31, 31, 21, 32,
+				22, 23, 33, 33, 23, 34,
+				23, 24, 34, 34, 24, 35,
+				24, 25, 35, 35, 25, 36,
+				25, 26, 36, 36, 26, 37,
+				26, 27, 37, 37, 27, 38,
+				27, 28, 38, 38, 28, 39,
+				28, 29, 39, 39, 29, 40,
+				29, 30, 40, 40, 30, 41,
+				30, 31, 41, 41, 31, 42,
+				31, 32, 42, 42, 32, 43,
+				33, 34, 44, 44, 34, 45,
+				34, 35, 45, 45, 35, 46,
+				35, 36, 46, 46, 36, 47,
+				36, 37, 47, 47, 37, 48,
+				37, 38, 48, 48, 38, 49,
+				38, 39, 49, 49, 39, 50,
+				39, 40, 50, 50, 40, 51,
+				40, 41, 51, 51, 41, 52,
+				41, 42, 52, 52, 42, 53,
+				42, 43, 53, 53, 43, 54,
+				44, 45, 55, 55, 45, 56,
+				45, 46, 56, 56, 46, 57,
+				46, 47, 57, 57, 47, 58,
+				47, 48, 58, 58, 48, 59,
+				48, 49, 59, 59, 49, 60,
+				49, 50, 60, 60, 50, 61,
+				50, 51, 61, 61, 51, 62,
+				51, 52, 62, 62, 52, 63,
+				52, 53, 63, 63, 53, 64,
+				53, 54, 64, 64, 54, 65,
+				55, 56, 66, 66, 56, 67,
+				56, 57, 67, 67, 57, 68,
+				57, 58, 68, 68, 58, 69,
+				58, 59, 69, 69, 59, 70,
+				59, 60, 70, 70, 60, 71,
+				60, 61, 71, 71, 61, 72,
+				61, 62, 72, 72, 62, 73,
+				62, 63, 73, 73, 63, 74,
+				63, 64, 74, 74, 64, 75,
+				64, 65, 75, 75, 65, 76,
+				66, 67, 77, 77, 67, 78,
+				67, 68, 78, 78, 68, 79,
+				68, 69, 79, 79, 69, 80,
+				69, 70, 80, 80, 70, 81,
+				70, 71, 81, 81, 71, 82,
+				71, 72, 82, 82, 72, 83,
+				72, 73, 83, 83, 73, 84,
+				73, 74, 84, 84, 74, 85,
+				74, 75, 85, 85, 75, 86,
+				75, 76, 86, 86, 76, 87,
+				77, 78, 88, 88, 78, 89,
+				78, 79, 89, 89, 79, 90,
+				79, 80, 90, 90, 80, 91,
+				80, 81, 91, 91, 81, 92,
+				81, 82, 92, 92, 82, 93,
+				82, 83, 93, 93, 83, 94,
+				83, 84, 94, 94, 84, 95,
+				84, 85, 95, 95, 85, 96,
+				85, 86, 96, 96, 86, 97,
+				86, 87, 97, 97, 87, 98,
+				88, 89, 99, 99, 89, 100,
+				89, 90, 100, 100, 90, 101,
+				90, 91, 101, 101, 91, 102,
+				91, 92, 102, 102, 92, 103,
+				92, 93, 103, 103, 93, 104,
+				93, 94, 104, 104, 94, 105,
+				94, 95, 105, 105, 95, 106,
+				95, 96, 106, 106, 96, 107,
+				96, 97, 107, 107, 97, 108,
+				97, 98, 108, 108, 98, 109,
+				99, 100, 110, 110, 100, 111,
+				100, 101, 111, 111, 101, 112,
+				101, 102, 112, 112, 102, 113,
+				102, 103, 113, 113, 103, 114,
+				103, 104, 114, 114, 104, 115,
+				104, 105, 115, 115, 105, 116,
+				105, 106, 116, 116, 106, 117,
+				106, 107, 117, 117, 107, 118,
+				107, 108, 118, 118, 108, 119,
+				108, 109, 119, 119, 109, 120,
+				121, 122, 132, 132, 122, 133,
+				122, 123, 133, 133, 123, 134,
+				123, 124, 134, 134, 124, 135,
+				124, 125, 135, 135, 125, 136,
+				125, 126, 136, 136, 126, 137,
+				126, 127, 137, 137, 127, 138,
+				127, 128, 138, 138, 128, 139,
+				128, 129, 139, 139, 129, 140,
+				129, 130, 140, 140, 130, 141,
+				130, 131, 141, 141, 131, 142,
+				132, 133, 143, 143, 133, 144,
+				133, 134, 144, 144, 134, 145,
+				134, 135, 145, 145, 135, 146,
+				135, 136, 146, 146, 136, 147,
+				136, 137, 147, 147, 137, 148,
+				137, 138, 148, 148, 138, 149,
+				138, 139, 149, 149, 139, 150,
+				139, 140, 150, 150, 140, 151,
+				140, 141, 151, 151, 141, 152,
+				141, 142, 152, 152, 142, 153,
+				143, 144, 154, 154, 144, 155,
+				144, 145, 155, 155, 145, 156,
+				145, 146, 156, 156, 146, 157,
+				146, 147, 157, 157, 147, 158,
+				147, 148, 158, 158, 148, 159,
+				148, 149, 159, 159, 149, 160,
+				149, 150, 160, 160, 150, 161,
+				150, 151, 161, 161, 151, 162,
+				151, 152, 162, 162, 152, 163,
+				152, 153, 163, 163, 153, 164,
+				154, 155, 165, 165, 155, 166,
+				155, 156, 166, 166, 156, 167,
+				156, 157, 167, 167, 157, 168,
+				157, 158, 168, 168, 158, 169,
+				158, 159, 169, 169, 159, 170,
+				159, 160, 170, 170, 160, 171,
+				160, 161, 171, 171, 161, 172,
+				161, 162, 172, 172, 162, 173,
+				162, 163, 173, 173, 163, 174,
+				163, 164, 174, 174, 164, 175,
+				165, 166, 176, 176, 166, 177,
+				166, 167, 177, 177, 167, 178,
+				167, 168, 178, 178, 168, 179,
+				168, 169, 179, 179, 169, 180,
+				169, 170, 180, 180, 170, 181,
+				170, 171, 181, 181, 171, 182,
+				171, 172, 182, 182, 172, 183,
+				172, 173, 183, 183, 173, 184,
+				173, 174, 184, 184, 174, 185,
+				174, 175, 185, 185, 175, 186,
+				176, 177, 187, 187, 177, 188,
+				177, 178, 188, 188, 178, 189,
+				178, 179, 189, 189, 179, 190,
+				179, 180, 190, 190, 180, 191,
+				180, 181, 191, 191, 181, 192,
+				181, 182, 192, 192, 182, 193,
+				182, 183, 193, 193, 183, 194,
+				183, 184, 194, 194, 184, 195,
+				184, 185, 195, 195, 185, 196,
+				185, 186, 196, 196, 186, 197,
+				187, 188, 198, 198, 188, 199,
+				188, 189, 199, 199, 189, 200,
+				189, 190, 200, 200, 190, 201,
+				190, 191, 201, 201, 191, 202,
+				191, 192, 202, 202, 192, 203,
+				192, 193, 203, 203, 193, 204,
+				193, 194, 204, 204, 194, 205,
+				194, 195, 205, 205, 195, 206,
+				195, 196, 206, 206, 196, 207,
+				196, 197, 207, 207, 197, 208,
+				198, 199, 209, 209, 199, 210,
+				199, 200, 210, 210, 200, 211,
+				200, 201, 211, 211, 201, 212,
+				201, 202, 212, 212, 202, 213,
+				202, 203, 213, 213, 203, 214,
+				203, 204, 214, 214, 204, 215,
+				204, 205, 215, 215, 205, 216,
+				205, 206, 216, 216, 206, 217,
+				206, 207, 217, 217, 207, 218,
+				207, 208, 218, 218, 208, 219,
+				209, 210, 220, 220, 210, 221,
+				210, 211, 221, 221, 211, 222,
+				211, 212, 222, 222, 212, 223,
+				212, 213, 223, 223, 213, 224,
+				213, 214, 224, 224, 214, 225,
+				214, 215, 225, 225, 215, 226,
+				215, 216, 226, 226, 216, 227,
+				216, 217, 227, 227, 217, 228,
+				217, 218, 228, 228, 218, 229,
+				218, 219, 229, 229, 219, 230,
+				220, 221, 231, 231, 221, 232,
+				221, 222, 232, 232, 222, 233,
+				222, 223, 233, 233, 223, 234,
+				223, 224, 234, 234, 224, 235,
+				224, 225, 235, 235, 225, 236,
+				225, 226, 236, 236, 226, 237,
+				226, 227, 237, 237, 227, 238,
+				227, 228, 238, 238, 228, 239,
+				228, 229, 239, 239, 229, 240,
+				229, 230, 240, 240, 230, 241,
+				242, 243, 253, 253, 243, 254,
+				243, 244, 254, 254, 244, 255,
+				244, 245, 255, 255, 245, 256,
+				245, 246, 256, 256, 246, 257,
+				246, 247, 257, 257, 247, 258,
+				247, 248, 258, 258, 248, 259,
+				248, 249, 259, 259, 249, 260,
+				249, 250, 260, 260, 250, 261,
+				250, 251, 261, 261, 251, 262,
+				251, 252, 262, 262, 252, 263,
+				253, 254, 264, 264, 254, 265,
+				254, 255, 265, 265, 255, 266,
+				255, 256, 266, 266, 256, 267,
+				256, 257, 267, 267, 257, 268,
+				257, 258, 268, 268, 258, 269,
+				258, 259, 269, 269, 259, 270,
+				259, 260, 270, 270, 260, 271,
+				260, 261, 271, 271, 261, 272,
+				261, 262, 272, 272, 262, 273,
+				262, 263, 273, 273, 263, 274,
+				264, 265, 275, 275, 265, 276,
+				265, 266, 276, 276, 266, 277,
+				266, 267, 277, 277, 267, 278,
+				267, 268, 278, 278, 268, 279,
+				268, 269, 279, 279, 269, 280,
+				269, 270, 280, 280, 270, 281,
+				270, 271, 281, 281, 271, 282,
+				271, 272, 282, 282, 272, 283,
+				272, 273, 283, 283, 273, 284,
+				273, 274, 284, 284, 274, 285,
+				275, 276, 286, 286, 276, 287,
+				276, 277, 287, 287, 277, 288,
+				277, 278, 288, 288, 278, 289,
+				278, 279, 289, 289, 279, 290,
+				279, 280, 290, 290, 280, 291,
+				280, 281, 291, 291, 281, 292,
+				281, 282, 292, 292, 282, 293,
+				282, 283, 293, 293, 283, 294,
+				283, 284, 294, 294, 284, 295,
+				284, 285, 295, 295, 285, 296,
+				286, 287, 297, 297, 287, 298,
+				287, 288, 298, 298, 288, 299,
+				288, 289, 299, 299, 289, 300,
+				289, 290, 300, 300, 290, 301,
+				290, 291, 301, 301, 291, 302,
+				291, 292, 302, 302, 292, 303,
+				292, 293, 303, 303, 293, 304,
+				293, 294, 304, 304, 294, 305,
+				294, 295, 305, 305, 295, 306,
+				295, 296, 306, 306, 296, 307,
+				297, 298, 308, 308, 298, 309,
+				298, 299, 309, 309, 299, 310,
+				299, 300, 310, 310, 300, 311,
+				300, 301, 311, 311, 301, 312,
+				301, 302, 312, 312, 302, 313,
+				302, 303, 313, 313, 303, 314,
+				303, 304, 314, 314, 304, 315,
+				304, 305, 315, 315, 305, 316,
+				305, 306, 316, 316, 306, 317,
+				306, 307, 317, 317, 307, 318,
+				308, 309, 319, 319, 309, 320,
+				309, 310, 320, 320, 310, 321,
+				310, 311, 321, 321, 311, 322,
+				311, 312, 322, 322, 312, 323,
+				312, 313, 323, 323, 313, 324,
+				313, 314, 324, 324, 314, 325,
+				314, 315, 325, 325, 315, 326,
+				315, 316, 326, 326, 316, 327,
+				316, 317, 327, 327, 317, 328,
+				317, 318, 328, 328, 318, 329,
+				319, 320, 330, 330, 320, 331,
+				320, 321, 331, 331, 321, 332,
+				321, 322, 332, 332, 322, 333,
+				322, 323, 333, 333, 323, 334,
+				323, 324, 334, 334, 324, 335,
+				324, 325, 335, 335, 325, 336,
+				325, 326, 336, 336, 326, 337,
+				326, 327, 337, 337, 327, 338,
+				327, 328, 338, 338, 328, 339,
+				328, 329, 339, 339, 329, 340,
+				330, 331, 341, 341, 331, 342,
+				331, 332, 342, 342, 332, 343,
+				332, 333, 343, 343, 333, 344,
+				333, 334, 344, 344, 334, 345,
+				334, 335, 345, 345, 335, 346,
+				335, 336, 346, 346, 336, 347,
+				336, 337, 347, 347, 337, 348,
+				337, 338, 348, 348, 338, 349,
+				338, 339, 349, 349, 339, 350,
+				339, 340, 350, 350, 340, 351,
+				341, 342, 352, 352, 342, 353,
+				342, 343, 353, 353, 343, 354,
+				343, 344, 354, 354, 344, 355,
+				344, 345, 355, 355, 345, 356,
+				345, 346, 356, 356, 346, 357,
+				346, 347, 357, 357, 347, 358,
+				347, 348, 358, 358, 348, 359,
+				348, 349, 359, 359, 349, 360,
+				349, 350, 360, 360, 350, 361,
+				350, 351, 361, 361, 351, 362,
+				363, 364, 374, 374, 364, 375,
+				364, 365, 375, 375, 365, 376,
+				365, 366, 376, 376, 366, 377,
+				366, 367, 377, 377, 367, 378,
+				367, 368, 378, 378, 368, 379,
+				368, 369, 379, 379, 369, 380,
+				369, 370, 380, 380, 370, 381,
+				370, 371, 381, 381, 371, 382,
+				371, 372, 382, 382, 372, 383,
+				372, 373, 383, 383, 373, 384,
+				374, 375, 385, 385, 375, 386,
+				375, 376, 386, 386, 376, 387,
+				376, 377, 387, 387, 377, 388,
+				377, 378, 388, 388, 378, 389,
+				378, 379, 389, 389, 379, 390,
+				379, 380, 390, 390, 380, 391,
+				380, 381, 391, 391, 381, 392,
+				381, 382, 392, 392, 382, 393,
+				382, 383, 393, 393, 383, 394,
+				383, 384, 394, 394, 384, 395,
+				385, 386, 396, 396, 386, 397,
+				386, 387, 397, 397, 387, 398,
+				387, 388, 398, 398, 388, 399,
+				388, 389, 399, 399, 389, 400,
+				389, 390, 400, 400, 390, 401,
+				390, 391, 401, 401, 391, 402,
+				391, 392, 402, 402, 392, 403,
+				392, 393, 403, 403, 393, 404,
+				393, 394, 404, 404, 394, 405,
+				394, 395, 405, 405, 395, 406,
+				396, 397, 407, 407, 397, 408,
+				397, 398, 408, 408, 398, 409,
+				398, 399, 409, 409, 399, 410,
+				399, 400, 410, 410, 400, 411,
+				400, 401, 411, 411, 401, 412,
+				401, 402, 412, 412, 402, 413,
+				402, 403, 413, 413, 403, 414,
+				403, 404, 414, 414, 404, 415,
+				404, 405, 415, 415, 405, 416,
+				405, 406, 416, 416, 406, 417,
+				407, 408, 418, 418, 408, 419,
+				408, 409, 419, 419, 409, 420,
+				409, 410, 420, 420, 410, 421,
+				410, 411, 421, 421, 411, 422,
+				411, 412, 422, 422, 412, 423,
+				412, 413, 423, 423, 413, 424,
+				413, 414, 424, 424, 414, 425,
+				414, 415, 425, 425, 415, 426,
+				415, 416, 426, 426, 416, 427,
+				416, 417, 427, 427, 417, 428,
+				418, 419, 429, 429, 419, 430,
+				419, 420, 430, 430, 420, 431,
+				420, 421, 431, 431, 421, 432,
+				421, 422, 432, 432, 422, 433,
+				422, 423, 433, 433, 423, 434,
+				423, 424, 434, 434, 424, 435,
+				424, 425, 435, 435, 425, 436,
+				425, 426, 436, 436, 426, 437,
+				426, 427, 437, 437, 427, 438,
+				427, 428, 438, 438, 428, 439,
+				429, 430, 440, 440, 430, 441,
+				430, 431, 441, 441, 431, 442,
+				431, 432, 442, 442, 432, 443,
+				432, 433, 443, 443, 433, 444,
+				433, 434, 444, 444, 434, 445,
+				434, 435, 445, 445, 435, 446,
+				435, 436, 446, 446, 436, 447,
+				436, 437, 447, 447, 437, 448,
+				437, 438, 448, 448, 438, 449,
+				438, 439, 449, 449, 439, 450,
+				440, 441, 451, 451, 441, 452,
+				441, 442, 452, 452, 442, 453,
+				442, 443, 453, 453, 443, 454,
+				443, 444, 454, 454, 444, 455,
+				444, 445, 455, 455, 445, 456,
+				445, 446, 456, 456, 446, 457,
+				446, 447, 457, 457, 447, 458,
+				447, 448, 458, 458, 448, 459,
+				448, 449, 459, 459, 449, 460,
+				449, 450, 460, 460, 450, 461,
+				451, 452, 462, 462, 452, 463,
+				452, 453, 463, 463, 453, 464,
+				453, 454, 464, 464, 454, 465,
+				454, 455, 465, 465, 455, 466,
+				455, 456, 466, 466, 456, 467,
+				456, 457, 467, 467, 457, 468,
+				457, 458, 468, 468, 458, 469,
+				458, 459, 469, 469, 459, 470,
+				459, 460, 470, 470, 460, 471,
+				460, 461, 471, 471, 461, 472,
+				462, 463, 473, 473, 463, 474,
+				463, 464, 474, 474, 464, 475,
+				464, 465, 475, 475, 465, 476,
+				465, 466, 476, 476, 466, 477,
+				466, 467, 477, 477, 467, 478,
+				467, 468, 478, 478, 468, 479,
+				468, 469, 479, 479, 469, 480,
+				469, 470, 480, 480, 470, 481,
+				470, 471, 481, 481, 471, 482,
+				471, 472, 482, 482, 472, 483,
+				484, 485, 495, 495, 485, 496,
+				485, 486, 496, 496, 486, 497,
+				486, 487, 497, 497, 487, 498,
+				487, 488, 498, 498, 488, 499,
+				488, 489, 499, 499, 489, 500,
+				489, 490, 500, 500, 490, 501,
+				490, 491, 501, 501, 491, 502,
+				491, 492, 502, 502, 492, 503,
+				492, 493, 503, 503, 493, 504,
+				493, 494, 504, 504, 494, 505,
+				495, 496, 506, 506, 496, 507,
+				496, 497, 507, 507, 497, 508,
+				497, 498, 508, 508, 498, 509,
+				498, 499, 509, 509, 499, 510,
+				499, 500, 510, 510, 500, 511,
+				500, 501, 511, 511, 501, 512,
+				501, 502, 512, 512, 502, 513,
+				502, 503, 513, 513, 503, 514,
+				503, 504, 514, 514, 504, 515,
+				504, 505, 515, 515, 505, 516,
+				506, 507, 517, 517, 507, 518,
+				507, 508, 518, 518, 508, 519,
+				508, 509, 519, 519, 509, 520,
+				509, 510, 520, 520, 510, 521,
+				510, 511, 521, 521, 511, 522,
+				511, 512, 522, 522, 512, 523,
+				512, 513, 523, 523, 513, 524,
+				513, 514, 524, 524, 514, 525,
+				514, 515, 525, 525, 515, 526,
+				515, 516, 526, 526, 516, 527,
+				517, 518, 528, 528, 518, 529,
+				518, 519, 529, 529, 519, 530,
+				519, 520, 530, 530, 520, 531,
+				520, 521, 531, 531, 521, 532,
+				521, 522, 532, 532, 522, 533,
+				522, 523, 533, 533, 523, 534,
+				523, 524, 534, 534, 524, 535,
+				524, 525, 535, 535, 525, 536,
+				525, 526, 536, 536, 526, 537,
+				526, 527, 537, 537, 527, 538,
+				528, 529, 539, 539, 529, 540,
+				529, 530, 540, 540, 530, 541,
+				530, 531, 541, 541, 531, 542,
+				531, 532, 542, 542, 532, 543,
+				532, 533, 543, 543, 533, 544,
+				533, 534, 544, 544, 534, 545,
+				534, 535, 545, 545, 535, 546,
+				535, 536, 546, 546, 536, 547,
+				536, 537, 547, 547, 537, 548,
+				537, 538, 548, 548, 538, 549,
+				539, 540, 550, 550, 540, 551,
+				540, 541, 551, 551, 541, 552,
+				541, 542, 552, 552, 542, 553,
+				542, 543, 553, 553, 543, 554,
+				543, 544, 554, 554, 544, 555,
+				544, 545, 555, 555, 545, 556,
+				545, 546, 556, 556, 546, 557,
+				546, 547, 557, 557, 547, 558,
+				547, 548, 558, 558, 548, 559,
+				548, 549, 559, 559, 549, 560,
+				550, 551, 561, 561, 551, 562,
+				551, 552, 562, 562, 552, 563,
+				552, 553, 563, 563, 553, 564,
+				553, 554, 564, 564, 554, 565,
+				554, 555, 565, 565, 555, 566,
+				555, 556, 566, 566, 556, 567,
+				556, 557, 567, 567, 557, 568,
+				557, 558, 568, 568, 558, 569,
+				558, 559, 569, 569, 559, 570,
+				559, 560, 570, 570, 560, 571,
+				561, 562, 572, 572, 562, 573,
+				562, 563, 573, 573, 563, 574,
+				563, 564, 574, 574, 564, 575,
+				564, 565, 575, 575, 565, 576,
+				565, 566, 576, 576, 566, 577,
+				566, 567, 577, 577, 567, 578,
+				567, 568, 578, 578, 568, 579,
+				568, 569, 579, 579, 569, 580,
+				569, 570, 580, 580, 570, 581,
+				570, 571, 581, 581, 571, 582,
+				572, 573, 583, 583, 573, 584,
+				573, 574, 584, 584, 574, 585,
+				574, 575, 585, 585, 575, 586,
+				575, 576, 586, 586, 576, 587,
+				576, 577, 587, 587, 577, 588,
+				577, 578, 588, 588, 578, 589,
+				578, 579, 589, 589, 579, 590,
+				579, 580, 590, 590, 580, 591,
+				580, 581, 591, 591, 581, 592,
+				581, 582, 592, 592, 582, 593,
+				583, 584, 594, 594, 584, 595,
+				584, 585, 595, 595, 585, 596,
+				585, 586, 596, 596, 586, 597,
+				586, 587, 597, 597, 587, 598,
+				587, 588, 598, 598, 588, 599,
+				588, 589, 599, 599, 589, 600,
+				589, 590, 600, 600, 590, 601,
+				590, 591, 601, 601, 591, 602,
+				591, 592, 602, 602, 592, 603,
+				592, 593, 603, 603, 593, 604,
+				605, 606, 616, 616, 606, 617,
+				606, 607, 617, 617, 607, 618,
+				607, 608, 618, 618, 608, 619,
+				608, 609, 619, 619, 609, 620,
+				609, 610, 620, 620, 610, 621,
+				610, 611, 621, 621, 611, 622,
+				611, 612, 622, 622, 612, 623,
+				612, 613, 623, 623, 613, 624,
+				613, 614, 624, 624, 614, 625,
+				614, 615, 625, 625, 615, 626,
+				616, 617, 627, 627, 617, 628,
+				617, 618, 628, 628, 618, 629,
+				618, 619, 629, 629, 619, 630,
+				619, 620, 630, 630, 620, 631,
+				620, 621, 631, 631, 621, 632,
+				621, 622, 632, 632, 622, 633,
+				622, 623, 633, 633, 623, 634,
+				623, 624, 634, 634, 624, 635,
+				624, 625, 635, 635, 625, 636,
+				625, 626, 636, 636, 626, 637,
+				627, 628, 638, 638, 628, 639,
+				628, 629, 639, 639, 629, 640,
+				629, 630, 640, 640, 630, 641,
+				630, 631, 641, 641, 631, 642,
+				631, 632, 642, 642, 632, 643,
+				632, 633, 643, 643, 633, 644,
+				633, 634, 644, 644, 634, 645,
+				634, 635, 645, 645, 635, 646,
+				635, 636, 646, 646, 636, 647,
+				636, 637, 647, 647, 637, 648,
+				638, 639, 649, 649, 639, 650,
+				639, 640, 650, 650, 640, 651,
+				640, 641, 651, 651, 641, 652,
+				641, 642, 652, 652, 642, 653,
+				642, 643, 653, 653, 643, 654,
+				643, 644, 654, 654, 644, 655,
+				644, 645, 655, 655, 645, 656,
+				645, 646, 656, 656, 646, 657,
+				646, 647, 657, 657, 647, 658,
+				647, 648, 658, 658, 648, 659,
+				649, 650, 660, 660, 650, 661,
+				650, 651, 661, 661, 651, 662,
+				651, 652, 662, 662, 652, 663,
+				652, 653, 663, 663, 653, 664,
+				653, 654, 664, 664, 654, 665,
+				654, 655, 665, 665, 655, 666,
+				655, 656, 666, 666, 656, 667,
+				656, 657, 667, 667, 657, 668,
+				657, 658, 668, 668, 658, 669,
+				658, 659, 669, 669, 659, 670,
+				660, 661, 671, 671, 661, 672,
+				661, 662, 672, 672, 662, 673,
+				662, 663, 673, 673, 663, 674,
+				663, 664, 674, 674, 664, 675,
+				664, 665, 675, 675, 665, 676,
+				665, 666, 676, 676, 666, 677,
+				666, 667, 677, 677, 667, 678,
+				667, 668, 678, 678, 668, 679,
+				668, 669, 679, 679, 669, 680,
+				669, 670, 680, 680, 670, 681,
+				671, 672, 682, 682, 672, 683,
+				672, 673, 683, 683, 673, 684,
+				673, 674, 684, 684, 674, 685,
+				674, 675, 685, 685, 675, 686,
+				675, 676, 686, 686, 676, 687,
+				676, 677, 687, 687, 677, 688,
+				677, 678, 688, 688, 678, 689,
+				678, 679, 689, 689, 679, 690,
+				679, 680, 690, 690, 680, 691,
+				680, 681, 691, 691, 681, 692,
+				682, 683, 693, 693, 683, 694,
+				683, 684, 694, 694, 684, 695,
+				684, 685, 695, 695, 685, 696,
+				685, 686, 696, 696, 686, 697,
+				686, 687, 697, 697, 687, 698,
+				687, 688, 698, 698, 688, 699,
+				688, 689, 699, 699, 689, 700,
+				689, 690, 700, 700, 690, 701,
+				690, 691, 701, 701, 691, 702,
+				691, 692, 702, 702, 692, 703,
+				693, 694, 704, 704, 694, 705,
+				694, 695, 705, 705, 695, 706,
+				695, 696, 706, 706, 696, 707,
+				696, 697, 707, 707, 697, 708,
+				697, 698, 708, 708, 698, 709,
+				698, 699, 709, 709, 699, 710,
+				699, 700, 710, 710, 700, 711,
+				700, 701, 711, 711, 701, 712,
+				701, 702, 712, 712, 702, 713,
+				702, 703, 713, 713, 703, 714,
+				704, 705, 715, 715, 705, 716,
+				705, 706, 716, 716, 706, 717,
+				706, 707, 717, 717, 707, 718,
+				707, 708, 718, 718, 708, 719,
+				708, 709, 719, 719, 709, 720,
+				709, 710, 720, 720, 710, 721,
+				710, 711, 721, 721, 711, 722,
+				711, 712, 722, 722, 712, 723,
+				712, 713, 723, 723, 713, 724,
+				713, 714, 724, 724, 714, 725,
+				726, 727, 737, 737, 727, 738,
+				727, 728, 738, 738, 728, 739,
+				728, 729, 739, 739, 729, 740,
+				729, 730, 740, 740, 730, 741,
+				730, 731, 741, 741, 731, 742,
+				731, 732, 742, 742, 732, 743,
+				732, 733, 743, 743, 733, 744,
+				733, 734, 744, 744, 734, 745,
+				734, 735, 745, 745, 735, 746,
+				735, 736, 746, 746, 736, 747,
+				737, 738, 748, 748, 738, 749,
+				738, 739, 749, 749, 739, 750,
+				739, 740, 750, 750, 740, 751,
+				740, 741, 751, 751, 741, 752,
+				741, 742, 752, 752, 742, 753,
+				742, 743, 753, 753, 743, 754,
+				743, 744, 754, 754, 744, 755,
+				744, 745, 755, 755, 745, 756,
+				745, 746, 756, 756, 746, 757,
+				746, 747, 757, 757, 747, 758,
+				748, 749, 759, 759, 749, 760,
+				749, 750, 760, 760, 750, 761,
+				750, 751, 761, 761, 751, 762,
+				751, 752, 762, 762, 752, 763,
+				752, 753, 763, 763, 753, 764,
+				753, 754, 764, 764, 754, 765,
+				754, 755, 765, 765, 755, 766,
+				755, 756, 766, 766, 756, 767,
+				756, 757, 767, 767, 757, 768,
+				757, 758, 768, 768, 758, 769,
+				759, 760, 770, 770, 760, 771,
+				760, 761, 771, 771, 761, 772,
+				761, 762, 772, 772, 762, 773,
+				762, 763, 773, 773, 763, 774,
+				763, 764, 774, 774, 764, 775,
+				764, 765, 775, 775, 765, 776,
+				765, 766, 776, 776, 766, 777,
+				766, 767, 777, 777, 767, 778,
+				767, 768, 778, 778, 768, 779,
+				768, 769, 779, 779, 769, 780,
+				770, 771, 781, 781, 771, 782,
+				771, 772, 782, 782, 772, 783,
+				772, 773, 783, 783, 773, 784,
+				773, 774, 784, 784, 774, 785,
+				774, 775, 785, 785, 775, 786,
+				775, 776, 786, 786, 776, 787,
+				776, 777, 787, 787, 777, 788,
+				777, 778, 788, 788, 778, 789,
+				778, 779, 789, 789, 779, 790,
+				779, 780, 790, 790, 780, 791,
+				781, 782, 792, 792, 782, 793,
+				782, 783, 793, 793, 783, 794,
+				783, 784, 794, 794, 784, 795,
+				784, 785, 795, 795, 785, 796,
+				785, 786, 796, 796, 786, 797,
+				786, 787, 797, 797, 787, 798,
+				787, 788, 798, 798, 788, 799,
+				788, 789, 799, 799, 789, 800,
+				789, 790, 800, 800, 790, 801,
+				790, 791, 801, 801, 791, 802,
+				792, 793, 803, 803, 793, 804,
+				793, 794, 804, 804, 794, 805,
+				794, 795, 805, 805, 795, 806,
+				795, 796, 806, 806, 796, 807,
+				796, 797, 807, 807, 797, 808,
+				797, 798, 808, 808, 798, 809,
+				798, 799, 809, 809, 799, 810,
+				799, 800, 810, 810, 800, 811,
+				800, 801, 811, 811, 801, 812,
+				801, 802, 812, 812, 802, 813,
+				803, 804, 814, 814, 804, 815,
+				804, 805, 815, 815, 805, 816,
+				805, 806, 816, 816, 806, 817,
+				806, 807, 817, 817, 807, 818,
+				807, 808, 818, 818, 808, 819,
+				808, 809, 819, 819, 809, 820,
+				809, 810, 820, 820, 810, 821,
+				810, 811, 821, 821, 811, 822,
+				811, 812, 822, 822, 812, 823,
+				812, 813, 823, 823, 813, 824,
+				814, 815, 825, 825, 815, 826,
+				815, 816, 826, 826, 816, 827,
+				816, 817, 827, 827, 817, 828,
+				817, 818, 828, 828, 818, 829,
+				818, 819, 829, 829, 819, 830,
+				819, 820, 830, 830, 820, 831,
+				820, 821, 831, 831, 821, 832,
+				821, 822, 832, 832, 822, 833,
+				822, 823, 833, 833, 823, 834,
+				823, 824, 834, 834, 824, 835,
+				825, 826, 836, 836, 826, 837,
+				826, 827, 837, 837, 827, 838,
+				827, 828, 838, 838, 828, 839,
+				828, 829, 839, 839, 829, 840,
+				829, 830, 840, 840, 830, 841,
+				830, 831, 841, 841, 831, 842,
+				831, 832, 842, 842, 832, 843,
+				832, 833, 843, 843, 833, 844,
+				833, 834, 844, 844, 834, 845,
+				834, 835, 845, 845, 835, 846,
+				847, 848, 858, 858, 848, 859,
+				848, 849, 859, 859, 849, 860,
+				849, 850, 860, 860, 850, 861,
+				850, 851, 861, 861, 851, 862,
+				851, 852, 862, 862, 852, 863,
+				852, 853, 863, 863, 853, 864,
+				853, 854, 864, 864, 854, 865,
+				854, 855, 865, 865, 855, 866,
+				855, 856, 866, 866, 856, 867,
+				856, 857, 867, 867, 857, 868,
+				858, 859, 869, 869, 859, 870,
+				859, 860, 870, 870, 860, 871,
+				860, 861, 871, 871, 861, 872,
+				861, 862, 872, 872, 862, 873,
+				862, 863, 873, 873, 863, 874,
+				863, 864, 874, 874, 864, 875,
+				864, 865, 875, 875, 865, 876,
+				865, 866, 876, 876, 866, 877,
+				866, 867, 877, 877, 867, 878,
+				867, 868, 878, 878, 868, 879,
+				869, 870, 880, 880, 870, 881,
+				870, 871, 881, 881, 871, 882,
+				871, 872, 882, 882, 872, 883,
+				872, 873, 883, 883, 873, 884,
+				873, 874, 884, 884, 874, 885,
+				874, 875, 885, 885, 875, 886,
+				875, 876, 886, 886, 876, 887,
+				876, 877, 887, 887, 877, 888,
+				877, 878, 888, 888, 878, 889,
+				878, 879, 889, 889, 879, 890,
+				880, 881, 891, 891, 881, 892,
+				881, 882, 892, 892, 882, 893,
+				882, 883, 893, 893, 883, 894,
+				883, 884, 894, 894, 884, 895,
+				884, 885, 895, 895, 885, 896,
+				885, 886, 896, 896, 886, 897,
+				886, 887, 897, 897, 887, 898,
+				887, 888, 898, 898, 888, 899,
+				888, 889, 899, 899, 889, 900,
+				889, 890, 900, 900, 890, 901,
+				891, 892, 902, 902, 892, 903,
+				892, 893, 903, 903, 893, 904,
+				893, 894, 904, 904, 894, 905,
+				894, 895, 905, 905, 895, 906,
+				895, 896, 906, 906, 896, 907,
+				896, 897, 907, 907, 897, 908,
+				897, 898, 908, 908, 898, 909,
+				898, 899, 909, 909, 899, 910,
+				899, 900, 910, 910, 900, 911,
+				900, 901, 911, 911, 901, 912,
+				902, 903, 913, 913, 903, 914,
+				903, 904, 914, 914, 904, 915,
+				904, 905, 915, 915, 905, 916,
+				905, 906, 916, 916, 906, 917,
+				906, 907, 917, 917, 907, 918,
+				907, 908, 918, 918, 908, 919,
+				908, 909, 919, 919, 909, 920,
+				909, 910, 920, 920, 910, 921,
+				910, 911, 921, 921, 911, 922,
+				911, 912, 922, 922, 912, 923,
+				913, 914, 924, 924, 914, 925,
+				914, 915, 925, 925, 915, 926,
+				915, 916, 926, 926, 916, 927,
+				916, 917, 927, 927, 917, 928,
+				917, 918, 928, 928, 918, 929,
+				918, 919, 929, 929, 919, 930,
+				919, 920, 930, 930, 920, 931,
+				920, 921, 931, 931, 921, 932,
+				921, 922, 932, 932, 922, 933,
+				922, 923, 933, 933, 923, 934,
+				924, 925, 935, 935, 925, 936,
+				925, 926, 936, 936, 926, 937,
+				926, 927, 937, 937, 927, 938,
+				927, 928, 938, 938, 928, 939,
+				928, 929, 939, 939, 929, 940,
+				929, 930, 940, 940, 930, 941,
+				930, 931, 941, 941, 931, 942,
+				931, 932, 942, 942, 932, 943,
+				932, 933, 943, 943, 933, 944,
+				933, 934, 944, 944, 934, 945,
+				935, 936, 946, 946, 936, 947,
+				936, 937, 947, 947, 937, 948,
+				937, 938, 948, 948, 938, 949,
+				938, 939, 949, 949, 939, 950,
+				939, 940, 950, 950, 940, 951,
+				940, 941, 951, 951, 941, 952,
+				941, 942, 952, 952, 942, 953,
+				942, 943, 953, 953, 943, 954,
+				943, 944, 954, 954, 944, 955,
+				944, 945, 955, 955, 945, 956,
+				946, 947, 957, 957, 947, 958,
+				947, 948, 958, 958, 948, 959,
+				948, 949, 959, 959, 949, 960,
+				949, 950, 960, 960, 950, 961,
+				950, 951, 961, 961, 951, 962,
+				951, 952, 962, 962, 952, 963,
+				952, 953, 963, 963, 953, 964,
+				953, 954, 964, 964, 954, 965,
+				954, 955, 965, 965, 955, 966,
+				955, 956, 966, 966, 956, 967,
+				968, 969, 979, 979, 969, 980,
+				969, 970, 980, 980, 970, 981,
+				970, 971, 981, 981, 971, 982,
+				971, 972, 982, 982, 972, 983,
+				972, 973, 983, 983, 973, 984,
+				973, 974, 984, 984, 974, 985,
+				974, 975, 985, 985, 975, 986,
+				975, 976, 986, 986, 976, 987,
+				976, 977, 987, 987, 977, 988,
+				977, 978, 988, 988, 978, 989,
+				979, 980, 990, 990, 980, 991,
+				980, 981, 991, 991, 981, 992,
+				981, 982, 992, 992, 982, 993,
+				982, 983, 993, 993, 983, 994,
+				983, 984, 994, 994, 984, 995,
+				984, 985, 995, 995, 985, 996,
+				985, 986, 996, 996, 986, 997,
+				986, 987, 997, 997, 987, 998,
+				987, 988, 998, 998, 988, 999,
+				988, 989, 999, 999, 989, 1000,
+				990, 991, 1001, 1001, 991, 1002,
+				991, 992, 1002, 1002, 992, 1003,
+				992, 993, 1003, 1003, 993, 1004,
+				993, 994, 1004, 1004, 994, 1005,
+				994, 995, 1005, 1005, 995, 1006,
+				995, 996, 1006, 1006, 996, 1007,
+				996, 997, 1007, 1007, 997, 1008,
+				997, 998, 1008, 1008, 998, 1009,
+				998, 999, 1009, 1009, 999, 1010,
+				999, 1000, 1010, 1010, 1000, 1011,
+				1001, 1002, 1012, 1012, 1002, 1013,
+				1002, 1003, 1013, 1013, 1003, 1014,
+				1003, 1004, 1014, 1014, 1004, 1015,
+				1004, 1005, 1015, 1015, 1005, 1016,
+				1005, 1006, 1016, 1016, 1006, 1017,
+				1006, 1007, 1017, 1017, 1007, 1018,
+				1007, 1008, 1018, 1018, 1008, 1019,
+				1008, 1009, 1019, 1019, 1009, 1020,
+				1009, 1010, 1020, 1020, 1010, 1021,
+				1010, 1011, 1021, 1021, 1011, 1022,
+				1012, 1013, 1023, 1023, 1013, 1024,
+				1013, 1014, 1024, 1024, 1014, 1025,
+				1014, 1015, 1025, 1025, 1015, 1026,
+				1015, 1016, 1026, 1026, 1016, 1027,
+				1016, 1017, 1027, 1027, 1017, 1028,
+				1017, 1018, 1028, 1028, 1018, 1029,
+				1018, 1019, 1029, 1029, 1019, 1030,
+				1019, 1020, 1030, 1030, 1020, 1031,
+				1020, 1021, 1031, 1031, 1021, 1032,
+				1021, 1022, 1032, 1032, 1022, 1033,
+				1023, 1024, 1034, 1034, 1024, 1035,
+				1024, 1025, 1035, 1035, 1025, 1036,
+				1025, 1026, 1036, 1036, 1026, 1037,
+				1026, 1027, 1037, 1037, 1027, 1038,
+				1027, 1028, 1038, 1038, 1028, 1039,
+				1028, 1029, 1039, 1039, 1029, 1040,
+				1029, 1030, 1040, 1040, 1030, 1041,
+				1030, 1031, 1041, 1041, 1031, 1042,
+				1031, 1032, 1042, 1042, 1032, 1043,
+				1032, 1033, 1043, 1043, 1033, 1044,
+				1034, 1035, 1045, 1045, 1035, 1046,
+				1035, 1036, 1046, 1046, 1036, 1047,
+				1036, 1037, 1047, 1047, 1037, 1048,
+				1037, 1038, 1048, 1048, 1038, 1049,
+				1038, 1039, 1049, 1049, 1039, 1050,
+				1039, 1040, 1050, 1050, 1040, 1051,
+				1040, 1041, 1051, 1051, 1041, 1052,
+				1041, 1042, 1052, 1052, 1042, 1053,
+				1042, 1043, 1053, 1053, 1043, 1054,
+				1043, 1044, 1054, 1054, 1044, 1055,
+				1045, 1046, 1056, 1056, 1046, 1057,
+				1046, 1047, 1057, 1057, 1047, 1058,
+				1047, 1048, 1058, 1058, 1048, 1059,
+				1048, 1049, 1059, 1059, 1049, 1060,
+				1049, 1050, 1060, 1060, 1050, 1061,
+				1050, 1051, 1061, 1061, 1051, 1062,
+				1051, 1052, 1062, 1062, 1052, 1063,
+				1052, 1053, 1063, 1063, 1053, 1064,
+				1053, 1054, 1064, 1064, 1054, 1065,
+				1054, 1055, 1065, 1065, 1055, 1066,
+				1056, 1057, 1067, 1067, 1057, 1068,
+				1057, 1058, 1068, 1068, 1058, 1069,
+				1058, 1059, 1069, 1069, 1059, 1070,
+				1059, 1060, 1070, 1070, 1060, 1071,
+				1060, 1061, 1071, 1071, 1061, 1072,
+				1061, 1062, 1072, 1072, 1062, 1073,
+				1062, 1063, 1073, 1073, 1063, 1074,
+				1063, 1064, 1074, 1074, 1064, 1075,
+				1064, 1065, 1075, 1075, 1065, 1076,
+				1065, 1066, 1076, 1076, 1066, 1077,
+				1067, 1068, 1078, 1078, 1068, 1079,
+				1068, 1069, 1079, 1079, 1069, 1080,
+				1069, 1070, 1080, 1080, 1070, 1081,
+				1070, 1071, 1081, 1081, 1071, 1082,
+				1071, 1072, 1082, 1082, 1072, 1083,
+				1072, 1073, 1083, 1083, 1073, 1084,
+				1073, 1074, 1084, 1084, 1074, 1085,
+				1074, 1075, 1085, 1085, 1075, 1086,
+				1075, 1076, 1086, 1086, 1076, 1087,
+				1076, 1077, 1087, 1087, 1077, 1088,
+				1089, 1090, 1100, 1100, 1090, 1101,
+				1090, 1091, 1101, 1101, 1091, 1102,
+				1091, 1092, 1102, 1102, 1092, 1103,
+				1092, 1093, 1103, 1103, 1093, 1104,
+				1093, 1094, 1104, 1104, 1094, 1105,
+				1094, 1095, 1105, 1105, 1095, 1106,
+				1095, 1096, 1106, 1106, 1096, 1107,
+				1096, 1097, 1107, 1107, 1097, 1108,
+				1097, 1098, 1108, 1108, 1098, 1109,
+				1098, 1099, 1109, 1109, 1099, 1110,
+				1100, 1101, 1111, 1111, 1101, 1112,
+				1101, 1102, 1112, 1112, 1102, 1113,
+				1102, 1103, 1113, 1113, 1103, 1114,
+				1103, 1104, 1114, 1114, 1104, 1115,
+				1104, 1105, 1115, 1115, 1105, 1116,
+				1105, 1106, 1116, 1116, 1106, 1117,
+				1106, 1107, 1117, 1117, 1107, 1118,
+				1107, 1108, 1118, 1118, 1108, 1119,
+				1108, 1109, 1119, 1119, 1109, 1120,
+				1109, 1110, 1120, 1120, 1110, 1121,
+				1111, 1112, 1122, 1122, 1112, 1123,
+				1112, 1113, 1123, 1123, 1113, 1124,
+				1113, 1114, 1124, 1124, 1114, 1125,
+				1114, 1115, 1125, 1125, 1115, 1126,
+				1115, 1116, 1126, 1126, 1116, 1127,
+				1116, 1117, 1127, 1127, 1117, 1128,
+				1117, 1118, 1128, 1128, 1118, 1129,
+				1118, 1119, 1129, 1129, 1119, 1130,
+				1119, 1120, 1130, 1130, 1120, 1131,
+				1120, 1121, 1131, 1131, 1121, 1132,
+				1122, 1123, 1133, 1133, 1123, 1134,
+				1123, 1124, 1134, 1134, 1124, 1135,
+				1124, 1125, 1135, 1135, 1125, 1136,
+				1125, 1126, 1136, 1136, 1126, 1137,
+				1126, 1127, 1137, 1137, 1127, 1138,
+				1127, 1128, 1138, 1138, 1128, 1139,
+				1128, 1129, 1139, 1139, 1129, 1140,
+				1129, 1130, 1140, 1140, 1130, 1141,
+				1130, 1131, 1141, 1141, 1131, 1142,
+				1131, 1132, 1142, 1142, 1132, 1143,
+				1133, 1134, 1144, 1144, 1134, 1145,
+				1134, 1135, 1145, 1145, 1135, 1146,
+				1135, 1136, 1146, 1146, 1136, 1147,
+				1136, 1137, 1147, 1147, 1137, 1148,
+				1137, 1138, 1148, 1148, 1138, 1149,
+				1138, 1139, 1149, 1149, 1139, 1150,
+				1139, 1140, 1150, 1150, 1140, 1151,
+				1140, 1141, 1151, 1151, 1141, 1152,
+				1141, 1142, 1152, 1152, 1142, 1153,
+				1142, 1143, 1153, 1153, 1143, 1154,
+				1144, 1145, 1155, 1155, 1145, 1156,
+				1145, 1146, 1156, 1156, 1146, 1157,
+				1146, 1147, 1157, 1157, 1147, 1158,
+				1147, 1148, 1158, 1158, 1148, 1159,
+				1148, 1149, 1159, 1159, 1149, 1160,
+				1149, 1150, 1160, 1160, 1150, 1161,
+				1150, 1151, 1161, 1161, 1151, 1162,
+				1151, 1152, 1162, 1162, 1152, 1163,
+				1152, 1153, 1163, 1163, 1153, 1164,
+				1153, 1154, 1164, 1164, 1154, 1165,
+				1155, 1156, 1166, 1166, 1156, 1167,
+				1156, 1157, 1167, 1167, 1157, 1168,
+				1157, 1158, 1168, 1168, 1158, 1169,
+				1158, 1159, 1169, 1169, 1159, 1170,
+				1159, 1160, 1170, 1170, 1160, 1171,
+				1160, 1161, 1171, 1171, 1161, 1172,
+				1161, 1162, 1172, 1172, 1162, 1173,
+				1162, 1163, 1173, 1173, 1163, 1174,
+				1163, 1164, 1174, 1174, 1164, 1175,
+				1164, 1165, 1175, 1175, 1165, 1176,
+				1166, 1167, 1177, 1177, 1167, 1178,
+				1167, 1168, 1178, 1178, 1168, 1179,
+				1168, 1169, 1179, 1179, 1169, 1180,
+				1169, 1170, 1180, 1180, 1170, 1181,
+				1170, 1171, 1181, 1181, 1171, 1182,
+				1171, 1172, 1182, 1182, 1172, 1183,
+				1172, 1173, 1183, 1183, 1173, 1184,
+				1173, 1174, 1184, 1184, 1174, 1185,
+				1174, 1175, 1185, 1185, 1175, 1186,
+				1175, 1176, 1186, 1186, 1176, 1187,
+				1177, 1178, 1188, 1188, 1178, 1189,
+				1178, 1179, 1189, 1189, 1179, 1190,
+				1179, 1180, 1190, 1190, 1180, 1191,
+				1180, 1181, 1191, 1191, 1181, 1192,
+				1181, 1182, 1192, 1192, 1182, 1193,
+				1182, 1183, 1193, 1193, 1183, 1194,
+				1183, 1184, 1194, 1194, 1184, 1195,
+				1184, 1185, 1195, 1195, 1185, 1196,
+				1185, 1186, 1196, 1196, 1186, 1197,
+				1186, 1187, 1197, 1197, 1187, 1198,
+				1188, 1189, 1199, 1199, 1189, 1200,
+				1189, 1190, 1200, 1200, 1190, 1201,
+				1190, 1191, 1201, 1201, 1191, 1202,
+				1191, 1192, 1202, 1202, 1192, 1203,
+				1192, 1193, 1203, 1203, 1193, 1204,
+				1193, 1194, 1204, 1204, 1194, 1205,
+				1194, 1195, 1205, 1205, 1195, 1206,
+				1195, 1196, 1206, 1206, 1196, 1207,
+				1196, 1197, 1207, 1207, 1197, 1208,
+				1197, 1198, 1208, 1208, 1198, 1209,
+				1210, 1211, 1221, 1221, 1211, 1222,
+				1211, 1212, 1222, 1222, 1212, 1223,
+				1212, 1213, 1223, 1223, 1213, 1224,
+				1213, 1214, 1224, 1224, 1214, 1225,
+				1214, 1215, 1225, 1225, 1215, 1226,
+				1215, 1216, 1226, 1226, 1216, 1227,
+				1216, 1217, 1227, 1227, 1217, 1228,
+				1217, 1218, 1228, 1228, 1218, 1229,
+				1218, 1219, 1229, 1229, 1219, 1230,
+				1219, 1220, 1230, 1230, 1220, 1231,
+				1221, 1222, 1232, 1232, 1222, 1233,
+				1222, 1223, 1233, 1233, 1223, 1234,
+				1223, 1224, 1234, 1234, 1224, 1235,
+				1224, 1225, 1235, 1235, 1225, 1236,
+				1225, 1226, 1236, 1236, 1226, 1237,
+				1226, 1227, 1237, 1237, 1227, 1238,
+				1227, 1228, 1238, 1238, 1228, 1239,
+				1228, 1229, 1239, 1239, 1229, 1240,
+				1229, 1230, 1240, 1240, 1230, 1241,
+				1230, 1231, 1241, 1241, 1231, 1242,
+				1232, 1233, 1243, 1243, 1233, 1244,
+				1233, 1234, 1244, 1244, 1234, 1245,
+				1234, 1235, 1245, 1245, 1235, 1246,
+				1235, 1236, 1246, 1246, 1236, 1247,
+				1236, 1237, 1247, 1247, 1237, 1248,
+				1237, 1238, 1248, 1248, 1238, 1249,
+				1238, 1239, 1249, 1249, 1239, 1250,
+				1239, 1240, 1250, 1250, 1240, 1251,
+				1240, 1241, 1251, 1251, 1241, 1252,
+				1241, 1242, 1252, 1252, 1242, 1253,
+				1243, 1244, 1254, 1254, 1244, 1255,
+				1244, 1245, 1255, 1255, 1245, 1256,
+				1245, 1246, 1256, 1256, 1246, 1257,
+				1246, 1247, 1257, 1257, 1247, 1258,
+				1247, 1248, 1258, 1258, 1248, 1259,
+				1248, 1249, 1259, 1259, 1249, 1260,
+				1249, 1250, 1260, 1260, 1250, 1261,
+				1250, 1251, 1261, 1261, 1251, 1262,
+				1251, 1252, 1262, 1262, 1252, 1263,
+				1252, 1253, 1263, 1263, 1253, 1264,
+				1254, 1255, 1265, 1265, 1255, 1266,
+				1255, 1256, 1266, 1266, 1256, 1267,
+				1256, 1257, 1267, 1267, 1257, 1268,
+				1257, 1258, 1268, 1268, 1258, 1269,
+				1258, 1259, 1269, 1269, 1259, 1270,
+				1259, 1260, 1270, 1270, 1260, 1271,
+				1260, 1261, 1271, 1271, 1261, 1272,
+				1261, 1262, 1272, 1272, 1262, 1273,
+				1262, 1263, 1273, 1273, 1263, 1274,
+				1263, 1264, 1274, 1274, 1264, 1275,
+				1265, 1266, 1276, 1276, 1266, 1277,
+				1266, 1267, 1277, 1277, 1267, 1278,
+				1267, 1268, 1278, 1278, 1268, 1279,
+				1268, 1269, 1279, 1279, 1269, 1280,
+				1269, 1270, 1280, 1280, 1270, 1281,
+				1270, 1271, 1281, 1281, 1271, 1282,
+				1271, 1272, 1282, 1282, 1272, 1283,
+				1272, 1273, 1283, 1283, 1273, 1284,
+				1273, 1274, 1284, 1284, 1274, 1285,
+				1274, 1275, 1285, 1285, 1275, 1286,
+				1276, 1277, 1287, 1287, 1277, 1288,
+				1277, 1278, 1288, 1288, 1278, 1289,
+				1278, 1279, 1289, 1289, 1279, 1290,
+				1279, 1280, 1290, 1290, 1280, 1291,
+				1280, 1281, 1291, 1291, 1281, 1292,
+				1281, 1282, 1292, 1292, 1282, 1293,
+				1282, 1283, 1293, 1293, 1283, 1294,
+				1283, 1284, 1294, 1294, 1284, 1295,
+				1284, 1285, 1295, 1295, 1285, 1296,
+				1285, 1286, 1296, 1296, 1286, 1297,
+				1287, 1288, 1298, 1298, 1288, 1299,
+				1288, 1289, 1299, 1299, 1289, 1300,
+				1289, 1290, 1300, 1300, 1290, 1301,
+				1290, 1291, 1301, 1301, 1291, 1302,
+				1291, 1292, 1302, 1302, 1292, 1303,
+				1292, 1293, 1303, 1303, 1293, 1304,
+				1293, 1294, 1304, 1304, 1294, 1305,
+				1294, 1295, 1305, 1305, 1295, 1306,
+				1295, 1296, 1306, 1306, 1296, 1307,
+				1296, 1297, 1307, 1307, 1297, 1308,
+				1298, 1299, 1309, 1309, 1299, 1310,
+				1299, 1300, 1310, 1310, 1300, 1311,
+				1300, 1301, 1311, 1311, 1301, 1312,
+				1301, 1302, 1312, 1312, 1302, 1313,
+				1302, 1303, 1313, 1313, 1303, 1314,
+				1303, 1304, 1314, 1314, 1304, 1315,
+				1304, 1305, 1315, 1315, 1305, 1316,
+				1305, 1306, 1316, 1316, 1306, 1317,
+				1306, 1307, 1317, 1317, 1307, 1318,
+				1307, 1308, 1318, 1318, 1308, 1319,
+				1309, 1310, 1320, 1320, 1310, 1321,
+				1310, 1311, 1321, 1321, 1311, 1322,
+				1311, 1312, 1322, 1322, 1312, 1323,
+				1312, 1313, 1323, 1323, 1313, 1324,
+				1313, 1314, 1324, 1324, 1314, 1325,
+				1314, 1315, 1325, 1325, 1315, 1326,
+				1315, 1316, 1326, 1326, 1316, 1327,
+				1316, 1317, 1327, 1327, 1317, 1328,
+				1317, 1318, 1328, 1328, 1318, 1329,
+				1318, 1319, 1329, 1329, 1319, 1330,
+				1331, 1332, 1342, 1342, 1332, 1343,
+				1332, 1333, 1343, 1343, 1333, 1344,
+				1333, 1334, 1344, 1344, 1334, 1345,
+				1334, 1335, 1345, 1345, 1335, 1346,
+				1335, 1336, 1346, 1346, 1336, 1347,
+				1336, 1337, 1347, 1347, 1337, 1348,
+				1337, 1338, 1348, 1348, 1338, 1349,
+				1338, 1339, 1349, 1349, 1339, 1350,
+				1339, 1340, 1350, 1350, 1340, 1351,
+				1340, 1341, 1351, 1351, 1341, 1352,
+				1342, 1343, 1353, 1353, 1343, 1354,
+				1343, 1344, 1354, 1354, 1344, 1355,
+				1344, 1345, 1355, 1355, 1345, 1356,
+				1345, 1346, 1356, 1356, 1346, 1357,
+				1346, 1347, 1357, 1357, 1347, 1358,
+				1347, 1348, 1358, 1358, 1348, 1359,
+				1348, 1349, 1359, 1359, 1349, 1360,
+				1349, 1350, 1360, 1360, 1350, 1361,
+				1350, 1351, 1361, 1361, 1351, 1362,
+				1351, 1352, 1362, 1362, 1352, 1363,
+				1353, 1354, 1364, 1364, 1354, 1365,
+				1354, 1355, 1365, 1365, 1355, 1366,
+				1355, 1356, 1366, 1366, 1356, 1367,
+				1356, 1357, 1367, 1367, 1357, 1368,
+				1357, 1358, 1368, 1368, 1358, 1369,
+				1358, 1359, 1369, 1369, 1359, 1370,
+				1359, 1360, 1370, 1370, 1360, 1371,
+				1360, 1361, 1371, 1371, 1361, 1372,
+				1361, 1362, 1372, 1372, 1362, 1373,
+				1362, 1363, 1373, 1373, 1363, 1374,
+				1364, 1365, 1375, 1375, 1365, 1376,
+				1365, 1366, 1376, 1376, 1366, 1377,
+				1366, 1367, 1377, 1377, 1367, 1378,
+				1367, 1368, 1378, 1378, 1368, 1379,
+				1368, 1369, 1379, 1379, 1369, 1380,
+				1369, 1370, 1380, 1380, 1370, 1381,
+				1370, 1371, 1381, 1381, 1371, 1382,
+				1371, 1372, 1382, 1382, 1372, 1383,
+				1372, 1373, 1383, 1383, 1373, 1384,
+				1373, 1374, 1384, 1384, 1374, 1385,
+				1375, 1376, 1386, 1386, 1376, 1387,
+				1376, 1377, 1387, 1387, 1377, 1388,
+				1377, 1378, 1388, 1388, 1378, 1389,
+				1378, 1379, 1389, 1389, 1379, 1390,
+				1379, 1380, 1390, 1390, 1380, 1391,
+				1380, 1381, 1391, 1391, 1381, 1392,
+				1381, 1382, 1392, 1392, 1382, 1393,
+				1382, 1383, 1393, 1393, 1383, 1394,
+				1383, 1384, 1394, 1394, 1384, 1395,
+				1384, 1385, 1395, 1395, 1385, 1396,
+				1386, 1387, 1397, 1397, 1387, 1398,
+				1387, 1388, 1398, 1398, 1388, 1399,
+				1388, 1389, 1399, 1399, 1389, 1400,
+				1389, 1390, 1400, 1400, 1390, 1401,
+				1390, 1391, 1401, 1401, 1391, 1402,
+				1391, 1392, 1402, 1402, 1392, 1403,
+				1392, 1393, 1403, 1403, 1393, 1404,
+				1393, 1394, 1404, 1404, 1394, 1405,
+				1394, 1395, 1405, 1405, 1395, 1406,
+				1395, 1396, 1406, 1406, 1396, 1407,
+				1397, 1398, 1408, 1408, 1398, 1409,
+				1398, 1399, 1409, 1409, 1399, 1410,
+				1399, 1400, 1410, 1410, 1400, 1411,
+				1400, 1401, 1411, 1411, 1401, 1412,
+				1401, 1402, 1412, 1412, 1402, 1413,
+				1402, 1403, 1413, 1413, 1403, 1414,
+				1403, 1404, 1414, 1414, 1404, 1415,
+				1404, 1405, 1415, 1415, 1405, 1416,
+				1405, 1406, 1416, 1416, 1406, 1417,
+				1406, 1407, 1417, 1417, 1407, 1418,
+				1408, 1409, 1419, 1419, 1409, 1420,
+				1409, 1410, 1420, 1420, 1410, 1421,
+				1410, 1411, 1421, 1421, 1411, 1422,
+				1411, 1412, 1422, 1422, 1412, 1423,
+				1412, 1413, 1423, 1423, 1413, 1424,
+				1413, 1414, 1424, 1424, 1414, 1425,
+				1414, 1415, 1425, 1425, 1415, 1426,
+				1415, 1416, 1426, 1426, 1416, 1427,
+				1416, 1417, 1427, 1427, 1417, 1428,
+				1417, 1418, 1428, 1428, 1418, 1429,
+				1419, 1420, 1430, 1430, 1420, 1431,
+				1420, 1421, 1431, 1431, 1421, 1432,
+				1421, 1422, 1432, 1432, 1422, 1433,
+				1422, 1423, 1433, 1433, 1423, 1434,
+				1423, 1424, 1434, 1434, 1424, 1435,
+				1424, 1425, 1435, 1435, 1425, 1436,
+				1425, 1426, 1436, 1436, 1426, 1437,
+				1426, 1427, 1437, 1437, 1427, 1438,
+				1427, 1428, 1438, 1438, 1428, 1439,
+				1428, 1429, 1439, 1439, 1429, 1440,
+				1430, 1431, 1441, 1441, 1431, 1442,
+				1431, 1432, 1442, 1442, 1432, 1443,
+				1432, 1433, 1443, 1443, 1433, 1444,
+				1433, 1434, 1444, 1444, 1434, 1445,
+				1434, 1435, 1445, 1445, 1435, 1446,
+				1435, 1436, 1446, 1446, 1436, 1447,
+				1436, 1437, 1447, 1447, 1437, 1448,
+				1437, 1438, 1448, 1448, 1438, 1449,
+				1438, 1439, 1449, 1449, 1439, 1450,
+				1439, 1440, 1450, 1450, 1440, 1451,
+				1452, 1453, 1463, 1463, 1453, 1464,
+				1453, 1454, 1464, 1464, 1454, 1465,
+				1454, 1455, 1465, 1465, 1455, 1466,
+				1455, 1456, 1466, 1466, 1456, 1467,
+				1456, 1457, 1467, 1467, 1457, 1468,
+				1457, 1458, 1468, 1468, 1458, 1469,
+				1458, 1459, 1469, 1469, 1459, 1470,
+				1459, 1460, 1470, 1470, 1460, 1471,
+				1460, 1461, 1471, 1471, 1461, 1472,
+				1461, 1462, 1472, 1472, 1462, 1473,
+				1463, 1464, 1474, 1474, 1464, 1475,
+				1464, 1465, 1475, 1475, 1465, 1476,
+				1465, 1466, 1476, 1476, 1466, 1477,
+				1466, 1467, 1477, 1477, 1467, 1478,
+				1467, 1468, 1478, 1478, 1468, 1479,
+				1468, 1469, 1479, 1479, 1469, 1480,
+				1469, 1470, 1480, 1480, 1470, 1481,
+				1470, 1471, 1481, 1481, 1471, 1482,
+				1471, 1472, 1482, 1482, 1472, 1483,
+				1472, 1473, 1483, 1483, 1473, 1484,
+				1474, 1475, 1485, 1485, 1475, 1486,
+				1475, 1476, 1486, 1486, 1476, 1487,
+				1476, 1477, 1487, 1487, 1477, 1488,
+				1477, 1478, 1488, 1488, 1478, 1489,
+				1478, 1479, 1489, 1489, 1479, 1490,
+				1479, 1480, 1490, 1490, 1480, 1491,
+				1480, 1481, 1491, 1491, 1481, 1492,
+				1481, 1482, 1492, 1492, 1482, 1493,
+				1482, 1483, 1493, 1493, 1483, 1494,
+				1483, 1484, 1494, 1494, 1484, 1495,
+				1485, 1486, 1496, 1496, 1486, 1497,
+				1486, 1487, 1497, 1497, 1487, 1498,
+				1487, 1488, 1498, 1498, 1488, 1499,
+				1488, 1489, 1499, 1499, 1489, 1500,
+				1489, 1490, 1500, 1500, 1490, 1501,
+				1490, 1491, 1501, 1501, 1491, 1502,
+				1491, 1492, 1502, 1502, 1492, 1503,
+				1492, 1493, 1503, 1503, 1493, 1504,
+				1493, 1494, 1504, 1504, 1494, 1505,
+				1494, 1495, 1505, 1505, 1495, 1506,
+				1496, 1497, 1507, 1507, 1497, 1508,
+				1497, 1498, 1508, 1508, 1498, 1509,
+				1498, 1499, 1509, 1509, 1499, 1510,
+				1499, 1500, 1510, 1510, 1500, 1511,
+				1500, 1501, 1511, 1511, 1501, 1512,
+				1501, 1502, 1512, 1512, 1502, 1513,
+				1502, 1503, 1513, 1513, 1503, 1514,
+				1503, 1504, 1514, 1514, 1504, 1515,
+				1504, 1505, 1515, 1515, 1505, 1516,
+				1505, 1506, 1516, 1516, 1506, 1517,
+				1507, 1508, 1518, 1518, 1508, 1519,
+				1508, 1509, 1519, 1519, 1509, 1520,
+				1509, 1510, 1520, 1520, 1510, 1521,
+				1510, 1511, 1521, 1521, 1511, 1522,
+				1511, 1512, 1522, 1522, 1512, 1523,
+				1512, 1513, 1523, 1523, 1513, 1524,
+				1513, 1514, 1524, 1524, 1514, 1525,
+				1514, 1515, 1525, 1525, 1515, 1526,
+				1515, 1516, 1526, 1526, 1516, 1527,
+				1516, 1517, 1527, 1527, 1517, 1528,
+				1518, 1519, 1529, 1529, 1519, 1530,
+				1519, 1520, 1530, 1530, 1520, 1531,
+				1520, 1521, 1531, 1531, 1521, 1532,
+				1521, 1522, 1532, 1532, 1522, 1533,
+				1522, 1523, 1533, 1533, 1523, 1534,
+				1523, 1524, 1534, 1534, 1524, 1535,
+				1524, 1525, 1535, 1535, 1525, 1536,
+				1525, 1526, 1536, 1536, 1526, 1537,
+				1526, 1527, 1537, 1537, 1527, 1538,
+				1527, 1528, 1538, 1538, 1528, 1539,
+				1529, 1530, 1540, 1540, 1530, 1541,
+				1530, 1531, 1541, 1541, 1531, 1542,
+				1531, 1532, 1542, 1542, 1532, 1543,
+				1532, 1533, 1543, 1543, 1533, 1544,
+				1533, 1534, 1544, 1544, 1534, 1545,
+				1534, 1535, 1545, 1545, 1535, 1546,
+				1535, 1536, 1546, 1546, 1536, 1547,
+				1536, 1537, 1547, 1547, 1537, 1548,
+				1537, 1538, 1548, 1548, 1538, 1549,
+				1538, 1539, 1549, 1549, 1539, 1550,
+				1540, 1541, 1551, 1551, 1541, 1552,
+				1541, 1542, 1552, 1552, 1542, 1553,
+				1542, 1543, 1553, 1553, 1543, 1554,
+				1543, 1544, 1554, 1554, 1544, 1555,
+				1544, 1545, 1555, 1555, 1545, 1556,
+				1545, 1546, 1556, 1556, 1546, 1557,
+				1546, 1547, 1557, 1557, 1547, 1558,
+				1547, 1548, 1558, 1558, 1548, 1559,
+				1548, 1549, 1559, 1559, 1549, 1560,
+				1549, 1550, 1560, 1560, 1550, 1561,
+				1551, 1552, 1562, 1562, 1552, 1563,
+				1552, 1553, 1563, 1563, 1553, 1564,
+				1553, 1554, 1564, 1564, 1554, 1565,
+				1554, 1555, 1565, 1565, 1555, 1566,
+				1555, 1556, 1566, 1566, 1556, 1567,
+				1556, 1557, 1567, 1567, 1557, 1568,
+				1557, 1558, 1568, 1568, 1558, 1569,
+				1558, 1559, 1569, 1569, 1559, 1570,
+				1559, 1560, 1570, 1570, 1560, 1571,
+				1560, 1561, 1571, 1571, 1561, 1572,
+				1573, 1574, 1584, 1584, 1574, 1585,
+				1574, 1575, 1585, 1585, 1575, 1586,
+				1575, 1576, 1586, 1586, 1576, 1587,
+				1576, 1577, 1587, 1587, 1577, 1588,
+				1577, 1578, 1588, 1588, 1578, 1589,
+				1578, 1579, 1589, 1589, 1579, 1590,
+				1579, 1580, 1590, 1590, 1580, 1591,
+				1580, 1581, 1591, 1591, 1581, 1592,
+				1581, 1582, 1592, 1592, 1582, 1593,
+				1582, 1583, 1593, 1593, 1583, 1594,
+				1584, 1585, 1595, 1595, 1585, 1596,
+				1585, 1586, 1596, 1596, 1586, 1597,
+				1586, 1587, 1597, 1597, 1587, 1598,
+				1587, 1588, 1598, 1598, 1588, 1599,
+				1588, 1589, 1599, 1599, 1589, 1600,
+				1589, 1590, 1600, 1600, 1590, 1601,
+				1590, 1591, 1601, 1601, 1591, 1602,
+				1591, 1592, 1602, 1602, 1592, 1603,
+				1592, 1593, 1603, 1603, 1593, 1604,
+				1593, 1594, 1604, 1604, 1594, 1605,
+				1595, 1596, 1606, 1606, 1596, 1607,
+				1596, 1597, 1607, 1607, 1597, 1608,
+				1597, 1598, 1608, 1608, 1598, 1609,
+				1598, 1599, 1609, 1609, 1599, 1610,
+				1599, 1600, 1610, 1610, 1600, 1611,
+				1600, 1601, 1611, 1611, 1601, 1612,
+				1601, 1602, 1612, 1612, 1602, 1613,
+				1602, 1603, 1613, 1613, 1603, 1614,
+				1603, 1604, 1614, 1614, 1604, 1615,
+				1604, 1605, 1615, 1615, 1605, 1616,
+				1606, 1607, 1617, 1617, 1607, 1618,
+				1607, 1608, 1618, 1618, 1608, 1619,
+				1608, 1609, 1619, 1619, 1609, 1620,
+				1609, 1610, 1620, 1620, 1610, 1621,
+				1610, 1611, 1621, 1621, 1611, 1622,
+				1611, 1612, 1622, 1622, 1612, 1623,
+				1612, 1613, 1623, 1623, 1613, 1624,
+				1613, 1614, 1624, 1624, 1614, 1625,
+				1614, 1615, 1625, 1625, 1615, 1626,
+				1615, 1616, 1626, 1626, 1616, 1627,
+				1617, 1618, 1628, 1628, 1618, 1629,
+				1618, 1619, 1629, 1629, 1619, 1630,
+				1619, 1620, 1630, 1630, 1620, 1631,
+				1620, 1621, 1631, 1631, 1621, 1632,
+				1621, 1622, 1632, 1632, 1622, 1633,
+				1622, 1623, 1633, 1633, 1623, 1634,
+				1623, 1624, 1634, 1634, 1624, 1635,
+				1624, 1625, 1635, 1635, 1625, 1636,
+				1625, 1626, 1636, 1636, 1626, 1637,
+				1626, 1627, 1637, 1637, 1627, 1638,
+				1628, 1629, 1639, 1639, 1629, 1640,
+				1629, 1630, 1640, 1640, 1630, 1641,
+				1630, 1631, 1641, 1641, 1631, 1642,
+				1631, 1632, 1642, 1642, 1632, 1643,
+				1632, 1633, 1643, 1643, 1633, 1644,
+				1633, 1634, 1644, 1644, 1634, 1645,
+				1634, 1635, 1645, 1645, 1635, 1646,
+				1635, 1636, 1646, 1646, 1636, 1647,
+				1636, 1637, 1647, 1647, 1637, 1648,
+				1637, 1638, 1648, 1648, 1638, 1649,
+				1639, 1640, 1650, 1650, 1640, 1651,
+				1640, 1641, 1651, 1651, 1641, 1652,
+				1641, 1642, 1652, 1652, 1642, 1653,
+				1642, 1643, 1653, 1653, 1643, 1654,
+				1643, 1644, 1654, 1654, 1644, 1655,
+				1644, 1645, 1655, 1655, 1645, 1656,
+				1645, 1646, 1656, 1656, 1646, 1657,
+				1646, 1647, 1657, 1657, 1647, 1658,
+				1647, 1648, 1658, 1658, 1648, 1659,
+				1648, 1649, 1659, 1659, 1649, 1660,
+				1650, 1651, 1661, 1661, 1651, 1662,
+				1651, 1652, 1662, 1662, 1652, 1663,
+				1652, 1653, 1663, 1663, 1653, 1664,
+				1653, 1654, 1664, 1664, 1654, 1665,
+				1654, 1655, 1665, 1665, 1655, 1666,
+				1655, 1656, 1666, 1666, 1656, 1667,
+				1656, 1657, 1667, 1667, 1657, 1668,
+				1657, 1658, 1668, 1668, 1658, 1669,
+				1658, 1659, 1669, 1669, 1659, 1670,
+				1659, 1660, 1670, 1670, 1660, 1671,
+				1661, 1662, 1672, 1672, 1662, 1673,
+				1662, 1663, 1673, 1673, 1663, 1674,
+				1663, 1664, 1674, 1674, 1664, 1675,
+				1664, 1665, 1675, 1675, 1665, 1676,
+				1665, 1666, 1676, 1676, 1666, 1677,
+				1666, 1667, 1677, 1677, 1667, 1678,
+				1667, 1668, 1678, 1678, 1668, 1679,
+				1668, 1669, 1679, 1679, 1669, 1680,
+				1669, 1670, 1680, 1680, 1670, 1681,
+				1670, 1671, 1681, 1681, 1671, 1682,
+				1672, 1673, 1683, 1683, 1673, 1684,
+				1673, 1674, 1684, 1684, 1674, 1685,
+				1674, 1675, 1685, 1685, 1675, 1686,
+				1675, 1676, 1686, 1686, 1676, 1687,
+				1676, 1677, 1687, 1687, 1677, 1688,
+				1677, 1678, 1688, 1688, 1678, 1689,
+				1678, 1679, 1689, 1689, 1679, 1690,
+				1679, 1680, 1690, 1690, 1680, 1691,
+				1680, 1681, 1691, 1691, 1681, 1692,
+				1681, 1682, 1692, 1692, 1682, 1693,
+				1694, 1695, 1705, 1705, 1695, 1706,
+				1695, 1696, 1706, 1706, 1696, 1707,
+				1696, 1697, 1707, 1707, 1697, 1708,
+				1697, 1698, 1708, 1708, 1698, 1709,
+				1698, 1699, 1709, 1709, 1699, 1710,
+				1699, 1700, 1710, 1710, 1700, 1711,
+				1700, 1701, 1711, 1711, 1701, 1712,
+				1701, 1702, 1712, 1712, 1702, 1713,
+				1702, 1703, 1713, 1713, 1703, 1714,
+				1703, 1704, 1714, 1714, 1704, 1715,
+				1705, 1706, 1716, 1716, 1706, 1717,
+				1706, 1707, 1717, 1717, 1707, 1718,
+				1707, 1708, 1718, 1718, 1708, 1719,
+				1708, 1709, 1719, 1719, 1709, 1720,
+				1709, 1710, 1720, 1720, 1710, 1721,
+				1710, 1711, 1721, 1721, 1711, 1722,
+				1711, 1712, 1722, 1722, 1712, 1723,
+				1712, 1713, 1723, 1723, 1713, 1724,
+				1713, 1714, 1724, 1724, 1714, 1725,
+				1714, 1715, 1725, 1725, 1715, 1726,
+				1716, 1717, 1727, 1727, 1717, 1728,
+				1717, 1718, 1728, 1728, 1718, 1729,
+				1718, 1719, 1729, 1729, 1719, 1730,
+				1719, 1720, 1730, 1730, 1720, 1731,
+				1720, 1721, 1731, 1731, 1721, 1732,
+				1721, 1722, 1732, 1732, 1722, 1733,
+				1722, 1723, 1733, 1733, 1723, 1734,
+				1723, 1724, 1734, 1734, 1724, 1735,
+				1724, 1725, 1735, 1735, 1725, 1736,
+				1725, 1726, 1736, 1736, 1726, 1737,
+				1727, 1728, 1738, 1738, 1728, 1739,
+				1728, 1729, 1739, 1739, 1729, 1740,
+				1729, 1730, 1740, 1740, 1730, 1741,
+				1730, 1731, 1741, 1741, 1731, 1742,
+				1731, 1732, 1742, 1742, 1732, 1743,
+				1732, 1733, 1743, 1743, 1733, 1744,
+				1733, 1734, 1744, 1744, 1734, 1745,
+				1734, 1735, 1745, 1745, 1735, 1746,
+				1735, 1736, 1746, 1746, 1736, 1747,
+				1736, 1737, 1747, 1747, 1737, 1748,
+				1738, 1739, 1749, 1749, 1739, 1750,
+				1739, 1740, 1750, 1750, 1740, 1751,
+				1740, 1741, 1751, 1751, 1741, 1752,
+				1741, 1742, 1752, 1752, 1742, 1753,
+				1742, 1743, 1753, 1753, 1743, 1754,
+				1743, 1744, 1754, 1754, 1744, 1755,
+				1744, 1745, 1755, 1755, 1745, 1756,
+				1745, 1746, 1756, 1756, 1746, 1757,
+				1746, 1747, 1757, 1757, 1747, 1758,
+				1747, 1748, 1758, 1758, 1748, 1759,
+				1749, 1750, 1760, 1760, 1750, 1761,
+				1750, 1751, 1761, 1761, 1751, 1762,
+				1751, 1752, 1762, 1762, 1752, 1763,
+				1752, 1753, 1763, 1763, 1753, 1764,
+				1753, 1754, 1764, 1764, 1754, 1765,
+				1754, 1755, 1765, 1765, 1755, 1766,
+				1755, 1756, 1766, 1766, 1756, 1767,
+				1756, 1757, 1767, 1767, 1757, 1768,
+				1757, 1758, 1768, 1768, 1758, 1769,
+				1758, 1759, 1769, 1769, 1759, 1770,
+				1760, 1761, 1771, 1771, 1761, 1772,
+				1761, 1762, 1772, 1772, 1762, 1773,
+				1762, 1763, 1773, 1773, 1763, 1774,
+				1763, 1764, 1774, 1774, 1764, 1775,
+				1764, 1765, 1775, 1775, 1765, 1776,
+				1765, 1766, 1776, 1776, 1766, 1777,
+				1766, 1767, 1777, 1777, 1767, 1778,
+				1767, 1768, 1778, 1778, 1768, 1779,
+				1768, 1769, 1779, 1779, 1769, 1780,
+				1769, 1770, 1780, 1780, 1770, 1781,
+				1771, 1772, 1782, 1782, 1772, 1783,
+				1772, 1773, 1783, 1783, 1773, 1784,
+				1773, 1774, 1784, 1784, 1774, 1785,
+				1774, 1775, 1785, 1785, 1775, 1786,
+				1775, 1776, 1786, 1786, 1776, 1787,
+				1776, 1777, 1787, 1787, 1777, 1788,
+				1777, 1778, 1788, 1788, 1778, 1789,
+				1778, 1779, 1789, 1789, 1779, 1790,
+				1779, 1780, 1790, 1790, 1780, 1791,
+				1780, 1781, 1791, 1791, 1781, 1792,
+				1782, 1783, 1793, 1793, 1783, 1794,
+				1783, 1784, 1794, 1794, 1784, 1795,
+				1784, 1785, 1795, 1795, 1785, 1796,
+				1785, 1786, 1796, 1796, 1786, 1797,
+				1786, 1787, 1797, 1797, 1787, 1798,
+				1787, 1788, 1798, 1798, 1788, 1799,
+				1788, 1789, 1799, 1799, 1789, 1800,
+				1789, 1790, 1800, 1800, 1790, 1801,
+				1790, 1791, 1801, 1801, 1791, 1802,
+				1791, 1792, 1802, 1802, 1792, 1803,
+				1793, 1794, 1804, 1804, 1794, 1805,
+				1794, 1795, 1805, 1805, 1795, 1806,
+				1795, 1796, 1806, 1806, 1796, 1807,
+				1796, 1797, 1807, 1807, 1797, 1808,
+				1797, 1798, 1808, 1808, 1798, 1809,
+				1798, 1799, 1809, 1809, 1799, 1810,
+				1799, 1800, 1810, 1810, 1800, 1811,
+				1800, 1801, 1811, 1811, 1801, 1812,
+				1801, 1802, 1812, 1812, 1802, 1813,
+				1802, 1803, 1813, 1813, 1803, 1814,
+				1815, 1816, 1826, 1826, 1816, 1827,
+				1816, 1817, 1827, 1827, 1817, 1828,
+				1817, 1818, 1828, 1828, 1818, 1829,
+				1818, 1819, 1829, 1829, 1819, 1830,
+				1819, 1820, 1830, 1830, 1820, 1831,
+				1820, 1821, 1831, 1831, 1821, 1832,
+				1821, 1822, 1832, 1832, 1822, 1833,
+				1822, 1823, 1833, 1833, 1823, 1834,
+				1823, 1824, 1834, 1834, 1824, 1835,
+				1824, 1825, 1835, 1835, 1825, 1836,
+				1826, 1827, 1837, 1837, 1827, 1838,
+				1827, 1828, 1838, 1838, 1828, 1839,
+				1828, 1829, 1839, 1839, 1829, 1840,
+				1829, 1830, 1840, 1840, 1830, 1841,
+				1830, 1831, 1841, 1841, 1831, 1842,
+				1831, 1832, 1842, 1842, 1832, 1843,
+				1832, 1833, 1843, 1843, 1833, 1844,
+				1833, 1834, 1844, 1844, 1834, 1845,
+				1834, 1835, 1845, 1845, 1835, 1846,
+				1835, 1836, 1846, 1846, 1836, 1847,
+				1837, 1838, 1848, 1848, 1838, 1849,
+				1838, 1839, 1849, 1849, 1839, 1850,
+				1839, 1840, 1850, 1850, 1840, 1851,
+				1840, 1841, 1851, 1851, 1841, 1852,
+				1841, 1842, 1852, 1852, 1842, 1853,
+				1842, 1843, 1853, 1853, 1843, 1854,
+				1843, 1844, 1854, 1854, 1844, 1855,
+				1844, 1845, 1855, 1855, 1845, 1856,
+				1845, 1846, 1856, 1856, 1846, 1857,
+				1846, 1847, 1857, 1857, 1847, 1858,
+				1848, 1849, 1859, 1859, 1849, 1860,
+				1849, 1850, 1860, 1860, 1850, 1861,
+				1850, 1851, 1861, 1861, 1851, 1862,
+				1851, 1852, 1862, 1862, 1852, 1863,
+				1852, 1853, 1863, 1863, 1853, 1864,
+				1853, 1854, 1864, 1864, 1854, 1865,
+				1854, 1855, 1865, 1865, 1855, 1866,
+				1855, 1856, 1866, 1866, 1856, 1867,
+				1856, 1857, 1867, 1867, 1857, 1868,
+				1857, 1858, 1868, 1868, 1858, 1869,
+				1859, 1860, 1870, 1870, 1860, 1871,
+				1860, 1861, 1871, 1871, 1861, 1872,
+				1861, 1862, 1872, 1872, 1862, 1873,
+				1862, 1863, 1873, 1873, 1863, 1874,
+				1863, 1864, 1874, 1874, 1864, 1875,
+				1864, 1865, 1875, 1875, 1865, 1876,
+				1865, 1866, 1876, 1876, 1866, 1877,
+				1866, 1867, 1877, 1877, 1867, 1878,
+				1867, 1868, 1878, 1878, 1868, 1879,
+				1868, 1869, 1879, 1879, 1869, 1880,
+				1870, 1871, 1881, 1881, 1871, 1882,
+				1871, 1872, 1882, 1882, 1872, 1883,
+				1872, 1873, 1883, 1883, 1873, 1884,
+				1873, 1874, 1884, 1884, 1874, 1885,
+				1874, 1875, 1885, 1885, 1875, 1886,
+				1875, 1876, 1886, 1886, 1876, 1887,
+				1876, 1877, 1887, 1887, 1877, 1888,
+				1877, 1878, 1888, 1888, 1878, 1889,
+				1878, 1879, 1889, 1889, 1879, 1890,
+				1879, 1880, 1890, 1890, 1880, 1891,
+				1881, 1882, 1892, 1892, 1882, 1893,
+				1882, 1883, 1893, 1893, 1883, 1894,
+				1883, 1884, 1894, 1894, 1884, 1895,
+				1884, 1885, 1895, 1895, 1885, 1896,
+				1885, 1886, 1896, 1896, 1886, 1897,
+				1886, 1887, 1897, 1897, 1887, 1898,
+				1887, 1888, 1898, 1898, 1888, 1899,
+				1888, 1889, 1899, 1899, 1889, 1900,
+				1889, 1890, 1900, 1900, 1890, 1901,
+				1890, 1891, 1901, 1901, 1891, 1902,
+				1892, 1893, 1903, 1903, 1893, 1904,
+				1893, 1894, 1904, 1904, 1894, 1905,
+				1894, 1895, 1905, 1905, 1895, 1906,
+				1895, 1896, 1906, 1906, 1896, 1907,
+				1896, 1897, 1907, 1907, 1897, 1908,
+				1897, 1898, 1908, 1908, 1898, 1909,
+				1898, 1899, 1909, 1909, 1899, 1910,
+				1899, 1900, 1910, 1910, 1900, 1911,
+				1900, 1901, 1911, 1911, 1901, 1912,
+				1901, 1902, 1912, 1912, 1902, 1913,
+				1903, 1904, 1914, 1914, 1904, 1915,
+				1904, 1905, 1915, 1915, 1905, 1916,
+				1905, 1906, 1916, 1916, 1906, 1917,
+				1906, 1907, 1917, 1917, 1907, 1918,
+				1907, 1908, 1918, 1918, 1908, 1919,
+				1908, 1909, 1919, 1919, 1909, 1920,
+				1909, 1910, 1920, 1920, 1910, 1921,
+				1910, 1911, 1921, 1921, 1911, 1922,
+				1911, 1912, 1922, 1922, 1912, 1923,
+				1912, 1913, 1923, 1923, 1913, 1924,
+				1914, 1915, 1925, 1925, 1915, 1926,
+				1915, 1916, 1926, 1926, 1916, 1927,
+				1916, 1917, 1927, 1927, 1917, 1928,
+				1917, 1918, 1928, 1928, 1918, 1929,
+				1918, 1919, 1929, 1929, 1919, 1930,
+				1919, 1920, 1930, 1930, 1920, 1931,
+				1920, 1921, 1931, 1931, 1921, 1932,
+				1921, 1922, 1932, 1932, 1922, 1933,
+				1922, 1923, 1933, 1933, 1923, 1934,
+				1923, 1924, 1934, 1934, 1924, 1935,
+				1936, 1937, 1947, 1947, 1937, 1948,
+				1937, 1938, 1948, 1948, 1938, 1949,
+				1938, 1939, 1949, 1949, 1939, 1950,
+				1939, 1940, 1950, 1950, 1940, 1951,
+				1940, 1941, 1951, 1951, 1941, 1952,
+				1941, 1942, 1952, 1952, 1942, 1953,
+				1942, 1943, 1953, 1953, 1943, 1954,
+				1943, 1944, 1954, 1954, 1944, 1955,
+				1944, 1945, 1955, 1955, 1945, 1956,
+				1945, 1946, 1956, 1956, 1946, 1957,
+				1947, 1948, 1958, 1958, 1948, 1959,
+				1948, 1949, 1959, 1959, 1949, 1960,
+				1949, 1950, 1960, 1960, 1950, 1961,
+				1950, 1951, 1961, 1961, 1951, 1962,
+				1951, 1952, 1962, 1962, 1952, 1963,
+				1952, 1953, 1963, 1963, 1953, 1964,
+				1953, 1954, 1964, 1964, 1954, 1965,
+				1954, 1955, 1965, 1965, 1955, 1966,
+				1955, 1956, 1966, 1966, 1956, 1967,
+				1956, 1957, 1967, 1967, 1957, 1968,
+				1958, 1959, 1969, 1969, 1959, 1970,
+				1959, 1960, 1970, 1970, 1960, 1971,
+				1960, 1961, 1971, 1971, 1961, 1972,
+				1961, 1962, 1972, 1972, 1962, 1973,
+				1962, 1963, 1973, 1973, 1963, 1974,
+				1963, 1964, 1974, 1974, 1964, 1975,
+				1964, 1965, 1975, 1975, 1965, 1976,
+				1965, 1966, 1976, 1976, 1966, 1977,
+				1966, 1967, 1977, 1977, 1967, 1978,
+				1967, 1968, 1978, 1978, 1968, 1979,
+				1969, 1970, 1980, 1980, 1970, 1981,
+				1970, 1971, 1981, 1981, 1971, 1982,
+				1971, 1972, 1982, 1982, 1972, 1983,
+				1972, 1973, 1983, 1983, 1973, 1984,
+				1973, 1974, 1984, 1984, 1974, 1985,
+				1974, 1975, 1985, 1985, 1975, 1986,
+				1975, 1976, 1986, 1986, 1976, 1987,
+				1976, 1977, 1987, 1987, 1977, 1988,
+				1977, 1978, 1988, 1988, 1978, 1989,
+				1978, 1979, 1989, 1989, 1979, 1990,
+				1980, 1981, 1991, 1991, 1981, 1992,
+				1981, 1982, 1992, 1992, 1982, 1993,
+				1982, 1983, 1993, 1993, 1983, 1994,
+				1983, 1984, 1994, 1994, 1984, 1995,
+				1984, 1985, 1995, 1995, 1985, 1996,
+				1985, 1986, 1996, 1996, 1986, 1997,
+				1986, 1987, 1997, 1997, 1987, 1998,
+				1987, 1988, 1998, 1998, 1988, 1999,
+				1988, 1989, 1999, 1999, 1989, 2000,
+				1989, 1990, 2000, 2000, 1990, 2001,
+				1991, 1992, 2002, 2002, 1992, 2003,
+				1992, 1993, 2003, 2003, 1993, 2004,
+				1993, 1994, 2004, 2004, 1994, 2005,
+				1994, 1995, 2005, 2005, 1995, 2006,
+				1995, 1996, 2006, 2006, 1996, 2007,
+				1996, 1997, 2007, 2007, 1997, 2008,
+				1997, 1998, 2008, 2008, 1998, 2009,
+				1998, 1999, 2009, 2009, 1999, 2010,
+				1999, 2000, 2010, 2010, 2000, 2011,
+				2000, 2001, 2011, 2011, 2001, 2012,
+				2002, 2003, 2013, 2013, 2003, 2014,
+				2003, 2004, 2014, 2014, 2004, 2015,
+				2004, 2005, 2015, 2015, 2005, 2016,
+				2005, 2006, 2016, 2016, 2006, 2017,
+				2006, 2007, 2017, 2017, 2007, 2018,
+				2007, 2008, 2018, 2018, 2008, 2019,
+				2008, 2009, 2019, 2019, 2009, 2020,
+				2009, 2010, 2020, 2020, 2010, 2021,
+				2010, 2011, 2021, 2021, 2011, 2022,
+				2011, 2012, 2022, 2022, 2012, 2023,
+				2013, 2014, 2024, 2024, 2014, 2025,
+				2014, 2015, 2025, 2025, 2015, 2026,
+				2015, 2016, 2026, 2026, 2016, 2027,
+				2016, 2017, 2027, 2027, 2017, 2028,
+				2017, 2018, 2028, 2028, 2018, 2029,
+				2018, 2019, 2029, 2029, 2019, 2030,
+				2019, 2020, 2030, 2030, 2020, 2031,
+				2020, 2021, 2031, 2031, 2021, 2032,
+				2021, 2022, 2032, 2032, 2022, 2033,
+				2022, 2023, 2033, 2033, 2023, 2034,
+				2024, 2025, 2035, 2035, 2025, 2036,
+				2025, 2026, 2036, 2036, 2026, 2037,
+				2026, 2027, 2037, 2037, 2027, 2038,
+				2027, 2028, 2038, 2038, 2028, 2039,
+				2028, 2029, 2039, 2039, 2029, 2040,
+				2029, 2030, 2040, 2040, 2030, 2041,
+				2030, 2031, 2041, 2041, 2031, 2042,
+				2031, 2032, 2042, 2042, 2032, 2043,
+				2032, 2033, 2043, 2043, 2033, 2044,
+				2033, 2034, 2044, 2044, 2034, 2045,
+				2035, 2036, 2046, 2046, 2036, 2047,
+				2036, 2037, 2047, 2047, 2037, 2048,
+				2037, 2038, 2048, 2048, 2038, 2049,
+				2038, 2039, 2049, 2049, 2039, 2050,
+				2039, 2040, 2050, 2050, 2040, 2051,
+				2040, 2041, 2051, 2051, 2041, 2052,
+				2041, 2042, 2052, 2052, 2042, 2053,
+				2042, 2043, 2053, 2053, 2043, 2054,
+				2043, 2044, 2054, 2054, 2044, 2055,
+				2044, 2045, 2055, 2055, 2045, 2056,
+				2057, 2058, 2068, 2068, 2058, 2069,
+				2058, 2059, 2069, 2069, 2059, 2070,
+				2059, 2060, 2070, 2070, 2060, 2071,
+				2060, 2061, 2071, 2071, 2061, 2072,
+				2061, 2062, 2072, 2072, 2062, 2073,
+				2062, 2063, 2073, 2073, 2063, 2074,
+				2063, 2064, 2074, 2074, 2064, 2075,
+				2064, 2065, 2075, 2075, 2065, 2076,
+				2065, 2066, 2076, 2076, 2066, 2077,
+				2066, 2067, 2077, 2077, 2067, 2078,
+				2068, 2069, 2079, 2079, 2069, 2080,
+				2069, 2070, 2080, 2080, 2070, 2081,
+				2070, 2071, 2081, 2081, 2071, 2082,
+				2071, 2072, 2082, 2082, 2072, 2083,
+				2072, 2073, 2083, 2083, 2073, 2084,
+				2073, 2074, 2084, 2084, 2074, 2085,
+				2074, 2075, 2085, 2085, 2075, 2086,
+				2075, 2076, 2086, 2086, 2076, 2087,
+				2076, 2077, 2087, 2087, 2077, 2088,
+				2077, 2078, 2088, 2088, 2078, 2089,
+				2079, 2080, 2090, 2090, 2080, 2091,
+				2080, 2081, 2091, 2091, 2081, 2092,
+				2081, 2082, 2092, 2092, 2082, 2093,
+				2082, 2083, 2093, 2093, 2083, 2094,
+				2083, 2084, 2094, 2094, 2084, 2095,
+				2084, 2085, 2095, 2095, 2085, 2096,
+				2085, 2086, 2096, 2096, 2086, 2097,
+				2086, 2087, 2097, 2097, 2087, 2098,
+				2087, 2088, 2098, 2098, 2088, 2099,
+				2088, 2089, 2099, 2099, 2089, 2100,
+				2090, 2091, 2101, 2101, 2091, 2102,
+				2091, 2092, 2102, 2102, 2092, 2103,
+				2092, 2093, 2103, 2103, 2093, 2104,
+				2093, 2094, 2104, 2104, 2094, 2105,
+				2094, 2095, 2105, 2105, 2095, 2106,
+				2095, 2096, 2106, 2106, 2096, 2107,
+				2096, 2097, 2107, 2107, 2097, 2108,
+				2097, 2098, 2108, 2108, 2098, 2109,
+				2098, 2099, 2109, 2109, 2099, 2110,
+				2099, 2100, 2110, 2110, 2100, 2111,
+				2101, 2102, 2112, 2112, 2102, 2113,
+				2102, 2103, 2113, 2113, 2103, 2114,
+				2103, 2104, 2114, 2114, 2104, 2115,
+				2104, 2105, 2115, 2115, 2105, 2116,
+				2105, 2106, 2116, 2116, 2106, 2117,
+				2106, 2107, 2117, 2117, 2107, 2118,
+				2107, 2108, 2118, 2118, 2108, 2119,
+				2108, 2109, 2119, 2119, 2109, 2120,
+				2109, 2110, 2120, 2120, 2110, 2121,
+				2110, 2111, 2121, 2121, 2111, 2122,
+				2112, 2113, 2123, 2123, 2113, 2124,
+				2113, 2114, 2124, 2124, 2114, 2125,
+				2114, 2115, 2125, 2125, 2115, 2126,
+				2115, 2116, 2126, 2126, 2116, 2127,
+				2116, 2117, 2127, 2127, 2117, 2128,
+				2117, 2118, 2128, 2128, 2118, 2129,
+				2118, 2119, 2129, 2129, 2119, 2130,
+				2119, 2120, 2130, 2130, 2120, 2131,
+				2120, 2121, 2131, 2131, 2121, 2132,
+				2121, 2122, 2132, 2132, 2122, 2133,
+				2123, 2124, 2134, 2134, 2124, 2135,
+				2124, 2125, 2135, 2135, 2125, 2136,
+				2125, 2126, 2136, 2136, 2126, 2137,
+				2126, 2127, 2137, 2137, 2127, 2138,
+				2127, 2128, 2138, 2138, 2128, 2139,
+				2128, 2129, 2139, 2139, 2129, 2140,
+				2129, 2130, 2140, 2140, 2130, 2141,
+				2130, 2131, 2141, 2141, 2131, 2142,
+				2131, 2132, 2142, 2142, 2132, 2143,
+				2132, 2133, 2143, 2143, 2133, 2144,
+				2134, 2135, 2145, 2145, 2135, 2146,
+				2135, 2136, 2146, 2146, 2136, 2147,
+				2136, 2137, 2147, 2147, 2137, 2148,
+				2137, 2138, 2148, 2148, 2138, 2149,
+				2138, 2139, 2149, 2149, 2139, 2150,
+				2139, 2140, 2150, 2150, 2140, 2151,
+				2140, 2141, 2151, 2151, 2141, 2152,
+				2141, 2142, 2152, 2152, 2142, 2153,
+				2142, 2143, 2153, 2153, 2143, 2154,
+				2143, 2144, 2154, 2154, 2144, 2155,
+				2145, 2146, 2156, 2156, 2146, 2157,
+				2146, 2147, 2157, 2157, 2147, 2158,
+				2147, 2148, 2158, 2158, 2148, 2159,
+				2148, 2149, 2159, 2159, 2149, 2160,
+				2149, 2150, 2160, 2160, 2150, 2161,
+				2150, 2151, 2161, 2161, 2151, 2162,
+				2151, 2152, 2162, 2162, 2152, 2163,
+				2152, 2153, 2163, 2163, 2153, 2164,
+				2153, 2154, 2164, 2164, 2154, 2165,
+				2154, 2155, 2165, 2165, 2155, 2166,
+				2156, 2157, 2167, 2167, 2157, 2168,
+				2157, 2158, 2168, 2168, 2158, 2169,
+				2158, 2159, 2169, 2169, 2159, 2170,
+				2159, 2160, 2170, 2170, 2160, 2171,
+				2160, 2161, 2171, 2171, 2161, 2172,
+				2161, 2162, 2172, 2172, 2162, 2173,
+				2162, 2163, 2173, 2173, 2163, 2174,
+				2163, 2164, 2174, 2174, 2164, 2175,
+				2164, 2165, 2175, 2175, 2165, 2176,
+				2165, 2166, 2176, 2176, 2166, 2177,
+				2178, 2179, 2189, 2189, 2179, 2190,
+				2179, 2180, 2190, 2190, 2180, 2191,
+				2180, 2181, 2191, 2191, 2181, 2192,
+				2181, 2182, 2192, 2192, 2182, 2193,
+				2182, 2183, 2193, 2193, 2183, 2194,
+				2183, 2184, 2194, 2194, 2184, 2195,
+				2184, 2185, 2195, 2195, 2185, 2196,
+				2185, 2186, 2196, 2196, 2186, 2197,
+				2186, 2187, 2197, 2197, 2187, 2198,
+				2187, 2188, 2198, 2198, 2188, 2199,
+				2189, 2190, 2200, 2200, 2190, 2201,
+				2190, 2191, 2201, 2201, 2191, 2202,
+				2191, 2192, 2202, 2202, 2192, 2203,
+				2192, 2193, 2203, 2203, 2193, 2204,
+				2193, 2194, 2204, 2204, 2194, 2205,
+				2194, 2195, 2205, 2205, 2195, 2206,
+				2195, 2196, 2206, 2206, 2196, 2207,
+				2196, 2197, 2207, 2207, 2197, 2208,
+				2197, 2198, 2208, 2208, 2198, 2209,
+				2198, 2199, 2209, 2209, 2199, 2210,
+				2200, 2201, 2211, 2211, 2201, 2212,
+				2201, 2202, 2212, 2212, 2202, 2213,
+				2202, 2203, 2213, 2213, 2203, 2214,
+				2203, 2204, 2214, 2214, 2204, 2215,
+				2204, 2205, 2215, 2215, 2205, 2216,
+				2205, 2206, 2216, 2216, 2206, 2217,
+				2206, 2207, 2217, 2217, 2207, 2218,
+				2207, 2208, 2218, 2218, 2208, 2219,
+				2208, 2209, 2219, 2219, 2209, 2220,
+				2209, 2210, 2220, 2220, 2210, 2221,
+				2211, 2212, 2222, 2222, 2212, 2223,
+				2212, 2213, 2223, 2223, 2213, 2224,
+				2213, 2214, 2224, 2224, 2214, 2225,
+				2214, 2215, 2225, 2225, 2215, 2226,
+				2215, 2216, 2226, 2226, 2216, 2227,
+				2216, 2217, 2227, 2227, 2217, 2228,
+				2217, 2218, 2228, 2228, 2218, 2229,
+				2218, 2219, 2229, 2229, 2219, 2230,
+				2219, 2220, 2230, 2230, 2220, 2231,
+				2220, 2221, 2231, 2231, 2221, 2232,
+				2222, 2223, 2233, 2233, 2223, 2234,
+				2223, 2224, 2234, 2234, 2224, 2235,
+				2224, 2225, 2235, 2235, 2225, 2236,
+				2225, 2226, 2236, 2236, 2226, 2237,
+				2226, 2227, 2237, 2237, 2227, 2238,
+				2227, 2228, 2238, 2238, 2228, 2239,
+				2228, 2229, 2239, 2239, 2229, 2240,
+				2229, 2230, 2240, 2240, 2230, 2241,
+				2230, 2231, 2241, 2241, 2231, 2242,
+				2231, 2232, 2242, 2242, 2232, 2243,
+				2233, 2234, 2244, 2244, 2234, 2245,
+				2234, 2235, 2245, 2245, 2235, 2246,
+				2235, 2236, 2246, 2246, 2236, 2247,
+				2236, 2237, 2247, 2247, 2237, 2248,
+				2237, 2238, 2248, 2248, 2238, 2249,
+				2238, 2239, 2249, 2249, 2239, 2250,
+				2239, 2240, 2250, 2250, 2240, 2251,
+				2240, 2241, 2251, 2251, 2241, 2252,
+				2241, 2242, 2252, 2252, 2242, 2253,
+				2242, 2243, 2253, 2253, 2243, 2254,
+				2244, 2245, 2255, 2255, 2245, 2256,
+				2245, 2246, 2256, 2256, 2246, 2257,
+				2246, 2247, 2257, 2257, 2247, 2258,
+				2247, 2248, 2258, 2258, 2248, 2259,
+				2248, 2249, 2259, 2259, 2249, 2260,
+				2249, 2250, 2260, 2260, 2250, 2261,
+				2250, 2251, 2261, 2261, 2251, 2262,
+				2251, 2252, 2262, 2262, 2252, 2263,
+				2252, 2253, 2263, 2263, 2253, 2264,
+				2253, 2254, 2264, 2264, 2254, 2265,
+				2255, 2256, 2266, 2266, 2256, 2267,
+				2256, 2257, 2267, 2267, 2257, 2268,
+				2257, 2258, 2268, 2268, 2258, 2269,
+				2258, 2259, 2269, 2269, 2259, 2270,
+				2259, 2260, 2270, 2270, 2260, 2271,
+				2260, 2261, 2271, 2271, 2261, 2272,
+				2261, 2262, 2272, 2272, 2262, 2273,
+				2262, 2263, 2273, 2273, 2263, 2274,
+				2263, 2264, 2274, 2274, 2264, 2275,
+				2264, 2265, 2275, 2275, 2265, 2276,
+				2266, 2267, 2277, 2277, 2267, 2278,
+				2267, 2268, 2278, 2278, 2268, 2279,
+				2268, 2269, 2279, 2279, 2269, 2280,
+				2269, 2270, 2280, 2280, 2270, 2281,
+				2270, 2271, 2281, 2281, 2271, 2282,
+				2271, 2272, 2282, 2282, 2272, 2283,
+				2272, 2273, 2283, 2283, 2273, 2284,
+				2273, 2274, 2284, 2284, 2274, 2285,
+				2274, 2275, 2285, 2285, 2275, 2286,
+				2275, 2276, 2286, 2286, 2276, 2287,
+				2277, 2278, 2288, 2288, 2278, 2289,
+				2278, 2279, 2289, 2289, 2279, 2290,
+				2279, 2280, 2290, 2290, 2280, 2291,
+				2280, 2281, 2291, 2291, 2281, 2292,
+				2281, 2282, 2292, 2292, 2282, 2293,
+				2282, 2283, 2293, 2293, 2283, 2294,
+				2283, 2284, 2294, 2294, 2284, 2295,
+				2284, 2285, 2295, 2295, 2285, 2296,
+				2285, 2286, 2296, 2296, 2286, 2297,
+				2286, 2287, 2297, 2297, 2287, 2298,
+				2299, 2300, 2310, 2310, 2300, 2311,
+				2300, 2301, 2311, 2311, 2301, 2312,
+				2301, 2302, 2312, 2312, 2302, 2313,
+				2302, 2303, 2313, 2313, 2303, 2314,
+				2303, 2304, 2314, 2314, 2304, 2315,
+				2304, 2305, 2315, 2315, 2305, 2316,
+				2305, 2306, 2316, 2316, 2306, 2317,
+				2306, 2307, 2317, 2317, 2307, 2318,
+				2307, 2308, 2318, 2318, 2308, 2319,
+				2308, 2309, 2319, 2319, 2309, 2320,
+				2310, 2311, 2321, 2321, 2311, 2322,
+				2311, 2312, 2322, 2322, 2312, 2323,
+				2312, 2313, 2323, 2323, 2313, 2324,
+				2313, 2314, 2324, 2324, 2314, 2325,
+				2314, 2315, 2325, 2325, 2315, 2326,
+				2315, 2316, 2326, 2326, 2316, 2327,
+				2316, 2317, 2327, 2327, 2317, 2328,
+				2317, 2318, 2328, 2328, 2318, 2329,
+				2318, 2319, 2329, 2329, 2319, 2330,
+				2319, 2320, 2330, 2330, 2320, 2331,
+				2321, 2322, 2332, 2332, 2322, 2333,
+				2322, 2323, 2333, 2333, 2323, 2334,
+				2323, 2324, 2334, 2334, 2324, 2335,
+				2324, 2325, 2335, 2335, 2325, 2336,
+				2325, 2326, 2336, 2336, 2326, 2337,
+				2326, 2327, 2337, 2337, 2327, 2338,
+				2327, 2328, 2338, 2338, 2328, 2339,
+				2328, 2329, 2339, 2339, 2329, 2340,
+				2329, 2330, 2340, 2340, 2330, 2341,
+				2330, 2331, 2341, 2341, 2331, 2342,
+				2332, 2333, 2343, 2343, 2333, 2344,
+				2333, 2334, 2344, 2344, 2334, 2345,
+				2334, 2335, 2345, 2345, 2335, 2346,
+				2335, 2336, 2346, 2346, 2336, 2347,
+				2336, 2337, 2347, 2347, 2337, 2348,
+				2337, 2338, 2348, 2348, 2338, 2349,
+				2338, 2339, 2349, 2349, 2339, 2350,
+				2339, 2340, 2350, 2350, 2340, 2351,
+				2340, 2341, 2351, 2351, 2341, 2352,
+				2341, 2342, 2352, 2352, 2342, 2353,
+				2343, 2344, 2354, 2354, 2344, 2355,
+				2344, 2345, 2355, 2355, 2345, 2356,
+				2345, 2346, 2356, 2356, 2346, 2357,
+				2346, 2347, 2357, 2357, 2347, 2358,
+				2347, 2348, 2358, 2358, 2348, 2359,
+				2348, 2349, 2359, 2359, 2349, 2360,
+				2349, 2350, 2360, 2360, 2350, 2361,
+				2350, 2351, 2361, 2361, 2351, 2362,
+				2351, 2352, 2362, 2362, 2352, 2363,
+				2352, 2353, 2363, 2363, 2353, 2364,
+				2354, 2355, 2365, 2365, 2355, 2366,
+				2355, 2356, 2366, 2366, 2356, 2367,
+				2356, 2357, 2367, 2367, 2357, 2368,
+				2357, 2358, 2368, 2368, 2358, 2369,
+				2358, 2359, 2369, 2369, 2359, 2370,
+				2359, 2360, 2370, 2370, 2360, 2371,
+				2360, 2361, 2371, 2371, 2361, 2372,
+				2361, 2362, 2372, 2372, 2362, 2373,
+				2362, 2363, 2373, 2373, 2363, 2374,
+				2363, 2364, 2374, 2374, 2364, 2375,
+				2365, 2366, 2376, 2376, 2366, 2377,
+				2366, 2367, 2377, 2377, 2367, 2378,
+				2367, 2368, 2378, 2378, 2368, 2379,
+				2368, 2369, 2379, 2379, 2369, 2380,
+				2369, 2370, 2380, 2380, 2370, 2381,
+				2370, 2371, 2381, 2381, 2371, 2382,
+				2371, 2372, 2382, 2382, 2372, 2383,
+				2372, 2373, 2383, 2383, 2373, 2384,
+				2373, 2374, 2384, 2384, 2374, 2385,
+				2374, 2375, 2385, 2385, 2375, 2386,
+				2376, 2377, 2387, 2387, 2377, 2388,
+				2377, 2378, 2388, 2388, 2378, 2389,
+				2378, 2379, 2389, 2389, 2379, 2390,
+				2379, 2380, 2390, 2390, 2380, 2391,
+				2380, 2381, 2391, 2391, 2381, 2392,
+				2381, 2382, 2392, 2392, 2382, 2393,
+				2382, 2383, 2393, 2393, 2383, 2394,
+				2383, 2384, 2394, 2394, 2384, 2395,
+				2384, 2385, 2395, 2395, 2385, 2396,
+				2385, 2386, 2396, 2396, 2386, 2397,
+				2387, 2388, 2398, 2398, 2388, 2399,
+				2388, 2389, 2399, 2399, 2389, 2400,
+				2389, 2390, 2400, 2400, 2390, 2401,
+				2390, 2391, 2401, 2401, 2391, 2402,
+				2391, 2392, 2402, 2402, 2392, 2403,
+				2392, 2393, 2403, 2403, 2393, 2404,
+				2393, 2394, 2404, 2404, 2394, 2405,
+				2394, 2395, 2405, 2405, 2395, 2406,
+				2395, 2396, 2406, 2406, 2396, 2407,
+				2396, 2397, 2407, 2407, 2397, 2408,
+				2398, 2399, 2409, 2409, 2399, 2410,
+				2399, 2400, 2410, 2410, 2400, 2411,
+				2400, 2401, 2411, 2411, 2401, 2412,
+				2401, 2402, 2412, 2412, 2402, 2413,
+				2402, 2403, 2413, 2413, 2403, 2414,
+				2403, 2404, 2414, 2414, 2404, 2415,
+				2404, 2405, 2415, 2415, 2405, 2416,
+				2405, 2406, 2416, 2416, 2406, 2417,
+				2406, 2407, 2417, 2417, 2407, 2418,
+				2407, 2408, 2418, 2418, 2408, 2419,
+				2420, 2421, 2431, 2431, 2421, 2432,
+				2421, 2422, 2432, 2432, 2422, 2433,
+				2422, 2423, 2433, 2433, 2423, 2434,
+				2423, 2424, 2434, 2434, 2424, 2435,
+				2424, 2425, 2435, 2435, 2425, 2436,
+				2425, 2426, 2436, 2436, 2426, 2437,
+				2426, 2427, 2437, 2437, 2427, 2438,
+				2427, 2428, 2438, 2438, 2428, 2439,
+				2428, 2429, 2439, 2439, 2429, 2440,
+				2429, 2430, 2440, 2440, 2430, 2441,
+				2431, 2432, 2442, 2442, 2432, 2443,
+				2432, 2433, 2443, 2443, 2433, 2444,
+				2433, 2434, 2444, 2444, 2434, 2445,
+				2434, 2435, 2445, 2445, 2435, 2446,
+				2435, 2436, 2446, 2446, 2436, 2447,
+				2436, 2437, 2447, 2447, 2437, 2448,
+				2437, 2438, 2448, 2448, 2438, 2449,
+				2438, 2439, 2449, 2449, 2439, 2450,
+				2439, 2440, 2450, 2450, 2440, 2451,
+				2440, 2441, 2451, 2451, 2441, 2452,
+				2442, 2443, 2453, 2453, 2443, 2454,
+				2443, 2444, 2454, 2454, 2444, 2455,
+				2444, 2445, 2455, 2455, 2445, 2456,
+				2445, 2446, 2456, 2456, 2446, 2457,
+				2446, 2447, 2457, 2457, 2447, 2458,
+				2447, 2448, 2458, 2458, 2448, 2459,
+				2448, 2449, 2459, 2459, 2449, 2460,
+				2449, 2450, 2460, 2460, 2450, 2461,
+				2450, 2451, 2461, 2461, 2451, 2462,
+				2451, 2452, 2462, 2462, 2452, 2463,
+				2453, 2454, 2464, 2464, 2454, 2465,
+				2454, 2455, 2465, 2465, 2455, 2466,
+				2455, 2456, 2466, 2466, 2456, 2467,
+				2456, 2457, 2467, 2467, 2457, 2468,
+				2457, 2458, 2468, 2468, 2458, 2469,
+				2458, 2459, 2469, 2469, 2459, 2470,
+				2459, 2460, 2470, 2470, 2460, 2471,
+				2460, 2461, 2471, 2471, 2461, 2472,
+				2461, 2462, 2472, 2472, 2462, 2473,
+				2462, 2463, 2473, 2473, 2463, 2474,
+				2464, 2465, 2475, 2475, 2465, 2476,
+				2465, 2466, 2476, 2476, 2466, 2477,
+				2466, 2467, 2477, 2477, 2467, 2478,
+				2467, 2468, 2478, 2478, 2468, 2479,
+				2468, 2469, 2479, 2479, 2469, 2480,
+				2469, 2470, 2480, 2480, 2470, 2481,
+				2470, 2471, 2481, 2481, 2471, 2482,
+				2471, 2472, 2482, 2482, 2472, 2483,
+				2472, 2473, 2483, 2483, 2473, 2484,
+				2473, 2474, 2484, 2484, 2474, 2485,
+				2475, 2476, 2486, 2486, 2476, 2487,
+				2476, 2477, 2487, 2487, 2477, 2488,
+				2477, 2478, 2488, 2488, 2478, 2489,
+				2478, 2479, 2489, 2489, 2479, 2490,
+				2479, 2480, 2490, 2490, 2480, 2491,
+				2480, 2481, 2491, 2491, 2481, 2492,
+				2481, 2482, 2492, 2492, 2482, 2493,
+				2482, 2483, 2493, 2493, 2483, 2494,
+				2483, 2484, 2494, 2494, 2484, 2495,
+				2484, 2485, 2495, 2495, 2485, 2496,
+				2486, 2487, 2497, 2497, 2487, 2498,
+				2487, 2488, 2498, 2498, 2488, 2499,
+				2488, 2489, 2499, 2499, 2489, 2500,
+				2489, 2490, 2500, 2500, 2490, 2501,
+				2490, 2491, 2501, 2501, 2491, 2502,
+				2491, 2492, 2502, 2502, 2492, 2503,
+				2492, 2493, 2503, 2503, 2493, 2504,
+				2493, 2494, 2504, 2504, 2494, 2505,
+				2494, 2495, 2505, 2505, 2495, 2506,
+				2495, 2496, 2506, 2506, 2496, 2507,
+				2497, 2498, 2508, 2508, 2498, 2509,
+				2498, 2499, 2509, 2509, 2499, 2510,
+				2499, 2500, 2510, 2510, 2500, 2511,
+				2500, 2501, 2511, 2511, 2501, 2512,
+				2501, 2502, 2512, 2512, 2502, 2513,
+				2502, 2503, 2513, 2513, 2503, 2514,
+				2503, 2504, 2514, 2514, 2504, 2515,
+				2504, 2505, 2515, 2515, 2505, 2516,
+				2505, 2506, 2516, 2516, 2506, 2517,
+				2506, 2507, 2517, 2517, 2507, 2518,
+				2508, 2509, 2519, 2519, 2509, 2520,
+				2509, 2510, 2520, 2520, 2510, 2521,
+				2510, 2511, 2521, 2521, 2511, 2522,
+				2511, 2512, 2522, 2522, 2512, 2523,
+				2512, 2513, 2523, 2523, 2513, 2524,
+				2513, 2514, 2524, 2524, 2514, 2525,
+				2514, 2515, 2525, 2525, 2515, 2526,
+				2515, 2516, 2526, 2526, 2516, 2527,
+				2516, 2517, 2527, 2527, 2517, 2528,
+				2517, 2518, 2528, 2528, 2518, 2529,
+				2519, 2520, 2530, 2530, 2520, 2531,
+				2520, 2521, 2531, 2531, 2521, 2532,
+				2521, 2522, 2532, 2532, 2522, 2533,
+				2522, 2523, 2533, 2533, 2523, 2534,
+				2523, 2524, 2534, 2534, 2524, 2535,
+				2524, 2525, 2535, 2535, 2525, 2536,
+				2525, 2526, 2536, 2536, 2526, 2537,
+				2526, 2527, 2537, 2537, 2527, 2538,
+				2527, 2528, 2538, 2538, 2528, 2539,
+				2528, 2529, 2539, 2539, 2529, 2540,
+				2541, 2542, 2552, 2552, 2542, 2553,
+				2542, 2543, 2553, 2553, 2543, 2554,
+				2543, 2544, 2554, 2554, 2544, 2555,
+				2544, 2545, 2555, 2555, 2545, 2556,
+				2545, 2546, 2556, 2556, 2546, 2557,
+				2546, 2547, 2557, 2557, 2547, 2558,
+				2547, 2548, 2558, 2558, 2548, 2559,
+				2548, 2549, 2559, 2559, 2549, 2560,
+				2549, 2550, 2560, 2560, 2550, 2561,
+				2550, 2551, 2561, 2561, 2551, 2562,
+				2552, 2553, 2563, 2563, 2553, 2564,
+				2553, 2554, 2564, 2564, 2554, 2565,
+				2554, 2555, 2565, 2565, 2555, 2566,
+				2555, 2556, 2566, 2566, 2556, 2567,
+				2556, 2557, 2567, 2567, 2557, 2568,
+				2557, 2558, 2568, 2568, 2558, 2569,
+				2558, 2559, 2569, 2569, 2559, 2570,
+				2559, 2560, 2570, 2570, 2560, 2571,
+				2560, 2561, 2571, 2571, 2561, 2572,
+				2561, 2562, 2572, 2572, 2562, 2573,
+				2563, 2564, 2574, 2574, 2564, 2575,
+				2564, 2565, 2575, 2575, 2565, 2576,
+				2565, 2566, 2576, 2576, 2566, 2577,
+				2566, 2567, 2577, 2577, 2567, 2578,
+				2567, 2568, 2578, 2578, 2568, 2579,
+				2568, 2569, 2579, 2579, 2569, 2580,
+				2569, 2570, 2580, 2580, 2570, 2581,
+				2570, 2571, 2581, 2581, 2571, 2582,
+				2571, 2572, 2582, 2582, 2572, 2583,
+				2572, 2573, 2583, 2583, 2573, 2584,
+				2574, 2575, 2585, 2585, 2575, 2586,
+				2575, 2576, 2586, 2586, 2576, 2587,
+				2576, 2577, 2587, 2587, 2577, 2588,
+				2577, 2578, 2588, 2588, 2578, 2589,
+				2578, 2579, 2589, 2589, 2579, 2590,
+				2579, 2580, 2590, 2590, 2580, 2591,
+				2580, 2581, 2591, 2591, 2581, 2592,
+				2581, 2582, 2592, 2592, 2582, 2593,
+				2582, 2583, 2593, 2593, 2583, 2594,
+				2583, 2584, 2594, 2594, 2584, 2595,
+				2585, 2586, 2596, 2596, 2586, 2597,
+				2586, 2587, 2597, 2597, 2587, 2598,
+				2587, 2588, 2598, 2598, 2588, 2599,
+				2588, 2589, 2599, 2599, 2589, 2600,
+				2589, 2590, 2600, 2600, 2590, 2601,
+				2590, 2591, 2601, 2601, 2591, 2602,
+				2591, 2592, 2602, 2602, 2592, 2603,
+				2592, 2593, 2603, 2603, 2593, 2604,
+				2593, 2594, 2604, 2604, 2594, 2605,
+				2594, 2595, 2605, 2605, 2595, 2606,
+				2596, 2597, 2607, 2607, 2597, 2608,
+				2597, 2598, 2608, 2608, 2598, 2609,
+				2598, 2599, 2609, 2609, 2599, 2610,
+				2599, 2600, 2610, 2610, 2600, 2611,
+				2600, 2601, 2611, 2611, 2601, 2612,
+				2601, 2602, 2612, 2612, 2602, 2613,
+				2602, 2603, 2613, 2613, 2603, 2614,
+				2603, 2604, 2614, 2614, 2604, 2615,
+				2604, 2605, 2615, 2615, 2605, 2616,
+				2605, 2606, 2616, 2616, 2606, 2617,
+				2607, 2608, 2618, 2618, 2608, 2619,
+				2608, 2609, 2619, 2619, 2609, 2620,
+				2609, 2610, 2620, 2620, 2610, 2621,
+				2610, 2611, 2621, 2621, 2611, 2622,
+				2611, 2612, 2622, 2622, 2612, 2623,
+				2612, 2613, 2623, 2623, 2613, 2624,
+				2613, 2614, 2624, 2624, 2614, 2625,
+				2614, 2615, 2625, 2625, 2615, 2626,
+				2615, 2616, 2626, 2626, 2616, 2627,
+				2616, 2617, 2627, 2627, 2617, 2628,
+				2618, 2619, 2629, 2629, 2619, 2630,
+				2619, 2620, 2630, 2630, 2620, 2631,
+				2620, 2621, 2631, 2631, 2621, 2632,
+				2621, 2622, 2632, 2632, 2622, 2633,
+				2622, 2623, 2633, 2633, 2623, 2634,
+				2623, 2624, 2634, 2634, 2624, 2635,
+				2624, 2625, 2635, 2635, 2625, 2636,
+				2625, 2626, 2636, 2636, 2626, 2637,
+				2626, 2627, 2637, 2637, 2627, 2638,
+				2627, 2628, 2638, 2638, 2628, 2639,
+				2629, 2630, 2640, 2640, 2630, 2641,
+				2630, 2631, 2641, 2641, 2631, 2642,
+				2631, 2632, 2642, 2642, 2632, 2643,
+				2632, 2633, 2643, 2643, 2633, 2644,
+				2633, 2634, 2644, 2644, 2634, 2645,
+				2634, 2635, 2645, 2645, 2635, 2646,
+				2635, 2636, 2646, 2646, 2636, 2647,
+				2636, 2637, 2647, 2647, 2637, 2648,
+				2637, 2638, 2648, 2648, 2638, 2649,
+				2638, 2639, 2649, 2649, 2639, 2650,
+				2640, 2641, 2651, 2651, 2641, 2652,
+				2641, 2642, 2652, 2652, 2642, 2653,
+				2642, 2643, 2653, 2653, 2643, 2654,
+				2643, 2644, 2654, 2654, 2644, 2655,
+				2644, 2645, 2655, 2655, 2645, 2656,
+				2645, 2646, 2656, 2656, 2646, 2657,
+				2646, 2647, 2657, 2657, 2647, 2658,
+				2647, 2648, 2658, 2658, 2648, 2659,
+				2648, 2649, 2659, 2659, 2649, 2660,
+				2649, 2650, 2660, 2660, 2650, 2661,
+				2662, 2663, 2673, 2673, 2663, 2674,
+				2663, 2664, 2674, 2674, 2664, 2675,
+				2664, 2665, 2675, 2675, 2665, 2676,
+				2665, 2666, 2676, 2676, 2666, 2677,
+				2666, 2667, 2677, 2677, 2667, 2678,
+				2667, 2668, 2678, 2678, 2668, 2679,
+				2668, 2669, 2679, 2679, 2669, 2680,
+				2669, 2670, 2680, 2680, 2670, 2681,
+				2670, 2671, 2681, 2681, 2671, 2682,
+				2671, 2672, 2682, 2682, 2672, 2683,
+				2673, 2674, 2684, 2684, 2674, 2685,
+				2674, 2675, 2685, 2685, 2675, 2686,
+				2675, 2676, 2686, 2686, 2676, 2687,
+				2676, 2677, 2687, 2687, 2677, 2688,
+				2677, 2678, 2688, 2688, 2678, 2689,
+				2678, 2679, 2689, 2689, 2679, 2690,
+				2679, 2680, 2690, 2690, 2680, 2691,
+				2680, 2681, 2691, 2691, 2681, 2692,
+				2681, 2682, 2692, 2692, 2682, 2693,
+				2682, 2683, 2693, 2693, 2683, 2694,
+				2684, 2685, 2695, 2695, 2685, 2696,
+				2685, 2686, 2696, 2696, 2686, 2697,
+				2686, 2687, 2697, 2697, 2687, 2698,
+				2687, 2688, 2698, 2698, 2688, 2699,
+				2688, 2689, 2699, 2699, 2689, 2700,
+				2689, 2690, 2700, 2700, 2690, 2701,
+				2690, 2691, 2701, 2701, 2691, 2702,
+				2691, 2692, 2702, 2702, 2692, 2703,
+				2692, 2693, 2703, 2703, 2693, 2704,
+				2693, 2694, 2704, 2704, 2694, 2705,
+				2695, 2696, 2706, 2706, 2696, 2707,
+				2696, 2697, 2707, 2707, 2697, 2708,
+				2697, 2698, 2708, 2708, 2698, 2709,
+				2698, 2699, 2709, 2709, 2699, 2710,
+				2699, 2700, 2710, 2710, 2700, 2711,
+				2700, 2701, 2711, 2711, 2701, 2712,
+				2701, 2702, 2712, 2712, 2702, 2713,
+				2702, 2703, 2713, 2713, 2703, 2714,
+				2703, 2704, 2714, 2714, 2704, 2715,
+				2704, 2705, 2715, 2715, 2705, 2716,
+				2706, 2707, 2717, 2717, 2707, 2718,
+				2707, 2708, 2718, 2718, 2708, 2719,
+				2708, 2709, 2719, 2719, 2709, 2720,
+				2709, 2710, 2720, 2720, 2710, 2721,
+				2710, 2711, 2721, 2721, 2711, 2722,
+				2711, 2712, 2722, 2722, 2712, 2723,
+				2712, 2713, 2723, 2723, 2713, 2724,
+				2713, 2714, 2724, 2724, 2714, 2725,
+				2714, 2715, 2725, 2725, 2715, 2726,
+				2715, 2716, 2726, 2726, 2716, 2727,
+				2717, 2718, 2728, 2728, 2718, 2729,
+				2718, 2719, 2729, 2729, 2719, 2730,
+				2719, 2720, 2730, 2730, 2720, 2731,
+				2720, 2721, 2731, 2731, 2721, 2732,
+				2721, 2722, 2732, 2732, 2722, 2733,
+				2722, 2723, 2733, 2733, 2723, 2734,
+				2723, 2724, 2734, 2734, 2724, 2735,
+				2724, 2725, 2735, 2735, 2725, 2736,
+				2725, 2726, 2736, 2736, 2726, 2737,
+				2726, 2727, 2737, 2737, 2727, 2738,
+				2728, 2729, 2739, 2739, 2729, 2740,
+				2729, 2730, 2740, 2740, 2730, 2741,
+				2730, 2731, 2741, 2741, 2731, 2742,
+				2731, 2732, 2742, 2742, 2732, 2743,
+				2732, 2733, 2743, 2743, 2733, 2744,
+				2733, 2734, 2744, 2744, 2734, 2745,
+				2734, 2735, 2745, 2745, 2735, 2746,
+				2735, 2736, 2746, 2746, 2736, 2747,
+				2736, 2737, 2747, 2747, 2737, 2748,
+				2737, 2738, 2748, 2748, 2738, 2749,
+				2739, 2740, 2750, 2750, 2740, 2751,
+				2740, 2741, 2751, 2751, 2741, 2752,
+				2741, 2742, 2752, 2752, 2742, 2753,
+				2742, 2743, 2753, 2753, 2743, 2754,
+				2743, 2744, 2754, 2754, 2744, 2755,
+				2744, 2745, 2755, 2755, 2745, 2756,
+				2745, 2746, 2756, 2756, 2746, 2757,
+				2746, 2747, 2757, 2757, 2747, 2758,
+				2747, 2748, 2758, 2758, 2748, 2759,
+				2748, 2749, 2759, 2759, 2749, 2760,
+				2750, 2751, 2761, 2761, 2751, 2762,
+				2751, 2752, 2762, 2762, 2752, 2763,
+				2752, 2753, 2763, 2763, 2753, 2764,
+				2753, 2754, 2764, 2764, 2754, 2765,
+				2754, 2755, 2765, 2765, 2755, 2766,
+				2755, 2756, 2766, 2766, 2756, 2767,
+				2756, 2757, 2767, 2767, 2757, 2768,
+				2757, 2758, 2768, 2768, 2758, 2769,
+				2758, 2759, 2769, 2769, 2759, 2770,
+				2759, 2760, 2770, 2770, 2760, 2771,
+				2761, 2762, 2772, 2772, 2762, 2773,
+				2762, 2763, 2773, 2773, 2763, 2774,
+				2763, 2764, 2774, 2774, 2764, 2775,
+				2764, 2765, 2775, 2775, 2765, 2776,
+				2765, 2766, 2776, 2776, 2766, 2777,
+				2766, 2767, 2777, 2777, 2767, 2778,
+				2767, 2768, 2778, 2778, 2768, 2779,
+				2768, 2769, 2779, 2779, 2769, 2780,
+				2769, 2770, 2780, 2780, 2770, 2781,
+				2770, 2771, 2781, 2781, 2771, 2782,
+				2783, 2784, 2794, 2794, 2784, 2795,
+				2784, 2785, 2795, 2795, 2785, 2796,
+				2785, 2786, 2796, 2796, 2786, 2797,
+				2786, 2787, 2797, 2797, 2787, 2798,
+				2787, 2788, 2798, 2798, 2788, 2799,
+				2788, 2789, 2799, 2799, 2789, 2800,
+				2789, 2790, 2800, 2800, 2790, 2801,
+				2790, 2791, 2801, 2801, 2791, 2802,
+				2791, 2792, 2802, 2802, 2792, 2803,
+				2792, 2793, 2803, 2803, 2793, 2804,
+				2794, 2795, 2805, 2805, 2795, 2806,
+				2795, 2796, 2806, 2806, 2796, 2807,
+				2796, 2797, 2807, 2807, 2797, 2808,
+				2797, 2798, 2808, 2808, 2798, 2809,
+				2798, 2799, 2809, 2809, 2799, 2810,
+				2799, 2800, 2810, 2810, 2800, 2811,
+				2800, 2801, 2811, 2811, 2801, 2812,
+				2801, 2802, 2812, 2812, 2802, 2813,
+				2802, 2803, 2813, 2813, 2803, 2814,
+				2803, 2804, 2814, 2814, 2804, 2815,
+				2805, 2806, 2816, 2816, 2806, 2817,
+				2806, 2807, 2817, 2817, 2807, 2818,
+				2807, 2808, 2818, 2818, 2808, 2819,
+				2808, 2809, 2819, 2819, 2809, 2820,
+				2809, 2810, 2820, 2820, 2810, 2821,
+				2810, 2811, 2821, 2821, 2811, 2822,
+				2811, 2812, 2822, 2822, 2812, 2823,
+				2812, 2813, 2823, 2823, 2813, 2824,
+				2813, 2814, 2824, 2824, 2814, 2825,
+				2814, 2815, 2825, 2825, 2815, 2826,
+				2816, 2817, 2827, 2827, 2817, 2828,
+				2817, 2818, 2828, 2828, 2818, 2829,
+				2818, 2819, 2829, 2829, 2819, 2830,
+				2819, 2820, 2830, 2830, 2820, 2831,
+				2820, 2821, 2831, 2831, 2821, 2832,
+				2821, 2822, 2832, 2832, 2822, 2833,
+				2822, 2823, 2833, 2833, 2823, 2834,
+				2823, 2824, 2834, 2834, 2824, 2835,
+				2824, 2825, 2835, 2835, 2825, 2836,
+				2825, 2826, 2836, 2836, 2826, 2837,
+				2827, 2828, 2838, 2838, 2828, 2839,
+				2828, 2829, 2839, 2839, 2829, 2840,
+				2829, 2830, 2840, 2840, 2830, 2841,
+				2830, 2831, 2841, 2841, 2831, 2842,
+				2831, 2832, 2842, 2842, 2832, 2843,
+				2832, 2833, 2843, 2843, 2833, 2844,
+				2833, 2834, 2844, 2844, 2834, 2845,
+				2834, 2835, 2845, 2845, 2835, 2846,
+				2835, 2836, 2846, 2846, 2836, 2847,
+				2836, 2837, 2847, 2847, 2837, 2848,
+				2838, 2839, 2849, 2849, 2839, 2850,
+				2839, 2840, 2850, 2850, 2840, 2851,
+				2840, 2841, 2851, 2851, 2841, 2852,
+				2841, 2842, 2852, 2852, 2842, 2853,
+				2842, 2843, 2853, 2853, 2843, 2854,
+				2843, 2844, 2854, 2854, 2844, 2855,
+				2844, 2845, 2855, 2855, 2845, 2856,
+				2845, 2846, 2856, 2856, 2846, 2857,
+				2846, 2847, 2857, 2857, 2847, 2858,
+				2847, 2848, 2858, 2858, 2848, 2859,
+				2849, 2850, 2860, 2860, 2850, 2861,
+				2850, 2851, 2861, 2861, 2851, 2862,
+				2851, 2852, 2862, 2862, 2852, 2863,
+				2852, 2853, 2863, 2863, 2853, 2864,
+				2853, 2854, 2864, 2864, 2854, 2865,
+				2854, 2855, 2865, 2865, 2855, 2866,
+				2855, 2856, 2866, 2866, 2856, 2867,
+				2856, 2857, 2867, 2867, 2857, 2868,
+				2857, 2858, 2868, 2868, 2858, 2869,
+				2858, 2859, 2869, 2869, 2859, 2870,
+				2860, 2861, 2871, 2871, 2861, 2872,
+				2861, 2862, 2872, 2872, 2862, 2873,
+				2862, 2863, 2873, 2873, 2863, 2874,
+				2863, 2864, 2874, 2874, 2864, 2875,
+				2864, 2865, 2875, 2875, 2865, 2876,
+				2865, 2866, 2876, 2876, 2866, 2877,
+				2866, 2867, 2877, 2877, 2867, 2878,
+				2867, 2868, 2878, 2878, 2868, 2879,
+				2868, 2869, 2879, 2879, 2869, 2880,
+				2869, 2870, 2880, 2880, 2870, 2881,
+				2871, 2872, 2882, 2882, 2872, 2883,
+				2872, 2873, 2883, 2883, 2873, 2884,
+				2873, 2874, 2884, 2884, 2874, 2885,
+				2874, 2875, 2885, 2885, 2875, 2886,
+				2875, 2876, 2886, 2886, 2876, 2887,
+				2876, 2877, 2887, 2887, 2877, 2888,
+				2877, 2878, 2888, 2888, 2878, 2889,
+				2878, 2879, 2889, 2889, 2879, 2890,
+				2879, 2880, 2890, 2890, 2880, 2891,
+				2880, 2881, 2891, 2891, 2881, 2892,
+				2882, 2883, 2893, 2893, 2883, 2894,
+				2883, 2884, 2894, 2894, 2884, 2895,
+				2884, 2885, 2895, 2895, 2885, 2896,
+				2885, 2886, 2896, 2896, 2886, 2897,
+				2886, 2887, 2897, 2897, 2887, 2898,
+				2887, 2888, 2898, 2898, 2888, 2899,
+				2888, 2889, 2899, 2899, 2889, 2900,
+				2889, 2890, 2900, 2900, 2890, 2901,
+				2890, 2891, 2901, 2901, 2891, 2902,
+				2891, 2892, 2902, 2902, 2892, 2903,
+				2904, 2905, 2915, 2915, 2905, 2916,
+				2905, 2906, 2916, 2916, 2906, 2917,
+				2906, 2907, 2917, 2917, 2907, 2918,
+				2907, 2908, 2918, 2918, 2908, 2919,
+				2908, 2909, 2919, 2919, 2909, 2920,
+				2909, 2910, 2920, 2920, 2910, 2921,
+				2910, 2911, 2921, 2921, 2911, 2922,
+				2911, 2912, 2922, 2922, 2912, 2923,
+				2912, 2913, 2923, 2923, 2913, 2924,
+				2913, 2914, 2924, 2924, 2914, 2925,
+				2915, 2916, 2926, 2926, 2916, 2927,
+				2916, 2917, 2927, 2927, 2917, 2928,
+				2917, 2918, 2928, 2928, 2918, 2929,
+				2918, 2919, 2929, 2929, 2919, 2930,
+				2919, 2920, 2930, 2930, 2920, 2931,
+				2920, 2921, 2931, 2931, 2921, 2932,
+				2921, 2922, 2932, 2932, 2922, 2933,
+				2922, 2923, 2933, 2933, 2923, 2934,
+				2923, 2924, 2934, 2934, 2924, 2935,
+				2924, 2925, 2935, 2935, 2925, 2936,
+				2926, 2927, 2937, 2937, 2927, 2938,
+				2927, 2928, 2938, 2938, 2928, 2939,
+				2928, 2929, 2939, 2939, 2929, 2940,
+				2929, 2930, 2940, 2940, 2930, 2941,
+				2930, 2931, 2941, 2941, 2931, 2942,
+				2931, 2932, 2942, 2942, 2932, 2943,
+				2932, 2933, 2943, 2943, 2933, 2944,
+				2933, 2934, 2944, 2944, 2934, 2945,
+				2934, 2935, 2945, 2945, 2935, 2946,
+				2935, 2936, 2946, 2946, 2936, 2947,
+				2937, 2938, 2948, 2948, 2938, 2949,
+				2938, 2939, 2949, 2949, 2939, 2950,
+				2939, 2940, 2950, 2950, 2940, 2951,
+				2940, 2941, 2951, 2951, 2941, 2952,
+				2941, 2942, 2952, 2952, 2942, 2953,
+				2942, 2943, 2953, 2953, 2943, 2954,
+				2943, 2944, 2954, 2954, 2944, 2955,
+				2944, 2945, 2955, 2955, 2945, 2956,
+				2945, 2946, 2956, 2956, 2946, 2957,
+				2946, 2947, 2957, 2957, 2947, 2958,
+				2948, 2949, 2959, 2959, 2949, 2960,
+				2949, 2950, 2960, 2960, 2950, 2961,
+				2950, 2951, 2961, 2961, 2951, 2962,
+				2951, 2952, 2962, 2962, 2952, 2963,
+				2952, 2953, 2963, 2963, 2953, 2964,
+				2953, 2954, 2964, 2964, 2954, 2965,
+				2954, 2955, 2965, 2965, 2955, 2966,
+				2955, 2956, 2966, 2966, 2956, 2967,
+				2956, 2957, 2967, 2967, 2957, 2968,
+				2957, 2958, 2968, 2968, 2958, 2969,
+				2959, 2960, 2970, 2970, 2960, 2971,
+				2960, 2961, 2971, 2971, 2961, 2972,
+				2961, 2962, 2972, 2972, 2962, 2973,
+				2962, 2963, 2973, 2973, 2963, 2974,
+				2963, 2964, 2974, 2974, 2964, 2975,
+				2964, 2965, 2975, 2975, 2965, 2976,
+				2965, 2966, 2976, 2976, 2966, 2977,
+				2966, 2967, 2977, 2977, 2967, 2978,
+				2967, 2968, 2978, 2978, 2968, 2979,
+				2968, 2969, 2979, 2979, 2969, 2980,
+				2970, 2971, 2981, 2981, 2971, 2982,
+				2971, 2972, 2982, 2982, 2972, 2983,
+				2972, 2973, 2983, 2983, 2973, 2984,
+				2973, 2974, 2984, 2984, 2974, 2985,
+				2974, 2975, 2985, 2985, 2975, 2986,
+				2975, 2976, 2986, 2986, 2976, 2987,
+				2976, 2977, 2987, 2987, 2977, 2988,
+				2977, 2978, 2988, 2988, 2978, 2989,
+				2978, 2979, 2989, 2989, 2979, 2990,
+				2979, 2980, 2990, 2990, 2980, 2991,
+				2981, 2982, 2992, 2992, 2982, 2993,
+				2982, 2983, 2993, 2993, 2983, 2994,
+				2983, 2984, 2994, 2994, 2984, 2995,
+				2984, 2985, 2995, 2995, 2985, 2996,
+				2985, 2986, 2996, 2996, 2986, 2997,
+				2986, 2987, 2997, 2997, 2987, 2998,
+				2987, 2988, 2998, 2998, 2988, 2999,
+				2988, 2989, 2999, 2999, 2989, 3000,
+				2989, 2990, 3000, 3000, 2990, 3001,
+				2990, 2991, 3001, 3001, 2991, 3002,
+				2992, 2993, 3003, 3003, 2993, 3004,
+				2993, 2994, 3004, 3004, 2994, 3005,
+				2994, 2995, 3005, 3005, 2995, 3006,
+				2995, 2996, 3006, 3006, 2996, 3007,
+				2996, 2997, 3007, 3007, 2997, 3008,
+				2997, 2998, 3008, 3008, 2998, 3009,
+				2998, 2999, 3009, 3009, 2999, 3010,
+				2999, 3000, 3010, 3010, 3000, 3011,
+				3000, 3001, 3011, 3011, 3001, 3012,
+				3001, 3002, 3012, 3012, 3002, 3013,
+				3003, 3004, 3014, 3014, 3004, 3015,
+				3004, 3005, 3015, 3015, 3005, 3016,
+				3005, 3006, 3016, 3016, 3006, 3017,
+				3006, 3007, 3017, 3017, 3007, 3018,
+				3007, 3008, 3018, 3018, 3008, 3019,
+				3008, 3009, 3019, 3019, 3009, 3020,
+				3009, 3010, 3020, 3020, 3010, 3021,
+				3010, 3011, 3021, 3021, 3011, 3022,
+				3011, 3012, 3022, 3022, 3012, 3023,
+				3012, 3013, 3023, 3023, 3013, 3024,
+				3025, 3026, 3036, 3036, 3026, 3037,
+				3026, 3027, 3037, 3037, 3027, 3038,
+				3027, 3028, 3038, 3038, 3028, 3039,
+				3028, 3029, 3039, 3039, 3029, 3040,
+				3029, 3030, 3040, 3040, 3030, 3041,
+				3030, 3031, 3041, 3041, 3031, 3042,
+				3031, 3032, 3042, 3042, 3032, 3043,
+				3032, 3033, 3043, 3043, 3033, 3044,
+				3033, 3034, 3044, 3044, 3034, 3045,
+				3034, 3035, 3045, 3045, 3035, 3046,
+				3036, 3037, 3047, 3047, 3037, 3048,
+				3037, 3038, 3048, 3048, 3038, 3049,
+				3038, 3039, 3049, 3049, 3039, 3050,
+				3039, 3040, 3050, 3050, 3040, 3051,
+				3040, 3041, 3051, 3051, 3041, 3052,
+				3041, 3042, 3052, 3052, 3042, 3053,
+				3042, 3043, 3053, 3053, 3043, 3054,
+				3043, 3044, 3054, 3054, 3044, 3055,
+				3044, 3045, 3055, 3055, 3045, 3056,
+				3045, 3046, 3056, 3056, 3046, 3057,
+				3047, 3048, 3058, 3058, 3048, 3059,
+				3048, 3049, 3059, 3059, 3049, 3060,
+				3049, 3050, 3060, 3060, 3050, 3061,
+				3050, 3051, 3061, 3061, 3051, 3062,
+				3051, 3052, 3062, 3062, 3052, 3063,
+				3052, 3053, 3063, 3063, 3053, 3064,
+				3053, 3054, 3064, 3064, 3054, 3065,
+				3054, 3055, 3065, 3065, 3055, 3066,
+				3055, 3056, 3066, 3066, 3056, 3067,
+				3056, 3057, 3067, 3067, 3057, 3068,
+				3058, 3059, 3069, 3069, 3059, 3070,
+				3059, 3060, 3070, 3070, 3060, 3071,
+				3060, 3061, 3071, 3071, 3061, 3072,
+				3061, 3062, 3072, 3072, 3062, 3073,
+				3062, 3063, 3073, 3073, 3063, 3074,
+				3063, 3064, 3074, 3074, 3064, 3075,
+				3064, 3065, 3075, 3075, 3065, 3076,
+				3065, 3066, 3076, 3076, 3066, 3077,
+				3066, 3067, 3077, 3077, 3067, 3078,
+				3067, 3068, 3078, 3078, 3068, 3079,
+				3069, 3070, 3080, 3080, 3070, 3081,
+				3070, 3071, 3081, 3081, 3071, 3082,
+				3071, 3072, 3082, 3082, 3072, 3083,
+				3072, 3073, 3083, 3083, 3073, 3084,
+				3073, 3074, 3084, 3084, 3074, 3085,
+				3074, 3075, 3085, 3085, 3075, 3086,
+				3075, 3076, 3086, 3086, 3076, 3087,
+				3076, 3077, 3087, 3087, 3077, 3088,
+				3077, 3078, 3088, 3088, 3078, 3089,
+				3078, 3079, 3089, 3089, 3079, 3090,
+				3080, 3081, 3091, 3091, 3081, 3092,
+				3081, 3082, 3092, 3092, 3082, 3093,
+				3082, 3083, 3093, 3093, 3083, 3094,
+				3083, 3084, 3094, 3094, 3084, 3095,
+				3084, 3085, 3095, 3095, 3085, 3096,
+				3085, 3086, 3096, 3096, 3086, 3097,
+				3086, 3087, 3097, 3097, 3087, 3098,
+				3087, 3088, 3098, 3098, 3088, 3099,
+				3088, 3089, 3099, 3099, 3089, 3100,
+				3089, 3090, 3100, 3100, 3090, 3101,
+				3091, 3092, 3102, 3102, 3092, 3103,
+				3092, 3093, 3103, 3103, 3093, 3104,
+				3093, 3094, 3104, 3104, 3094, 3105,
+				3094, 3095, 3105, 3105, 3095, 3106,
+				3095, 3096, 3106, 3106, 3096, 3107,
+				3096, 3097, 3107, 3107, 3097, 3108,
+				3097, 3098, 3108, 3108, 3098, 3109,
+				3098, 3099, 3109, 3109, 3099, 3110,
+				3099, 3100, 3110, 3110, 3100, 3111,
+				3100, 3101, 3111, 3111, 3101, 3112,
+				3102, 3103, 3113, 3113, 3103, 3114,
+				3103, 3104, 3114, 3114, 3104, 3115,
+				3104, 3105, 3115, 3115, 3105, 3116,
+				3105, 3106, 3116, 3116, 3106, 3117,
+				3106, 3107, 3117, 3117, 3107, 3118,
+				3107, 3108, 3118, 3118, 3108, 3119,
+				3108, 3109, 3119, 3119, 3109, 3120,
+				3109, 3110, 3120, 3120, 3110, 3121,
+				3110, 3111, 3121, 3121, 3111, 3122,
+				3111, 3112, 3122, 3122, 3112, 3123,
+				3113, 3114, 3124, 3124, 3114, 3125,
+				3114, 3115, 3125, 3125, 3115, 3126,
+				3115, 3116, 3126, 3126, 3116, 3127,
+				3116, 3117, 3127, 3127, 3117, 3128,
+				3117, 3118, 3128, 3128, 3118, 3129,
+				3118, 3119, 3129, 3129, 3119, 3130,
+				3119, 3120, 3130, 3130, 3120, 3131,
+				3120, 3121, 3131, 3131, 3121, 3132,
+				3121, 3122, 3132, 3132, 3122, 3133,
+				3122, 3123, 3133, 3133, 3123, 3134,
+				3124, 3125, 3135, 3135, 3125, 3136,
+				3125, 3126, 3136, 3136, 3126, 3137,
+				3126, 3127, 3137, 3137, 3127, 3138,
+				3127, 3128, 3138, 3138, 3128, 3139,
+				3128, 3129, 3139, 3139, 3129, 3140,
+				3129, 3130, 3140, 3140, 3130, 3141,
+				3130, 3131, 3141, 3141, 3131, 3142,
+				3131, 3132, 3142, 3142, 3132, 3143,
+				3132, 3133, 3143, 3143, 3133, 3144,
+				3133, 3134, 3144, 3144, 3134, 3145,
+				3146, 3147, 3157, 3157, 3147, 3158,
+				3147, 3148, 3158, 3158, 3148, 3159,
+				3148, 3149, 3159, 3159, 3149, 3160,
+				3149, 3150, 3160, 3160, 3150, 3161,
+				3150, 3151, 3161, 3161, 3151, 3162,
+				3151, 3152, 3162, 3162, 3152, 3163,
+				3152, 3153, 3163, 3163, 3153, 3164,
+				3153, 3154, 3164, 3164, 3154, 3165,
+				3154, 3155, 3165, 3165, 3155, 3166,
+				3155, 3156, 3166, 3166, 3156, 3167,
+				3157, 3158, 3168, 3168, 3158, 3169,
+				3158, 3159, 3169, 3169, 3159, 3170,
+				3159, 3160, 3170, 3170, 3160, 3171,
+				3160, 3161, 3171, 3171, 3161, 3172,
+				3161, 3162, 3172, 3172, 3162, 3173,
+				3162, 3163, 3173, 3173, 3163, 3174,
+				3163, 3164, 3174, 3174, 3164, 3175,
+				3164, 3165, 3175, 3175, 3165, 3176,
+				3165, 3166, 3176, 3176, 3166, 3177,
+				3166, 3167, 3177, 3177, 3167, 3178,
+				3168, 3169, 3179, 3179, 3169, 3180,
+				3169, 3170, 3180, 3180, 3170, 3181,
+				3170, 3171, 3181, 3181, 3171, 3182,
+				3171, 3172, 3182, 3182, 3172, 3183,
+				3172, 3173, 3183, 3183, 3173, 3184,
+				3173, 3174, 3184, 3184, 3174, 3185,
+				3174, 3175, 3185, 3185, 3175, 3186,
+				3175, 3176, 3186, 3186, 3176, 3187,
+				3176, 3177, 3187, 3187, 3177, 3188,
+				3177, 3178, 3188, 3188, 3178, 3189,
+				3179, 3180, 3190, 3190, 3180, 3191,
+				3180, 3181, 3191, 3191, 3181, 3192,
+				3181, 3182, 3192, 3192, 3182, 3193,
+				3182, 3183, 3193, 3193, 3183, 3194,
+				3183, 3184, 3194, 3194, 3184, 3195,
+				3184, 3185, 3195, 3195, 3185, 3196,
+				3185, 3186, 3196, 3196, 3186, 3197,
+				3186, 3187, 3197, 3197, 3187, 3198,
+				3187, 3188, 3198, 3198, 3188, 3199,
+				3188, 3189, 3199, 3199, 3189, 3200,
+				3190, 3191, 3201, 3201, 3191, 3202,
+				3191, 3192, 3202, 3202, 3192, 3203,
+				3192, 3193, 3203, 3203, 3193, 3204,
+				3193, 3194, 3204, 3204, 3194, 3205,
+				3194, 3195, 3205, 3205, 3195, 3206,
+				3195, 3196, 3206, 3206, 3196, 3207,
+				3196, 3197, 3207, 3207, 3197, 3208,
+				3197, 3198, 3208, 3208, 3198, 3209,
+				3198, 3199, 3209, 3209, 3199, 3210,
+				3199, 3200, 3210, 3210, 3200, 3211,
+				3201, 3202, 3212, 3212, 3202, 3213,
+				3202, 3203, 3213, 3213, 3203, 3214,
+				3203, 3204, 3214, 3214, 3204, 3215,
+				3204, 3205, 3215, 3215, 3205, 3216,
+				3205, 3206, 3216, 3216, 3206, 3217,
+				3206, 3207, 3217, 3217, 3207, 3218,
+				3207, 3208, 3218, 3218, 3208, 3219,
+				3208, 3209, 3219, 3219, 3209, 3220,
+				3209, 3210, 3220, 3220, 3210, 3221,
+				3210, 3211, 3221, 3221, 3211, 3222,
+				3212, 3213, 3223, 3223, 3213, 3224,
+				3213, 3214, 3224, 3224, 3214, 3225,
+				3214, 3215, 3225, 3225, 3215, 3226,
+				3215, 3216, 3226, 3226, 3216, 3227,
+				3216, 3217, 3227, 3227, 3217, 3228,
+				3217, 3218, 3228, 3228, 3218, 3229,
+				3218, 3219, 3229, 3229, 3219, 3230,
+				3219, 3220, 3230, 3230, 3220, 3231,
+				3220, 3221, 3231, 3231, 3221, 3232,
+				3221, 3222, 3232, 3232, 3222, 3233,
+				3223, 3224, 3234, 3234, 3224, 3235,
+				3224, 3225, 3235, 3235, 3225, 3236,
+				3225, 3226, 3236, 3236, 3226, 3237,
+				3226, 3227, 3237, 3237, 3227, 3238,
+				3227, 3228, 3238, 3238, 3228, 3239,
+				3228, 3229, 3239, 3239, 3229, 3240,
+				3229, 3230, 3240, 3240, 3230, 3241,
+				3230, 3231, 3241, 3241, 3231, 3242,
+				3231, 3232, 3242, 3242, 3232, 3243,
+				3232, 3233, 3243, 3243, 3233, 3244,
+				3234, 3235, 3245, 3245, 3235, 3246,
+				3235, 3236, 3246, 3246, 3236, 3247,
+				3236, 3237, 3247, 3247, 3237, 3248,
+				3237, 3238, 3248, 3248, 3238, 3249,
+				3238, 3239, 3249, 3249, 3239, 3250,
+				3239, 3240, 3250, 3250, 3240, 3251,
+				3240, 3241, 3251, 3251, 3241, 3252,
+				3241, 3242, 3252, 3252, 3242, 3253,
+				3242, 3243, 3253, 3253, 3243, 3254,
+				3243, 3244, 3254, 3254, 3244, 3255,
+				3245, 3246, 3256, 3256, 3246, 3257,
+				3246, 3247, 3257, 3257, 3247, 3258,
+				3247, 3248, 3258, 3258, 3248, 3259,
+				3248, 3249, 3259, 3259, 3249, 3260,
+				3249, 3250, 3260, 3260, 3250, 3261,
+				3250, 3251, 3261, 3261, 3251, 3262,
+				3251, 3252, 3262, 3262, 3252, 3263,
+				3252, 3253, 3263, 3263, 3253, 3264,
+				3253, 3254, 3264, 3264, 3254, 3265,
+				3254, 3255, 3265, 3265, 3255, 3266,
+				3267, 3268, 3278, 3278, 3268, 3279,
+				3268, 3269, 3279, 3279, 3269, 3280,
+				3269, 3270, 3280, 3280, 3270, 3281,
+				3270, 3271, 3281, 3281, 3271, 3282,
+				3271, 3272, 3282, 3282, 3272, 3283,
+				3272, 3273, 3283, 3283, 3273, 3284,
+				3273, 3274, 3284, 3284, 3274, 3285,
+				3274, 3275, 3285, 3285, 3275, 3286,
+				3275, 3276, 3286, 3286, 3276, 3287,
+				3276, 3277, 3287, 3287, 3277, 3288,
+				3278, 3279, 3289, 3289, 3279, 3290,
+				3279, 3280, 3290, 3290, 3280, 3291,
+				3280, 3281, 3291, 3291, 3281, 3292,
+				3281, 3282, 3292, 3292, 3282, 3293,
+				3282, 3283, 3293, 3293, 3283, 3294,
+				3283, 3284, 3294, 3294, 3284, 3295,
+				3284, 3285, 3295, 3295, 3285, 3296,
+				3285, 3286, 3296, 3296, 3286, 3297,
+				3286, 3287, 3297, 3297, 3287, 3298,
+				3287, 3288, 3298, 3298, 3288, 3299,
+				3289, 3290, 3300, 3300, 3290, 3301,
+				3290, 3291, 3301, 3301, 3291, 3302,
+				3291, 3292, 3302, 3302, 3292, 3303,
+				3292, 3293, 3303, 3303, 3293, 3304,
+				3293, 3294, 3304, 3304, 3294, 3305,
+				3294, 3295, 3305, 3305, 3295, 3306,
+				3295, 3296, 3306, 3306, 3296, 3307,
+				3296, 3297, 3307, 3307, 3297, 3308,
+				3297, 3298, 3308, 3308, 3298, 3309,
+				3298, 3299, 3309, 3309, 3299, 3310,
+				3300, 3301, 3311, 3311, 3301, 3312,
+				3301, 3302, 3312, 3312, 3302, 3313,
+				3302, 3303, 3313, 3313, 3303, 3314,
+				3303, 3304, 3314, 3314, 3304, 3315,
+				3304, 3305, 3315, 3315, 3305, 3316,
+				3305, 3306, 3316, 3316, 3306, 3317,
+				3306, 3307, 3317, 3317, 3307, 3318,
+				3307, 3308, 3318, 3318, 3308, 3319,
+				3308, 3309, 3319, 3319, 3309, 3320,
+				3309, 3310, 3320, 3320, 3310, 3321,
+				3311, 3312, 3322, 3322, 3312, 3323,
+				3312, 3313, 3323, 3323, 3313, 3324,
+				3313, 3314, 3324, 3324, 3314, 3325,
+				3314, 3315, 3325, 3325, 3315, 3326,
+				3315, 3316, 3326, 3326, 3316, 3327,
+				3316, 3317, 3327, 3327, 3317, 3328,
+				3317, 3318, 3328, 3328, 3318, 3329,
+				3318, 3319, 3329, 3329, 3319, 3330,
+				3319, 3320, 3330, 3330, 3320, 3331,
+				3320, 3321, 3331, 3331, 3321, 3332,
+				3322, 3323, 3333, 3333, 3323, 3334,
+				3323, 3324, 3334, 3334, 3324, 3335,
+				3324, 3325, 3335, 3335, 3325, 3336,
+				3325, 3326, 3336, 3336, 3326, 3337,
+				3326, 3327, 3337, 3337, 3327, 3338,
+				3327, 3328, 3338, 3338, 3328, 3339,
+				3328, 3329, 3339, 3339, 3329, 3340,
+				3329, 3330, 3340, 3340, 3330, 3341,
+				3330, 3331, 3341, 3341, 3331, 3342,
+				3331, 3332, 3342, 3342, 3332, 3343,
+				3333, 3334, 3344, 3344, 3334, 3345,
+				3334, 3335, 3345, 3345, 3335, 3346,
+				3335, 3336, 3346, 3346, 3336, 3347,
+				3336, 3337, 3347, 3347, 3337, 3348,
+				3337, 3338, 3348, 3348, 3338, 3349,
+				3338, 3339, 3349, 3349, 3339, 3350,
+				3339, 3340, 3350, 3350, 3340, 3351,
+				3340, 3341, 3351, 3351, 3341, 3352,
+				3341, 3342, 3352, 3352, 3342, 3353,
+				3342, 3343, 3353, 3353, 3343, 3354,
+				3344, 3345, 3355, 3355, 3345, 3356,
+				3345, 3346, 3356, 3356, 3346, 3357,
+				3346, 3347, 3357, 3357, 3347, 3358,
+				3347, 3348, 3358, 3358, 3348, 3359,
+				3348, 3349, 3359, 3359, 3349, 3360,
+				3349, 3350, 3360, 3360, 3350, 3361,
+				3350, 3351, 3361, 3361, 3351, 3362,
+				3351, 3352, 3362, 3362, 3352, 3363,
+				3352, 3353, 3363, 3363, 3353, 3364,
+				3353, 3354, 3364, 3364, 3354, 3365,
+				3355, 3356, 3366, 3366, 3356, 3367,
+				3356, 3357, 3367, 3367, 3357, 3368,
+				3357, 3358, 3368, 3368, 3358, 3369,
+				3358, 3359, 3369, 3369, 3359, 3370,
+				3359, 3360, 3370, 3370, 3360, 3371,
+				3360, 3361, 3371, 3371, 3361, 3372,
+				3361, 3362, 3372, 3372, 3362, 3373,
+				3362, 3363, 3373, 3373, 3363, 3374,
+				3363, 3364, 3374, 3374, 3364, 3375,
+				3364, 3365, 3375, 3375, 3365, 3376,
+				3366, 3367, 3377, 3377, 3367, 3378,
+				3367, 3368, 3378, 3378, 3368, 3379,
+				3368, 3369, 3379, 3379, 3369, 3380,
+				3369, 3370, 3380, 3380, 3370, 3381,
+				3370, 3371, 3381, 3381, 3371, 3382,
+				3371, 3372, 3382, 3382, 3372, 3383,
+				3372, 3373, 3383, 3383, 3373, 3384,
+				3373, 3374, 3384, 3384, 3374, 3385,
+				3374, 3375, 3385, 3385, 3375, 3386,
+				3375, 3376, 3386, 3386, 3376, 3387,
+				3388, 3389, 3399, 3399, 3389, 3400,
+				3389, 3390, 3400, 3400, 3390, 3401,
+				3390, 3391, 3401, 3401, 3391, 3402,
+				3391, 3392, 3402, 3402, 3392, 3403,
+				3392, 3393, 3403, 3403, 3393, 3404,
+				3393, 3394, 3404, 3404, 3394, 3405,
+				3394, 3395, 3405, 3405, 3395, 3406,
+				3395, 3396, 3406, 3406, 3396, 3407,
+				3396, 3397, 3407, 3407, 3397, 3408,
+				3397, 3398, 3408, 3408, 3398, 3409,
+				3399, 3400, 3410, 3410, 3400, 3411,
+				3400, 3401, 3411, 3411, 3401, 3412,
+				3401, 3402, 3412, 3412, 3402, 3413,
+				3402, 3403, 3413, 3413, 3403, 3414,
+				3403, 3404, 3414, 3414, 3404, 3415,
+				3404, 3405, 3415, 3415, 3405, 3416,
+				3405, 3406, 3416, 3416, 3406, 3417,
+				3406, 3407, 3417, 3417, 3407, 3418,
+				3407, 3408, 3418, 3418, 3408, 3419,
+				3408, 3409, 3419, 3419, 3409, 3420,
+				3410, 3411, 3421, 3421, 3411, 3422,
+				3411, 3412, 3422, 3422, 3412, 3423,
+				3412, 3413, 3423, 3423, 3413, 3424,
+				3413, 3414, 3424, 3424, 3414, 3425,
+				3414, 3415, 3425, 3425, 3415, 3426,
+				3415, 3416, 3426, 3426, 3416, 3427,
+				3416, 3417, 3427, 3427, 3417, 3428,
+				3417, 3418, 3428, 3428, 3418, 3429,
+				3418, 3419, 3429, 3429, 3419, 3430,
+				3419, 3420, 3430, 3430, 3420, 3431,
+				3421, 3422, 3432, 3432, 3422, 3433,
+				3422, 3423, 3433, 3433, 3423, 3434,
+				3423, 3424, 3434, 3434, 3424, 3435,
+				3424, 3425, 3435, 3435, 3425, 3436,
+				3425, 3426, 3436, 3436, 3426, 3437,
+				3426, 3427, 3437, 3437, 3427, 3438,
+				3427, 3428, 3438, 3438, 3428, 3439,
+				3428, 3429, 3439, 3439, 3429, 3440,
+				3429, 3430, 3440, 3440, 3430, 3441,
+				3430, 3431, 3441, 3441, 3431, 3442,
+				3432, 3433, 3443, 3443, 3433, 3444,
+				3433, 3434, 3444, 3444, 3434, 3445,
+				3434, 3435, 3445, 3445, 3435, 3446,
+				3435, 3436, 3446, 3446, 3436, 3447,
+				3436, 3437, 3447, 3447, 3437, 3448,
+				3437, 3438, 3448, 3448, 3438, 3449,
+				3438, 3439, 3449, 3449, 3439, 3450,
+				3439, 3440, 3450, 3450, 3440, 3451,
+				3440, 3441, 3451, 3451, 3441, 3452,
+				3441, 3442, 3452, 3452, 3442, 3453,
+				3443, 3444, 3454, 3454, 3444, 3455,
+				3444, 3445, 3455, 3455, 3445, 3456,
+				3445, 3446, 3456, 3456, 3446, 3457,
+				3446, 3447, 3457, 3457, 3447, 3458,
+				3447, 3448, 3458, 3458, 3448, 3459,
+				3448, 3449, 3459, 3459, 3449, 3460,
+				3449, 3450, 3460, 3460, 3450, 3461,
+				3450, 3451, 3461, 3461, 3451, 3462,
+				3451, 3452, 3462, 3462, 3452, 3463,
+				3452, 3453, 3463, 3463, 3453, 3464,
+				3454, 3455, 3465, 3465, 3455, 3466,
+				3455, 3456, 3466, 3466, 3456, 3467,
+				3456, 3457, 3467, 3467, 3457, 3468,
+				3457, 3458, 3468, 3468, 3458, 3469,
+				3458, 3459, 3469, 3469, 3459, 3470,
+				3459, 3460, 3470, 3470, 3460, 3471,
+				3460, 3461, 3471, 3471, 3461, 3472,
+				3461, 3462, 3472, 3472, 3462, 3473,
+				3462, 3463, 3473, 3473, 3463, 3474,
+				3463, 3464, 3474, 3474, 3464, 3475,
+				3465, 3466, 3476, 3476, 3466, 3477,
+				3466, 3467, 3477, 3477, 3467, 3478,
+				3467, 3468, 3478, 3478, 3468, 3479,
+				3468, 3469, 3479, 3479, 3469, 3480,
+				3469, 3470, 3480, 3480, 3470, 3481,
+				3470, 3471, 3481, 3481, 3471, 3482,
+				3471, 3472, 3482, 3482, 3472, 3483,
+				3472, 3473, 3483, 3483, 3473, 3484,
+				3473, 3474, 3484, 3484, 3474, 3485,
+				3474, 3475, 3485, 3485, 3475, 3486,
+				3476, 3477, 3487, 3487, 3477, 3488,
+				3477, 3478, 3488, 3488, 3478, 3489,
+				3478, 3479, 3489, 3489, 3479, 3490,
+				3479, 3480, 3490, 3490, 3480, 3491,
+				3480, 3481, 3491, 3491, 3481, 3492,
+				3481, 3482, 3492, 3492, 3482, 3493,
+				3482, 3483, 3493, 3493, 3483, 3494,
+				3483, 3484, 3494, 3494, 3484, 3495,
+				3484, 3485, 3495, 3495, 3485, 3496,
+				3485, 3486, 3496, 3496, 3486, 3497,
+				3487, 3488, 3498, 3498, 3488, 3499,
+				3488, 3489, 3499, 3499, 3489, 3500,
+				3489, 3490, 3500, 3500, 3490, 3501,
+				3490, 3491, 3501, 3501, 3491, 3502,
+				3491, 3492, 3502, 3502, 3492, 3503,
+				3492, 3493, 3503, 3503, 3493, 3504,
+				3493, 3494, 3504, 3504, 3494, 3505,
+				3494, 3495, 3505, 3505, 3495, 3506,
+				3495, 3496, 3506, 3506, 3496, 3507,
+				3496, 3497, 3507, 3507, 3497, 3508,
+				3509, 3510, 3520, 3520, 3510, 3521,
+				3510, 3511, 3521, 3521, 3511, 3522,
+				3511, 3512, 3522, 3522, 3512, 3523,
+				3512, 3513, 3523, 3523, 3513, 3524,
+				3513, 3514, 3524, 3524, 3514, 3525,
+				3514, 3515, 3525, 3525, 3515, 3526,
+				3515, 3516, 3526, 3526, 3516, 3527,
+				3516, 3517, 3527, 3527, 3517, 3528,
+				3517, 3518, 3528, 3528, 3518, 3529,
+				3518, 3519, 3529, 3529, 3519, 3530,
+				3520, 3521, 3531, 3531, 3521, 3532,
+				3521, 3522, 3532, 3532, 3522, 3533,
+				3522, 3523, 3533, 3533, 3523, 3534,
+				3523, 3524, 3534, 3534, 3524, 3535,
+				3524, 3525, 3535, 3535, 3525, 3536,
+				3525, 3526, 3536, 3536, 3526, 3537,
+				3526, 3527, 3537, 3537, 3527, 3538,
+				3527, 3528, 3538, 3538, 3528, 3539,
+				3528, 3529, 3539, 3539, 3529, 3540,
+				3529, 3530, 3540, 3540, 3530, 3541,
+				3531, 3532, 3542, 3542, 3532, 3543,
+				3532, 3533, 3543, 3543, 3533, 3544,
+				3533, 3534, 3544, 3544, 3534, 3545,
+				3534, 3535, 3545, 3545, 3535, 3546,
+				3535, 3536, 3546, 3546, 3536, 3547,
+				3536, 3537, 3547, 3547, 3537, 3548,
+				3537, 3538, 3548, 3548, 3538, 3549,
+				3538, 3539, 3549, 3549, 3539, 3550,
+				3539, 3540, 3550, 3550, 3540, 3551,
+				3540, 3541, 3551, 3551, 3541, 3552,
+				3542, 3543, 3553, 3553, 3543, 3554,
+				3543, 3544, 3554, 3554, 3544, 3555,
+				3544, 3545, 3555, 3555, 3545, 3556,
+				3545, 3546, 3556, 3556, 3546, 3557,
+				3546, 3547, 3557, 3557, 3547, 3558,
+				3547, 3548, 3558, 3558, 3548, 3559,
+				3548, 3549, 3559, 3559, 3549, 3560,
+				3549, 3550, 3560, 3560, 3550, 3561,
+				3550, 3551, 3561, 3561, 3551, 3562,
+				3551, 3552, 3562, 3562, 3552, 3563,
+				3553, 3554, 3564, 3564, 3554, 3565,
+				3554, 3555, 3565, 3565, 3555, 3566,
+				3555, 3556, 3566, 3566, 3556, 3567,
+				3556, 3557, 3567, 3567, 3557, 3568,
+				3557, 3558, 3568, 3568, 3558, 3569,
+				3558, 3559, 3569, 3569, 3559, 3570,
+				3559, 3560, 3570, 3570, 3560, 3571,
+				3560, 3561, 3571, 3571, 3561, 3572,
+				3561, 3562, 3572, 3572, 3562, 3573,
+				3562, 3563, 3573, 3573, 3563, 3574,
+				3564, 3565, 3575, 3575, 3565, 3576,
+				3565, 3566, 3576, 3576, 3566, 3577,
+				3566, 3567, 3577, 3577, 3567, 3578,
+				3567, 3568, 3578, 3578, 3568, 3579,
+				3568, 3569, 3579, 3579, 3569, 3580,
+				3569, 3570, 3580, 3580, 3570, 3581,
+				3570, 3571, 3581, 3581, 3571, 3582,
+				3571, 3572, 3582, 3582, 3572, 3583,
+				3572, 3573, 3583, 3583, 3573, 3584,
+				3573, 3574, 3584, 3584, 3574, 3585,
+				3575, 3576, 3586, 3586, 3576, 3587,
+				3576, 3577, 3587, 3587, 3577, 3588,
+				3577, 3578, 3588, 3588, 3578, 3589,
+				3578, 3579, 3589, 3589, 3579, 3590,
+				3579, 3580, 3590, 3590, 3580, 3591,
+				3580, 3581, 3591, 3591, 3581, 3592,
+				3581, 3582, 3592, 3592, 3582, 3593,
+				3582, 3583, 3593, 3593, 3583, 3594,
+				3583, 3584, 3594, 3594, 3584, 3595,
+				3584, 3585, 3595, 3595, 3585, 3596,
+				3586, 3587, 3597, 3597, 3587, 3598,
+				3587, 3588, 3598, 3598, 3588, 3599,
+				3588, 3589, 3599, 3599, 3589, 3600,
+				3589, 3590, 3600, 3600, 3590, 3601,
+				3590, 3591, 3601, 3601, 3591, 3602,
+				3591, 3592, 3602, 3602, 3592, 3603,
+				3592, 3593, 3603, 3603, 3593, 3604,
+				3593, 3594, 3604, 3604, 3594, 3605,
+				3594, 3595, 3605, 3605, 3595, 3606,
+				3595, 3596, 3606, 3606, 3596, 3607,
+				3597, 3598, 3608, 3608, 3598, 3609,
+				3598, 3599, 3609, 3609, 3599, 3610,
+				3599, 3600, 3610, 3610, 3600, 3611,
+				3600, 3601, 3611, 3611, 3601, 3612,
+				3601, 3602, 3612, 3612, 3602, 3613,
+				3602, 3603, 3613, 3613, 3603, 3614,
+				3603, 3604, 3614, 3614, 3604, 3615,
+				3604, 3605, 3615, 3615, 3605, 3616,
+				3605, 3606, 3616, 3616, 3606, 3617,
+				3606, 3607, 3617, 3617, 3607, 3618,
+				3608, 3609, 3619, 3619, 3609, 3620,
+				3609, 3610, 3620, 3620, 3610, 3621,
+				3610, 3611, 3621, 3621, 3611, 3622,
+				3611, 3612, 3622, 3622, 3612, 3623,
+				3612, 3613, 3623, 3623, 3613, 3624,
+				3613, 3614, 3624, 3624, 3614, 3625,
+				3614, 3615, 3625, 3625, 3615, 3626,
+				3615, 3616, 3626, 3626, 3616, 3627,
+				3616, 3617, 3627, 3627, 3617, 3628,
+				3617, 3618, 3628, 3628, 3618, 3629,
+				3630, 3631, 3641, 3641, 3631, 3642,
+				3631, 3632, 3642, 3642, 3632, 3643,
+				3632, 3633, 3643, 3643, 3633, 3644,
+				3633, 3634, 3644, 3644, 3634, 3645,
+				3634, 3635, 3645, 3645, 3635, 3646,
+				3635, 3636, 3646, 3646, 3636, 3647,
+				3636, 3637, 3647, 3647, 3637, 3648,
+				3637, 3638, 3648, 3648, 3638, 3649,
+				3638, 3639, 3649, 3649, 3639, 3650,
+				3639, 3640, 3650, 3650, 3640, 3651,
+				3641, 3642, 3652, 3652, 3642, 3653,
+				3642, 3643, 3653, 3653, 3643, 3654,
+				3643, 3644, 3654, 3654, 3644, 3655,
+				3644, 3645, 3655, 3655, 3645, 3656,
+				3645, 3646, 3656, 3656, 3646, 3657,
+				3646, 3647, 3657, 3657, 3647, 3658,
+				3647, 3648, 3658, 3658, 3648, 3659,
+				3648, 3649, 3659, 3659, 3649, 3660,
+				3649, 3650, 3660, 3660, 3650, 3661,
+				3650, 3651, 3661, 3661, 3651, 3662,
+				3652, 3653, 3663, 3663, 3653, 3664,
+				3653, 3654, 3664, 3664, 3654, 3665,
+				3654, 3655, 3665, 3665, 3655, 3666,
+				3655, 3656, 3666, 3666, 3656, 3667,
+				3656, 3657, 3667, 3667, 3657, 3668,
+				3657, 3658, 3668, 3668, 3658, 3669,
+				3658, 3659, 3669, 3669, 3659, 3670,
+				3659, 3660, 3670, 3670, 3660, 3671,
+				3660, 3661, 3671, 3671, 3661, 3672,
+				3661, 3662, 3672, 3672, 3662, 3673,
+				3663, 3664, 3674, 3674, 3664, 3675,
+				3664, 3665, 3675, 3675, 3665, 3676,
+				3665, 3666, 3676, 3676, 3666, 3677,
+				3666, 3667, 3677, 3677, 3667, 3678,
+				3667, 3668, 3678, 3678, 3668, 3679,
+				3668, 3669, 3679, 3679, 3669, 3680,
+				3669, 3670, 3680, 3680, 3670, 3681,
+				3670, 3671, 3681, 3681, 3671, 3682,
+				3671, 3672, 3682, 3682, 3672, 3683,
+				3672, 3673, 3683, 3683, 3673, 3684,
+				3674, 3675, 3685, 3685, 3675, 3686,
+				3675, 3676, 3686, 3686, 3676, 3687,
+				3676, 3677, 3687, 3687, 3677, 3688,
+				3677, 3678, 3688, 3688, 3678, 3689,
+				3678, 3679, 3689, 3689, 3679, 3690,
+				3679, 3680, 3690, 3690, 3680, 3691,
+				3680, 3681, 3691, 3691, 3681, 3692,
+				3681, 3682, 3692, 3692, 3682, 3693,
+				3682, 3683, 3693, 3693, 3683, 3694,
+				3683, 3684, 3694, 3694, 3684, 3695,
+				3685, 3686, 3696, 3696, 3686, 3697,
+				3686, 3687, 3697, 3697, 3687, 3698,
+				3687, 3688, 3698, 3698, 3688, 3699,
+				3688, 3689, 3699, 3699, 3689, 3700,
+				3689, 3690, 3700, 3700, 3690, 3701,
+				3690, 3691, 3701, 3701, 3691, 3702,
+				3691, 3692, 3702, 3702, 3692, 3703,
+				3692, 3693, 3703, 3703, 3693, 3704,
+				3693, 3694, 3704, 3704, 3694, 3705,
+				3694, 3695, 3705, 3705, 3695, 3706,
+				3696, 3697, 3707, 3707, 3697, 3708,
+				3697, 3698, 3708, 3708, 3698, 3709,
+				3698, 3699, 3709, 3709, 3699, 3710,
+				3699, 3700, 3710, 3710, 3700, 3711,
+				3700, 3701, 3711, 3711, 3701, 3712,
+				3701, 3702, 3712, 3712, 3702, 3713,
+				3702, 3703, 3713, 3713, 3703, 3714,
+				3703, 3704, 3714, 3714, 3704, 3715,
+				3704, 3705, 3715, 3715, 3705, 3716,
+				3705, 3706, 3716, 3716, 3706, 3717,
+				3707, 3708, 3718, 3718, 3708, 3719,
+				3708, 3709, 3719, 3719, 3709, 3720,
+				3709, 3710, 3720, 3720, 3710, 3721,
+				3710, 3711, 3721, 3721, 3711, 3722,
+				3711, 3712, 3722, 3722, 3712, 3723,
+				3712, 3713, 3723, 3723, 3713, 3724,
+				3713, 3714, 3724, 3724, 3714, 3725,
+				3714, 3715, 3725, 3725, 3715, 3726,
+				3715, 3716, 3726, 3726, 3716, 3727,
+				3716, 3717, 3727, 3727, 3717, 3728,
+				3718, 3719, 3729, 3729, 3719, 3730,
+				3719, 3720, 3730, 3730, 3720, 3731,
+				3720, 3721, 3731, 3731, 3721, 3732,
+				3721, 3722, 3732, 3732, 3722, 3733,
+				3722, 3723, 3733, 3733, 3723, 3734,
+				3723, 3724, 3734, 3734, 3724, 3735,
+				3724, 3725, 3735, 3735, 3725, 3736,
+				3725, 3726, 3736, 3736, 3726, 3737,
+				3726, 3727, 3737, 3737, 3727, 3738,
+				3727, 3728, 3738, 3738, 3728, 3739,
+				3729, 3730, 3740, 3740, 3730, 3741,
+				3730, 3731, 3741, 3741, 3731, 3742,
+				3731, 3732, 3742, 3742, 3732, 3743,
+				3732, 3733, 3743, 3743, 3733, 3744,
+				3733, 3734, 3744, 3744, 3734, 3745,
+				3734, 3735, 3745, 3745, 3735, 3746,
+				3735, 3736, 3746, 3746, 3736, 3747,
+				3736, 3737, 3747, 3747, 3737, 3748,
+				3737, 3738, 3748, 3748, 3738, 3749,
+				3738, 3739, 3749, 3749, 3739, 3750,
+				3751, 3752, 3762, 3762, 3752, 3763,
+				3752, 3753, 3763, 3763, 3753, 3764,
+				3753, 3754, 3764, 3764, 3754, 3765,
+				3754, 3755, 3765, 3765, 3755, 3766,
+				3755, 3756, 3766, 3766, 3756, 3767,
+				3756, 3757, 3767, 3767, 3757, 3768,
+				3757, 3758, 3768, 3768, 3758, 3769,
+				3758, 3759, 3769, 3769, 3759, 3770,
+				3759, 3760, 3770, 3770, 3760, 3771,
+				3760, 3761, 3771, 3771, 3761, 3772,
+				3762, 3763, 3773, 3773, 3763, 3774,
+				3763, 3764, 3774, 3774, 3764, 3775,
+				3764, 3765, 3775, 3775, 3765, 3776,
+				3765, 3766, 3776, 3776, 3766, 3777,
+				3766, 3767, 3777, 3777, 3767, 3778,
+				3767, 3768, 3778, 3778, 3768, 3779,
+				3768, 3769, 3779, 3779, 3769, 3780,
+				3769, 3770, 3780, 3780, 3770, 3781,
+				3770, 3771, 3781, 3781, 3771, 3782,
+				3771, 3772, 3782, 3782, 3772, 3783,
+				3773, 3774, 3784, 3784, 3774, 3785,
+				3774, 3775, 3785, 3785, 3775, 3786,
+				3775, 3776, 3786, 3786, 3776, 3787,
+				3776, 3777, 3787, 3787, 3777, 3788,
+				3777, 3778, 3788, 3788, 3778, 3789,
+				3778, 3779, 3789, 3789, 3779, 3790,
+				3779, 3780, 3790, 3790, 3780, 3791,
+				3780, 3781, 3791, 3791, 3781, 3792,
+				3781, 3782, 3792, 3792, 3782, 3793,
+				3782, 3783, 3793, 3793, 3783, 3794,
+				3784, 3785, 3795, 3795, 3785, 3796,
+				3785, 3786, 3796, 3796, 3786, 3797,
+				3786, 3787, 3797, 3797, 3787, 3798,
+				3787, 3788, 3798, 3798, 3788, 3799,
+				3788, 3789, 3799, 3799, 3789, 3800,
+				3789, 3790, 3800, 3800, 3790, 3801,
+				3790, 3791, 3801, 3801, 3791, 3802,
+				3791, 3792, 3802, 3802, 3792, 3803,
+				3792, 3793, 3803, 3803, 3793, 3804,
+				3793, 3794, 3804, 3804, 3794, 3805,
+				3795, 3796, 3806, 3806, 3796, 3807,
+				3796, 3797, 3807, 3807, 3797, 3808,
+				3797, 3798, 3808, 3808, 3798, 3809,
+				3798, 3799, 3809, 3809, 3799, 3810,
+				3799, 3800, 3810, 3810, 3800, 3811,
+				3800, 3801, 3811, 3811, 3801, 3812,
+				3801, 3802, 3812, 3812, 3802, 3813,
+				3802, 3803, 3813, 3813, 3803, 3814,
+				3803, 3804, 3814, 3814, 3804, 3815,
+				3804, 3805, 3815, 3815, 3805, 3816,
+				3806, 3807, 3817, 3817, 3807, 3818,
+				3807, 3808, 3818, 3818, 3808, 3819,
+				3808, 3809, 3819, 3819, 3809, 3820,
+				3809, 3810, 3820, 3820, 3810, 3821,
+				3810, 3811, 3821, 3821, 3811, 3822,
+				3811, 3812, 3822, 3822, 3812, 3823,
+				3812, 3813, 3823, 3823, 3813, 3824,
+				3813, 3814, 3824, 3824, 3814, 3825,
+				3814, 3815, 3825, 3825, 3815, 3826,
+				3815, 3816, 3826, 3826, 3816, 3827,
+				3817, 3818, 3828, 3828, 3818, 3829,
+				3818, 3819, 3829, 3829, 3819, 3830,
+				3819, 3820, 3830, 3830, 3820, 3831,
+				3820, 3821, 3831, 3831, 3821, 3832,
+				3821, 3822, 3832, 3832, 3822, 3833,
+				3822, 3823, 3833, 3833, 3823, 3834,
+				3823, 3824, 3834, 3834, 3824, 3835,
+				3824, 3825, 3835, 3835, 3825, 3836,
+				3825, 3826, 3836, 3836, 3826, 3837,
+				3826, 3827, 3837, 3837, 3827, 3838,
+				3828, 3829, 3839, 3839, 3829, 3840,
+				3829, 3830, 3840, 3840, 3830, 3841,
+				3830, 3831, 3841, 3841, 3831, 3842,
+				3831, 3832, 3842, 3842, 3832, 3843,
+				3832, 3833, 3843, 3843, 3833, 3844,
+				3833, 3834, 3844, 3844, 3834, 3845,
+				3834, 3835, 3845, 3845, 3835, 3846,
+				3835, 3836, 3846, 3846, 3836, 3847,
+				3836, 3837, 3847, 3847, 3837, 3848,
+				3837, 3838, 3848, 3848, 3838, 3849,
+				3839, 3840, 3850, 3850, 3840, 3851,
+				3840, 3841, 3851, 3851, 3841, 3852,
+				3841, 3842, 3852, 3852, 3842, 3853,
+				3842, 3843, 3853, 3853, 3843, 3854,
+				3843, 3844, 3854, 3854, 3844, 3855,
+				3844, 3845, 3855, 3855, 3845, 3856,
+				3845, 3846, 3856, 3856, 3846, 3857,
+				3846, 3847, 3857, 3857, 3847, 3858,
+				3847, 3848, 3858, 3858, 3848, 3859,
+				3848, 3849, 3859, 3859, 3849, 3860,
+				3850, 3851, 3861, 3861, 3851, 3862,
+				3851, 3852, 3862, 3862, 3852, 3863,
+				3852, 3853, 3863, 3863, 3853, 3864,
+				3853, 3854, 3864, 3864, 3854, 3865,
+				3854, 3855, 3865, 3865, 3855, 3866,
+				3855, 3856, 3866, 3866, 3856, 3867,
+				3856, 3857, 3867, 3867, 3857, 3868,
+				3857, 3858, 3868, 3868, 3858, 3869,
+				3858, 3859, 3869, 3869, 3859, 3870,
+				3859, 3860, 3870, 3870, 3860, 3871
+		};
+		
+		std::vector<glm::vec3> teapotTangents;
+		teapotTangents.resize(teapotVertices.size() / 3, glm::vec3(0.0f));
+		
+		std::vector<size_t> teapotTangentWeights;
+		teapotTangentWeights.resize(teapotTangents.size(), 0);
+		
+		for (size_t i = 0; i < teapotIndices.size(); i += 3) {
+			const auto index0 = teapotIndices[i + 0];
+			const auto index1 = teapotIndices[i + 1];
+			const auto index2 = teapotIndices[i + 2];
+			
+			const std::array<glm::vec3, 3> positions = {
+					glm::vec3(
+							teapotVertices[index0 * 3 + 0],
+							teapotVertices[index0 * 3 + 1],
+							teapotVertices[index0 * 3 + 2]
+					),
+					glm::vec3(
+							teapotVertices[index1 * 3 + 0],
+							teapotVertices[index1 * 3 + 1],
+							teapotVertices[index1 * 3 + 2]
+					),
+					glm::vec3(
+							teapotVertices[index2 * 3 + 0],
+							teapotVertices[index2 * 3 + 1],
+							teapotVertices[index2 * 3 + 2]
+					)
+			};
+			
+			const std::array<glm::vec2, 3> uvs = {
+					glm::vec2(
+							teapotUVCoords[index0 * 3 + 0],
+							teapotUVCoords[index0 * 3 + 1]
+					),
+					glm::vec2(
+							teapotUVCoords[index1 * 3 + 0],
+							teapotUVCoords[index1 * 3 + 1]
+					),
+					glm::vec2(
+							teapotUVCoords[index2 * 3 + 0],
+							teapotUVCoords[index2 * 3 + 1]
+					)
+			};
+			
+			const glm::vec3 tangent = generateTangent(positions, uvs);
+			
+			teapotTangents[index0] += tangent;
+			teapotTangents[index1] += tangent;
+			teapotTangents[index2] += tangent;
+			
+			teapotTangentWeights[index0]++;
+			teapotTangentWeights[index1]++;
+			teapotTangentWeights[index2]++;
+		}
+		
+		for (size_t i = 0; i < teapotTangents.size(); i++) {
+			if (teapotTangentWeights[i] <= 0) {
+				continue;
+			}
+			
+			teapotTangents[i] /= teapotTangentWeights[i];
+		}
+		
+		const auto& position = getPosition();
+		const auto scale = getScale();
+		
+		for (size_t i = 0; i < vertexCount; i++) {
+			teapotVertices[i * 3 + 0] = teapotVertices[i * 3 + 0] * scale + position.x;
+			teapotVertices[i * 3 + 1] = teapotVertices[i * 3 + 1] * scale + position.y;
+			teapotVertices[i * 3 + 2] = teapotVertices[i * 3 + 2] * scale + position.z;
+		}
+		
+		auto positionBuffer = buffer<float>(core, BufferType::VERTEX, teapotVertices.size());
+		positionBuffer.fill(teapotVertices);
+		
+		auto normalBuffer = buffer<float>(core, BufferType::VERTEX, teapotNormals.size());
+		normalBuffer.fill(teapotNormals);
+		
+		auto uvBuffer = buffer<float>(core, BufferType::VERTEX, teapotUVCoords.size());
+		uvBuffer.fill(teapotUVCoords);
+		
+		auto tangentBuffer = buffer<glm::vec3>(core, BufferType::VERTEX, teapotTangents.size());
+		tangentBuffer.fill(teapotTangents);
+		
+		auto indexBuffer = buffer<uint16_t>(core, BufferType::INDEX, teapotIndices.size());
+		indexBuffer.fill(teapotIndices);
+		
+		VertexData data ({
+			vkcv::vertexBufferBinding(positionBuffer.getHandle()),
+			vkcv::vertexBufferBinding(normalBuffer.getHandle()),
+			vkcv::vertexBufferBinding(uvBuffer.getHandle()),
+			vkcv::vertexBufferBinding(tangentBuffer.getHandle())
+		});
+		
+		data.setIndexBuffer(indexBuffer.getHandle());
+		data.setCount(indexBuffer.getCount());
+		
+		return data;
+	}
+	
+}
diff --git a/modules/geometry/src/vkcv/geometry/Volume.cpp b/modules/geometry/src/vkcv/geometry/Volume.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0b503eb907aea6cc0aaeb24affb485d4d101bf4d
--- /dev/null
+++ b/modules/geometry/src/vkcv/geometry/Volume.cpp
@@ -0,0 +1,13 @@
+
+#include "vkcv/geometry/Volume.hpp"
+
+namespace vkcv::geometry {
+	
+	Volume::Volume(const glm::vec3 &position)
+	: Geometry(position) {}
+	
+	bool Volume::contains(const glm::vec3 &point) {
+		return (this->distanceTo(point) <= 0.0f);
+	}
+
+}
diff --git a/modules/gui/CMakeLists.txt b/modules/gui/CMakeLists.txt
index 3b5202ccfe454f38745c53ac711cc05095ef88a1..34d80c934f0444e3afb02ae3a22ec3c4a49b8eff 100644
--- a/modules/gui/CMakeLists.txt
+++ b/modules/gui/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.16)
 project(vkcv_gui)
 
 # setting c++ standard for the module
-set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD 20)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
 set(vkcv_gui_source ${PROJECT_SOURCE_DIR}/src)
@@ -22,7 +22,7 @@ set(vkcv_gui_lib_path ${PROJECT_SOURCE_DIR}/${vkcv_gui_lib})
 include(config/ImGui.cmake)
 
 # adding source files to the module
-add_library(vkcv_gui STATIC ${vkcv_gui_sources} ${vkcv_imgui_sources})
+add_library(vkcv_gui ${vkcv_build_attribute} ${vkcv_gui_sources} ${vkcv_imgui_sources})
 
 # link the required libraries to the module
 target_link_libraries(vkcv_gui ${vkcv_gui_libraries} vkcv ${vkcv_libraries})
@@ -34,3 +34,11 @@ target_include_directories(vkcv_gui SYSTEM BEFORE PRIVATE ${vkcv_gui_includes} $
 target_include_directories(vkcv_gui BEFORE PUBLIC ${vkcv_gui_include} ${vkcv_imgui_includes})
 
 target_compile_definitions(vkcv_gui PUBLIC ${vkcv_gui_defines})
+
+if (vkcv_parent_scope)
+	list(APPEND vkcv_modules_includes ${vkcv_gui_include})
+	list(APPEND vkcv_modules_libraries vkcv_gui)
+	
+	set(vkcv_modules_includes ${vkcv_modules_includes} PARENT_SCOPE)
+	set(vkcv_modules_libraries ${vkcv_modules_libraries} PARENT_SCOPE)
+endif()
diff --git a/modules/gui/README.md b/modules/gui/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..823900829d5a3ca7b2f64dc1c05bda3e9dd55b87
--- /dev/null
+++ b/modules/gui/README.md
@@ -0,0 +1,16 @@
+# GUI
+
+A VkCV module to integrate GUI rendering to your application as additional pass
+
+## Build
+
+### Dependencies (required):
+
+| Name of dependency | Used as submodule |
+|----------------------------------------------------|---|
+| [ImGUI](https://github.com/ocornut/imgui/)   | ✅ |
+
+## Docs
+
+Here is a [link](https://userpages.uni-koblenz.de/~vkcv/doc/group__vkcv__gui.html) to this module.
+
diff --git a/modules/gui/config/ImGui.cmake b/modules/gui/config/ImGui.cmake
index 90cdafdeee355af9e63723632572799e135b04da..624ea7701e245ab8c20731d2b226896667c18f89 100644
--- a/modules/gui/config/ImGui.cmake
+++ b/modules/gui/config/ImGui.cmake
@@ -1,5 +1,7 @@
 
-if (EXISTS "${vkcv_gui_lib_path}/imgui")
+use_git_submodule("${vkcv_gui_lib_path}/imgui" imgui_status)
+
+if (${imgui_status})
 	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/backends/imgui_impl_glfw.cpp)
 	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/backends/imgui_impl_glfw.h)
 	list(APPEND vkcv_imgui_sources ${vkcv_gui_lib_path}/imgui/backends/imgui_impl_vulkan.cpp)
@@ -22,6 +24,4 @@ if (EXISTS "${vkcv_gui_lib_path}/imgui")
 	list(APPEND vkcv_gui_include ${vkcv_gui_lib})
 	
 	list(APPEND vkcv_gui_defines IMGUI_DISABLE_WIN32_FUNCTIONS=1)
-else()
-	message(WARNING "IMGUI is required..! Update the submodules!")
 endif ()
diff --git a/modules/gui/include/vkcv/gui/GUI.hpp b/modules/gui/include/vkcv/gui/GUI.hpp
index d1a9986c5f69bfd9d4392bd5ae50f0b1f8b60642..303fbb2c0920b400cb7637641a70e26665b4dabd 100644
--- a/modules/gui/include/vkcv/gui/GUI.hpp
+++ b/modules/gui/include/vkcv/gui/GUI.hpp
@@ -9,31 +9,75 @@
 
 namespace vkcv::gui {
 
+    /**
+     * @defgroup vkcv_gui GUI Module
+     * A module to manage ImGUI integration for VkCV applications.
+     * @{
+     */
+
+    /**
+     * Class to manage ImGui integration for VkCV.
+     */
 	class GUI final {
 	private:
-		Window& m_window;
+        /**
+         * Window handle of the currently used window to draw user interface on.
+         */
+		WindowHandle m_windowHandle;
+
+        /**
+         * Reference to the current Core instance.
+         */
 		Core& m_core;
-		
+
+        /**
+         * Reference to the current Context instance.
+         */
 		const Context& m_context;
-		
+
+        /**
+         * ImGui context for drawing GUI.
+         */
 		ImGuiContext* m_gui_context;
-		
+
+        /**
+         * Vulkan handle for the ImGui descriptor pool.
+         */
 		vk::DescriptorPool m_descriptor_pool;
+
+        /**
+         * Vulkan handle for the ImGui render pass.
+         */
 		vk::RenderPass m_render_pass;
-		
+
+        /**
+         * Event handle for pressing a mouse button.
+         */
 		event_handle<int,int,int> f_mouseButton;
+
+        /**
+         * Event handle for scrolling with the mouse or touchpad.
+         */
 		event_handle<double,double> f_mouseScroll;
+
+        /**
+         * Event handle for pressing a key.
+         */
 		event_handle<int,int,int,int> f_key;
+
+        /**
+         * Event handle for typing a character.
+         */
 		event_handle<unsigned int> f_char;
 		
 	public:
 		/**
-		 * Constructor of a new instance of ImGui management
+		 * Constructor of a new instance for ImGui management.
 		 *
-		 * @param core Valid #Core instance of the framework
-		 * @param window Valid #Window instance of the framework
+		 * @param[in,out] core Valid Core instance of the framework
+		 * @param[in,out] windowHandle Valid Window handle of the framework
 		 */
-		GUI(Core& core, Window& window);
+		GUI(Core& core, WindowHandle& windowHandle);
 		
 		GUI(const GUI& other) = delete;
 		GUI(GUI&& other) = delete;
@@ -42,12 +86,12 @@ namespace vkcv::gui {
 		GUI& operator=(GUI&& other) = delete;
 		
 		/**
-		 * Destructor of a #GUI instance
+		 * Destructor of a GUI instance.
 		 */
 		virtual ~GUI();
 		
 		/**
-		 * Sets up a new frame for ImGui to draw
+		 * Sets up a new frame for ImGui to draw.
 		 */
 		void beginGUI();
 		
@@ -59,4 +103,6 @@ namespace vkcv::gui {
 		
 	};
 
+    /** @} */
+
 }
diff --git a/modules/gui/lib/imgui b/modules/gui/lib/imgui
index d5828cd988db525f27128edeadb1a689cd2d7461..08752b372e5ebeb39adec59387590dac9d9e68f7 160000
--- a/modules/gui/lib/imgui
+++ b/modules/gui/lib/imgui
@@ -1 +1 @@
-Subproject commit d5828cd988db525f27128edeadb1a689cd2d7461
+Subproject commit 08752b372e5ebeb39adec59387590dac9d9e68f7
diff --git a/modules/gui/src/vkcv/gui/GUI.cpp b/modules/gui/src/vkcv/gui/GUI.cpp
index 38bb6894fb2b40c6ab10445f19431f87f7370afc..15a1b049aa5496bdcb9ecb81c00f82b66ad86d71 100644
--- a/modules/gui/src/vkcv/gui/GUI.cpp
+++ b/modules/gui/src/vkcv/gui/GUI.cpp
@@ -6,6 +6,9 @@
 
 namespace vkcv::gui {
 	
+	const static vk::ImageLayout initialImageLayout = vk::ImageLayout::eColorAttachmentOptimal;
+	const static vk::ImageLayout finalImageLayout   = vk::ImageLayout::ePresentSrcKHR;
+
 	static void checkVulkanResult(VkResult resultCode) {
 		if (resultCode == 0)
 			return;
@@ -15,31 +18,33 @@ namespace vkcv::gui {
 		vkcv_log(LogLevel::ERROR, "ImGui has a problem with Vulkan! (%s)", vk::to_string(result).c_str());
 	}
 	
-	GUI::GUI(Core& core, Window& window) :
-	m_window(window),
+	GUI::GUI(Core& core, WindowHandle& windowHandle) :
+	m_windowHandle(windowHandle),
 	m_core(core),
 	m_context(m_core.getContext()),
 	m_gui_context(nullptr) {
 		IMGUI_CHECKVERSION();
 		
 		m_gui_context = ImGui::CreateContext();
+
+		Window& window = m_core.getWindow(windowHandle);
+
+		ImGui_ImplGlfw_InitForVulkan(window.getWindow(), false);
 		
-		ImGui_ImplGlfw_InitForVulkan(m_window.getWindow(), false);
-		
-		f_mouseButton = m_window.e_mouseButton.add([&](int button, int action, int mods) {
-			ImGui_ImplGlfw_MouseButtonCallback(m_window.getWindow(), button, action, mods);
+		f_mouseButton = window.e_mouseButton.add([&](int button, int action, int mods) {
+			ImGui_ImplGlfw_MouseButtonCallback(window.getWindow(), button, action, mods);
 		});
 		
-		f_mouseScroll = m_window.e_mouseScroll.add([&](double xoffset, double yoffset) {
-			ImGui_ImplGlfw_ScrollCallback(m_window.getWindow(), xoffset, yoffset);
+		f_mouseScroll = window.e_mouseScroll.add([&](double xoffset, double yoffset) {
+			ImGui_ImplGlfw_ScrollCallback(window.getWindow(), xoffset, yoffset);
 		});
 		
-		f_key = m_window.e_key.add([&](int key, int scancode, int action, int mods) {
-			ImGui_ImplGlfw_KeyCallback(m_window.getWindow(), key, scancode, action, mods);
+		f_key = window.e_key.add([&](int key, int scancode, int action, int mods) {
+			ImGui_ImplGlfw_KeyCallback(window.getWindow(), key, scancode, action, mods);
 		});
 		
-		f_char = m_window.e_char.add([&](unsigned int c) {
-			ImGui_ImplGlfw_CharCallback(m_window.getWindow(), c);
+		f_char = window.e_char.add([&](unsigned int c) {
+			ImGui_ImplGlfw_CharCallback(window.getWindow(), c);
 		});
 		
 		vk::DescriptorPoolSize pool_sizes[] = {
@@ -66,7 +71,8 @@ namespace vkcv::gui {
 		m_descriptor_pool = m_context.getDevice().createDescriptorPool(descriptorPoolCreateInfo);
 		
 		const vk::PhysicalDevice& physicalDevice = m_context.getPhysicalDevice();
-		const Swapchain& swapchain = m_core.getSwapchain();
+		const SwapchainHandle& swapchainHandle = m_core.getWindow(m_windowHandle).getSwapchain();
+		const uint32_t swapchainImageCount = m_core.getSwapchainImageCount(swapchainHandle);
 		
 		const uint32_t graphicsQueueFamilyIndex = (
 				m_context.getQueueManager().getGraphicsQueues()[0].familyIndex
@@ -81,20 +87,20 @@ namespace vkcv::gui {
 		init_info.PipelineCache = 0;
 		init_info.DescriptorPool = static_cast<VkDescriptorPool>(m_descriptor_pool);
 		init_info.Allocator = nullptr;
-		init_info.MinImageCount = swapchain.getImageCount();
-		init_info.ImageCount = swapchain.getImageCount();
+		init_info.MinImageCount = swapchainImageCount;
+		init_info.ImageCount = swapchainImageCount;
 		init_info.CheckVkResultFn = checkVulkanResult;
 		
 		const vk::AttachmentDescription attachment (
 				vk::AttachmentDescriptionFlags(),
-				swapchain.getFormat(),
+				m_core.getSwapchainFormat(swapchainHandle),
 				vk::SampleCountFlagBits::e1,
 				vk::AttachmentLoadOp::eLoad,
 				vk::AttachmentStoreOp::eStore,
 				vk::AttachmentLoadOp::eDontCare,
 				vk::AttachmentStoreOp::eDontCare,
-				vk::ImageLayout::eUndefined,
-				vk::ImageLayout::ePresentSrcKHR
+				initialImageLayout,
+				finalImageLayout
 		);
 		
 		const vk::AttachmentReference attachmentReference (
@@ -139,31 +145,33 @@ namespace vkcv::gui {
 		
 		ImGui_ImplVulkan_Init(&init_info, static_cast<VkRenderPass>(m_render_pass));
 		
-		const SubmitInfo submitInfo { QueueType::Graphics, {}, {} };
+		auto stream = m_core.createCommandStream(QueueType::Graphics);
 		
-		m_core.recordAndSubmitCommandsImmediate(submitInfo, [](const vk::CommandBuffer& commandBuffer) {
+		m_core.recordCommandsToStream(stream, [](const vk::CommandBuffer& commandBuffer) {
 			ImGui_ImplVulkan_CreateFontsTexture(static_cast<VkCommandBuffer>(commandBuffer));
 		}, []() {
 			ImGui_ImplVulkan_DestroyFontUploadObjects();
 		});
 		
+		m_core.submitCommandStream(stream, false);
 		m_context.getDevice().waitIdle();
 	}
 	
 	GUI::~GUI() {
 		m_context.getDevice().waitIdle();
-		
+		Window& window = m_core.getWindow(m_windowHandle);
+
 		ImGui_ImplVulkan_Shutdown();
 		
 		m_context.getDevice().destroyRenderPass(m_render_pass);
 		m_context.getDevice().destroyDescriptorPool(m_descriptor_pool);
 		
 		ImGui_ImplGlfw_Shutdown();
-		
-		m_window.e_mouseButton.remove(f_mouseButton);
-		m_window.e_mouseScroll.remove(f_mouseScroll);
-		m_window.e_key.remove(f_key);
-		m_window.e_char.remove(f_char);
+
+		window.e_mouseButton.remove(f_mouseButton);
+		window.e_mouseScroll.remove(f_mouseScroll);
+		window.e_key.remove(f_key);
+		window.e_char.remove(f_char);
 		
 		if (m_gui_context) {
 			ImGui::DestroyContext(m_gui_context);
@@ -171,11 +179,11 @@ namespace vkcv::gui {
 	}
 	
 	void GUI::beginGUI() {
-		const Swapchain& swapchain = m_core.getSwapchain();
-		const auto extent = swapchain.getExtent();
+		const auto swapchainHandle = m_core.getWindow(m_windowHandle).getSwapchain();
+		const auto& extent = m_core.getSwapchainExtent(swapchainHandle);
 		
 		if ((extent.width > 0) && (extent.height > 0)) {
-			ImGui_ImplVulkan_SetMinImageCount(swapchain.getImageCount());
+			ImGui_ImplVulkan_SetMinImageCount(m_core.getSwapchainImageCount(swapchainHandle));
 		}
 		
 		ImGui_ImplVulkan_NewFrame();
@@ -194,9 +202,9 @@ namespace vkcv::gui {
 			return;
 		}
 		
-		const Swapchain& swapchain = m_core.getSwapchain();
-		const auto extent = swapchain.getExtent();
-		
+		const auto swapchainHandle = m_core.getWindow(m_windowHandle).getSwapchain();
+		const auto& extent = m_core.getSwapchainExtent(swapchainHandle);
+
 		const vk::ImageView swapchainImageView = m_core.getSwapchainImageView();
 
 		const vk::FramebufferCreateInfo framebufferCreateInfo (
@@ -210,11 +218,13 @@ namespace vkcv::gui {
 		);
 		
 		const vk::Framebuffer framebuffer = m_context.getDevice().createFramebuffer(framebufferCreateInfo);
+		auto stream = m_core.createCommandStream(QueueType::Graphics);
 		
-		SubmitInfo submitInfo;
-		submitInfo.queueType = QueueType::Graphics;
-		
-		m_core.recordAndSubmitCommandsImmediate(submitInfo, [&](const vk::CommandBuffer& commandBuffer) {
+		m_core.recordCommandsToStream(stream, [&](const vk::CommandBuffer& commandBuffer) {
+
+			assert(initialImageLayout == vk::ImageLayout::eColorAttachmentOptimal);
+			m_core.prepareImageForAttachmentManually(commandBuffer, vkcv::ImageHandle::createSwapchainImageHandle());
+
 			const vk::Rect2D renderArea (
 					vk::Offset2D(0, 0),
 					extent
@@ -227,15 +237,22 @@ namespace vkcv::gui {
 					0,
 					nullptr
 			);
-			
+
 			commandBuffer.beginRenderPass(beginInfo, vk::SubpassContents::eInline);
 			
 			ImGui_ImplVulkan_RenderDrawData(drawData, static_cast<VkCommandBuffer>(commandBuffer));
 			
 			commandBuffer.endRenderPass();
+
+			// executing the renderpass changed the image layout without going through the image manager
+			// therefore the layout must be updated manually
+			m_core.updateImageLayoutManual(vkcv::ImageHandle::createSwapchainImageHandle(), finalImageLayout);
+
 		}, [&]() {
 			m_context.getDevice().destroyFramebuffer(framebuffer);
 		});
+		
+		m_core.submitCommandStream(stream, false);
 	}
 	
 }
diff --git a/modules/material/CMakeLists.txt b/modules/material/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ee3b36e8f411f3d4d6d86a19e498f2faa0513f86
--- /dev/null
+++ b/modules/material/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.16)
+project(vkcv_material)
+
+# setting c++ standard for the module
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(vkcv_material_source ${PROJECT_SOURCE_DIR}/src)
+set(vkcv_material_include ${PROJECT_SOURCE_DIR}/include)
+
+# Add source and header files to the module
+set(vkcv_material_sources
+		${vkcv_material_include}/vkcv/material/Material.hpp
+		${vkcv_material_source}/vkcv/material/Material.cpp
+)
+
+# adding source files to the module
+add_library(vkcv_material ${vkcv_build_attribute} ${vkcv_material_sources})
+
+# link the required libraries to the module
+target_link_libraries(vkcv_material vkcv ${vkcv_libraries})
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(vkcv_material SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes})
+
+# add the own include directory for public headers
+target_include_directories(vkcv_material BEFORE PUBLIC ${vkcv_material_include})
+
+if (vkcv_parent_scope)
+	list(APPEND vkcv_modules_includes ${vkcv_material_include})
+	list(APPEND vkcv_modules_libraries vkcv_material)
+	
+	set(vkcv_modules_includes ${vkcv_modules_includes} PARENT_SCOPE)
+	set(vkcv_modules_libraries ${vkcv_modules_libraries} PARENT_SCOPE)
+endif()
diff --git a/modules/material/README.md b/modules/material/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..a3cff07cc25900f4d06ee9059dcd7175d14f3e3d
--- /dev/null
+++ b/modules/material/README.md
@@ -0,0 +1,7 @@
+# Material
+
+A VkCV module to abstract typical kinds of materials for rendering
+
+## Docs
+
+Here is a [link](https://userpages.uni-koblenz.de/~vkcv/doc/group__vkcv__asset.html) to this module.
diff --git a/modules/material/include/vkcv/material/Material.hpp b/modules/material/include/vkcv/material/Material.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..345d22912b368ba5bb84e96d6ce8b1dbe49e8337
--- /dev/null
+++ b/modules/material/include/vkcv/material/Material.hpp
@@ -0,0 +1,212 @@
+#pragma once
+
+#include <vector>
+
+#include <vkcv/Core.hpp>
+#include <vkcv/Downsampler.hpp>
+#include <vkcv/Handles.hpp>
+
+namespace vkcv::material {
+
+    /**
+     * @defgroup vkcv_material Material Module
+     * A module to manage standardized materials for rendering.
+     * @{
+     */
+
+    /**
+     * Enum to handle standardized material types.
+     */
+	enum class MaterialType {
+        /**
+         * The material can be used for physically based rendering.
+         */
+		PBR_MATERIAL = 1,
+
+        /**
+         * The type is unknown.
+         */
+		UNKNOWN = 0
+	};
+
+    /**
+     * Class to manage required handles for materials using
+     * a wide range of textures with separate samplers and factors.
+     */
+	class Material {
+	private:
+
+        /**
+         * A nested structure for textures used by a material.
+         */
+		struct Texture {
+            /**
+             * The image handle of a texture.
+             */
+			ImageHandle m_Image;
+
+            /**
+             * The sampler handle of a texture.
+             */
+			SamplerHandle m_Sampler;
+
+            /**
+             * The list of custom factors for a given texture.
+             */
+			std::vector<float> m_Factors;
+		};
+
+        /**
+         * The type of a material.
+         */
+		MaterialType m_Type;
+
+        /**
+         * The descriptor set handle of a material.
+         */
+		DescriptorSetHandle m_DescriptorSet;
+
+        /**
+         * The descriptor set layout used by a material.
+         */
+		DescriptorSetLayoutHandle m_DescriptorSetLayout;
+
+        /**
+         * The list of textures used by a material.
+         */
+		std::vector<Texture> m_Textures;
+		
+	public:
+        /**
+         * Default constructor to create an invalid material instance.
+         */
+		Material();
+		
+		/**
+		 * Destructor to release handles and resources of a material instance.
+		 */
+		~Material() = default;
+		
+		/**
+		 * Copy-constructor to copy a given material instance.
+		 * @param other Other material
+		 */
+		Material(const Material& other) = default;
+		
+		/**
+		 * Move-constructor to move a given material instance.
+		 * @param other Other material
+		 */
+		Material(Material&& other) = default;
+		
+		/**
+		 * Copy-operator to copy a given material instance.
+		 * @param other Other material
+		 * @return Reference to the material
+		 */
+		Material& operator=(const Material& other) = default;
+		
+		/**
+		 * Move-operator to move a given material instance.
+		 * @param other Other material
+		 * @return Reference to the material
+		 */
+		Material& operator=(Material&& other) = default;
+
+        /**
+         * Returns the type of a material as MaterialType.
+         * @return Type of material
+         */
+		[[nodiscard]]
+		MaterialType getType() const;
+
+        /**
+         * Returns the descriptor set handle of the material.
+         * @return Descriptor set handle
+         */
+		[[nodiscard]]
+		const DescriptorSetHandle& getDescriptorSet() const;
+
+        /**
+         * Returns the descriptor set layout handle used by the material.
+         * @return Descriptor set layout handle
+         */
+		[[nodiscard]]
+		const DescriptorSetLayoutHandle& getDescriptorSetLayout() const;
+
+        /**
+         * Checks if the material is valid and returns the status
+         * as boolean value.
+         * @return true if the material is valid, otherwise false
+         */
+		explicit operator bool() const;
+
+        /**
+         * Checks if the material is invalid and returns the status
+         * as boolean value.
+         * @return true if the material is invalid, otherwise false
+         */
+		bool operator!() const;
+		
+		/**
+		 * @brief Records mip chain generation to command stream of the whole material.
+		 *
+		 * @param[out] cmdStream Command stream that the commands are recorded into
+		 * @param[in,out] downsampler Downsampler to generate mip levels with
+		 */
+		void recordMipChainGeneration(const vkcv::CommandStreamHandle& cmdStream,
+									  Downsampler &downsampler);
+
+        /**
+         * Returns the descriptor bindings required by a given material
+         * type to create the descriptor set layout.
+         * @param[in] type Type of material
+         * @return Descriptor bindings of a material type
+         */
+		static const DescriptorBindings& getDescriptorBindings(MaterialType type);
+
+        /**
+         * Creates a new valid material which supports physically based
+         * rendering.
+         * @param[in,out] core Reference to Core instance
+         * @param[in] colorImg Base color image handle
+         * @param[in] colorSmp Base color sampler handle
+         * @param[in] normalImg Normal map image handle
+         * @param[in] normalSmp Normal map sampler handle
+         * @param[in] metRoughImg Metallic and roughness image handle
+         * @param[in] metRoughSmp Metallic and roughness sampler handle
+         * @param[in] occlusionImg Occlusion map image handle
+         * @param[in] occlusionSmp Occlusion map sampler handle
+         * @param[in] emissiveImg Emissive image handle
+         * @param[in] emissiveSmp Emissive sampler handle
+         * @param[in] baseColorFactor 4D vector of base color factors
+         * @param[in] metallicFactor Metallic factor
+         * @param[in] roughnessFactor Roughness factor
+         * @param[in] normalScale Scale of normal map
+         * @param[in] occlusionStrength Strength of occlusion
+         * @param[in] emissiveFactor 3D vector of emmisive factors
+         * @return New material instance
+         */
+		static Material createPBR(Core &core,
+								  const ImageHandle &colorImg,
+								  const SamplerHandle &colorSmp,
+								  const ImageHandle &normalImg,
+								  const SamplerHandle &normalSmp,
+								  const ImageHandle &metRoughImg,
+								  const SamplerHandle &metRoughSmp,
+								  const ImageHandle &occlusionImg,
+								  const SamplerHandle &occlusionSmp,
+								  const ImageHandle &emissiveImg,
+								  const SamplerHandle &emissiveSmp,
+								  const float baseColorFactor [4],
+								  float metallicFactor,
+								  float roughnessFactor,
+								  float normalScale,
+								  float occlusionStrength,
+								  const float emissiveFactor [3]);
+	
+	};
+
+    /** @} */
+	
+}
diff --git a/modules/material/src/vkcv/material/Material.cpp b/modules/material/src/vkcv/material/Material.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8187b5de9205ba18ca32cd23dba9d5b457696712
--- /dev/null
+++ b/modules/material/src/vkcv/material/Material.cpp
@@ -0,0 +1,197 @@
+
+#include "vkcv/material/Material.hpp"
+
+#include <vkcv/Image.hpp>
+#include <vkcv/Sampler.hpp>
+
+namespace vkcv::material {
+	
+	Material::Material() {
+		m_Type = MaterialType::UNKNOWN;
+	}
+
+	MaterialType Material::getType() const {
+		return m_Type;
+	}
+	
+	const DescriptorSetHandle & Material::getDescriptorSet() const {
+		return m_DescriptorSet;
+	}
+
+	const DescriptorSetLayoutHandle & Material::getDescriptorSetLayout() const {
+        return m_DescriptorSetLayout;
+	}
+	
+	Material::operator bool() const {
+		return (m_Type != MaterialType::UNKNOWN);
+	}
+	
+	bool Material::operator!() const {
+		return (m_Type == MaterialType::UNKNOWN);
+	}
+	
+	void Material::recordMipChainGeneration(const vkcv::CommandStreamHandle& cmdStream,
+											Downsampler &downsampler) {
+		for (auto& texture : m_Textures) {
+			downsampler.recordDownsampling(cmdStream, texture.m_Image);
+		}
+	}
+	
+	const DescriptorBindings& Material::getDescriptorBindings(MaterialType type)
+	{
+		static DescriptorBindings pbr_bindings = {};
+		static DescriptorBindings default_bindings = {};
+		
+		switch (type) {
+			case MaterialType::PBR_MATERIAL:
+				if (pbr_bindings.empty())
+				{
+					pbr_bindings.insert(std::make_pair(0, DescriptorBinding {
+						0, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT, false
+					}));
+					pbr_bindings.insert(std::make_pair(1, DescriptorBinding {
+						1, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT, false
+					}));
+					pbr_bindings.insert(std::make_pair(2, DescriptorBinding {
+						2, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT, false
+					}));
+					pbr_bindings.insert(std::make_pair(3, DescriptorBinding {
+						3, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT, false
+					}));
+					pbr_bindings.insert(std::make_pair(4, DescriptorBinding {
+						4, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT, false
+					}));
+					pbr_bindings.insert(std::make_pair(5, DescriptorBinding {
+						5, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT, false
+					}));
+					pbr_bindings.insert(std::make_pair(6, DescriptorBinding {
+						6, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT, false
+					}));
+					pbr_bindings.insert(std::make_pair(7, DescriptorBinding {
+						7, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT, false
+					}));
+					pbr_bindings.insert(std::make_pair(8, DescriptorBinding {
+						8, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT, false
+					}));
+					pbr_bindings.insert(std::make_pair(9, DescriptorBinding {
+						9, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT, false
+					}));
+				}
+				
+				return pbr_bindings;
+			default:
+				return default_bindings;
+		}
+	}
+	
+	static void fillImage(Image& image, float data [4]) {
+		std::vector<float> vec (image.getWidth() * image.getHeight() * image.getDepth() * 4);
+		
+		for (size_t i = 0; i < vec.size(); i++) {
+			vec[i] = data[i % 4];
+		}
+		
+		image.fill(data);
+	}
+	
+	Material Material::createPBR(Core &core,
+								 const ImageHandle &colorImg, const SamplerHandle &colorSmp,
+								 const ImageHandle &normalImg, const SamplerHandle &normalSmp,
+								 const ImageHandle &metRoughImg, const SamplerHandle &metRoughSmp,
+								 const ImageHandle &occlusionImg, const SamplerHandle &occlusionSmp,
+								 const ImageHandle &emissiveImg, const SamplerHandle &emissiveSmp,
+								 const float baseColorFactor [4],
+								 float metallicFactor,
+								 float roughnessFactor,
+								 float normalScale,
+								 float occlusionStrength,
+								 const float emissiveFactor [3]) {
+		ImageHandle images [5] = {
+				colorImg, normalImg, metRoughImg, occlusionImg, emissiveImg
+		};
+		
+		SamplerHandle samplers [5] = {
+				colorSmp, normalSmp, metRoughSmp, occlusionSmp, emissiveSmp
+		};
+		
+		if (!colorImg) {
+			vkcv::Image defaultColor = image(core, vk::Format::eR8G8B8A8Srgb, 2, 2);
+			float colorData [4] = { 228, 51, 255, 1 };
+			fillImage(defaultColor, colorData);
+			images[0] = defaultColor.getHandle();
+		}
+		
+		if (!normalImg) {
+			vkcv::Image defaultNormal = image(core, vk::Format::eR8G8B8A8Unorm, 2, 2);
+			float normalData [4] = { 0, 0, 1, 0 };
+			fillImage(defaultNormal, normalData);
+			images[1] = defaultNormal.getHandle();
+		}
+		
+		if (!metRoughImg) {
+			vkcv::Image defaultRough = image(core, vk::Format::eR8G8B8A8Unorm, 2, 2);
+			float roughData [4] = { 228, 51, 255, 1 };
+			fillImage(defaultRough, roughData);
+			images[2] = defaultRough.getHandle();
+		}
+		
+		if (!occlusionImg) {
+			vkcv::Image defaultOcclusion = image(core, vk::Format::eR8G8B8A8Unorm, 2, 2);
+			float occlusionData [4] = { 228, 51, 255, 1 };
+			fillImage(defaultOcclusion, occlusionData);
+			images[3] = defaultOcclusion.getHandle();
+		}
+		
+		if (!emissiveImg) {
+			vkcv::Image defaultEmissive = image(core, vk::Format::eR8G8B8A8Srgb, 2, 2);
+			float emissiveData [4] = { 0, 0, 0, 1 };
+			fillImage(defaultEmissive, emissiveData);
+			images[4] = defaultEmissive.getHandle();
+		}
+		
+		if (!colorSmp) {
+			samplers[0] = samplerLinear(core);
+		}
+		
+		if (!normalSmp) {
+			samplers[1] = samplerLinear(core);
+		}
+		
+		if (!metRoughSmp) {
+			samplers[2] = samplerLinear(core);
+		}
+		
+		if (!occlusionSmp) {
+			samplers[3] = samplerLinear(core);
+		}
+		
+		if (!emissiveSmp) {
+			samplers[4] = samplerLinear(core);
+		}
+		
+		Material material;
+		material.m_Type = MaterialType::PBR_MATERIAL;
+		
+		const auto& bindings = getDescriptorBindings(material.m_Type);
+		material.m_DescriptorSetLayout = core.createDescriptorSetLayout(bindings);
+		material.m_DescriptorSet = core.createDescriptorSet(material.m_DescriptorSetLayout);;
+		
+		material.m_Textures.reserve(bindings.size());
+		material.m_Textures.push_back({ images[0], samplers[0], std::vector<float>(baseColorFactor, baseColorFactor+4) });
+		material.m_Textures.push_back({ images[1], samplers[1], { normalScale } });
+		material.m_Textures.push_back({ images[2], samplers[2], { metallicFactor, roughnessFactor } });
+		material.m_Textures.push_back({ images[3], samplers[3], { occlusionStrength } });
+		material.m_Textures.push_back({ images[4], samplers[4], std::vector<float>(emissiveFactor, emissiveFactor+3) });
+		
+		vkcv::DescriptorWrites setWrites;
+		
+		for (size_t i = 0; i < material.m_Textures.size(); i++) {
+			setWrites.writeSampledImage(i * 2, material.m_Textures[i].m_Image);
+			setWrites.writeSampler(i * 2 + 1, material.m_Textures[i].m_Sampler);
+		}
+		
+		core.writeDescriptorSet(material.m_DescriptorSet, setWrites);
+		return material;
+	}
+
+}
diff --git a/modules/meshlet/CMakeLists.txt b/modules/meshlet/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a3b232e56ec86f65e1114c6ee570f512e7d3b13b
--- /dev/null
+++ b/modules/meshlet/CMakeLists.txt
@@ -0,0 +1,44 @@
+cmake_minimum_required(VERSION 3.16)
+project(vkcv_meshlet)
+
+# setting c++ standard for the module
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(vkcv_meshlet_source ${PROJECT_SOURCE_DIR}/src)
+set(vkcv_meshlet_include ${PROJECT_SOURCE_DIR}/include)
+
+# Add source and header files to the module
+set(vkcv_meshlet_sources
+		${vkcv_meshlet_include}/vkcv/meshlet/Meshlet.hpp
+		${vkcv_meshlet_source}/vkcv/meshlet/Meshlet.cpp
+
+		${vkcv_meshlet_include}/vkcv/meshlet/Tipsify.hpp
+		${vkcv_meshlet_source}/vkcv/meshlet/Tipsify.cpp
+
+		${vkcv_meshlet_include}/vkcv/meshlet/Forsyth.hpp
+		${vkcv_meshlet_source}/vkcv/meshlet/Forsyth.cpp)
+
+# adding source files to the module
+add_library(vkcv_meshlet ${vkcv_build_attribute} ${vkcv_meshlet_sources})
+
+
+# link the required libraries to the module
+target_link_libraries(vkcv_meshlet vkcv ${vkcv_libraries})
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(vkcv_meshlet SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include})
+
+# add the own include directory for public headers
+target_include_directories(vkcv_meshlet BEFORE PUBLIC ${vkcv_meshlet_include})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(vkcv_meshlet vkcv vkcv_asset_loader vkcv_camera)
+
+if (vkcv_parent_scope)
+	list(APPEND vkcv_modules_includes ${vkcv_meshlet_include})
+	list(APPEND vkcv_modules_libraries vkcv_meshlet)
+	
+	set(vkcv_modules_includes ${vkcv_modules_includes} PARENT_SCOPE)
+	set(vkcv_modules_libraries ${vkcv_modules_libraries} PARENT_SCOPE)
+endif()
diff --git a/modules/meshlet/README.md b/modules/meshlet/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..4ef209723aa95b8d2e50ff6804f19819f3e11d54
--- /dev/null
+++ b/modules/meshlet/README.md
@@ -0,0 +1,7 @@
+# Meshlet
+
+A VkCV module to divide vertex data of a mesh into meshlets
+
+## Docs
+
+Here is a [link](https://userpages.uni-koblenz.de/~vkcv/doc/group__vkcv__meshlet.html) to this module.
diff --git a/modules/meshlet/include/vkcv/meshlet/Forsyth.hpp b/modules/meshlet/include/vkcv/meshlet/Forsyth.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..90625bd470ac4ecbc27f2e6f95ab9e18734557f8
--- /dev/null
+++ b/modules/meshlet/include/vkcv/meshlet/Forsyth.hpp
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "Meshlet.hpp"
+
+namespace vkcv::meshlet
+{
+
+    /**
+     * @addtogroup vkcv_meshlet
+     * @{
+     */
+
+    /**
+    * Reorders the index buffer, simulating a LRU cache, so that vertices are grouped together in close triangle patches
+    * @param idxBuf current IndexBuffer
+    * @param vertexCount of the mesh
+    * @return new reordered index buffer to replace the input index buffer
+    * References:
+    * https://tomforsyth1000.github.io/papers/fast_vert_cache_opt.html
+    * https://www.martin.st/thesis/efficient_triangle_reordering.pdf
+    * https://github.com/vivkin/forsyth/blob/master/forsyth.h
+    */
+    VertexCacheReorderResult forsythReorder(const std::vector<uint32_t> &idxBuf, const size_t vertexCount);
+
+    /** @} */
+
+}
diff --git a/modules/meshlet/include/vkcv/meshlet/Meshlet.hpp b/modules/meshlet/include/vkcv/meshlet/Meshlet.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ee32878726123a8d51c3816c9df2644da4ae9b6c
--- /dev/null
+++ b/modules/meshlet/include/vkcv/meshlet/Meshlet.hpp
@@ -0,0 +1,67 @@
+#pragma once
+
+#include <vector>
+#include <map>
+#include <glm/glm.hpp>
+#include <vkcv/asset/asset_loader.hpp>
+
+namespace vkcv::meshlet {
+
+    /**
+     * @defgroup vkcv_meshlet Meshlet Module
+     * A module to convert meshes into meshlets for rendering via mesh shaders.
+     * @{
+     */
+
+    struct Vertex {
+        glm::vec3   position;
+        float       padding0;
+        glm::vec3   normal;
+        float       padding1;
+    };
+
+    struct Meshlet {
+        uint32_t    vertexOffset;
+        uint32_t    vertexCount;
+        uint32_t    indexOffset;
+        uint32_t    indexCount;
+        glm::vec3   meanPosition;
+        float       boundingSphereRadius;
+    };
+
+    struct VertexCacheReorderResult {
+        /**
+         * @param indexBuffer new indexBuffer
+         * @param skippedIndices indices that have a spacial break
+         */
+        VertexCacheReorderResult(const std::vector<uint32_t> indexBuffer, const std::vector<uint32_t> skippedIndices)
+                :indexBuffer(indexBuffer), skippedIndices(skippedIndices) {}
+
+        std::vector<uint32_t> indexBuffer;
+        std::vector<uint32_t>  skippedIndices;
+    };
+
+    struct MeshShaderModelData {
+        std::vector<Vertex>     vertices;
+        std::vector<uint32_t>   localIndices;
+        std::vector<Meshlet>    meshlets;
+    };
+
+    std::vector<Vertex> convertToVertices(
+            const std::vector<uint8_t>&         vertexData,
+            const uint64_t                      vertexCount,
+            const vkcv::asset::VertexAttribute& positionAttribute,
+            const vkcv::asset::VertexAttribute& normalAttribute);
+
+    MeshShaderModelData createMeshShaderModelData(
+            const std::vector<Vertex>&      inVertices,
+            const std::vector<uint32_t>&    inIndices,
+            const std::vector<uint32_t>& deadEndIndices = {});
+
+    std::vector<uint32_t> assetLoaderIndicesTo32BitIndices(
+            const std::vector<uint8_t>& indexData,
+            vkcv::asset::IndexType indexType);
+
+    /** @} */
+
+}
\ No newline at end of file
diff --git a/modules/meshlet/include/vkcv/meshlet/Tipsify.hpp b/modules/meshlet/include/vkcv/meshlet/Tipsify.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..3c808f9565ee1e73b14356dc0f4c8f4cd7c6dd9c
--- /dev/null
+++ b/modules/meshlet/include/vkcv/meshlet/Tipsify.hpp
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "Meshlet.hpp"
+#include <algorithm>
+#include <iostream>
+
+namespace vkcv::meshlet {
+
+    /**
+     * @addtogroup vkcv_meshlet
+     * @{
+     */
+
+    /**
+     * reorders the IndexBuffer, so all usages of vertices to triangle are as close as possible
+     * @param indexBuffer32Bit current IndexBuffer
+     * @param vertexCount of the mesh
+     * @param cacheSize of the priority cache <br>
+     * Recommended: 20. Keep the value between 5 and 50 <br>
+     * low:         more random and patchy<br>
+     * high:        closer vertices have higher chance -> leads to sinuous lines
+     * @return new IndexBuffer that replaces the input IndexBuffer, and the indices that are skipped
+     *
+     * https://gfx.cs.princeton.edu/pubs/Sander_2007_%3ETR/tipsy.pdf
+     * https://www.martin.st/thesis/efficient_triangle_reordering.pdf
+     */
+    VertexCacheReorderResult tipsifyMesh(const std::vector<uint32_t> &indexBuffer32Bit,
+                                         const int vertexCount, const unsigned int cacheSize = 20);
+
+    /** @} */
+
+}
\ No newline at end of file
diff --git a/modules/meshlet/src/vkcv/meshlet/Forsyth.cpp b/modules/meshlet/src/vkcv/meshlet/Forsyth.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fd0f160d65b8db81102f9eb6a9d60cf735999d44
--- /dev/null
+++ b/modules/meshlet/src/vkcv/meshlet/Forsyth.cpp
@@ -0,0 +1,317 @@
+#include "vkcv/meshlet/Forsyth.hpp"
+#include <vkcv/Logger.hpp>
+#include <array>
+#include <cmath>
+
+namespace vkcv::meshlet
+{
+    /*
+     * CACHE AND VALENCE
+     * SIZE AND SCORE CONSTANTS
+     * CHANGE AS NEEDED
+     */
+
+    // set these to adjust performance and result quality
+    const size_t VERTEX_CACHE_SIZE = 8;
+    const size_t CACHE_FUNCTION_LENGTH = 32;
+
+    // score function constants
+    const float CACHE_DECAY_POWER = 1.5f;
+    const float LAST_TRI_SCORE = 0.75f;
+
+    const float VALENCE_BOOST_SCALE = 2.0f;
+    const float VALENCE_BOOST_POWER = 0.5f;
+
+    // sizes for precalculated tables
+    // make sure that cache score is always >= vertex_cache_size
+    const size_t CACHE_SCORE_TABLE_SIZE = 32;
+    const size_t VALENCE_SCORE_TABLE_SIZE = 32;
+
+    // precalculated tables
+    std::array<float, CACHE_SCORE_TABLE_SIZE> cachePositionScore = {};
+    std::array<float, VALENCE_SCORE_TABLE_SIZE> valenceScore = {};
+
+    // function to populate the cache position and valence score tables
+    void initScoreTables()
+    {
+        for(size_t i = 0; i < CACHE_SCORE_TABLE_SIZE; i++)
+        {
+            float score = 0.0f;
+            if (i < 3)
+            {
+                score = LAST_TRI_SCORE;
+            }
+            else
+            {
+                const float scaler = 1.0f / static_cast<float>(CACHE_FUNCTION_LENGTH - 3);
+                score = 1.0f - (i - 3) * scaler;
+                score = std::pow(score, CACHE_DECAY_POWER);
+            }
+            cachePositionScore[i] = score;
+        }
+
+        for(size_t i = 0; i < VALENCE_SCORE_TABLE_SIZE; i++)
+        {
+            const float valenceBoost = std::pow(i, -VALENCE_BOOST_POWER);
+            const float score = VALENCE_BOOST_SCALE * valenceBoost;
+
+            valenceScore[i] = score;
+        }
+    }
+
+    /**
+     * Return the vertex' score, depending on its current active triangle count and cache position
+     * Add a valence boost to score, if active triangles are below VALENCE_SCORE_TABLE_SIZE
+     * @param numActiveTris the active triangles on this vertex
+     * @param cachePos the vertex' position in the cache
+     * @return vertex' score
+     */
+    float findVertexScore(uint32_t numActiveTris, int32_t cachePos)
+    {
+        if(numActiveTris == 0)
+            return 0.0f;
+
+        float score = 0.0f;
+
+        if (cachePos >= 0)
+            score = cachePositionScore[cachePos];
+
+        if (numActiveTris < VALENCE_SCORE_TABLE_SIZE)
+            score += valenceScore[numActiveTris];
+
+        return score;
+    }
+
+    VertexCacheReorderResult forsythReorder(const std::vector<uint32_t> &idxBuf, const size_t vertexCount)
+    {
+        std::vector<uint32_t> skippedIndices;
+
+        initScoreTables();
+
+        // get the total triangle count from the index buffer
+        const size_t triangleCount = idxBuf.size() / 3;
+
+        // per-vertex active triangle count
+        std::vector<uint8_t> numActiveTris(vertexCount, 0);
+        // iterate over indices, count total occurrences of each vertex
+        for(const auto index : idxBuf)
+        {
+            if(numActiveTris[index] == UINT8_MAX)
+            {
+                vkcv_log(LogLevel::ERROR, "Unsupported mesh.");
+                vkcv_log(LogLevel::ERROR, "Vertex shared by too many triangles.");
+                return VertexCacheReorderResult({}, {});
+            }
+
+            numActiveTris[index]++;
+        }
+
+
+        // allocate remaining vectors
+        /**
+         * offsets: contains the vertices' offset into the triangleIndices vector
+         * Offset itself is the sum of triangles required by the previous vertices
+         *
+         * lastScore: the vertices' most recent calculated score
+         *
+         * cacheTag: the vertices' most recent cache score
+         *
+         * triangleAdded: boolean flags to denote whether a triangle has been processed or not
+         *
+         * triangleScore: total score of the three vertices making up the triangle
+         *
+         * triangleIndices: indices for the triangles
+         */
+        std::vector<uint32_t> offsets(vertexCount, 0);
+        std::vector<float> lastScore(vertexCount, 0.0f);
+        std::vector<int8_t> cacheTag(vertexCount, -1);
+
+        std::vector<bool> triangleAdded(triangleCount, false);
+        std::vector<float> triangleScore(triangleCount, 0.0f);
+
+        std::vector<int32_t> triangleIndices(idxBuf.size(), 0);
+
+
+        // sum the number of active triangles for all previous vertices
+        // null the number of active triangles afterwards for recalculation in second loop
+        uint32_t sum = 0;
+        for(size_t i = 0; i < vertexCount; i++)
+        {
+            offsets[i] = sum;
+            sum += numActiveTris[i];
+            numActiveTris[i] = 0;
+        }
+        // create the triangle indices, using the newly calculated offsets, and increment numActiveTris
+        // every vertex should be referenced by a triangle index now
+        for(size_t i = 0; i < triangleCount; i++)
+        {
+            for(size_t j = 0; j < 3; j++)
+            {
+                uint32_t v = idxBuf[3 * i + j];
+                triangleIndices[offsets[v] + numActiveTris[v]] = static_cast<int32_t>(i);
+                numActiveTris[v]++;
+            }
+        }
+
+        // calculate and initialize the triangle score, by summing the vertices' score
+        for (size_t i = 0; i < vertexCount; i++)
+        {
+            lastScore[i] = findVertexScore(numActiveTris[i], static_cast<int32_t>(cacheTag[i]));
+
+            for(size_t j = 0; j < numActiveTris[i]; j++)
+            {
+                triangleScore[triangleIndices[offsets[i] + j]] += lastScore[i];
+            }
+        }
+
+        // find best triangle to start reordering with
+        int32_t bestTriangle = -1;
+        float   bestScore    = -1.0f;
+        for(size_t i = 0; i < triangleCount; i++)
+        {
+            if(triangleScore[i] > bestScore)
+            {
+                bestScore = triangleScore[i];
+                bestTriangle = static_cast<int32_t>(i);
+            }
+        }
+
+        // allocate output triangles
+        std::vector<int32_t> outTriangles(triangleCount, 0);
+        uint32_t outPos = 0;
+
+        // initialize cache (with -1)
+        std::array<int32_t, VERTEX_CACHE_SIZE + 3> cache = {};
+        for(auto &element : cache)
+        {
+            element = -1;
+        }
+
+        uint32_t scanPos = 0;
+
+        // begin reordering routine
+        // output the currently best triangle, as long as there are triangles left to output
+        while(bestTriangle >= 0)
+        {
+            // mark best triangle as added
+            triangleAdded[bestTriangle] = true;
+            // output this triangle
+            outTriangles[outPos++] = bestTriangle;
+
+            // push best triangle's vertices into the cache
+            for(size_t i = 0; i < 3; i++)
+            {
+                uint32_t v = idxBuf[3 * bestTriangle + i];
+
+                // get vertex' cache position, if its -1, set its position to the end
+                int8_t endPos = cacheTag[v];
+                if(endPos < 0)
+                    endPos = static_cast<int8_t>(VERTEX_CACHE_SIZE + i);
+
+                // shift vertices' cache entries forward by one
+                for(int8_t j = endPos; j > i; j--)
+                {
+                    cache[j] = cache[j - 1];
+
+                    // if cache slot is valid vertex,
+                    // update the vertex cache tag accordingly
+                    if (cache[j] >= 0)
+                        cacheTag[cache[j]]++;
+                }
+
+                // insert current vertex into its new target slot
+                cache[i] = static_cast<int32_t>(v);
+                cacheTag[v] = static_cast<int8_t>(i);
+
+                // find current triangle in the list of active triangles
+                // remove it by moving the last triangle into the slot the current triangle is holding.
+                for (size_t j = 0; j < numActiveTris[v]; j++)
+                {
+                    if(triangleIndices[offsets[v] + j] == bestTriangle)
+                    {
+                        triangleIndices[offsets[v] + j] = triangleIndices[offsets[v] + numActiveTris[v] - 1];
+                        break;
+                    }
+                }
+                // shorten the list
+                numActiveTris[v]--;
+            }
+
+            // update scores of all triangles in cache
+            for (size_t i = 0; i < cache.size(); i++)
+            {
+                int32_t v = cache[i];
+                if (v < 0)
+                    break;
+
+                // this vertex has been pushed outside of the actual cache
+                if(i >= VERTEX_CACHE_SIZE)
+                {
+                    cacheTag[v] = -1;
+                    cache[i] = -1;
+                }
+
+                float newScore = findVertexScore(numActiveTris[v], cacheTag[v]);
+                float diff = newScore - lastScore[v];
+
+                for(size_t j = 0; j < numActiveTris[v]; j++)
+                {
+                    triangleScore[triangleIndices[offsets[v] + j]] += diff;
+                }
+                lastScore[v] = newScore;
+            }
+
+            // find best triangle reference by vertices in cache
+            bestTriangle = -1;
+            bestScore = -1.0f;
+            for(size_t i = 0; i < VERTEX_CACHE_SIZE; i++)
+            {
+                if (cache[i] < 0)
+                    break;
+
+                int32_t v = cache[i];
+                for(size_t j = 0; j < numActiveTris[v]; j++)
+                {
+                    int32_t t = triangleIndices[offsets[v] + j];
+                    if(triangleScore[t] > bestScore)
+                    {
+                        bestTriangle = t;
+                        bestScore = triangleScore[t];
+                    }
+                }
+            }
+
+            // if no triangle was found at all, continue scanning whole list of triangles
+            if (bestTriangle < 0)
+            {
+                for(; scanPos < triangleCount; scanPos++)
+                {
+                    if(!triangleAdded[scanPos])
+                    {
+                        bestTriangle = scanPos;
+
+                        skippedIndices.push_back(3 * outPos);
+
+                        break;
+                    }
+                }
+            }
+        }
+
+
+        // convert triangle index array into full triangle list
+        std::vector<uint32_t> outIndices(idxBuf.size(), 0);
+        outPos = 0;
+        for(size_t i = 0; i < triangleCount; i++)
+        {
+            int32_t t = outTriangles[i];
+            for(size_t j = 0; j < 3; j++)
+            {
+                int32_t v = idxBuf[3 * t + j];
+                outIndices[outPos++] = static_cast<uint32_t>(v);
+            }
+        }
+
+        return VertexCacheReorderResult(outIndices, skippedIndices);
+    }
+}
\ No newline at end of file
diff --git a/modules/meshlet/src/vkcv/meshlet/Meshlet.cpp b/modules/meshlet/src/vkcv/meshlet/Meshlet.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..abcad7207ed5a6f80cb292ab2f7e855d3b4c7797
--- /dev/null
+++ b/modules/meshlet/src/vkcv/meshlet/Meshlet.cpp
@@ -0,0 +1,167 @@
+
+#include "vkcv/meshlet/Meshlet.hpp"
+#include <vkcv/Logger.hpp>
+#include <cassert>
+#include <iostream>
+
+namespace vkcv::meshlet {
+
+std::vector<vkcv::meshlet::Vertex> convertToVertices(
+        const std::vector<uint8_t>&         vertexData,
+        const uint64_t                      vertexCount,
+        const vkcv::asset::VertexAttribute& positionAttribute,
+        const vkcv::asset::VertexAttribute& normalAttribute) {
+
+    assert(positionAttribute.type   == vkcv::asset::PrimitiveType::POSITION);
+    assert(normalAttribute.type     == vkcv::asset::PrimitiveType::NORMAL);
+
+    std::vector<vkcv::meshlet::Vertex> vertices;
+    vertices.reserve(vertexCount);
+
+    const size_t positionStepSize   = positionAttribute.stride == 0 ? sizeof(glm::vec3) : positionAttribute.stride;
+    const size_t normalStepSize     = normalAttribute.stride   == 0 ? sizeof(glm::vec3) : normalAttribute.stride;
+
+    for (int i = 0; i < vertexCount; i++) {
+        Vertex v;
+
+        const size_t positionOffset = positionAttribute.offset  + positionStepSize  * i;
+        const size_t normalOffset   = normalAttribute.offset    + normalStepSize    * i;
+
+        v.position  = *reinterpret_cast<const glm::vec3*>(&(vertexData[positionOffset]));
+        v.normal    = *reinterpret_cast<const glm::vec3*>(&(vertexData[normalOffset]));
+        vertices.push_back(v);
+    }
+    return vertices;
+}
+
+MeshShaderModelData createMeshShaderModelData(
+        const std::vector<Vertex>&      inVertices,
+        const std::vector<uint32_t>&    inIndices,
+        const std::vector<uint32_t>&    deadEndIndices) {
+
+    MeshShaderModelData data;
+    size_t currentIndex = 0;
+
+    const size_t maxVerticesPerMeshlet = 64;
+    const size_t maxIndicesPerMeshlet  = 126 * 3;
+
+    bool indicesAreLeft = true;
+
+    size_t deadEndIndicesIndex = 0;
+
+    while (indicesAreLeft) {
+        Meshlet meshlet;
+
+        meshlet.indexCount  = 0;
+        meshlet.vertexCount = 0;
+
+        meshlet.indexOffset  = data.localIndices.size();
+        meshlet.vertexOffset = data.vertices.size();
+
+        std::map<uint32_t, uint32_t> globalToLocalIndexMap;
+        std::vector<uint32_t> globalIndicesOrdered;
+
+        while (true) {
+
+            if (deadEndIndicesIndex < deadEndIndices.size()) {
+                const uint32_t deadEndIndex = deadEndIndices[deadEndIndicesIndex];
+                if (deadEndIndex == currentIndex) {
+                    deadEndIndicesIndex++;
+                    break;
+                }
+            }
+
+            indicesAreLeft = currentIndex + 1 <= inIndices.size();
+            if (!indicesAreLeft) {
+                break;
+            }
+
+            bool enoughSpaceForIndices = meshlet.indexCount + 3 < maxIndicesPerMeshlet;
+            if (!enoughSpaceForIndices) {
+                break;
+            }
+
+            size_t vertexCountToAdd = 0;
+            for (int i = 0; i < 3; i++) {
+                const uint32_t globalIndex = inIndices[currentIndex + i];
+                const bool containsVertex  = globalToLocalIndexMap.find(globalIndex) != globalToLocalIndexMap.end();
+                if (!containsVertex) {
+                    vertexCountToAdd++;
+                }
+            }
+
+            bool enoughSpaceForVertices = meshlet.vertexCount + vertexCountToAdd < maxVerticesPerMeshlet;
+            if (!enoughSpaceForVertices) {
+                break;
+            }
+
+            for (int i = 0; i < 3; i++) {
+                const uint32_t globalIndex = inIndices[currentIndex + i];
+
+                uint32_t localIndex;
+                const bool indexAlreadyExists = globalToLocalIndexMap.find(globalIndex) != globalToLocalIndexMap.end();
+                if (indexAlreadyExists) {
+                    localIndex = globalToLocalIndexMap[globalIndex];
+                }
+                else {
+                    localIndex = globalToLocalIndexMap.size();
+                    globalToLocalIndexMap[globalIndex] = localIndex;
+                    globalIndicesOrdered.push_back(globalIndex);
+                }
+
+                data.localIndices.push_back(localIndex);
+            }
+
+            meshlet.indexCount  += 3;
+            currentIndex        += 3;
+            meshlet.vertexCount += vertexCountToAdd;
+        }
+
+        for (const uint32_t globalIndex : globalIndicesOrdered) {
+            const Vertex v = inVertices[globalIndex];
+            data.vertices.push_back(v);
+        }
+
+        // compute mean position
+        meshlet.meanPosition = glm::vec3(0);
+        const uint32_t meshletLastVertexIndex = meshlet.vertexOffset + meshlet.vertexCount;
+
+        for (uint32_t vertexIndex = meshlet.vertexOffset; vertexIndex < meshletLastVertexIndex; vertexIndex++) {
+            const Vertex& v         = data.vertices[vertexIndex];
+            meshlet.meanPosition    += v.position;
+        }
+        meshlet.meanPosition /= meshlet.vertexCount;
+
+        // compute bounding sphere radius
+        meshlet.boundingSphereRadius = 0.f;
+        for (uint32_t vertexIndex = meshlet.vertexOffset; vertexIndex < meshletLastVertexIndex; vertexIndex++) {
+            const Vertex& v = data.vertices[vertexIndex];
+            const float d                   = glm::distance(v.position, meshlet.meanPosition);
+            meshlet.boundingSphereRadius    = glm::max(meshlet.boundingSphereRadius, d);
+        }
+
+        data.meshlets.push_back(meshlet);
+    }
+
+    return data;
+}
+
+std::vector<uint32_t> assetLoaderIndicesTo32BitIndices(const std::vector<uint8_t>& indexData, vkcv::asset::IndexType indexType) {
+    std::vector<uint32_t> indices;
+    if (indexType == vkcv::asset::IndexType::UINT16) {
+        for (int i = 0; i < indexData.size(); i += 2) {
+            const uint16_t index16Bit = *reinterpret_cast<const uint16_t *>(&(indexData[i]));
+            const uint32_t index32Bit = static_cast<uint32_t>(index16Bit);
+            indices.push_back(index32Bit);
+        }
+    } else if (indexType == vkcv::asset::IndexType::UINT32) {
+        for (int i = 0; i < indexData.size(); i += 4) {
+            const uint32_t index32Bit = *reinterpret_cast<const uint32_t *>(&(indexData[i]));
+            indices.push_back(index32Bit);
+        }
+    } else {
+        vkcv_log(vkcv::LogLevel::ERROR, "Unsupported index type");
+    }
+    return indices;
+}
+}
\ No newline at end of file
diff --git a/modules/meshlet/src/vkcv/meshlet/Tipsify.cpp b/modules/meshlet/src/vkcv/meshlet/Tipsify.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..101a777d4ef52d31579355f52c562a88e058cbb5
--- /dev/null
+++ b/modules/meshlet/src/vkcv/meshlet/Tipsify.cpp
@@ -0,0 +1,288 @@
+
+#include <vkcv/Logger.hpp>
+#include "vkcv/meshlet/Tipsify.hpp"
+#include <iostream>
+
+namespace vkcv::meshlet {
+
+    const int maxUsedVertices           = 128;
+
+    /**
+     * modulo operation with maxUsedVertices
+     * @param number for modulo operation
+     * @return number between 0 and maxUsedVertices - 1
+     */
+    int mod( int number ){
+        return (number + maxUsedVertices) % maxUsedVertices;
+    }
+
+    /**
+     * searches for the next VertexIndex that was used before or returns any vertexIndex if no used was found
+     * @param livingVertices vertice that still has a living triangle
+     * @param usedVerticeStack stack of vertices that are recently used
+     * @param usedVerticeCount number of last used vertices that maxes at @param maxUsedVertices
+     * @param usedVerticeOffset the @param usedVerticeStack wraps around and overrides old vertices, this offset keeps the wrapping intact
+     * @param vertexCount total vertex count to terminate correctly
+     * @param lowestLivingVertexIndex vertexIndex with the lowest number
+     * @param currentTriangleIndex index of the currently fanned triangle
+     * @param skippedIndices indices that define a new meshlet, even if they are close in memory
+     * @return a VertexIndex to be used as fanningVertexIndex
+     */
+    int skipDeadEnd(
+            const std::vector<uint8_t> &livingVertices,
+            const std::vector<uint32_t> &usedVerticeStack,
+            int &usedVerticeCount,
+            int &usedVerticeOffset,
+            int vertexCount,
+            int &lowestLivingVertexIndex,
+            int &currentTriangleIndex,
+            std::vector<uint32_t> &skippedIndices) {
+
+        // returns the latest vertex used that has a living triangle
+        while (mod(usedVerticeCount) != usedVerticeOffset) {
+            // iterate from the latest to the oldest. + maxUsedVertices to always make it a positive number in the range 0 to maxUsedVertices -1
+            int nextVertex = usedVerticeStack[mod(--usedVerticeCount)];
+
+            if (livingVertices[nextVertex] > 0) {
+                return nextVertex;
+            }
+        }
+        // returns any vertexIndex since no last used has a living triangle
+        while (lowestLivingVertexIndex + 1 < vertexCount) {
+            lowestLivingVertexIndex++;
+            if (livingVertices[lowestLivingVertexIndex] > 0) {
+                // add index of the vertex to skippedIndices
+                skippedIndices.push_back(static_cast<uint32_t>(currentTriangleIndex * 3));
+                return lowestLivingVertexIndex;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * searches for the best next candidate as a fanningVertexIndex
+     * @param vertexCount total vertexCount of the mesh
+     * @param lowestLivingVertexIndex vertexIndex with the lowest number
+     * @param cacheSize number to determine in which range vertices are prioritized to me reused
+     * @param possibleCandidates candidates for the next vertex to be fanned out
+     * @param numPossibleCandidates number of all possible candidates
+     * @param lastTimestampCache number of the last iteration the vertex was used
+     * @param currentTimeStamp current iteration of all vertices
+     * @param livingVertices vertice that still has a living triangle
+     * @param usedVerticeStack stack of vertices that are recently used
+     * @param usedVerticeCount number of last used vertices that maxes at @param maxUsedVertices
+     * @param usedVerticeOffset the @param usedVerticeStack wraps around and overrides old vertices, this offset keeps the wrapping intact
+     * @param currentTriangleIndex index of the currently fanned triangle
+     * @param skippedIndices indices that define a new meshlet, even if they are close in memory
+     * @return a VertexIndex to be used as fanningVertexIndex
+     */
+    int getNextVertexIndex(int vertexCount,
+                           int &lowestLivingVertexIndex,
+                           int cacheSize,
+                           const std::vector<uint32_t> &possibleCandidates,
+                           int numPossibleCandidates,
+                           const std::vector<uint32_t> &lastTimestampCache,
+                           int currentTimeStamp,
+                           const std::vector<uint8_t> &livingVertices,
+                           const std::vector<uint32_t> &usedVerticeStack,
+                           int &usedVerticeCount,
+                           int &usedVerticeOffset,
+                           int &currentTriangleIndex,
+                           std::vector<uint32_t> &skippedIndices) {
+        int nextVertexIndex = -1;
+        int maxPriority     = -1;
+        // calculates the next possibleCandidates that is recently used
+        for (int j = 0; j < numPossibleCandidates; j++) {
+            int vertexIndex = possibleCandidates[j];
+
+            // the candidate needs to be not fanned out yet
+            if (livingVertices[vertexIndex] > 0) {
+                int priority = -1;
+
+                // prioritizes recent used vertices, but tries not to pick one that has many triangles -> fills holes better
+                if ( currentTimeStamp - lastTimestampCache[vertexIndex] + 2 * livingVertices[vertexIndex] <=
+					 cacheSize) {
+                    priority = currentTimeStamp - lastTimestampCache[vertexIndex];
+                }
+                // select the vertexIndex with the highest priority
+                if (priority > maxPriority) {
+                    maxPriority     = priority;
+                    nextVertexIndex = vertexIndex;
+                }
+            }
+        }
+
+        // if no candidate is alive, try and find another one
+        if (nextVertexIndex == -1) {
+            nextVertexIndex = skipDeadEnd(
+					livingVertices,
+					usedVerticeStack,
+					usedVerticeCount,
+					usedVerticeOffset,
+					vertexCount,
+					lowestLivingVertexIndex,
+					currentTriangleIndex,
+					skippedIndices);
+        }
+        return nextVertexIndex;
+    }
+
+    VertexCacheReorderResult tipsifyMesh(
+            const std::vector<uint32_t> &indexBuffer32Bit,
+            const int vertexCount,
+            const unsigned int cacheSize) {
+
+        if (indexBuffer32Bit.empty() || vertexCount <= 0) {
+            vkcv_log(LogLevel::ERROR, "Invalid Input.");
+            return VertexCacheReorderResult(indexBuffer32Bit , {});
+        }
+        int triangleCount = indexBuffer32Bit.size() / 3;
+
+       // dynamic array for vertexOccurrence
+        std::vector<uint8_t> vertexOccurrence(vertexCount, 0);
+        // count the occurrence of a vertex in all among all triangles
+        for (size_t i = 0; i < triangleCount * 3; i++) {
+            vertexOccurrence[indexBuffer32Bit[i]]++;
+        }
+
+        int sum = 0;
+        std::vector<uint32_t> offsetVertexOccurrence(vertexCount + 1, 0);
+        // highest offset for later iteration
+        int maxOffset = 0;
+        // calculate the offset of each vertex from the start
+        for (int i = 0; i < vertexCount; i++) {
+            offsetVertexOccurrence[i]   = sum;
+            sum                         += vertexOccurrence[i];
+
+            if (vertexOccurrence[i] > maxOffset) {
+                maxOffset = vertexOccurrence[i];
+            }
+            // reset for reuse
+            vertexOccurrence[i] = 0;
+        }
+        offsetVertexOccurrence[vertexCount] = sum;
+
+        // vertexIndexToTriangle = which vertex belongs to which triangle
+        std::vector<uint32_t> vertexIndexToTriangle(3 * triangleCount, 0);
+        // vertexOccurrence functions as number of usages in all triangles
+        // lowestLivingVertexIndex = number of a triangle
+        for (int i = 0; i < triangleCount; i++) {
+            // get the pointer to the first vertex of the triangle
+            // this allows us to iterate over the indexBuffer with the first vertex of the triangle as start
+            const uint32_t *vertexIndexOfTriangle = &indexBuffer32Bit[i * 3];
+
+            vertexIndexToTriangle[offsetVertexOccurrence[vertexIndexOfTriangle[0]] + vertexOccurrence[vertexIndexOfTriangle[0]]] = i;
+            vertexOccurrence[vertexIndexOfTriangle[0]]++;
+
+            vertexIndexToTriangle[offsetVertexOccurrence[vertexIndexOfTriangle[1]] + vertexOccurrence[vertexIndexOfTriangle[1]]] = i;
+            vertexOccurrence[vertexIndexOfTriangle[1]]++;
+
+            vertexIndexToTriangle[offsetVertexOccurrence[vertexIndexOfTriangle[2]] + vertexOccurrence[vertexIndexOfTriangle[2]]] = i;
+            vertexOccurrence[vertexIndexOfTriangle[2]]++;
+        }
+
+        // counts if a triangle still uses this vertex
+        std::vector<uint8_t>  livingVertices = vertexOccurrence;
+        std::vector<uint32_t> lastTimestampCache(vertexCount, 0);
+
+        // stack of already used vertices, if it'currentTimeStamp full it will write to 0 again
+        std::vector<uint32_t> usedVerticeStack(maxUsedVertices, 0);
+
+        //currently used vertices
+        int usedVerticeCount     = 0;
+        // offset if maxUsedVertices was reached and it loops back to 0
+        int usedVerticeOffset    = 0;
+
+        // saves if a triangle was emitted (used in the IndexBuffer)
+        std::vector<bool> isEmittedTriangles(triangleCount, false);
+
+        // reordered Triangles that get rewritten to the new IndexBuffer
+        std::vector<uint32_t> reorderedTriangleIndexBuffer(triangleCount, 0);
+
+        // offset to the latest not used triangleIndex
+        int triangleOutputOffset    = 0;
+        // vertexIndex to fan out from (fanning VertexIndex)
+        int currentVertexIndex      = 0;
+        int currentTimeStamp        = cacheSize + 1;
+        int lowestLivingVertexIndex = 0;
+
+        std::vector<uint32_t> possibleCandidates(3 * maxOffset);
+
+        int currentTriangleIndex = 0;
+        // list of vertex indices where a deadEnd was reached
+        // useful to know where the mesh is potentially not contiguous
+        std::vector<uint32_t> skippedIndices;
+
+        // run while not all indices are fanned out, -1 equals all are fanned out
+        while (currentVertexIndex >= 0) {
+            // number of possible candidates for a fanning VertexIndex
+            int numPossibleCandidates   = 0;
+            // offset of currentVertexIndex and the next VertexIndex
+            int startOffset             = offsetVertexOccurrence[currentVertexIndex];
+            int endOffset               = offsetVertexOccurrence[currentVertexIndex + 1];
+            // iterates over every triangle of currentVertexIndex
+            for (int offset = startOffset; offset < endOffset; offset++) {
+                int triangleIndex = vertexIndexToTriangle[offset];
+
+                // checks if the triangle is already emitted
+                if (!isEmittedTriangles[triangleIndex]) {
+
+                    // get the pointer to the first vertex of the triangle
+                    // this allows us to iterate over the indexBuffer with the first vertex of the triangle as start
+                    const uint32_t *vertexIndexOfTriangle        = &indexBuffer32Bit[3 * triangleIndex];
+
+                    currentTriangleIndex++;
+
+                    // save emitted vertexIndexOfTriangle to reorderedTriangleIndexBuffer and set it to emitted
+                    reorderedTriangleIndexBuffer[triangleOutputOffset++]    = triangleIndex;
+                    isEmittedTriangles[triangleIndex]                       = true;
+
+                    // save all vertexIndices of the triangle to reuse as soon as possible
+                    for (int j = 0; j < 3; j++) {
+                        int vertexIndex = vertexIndexOfTriangle[j];
+
+                        //save vertexIndex to reuseStack
+                        usedVerticeStack[mod(usedVerticeCount++)] = vertexIndex;
+
+                        // after looping back increase the start, so it only overrides the oldest vertexIndex
+                        if ((mod(usedVerticeCount)) ==
+                            (mod(usedVerticeOffset))) {
+                            usedVerticeOffset = mod(usedVerticeOffset + 1);
+                        }
+                        // add vertex to next possibleCandidates as fanning vertex
+                        possibleCandidates[numPossibleCandidates++] = vertexIndex;
+
+                        // remove one occurrence of the vertex, since the triangle is used
+                        livingVertices[vertexIndex]--;
+
+                        // writes the timestamp (number of iteration) of the last usage, if it wasn't used within the last cacheSize iterations
+                        if (currentTimeStamp - lastTimestampCache[vertexIndex] > cacheSize) {
+                            lastTimestampCache[vertexIndex] = currentTimeStamp;
+                            currentTimeStamp++;
+                        }
+                    }
+                }
+            }
+
+            // search for the next vertexIndex to fan out
+            currentVertexIndex = getNextVertexIndex(
+                    vertexCount, lowestLivingVertexIndex, cacheSize, possibleCandidates, numPossibleCandidates, lastTimestampCache, currentTimeStamp,
+                    livingVertices, usedVerticeStack, usedVerticeCount, usedVerticeOffset, currentTriangleIndex, skippedIndices);
+        }
+
+        std::vector<uint32_t> reorderedIndexBuffer(3 * triangleCount);
+
+        triangleOutputOffset = 0;
+        // rewriting the TriangleIndexBuffer to the new IndexBuffer
+        for (int i = 0; i < triangleCount; i++) {
+            int triangleIndex = reorderedTriangleIndexBuffer[i];
+            // rewriting the triangle index to vertices
+            for (int j = 0; j < 3; j++) {
+                int vertexIndex = indexBuffer32Bit[(3 * triangleIndex) + j];
+                reorderedIndexBuffer[triangleOutputOffset++] = vertexIndex;
+            }
+        }
+
+        return VertexCacheReorderResult(reorderedIndexBuffer, skippedIndices);
+    }
+}
\ No newline at end of file
diff --git a/modules/scene/CMakeLists.txt b/modules/scene/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..39effd0f802bb9d8ef4b318d5f1d233fa93db492
--- /dev/null
+++ b/modules/scene/CMakeLists.txt
@@ -0,0 +1,64 @@
+cmake_minimum_required(VERSION 3.16)
+project(vkcv_scene)
+
+# setting c++ standard for the module
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(vkcv_scene_source ${PROJECT_SOURCE_DIR}/src)
+set(vkcv_scene_include ${PROJECT_SOURCE_DIR}/include)
+
+# Add source and header files to the module
+set(vkcv_scene_sources
+		${vkcv_scene_include}/vkcv/scene/Bounds.hpp
+		${vkcv_scene_source}/vkcv/scene/Bounds.cpp
+		
+		${vkcv_scene_include}/vkcv/scene/Frustum.hpp
+		${vkcv_scene_source}/vkcv/scene/Frustum.cpp
+		
+		${vkcv_scene_include}/vkcv/scene/MeshPart.hpp
+		${vkcv_scene_source}/vkcv/scene/MeshPart.cpp
+		
+		${vkcv_scene_include}/vkcv/scene/Mesh.hpp
+		${vkcv_scene_source}/vkcv/scene/Mesh.cpp
+
+		${vkcv_scene_include}/vkcv/scene/Node.hpp
+		${vkcv_scene_source}/vkcv/scene/Node.cpp
+		
+		${vkcv_scene_include}/vkcv/scene/Scene.hpp
+		${vkcv_scene_source}/vkcv/scene/Scene.cpp
+)
+
+# adding source files to the module
+add_library(vkcv_scene ${vkcv_build_attribute} ${vkcv_scene_sources})
+
+# link the required libraries to the module
+target_link_libraries(vkcv_scene vkcv)
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(vkcv_scene SYSTEM BEFORE PRIVATE
+		${vkcv_include}
+		${vkcv_includes}
+		${vkcv_asset_loader_include}
+		${vkcv_material_include}
+		${vkcv_camera_include}
+		${vkcv_algorithm_include})
+
+# add the own include directory for public headers
+target_include_directories(vkcv_scene BEFORE PUBLIC ${vkcv_scene_include})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(vkcv_scene
+		vkcv
+		vkcv_asset_loader
+		vkcv_material
+		vkcv_camera
+		vkcv_algorithm)
+
+if (vkcv_parent_scope)
+	list(APPEND vkcv_modules_includes ${vkcv_scene_include})
+	list(APPEND vkcv_modules_libraries vkcv_scene)
+	
+	set(vkcv_modules_includes ${vkcv_modules_includes} PARENT_SCOPE)
+	set(vkcv_modules_libraries ${vkcv_modules_libraries} PARENT_SCOPE)
+endif()
diff --git a/modules/scene/README.md b/modules/scene/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..77837163d7de34fb6ce280697e84c8b047180369
--- /dev/null
+++ b/modules/scene/README.md
@@ -0,0 +1,7 @@
+# Scene
+
+A VkCV module to load and manage a scene, simplify its rendering and potentially optimize it
+
+## Docs
+
+Here is a [link](https://userpages.uni-koblenz.de/~vkcv/doc/group__vkcv__scene.html) to this module.
diff --git a/modules/scene/include/vkcv/scene/Bounds.hpp b/modules/scene/include/vkcv/scene/Bounds.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..8ad5806506610dbef6e4f1e0e26d66dfe0dbb63b
--- /dev/null
+++ b/modules/scene/include/vkcv/scene/Bounds.hpp
@@ -0,0 +1,209 @@
+#pragma once
+
+#include <array>
+#include <iostream>
+#include <glm/vec3.hpp>
+
+namespace vkcv::scene {
+
+    /**
+     * @addtogroup vkcv_scene
+     * @{
+     */
+
+    /**
+     * A basic class to represent axis aligned bounding boxes.
+     */
+	class Bounds {
+	private:
+        /**
+         * Minimum values of the box as 3D vector.
+         */
+		glm::vec3 m_min;
+
+        /**
+         * Maximum values of the box as 3D vector.
+         */
+		glm::vec3 m_max;
+		
+	public:
+        /**
+         * Default constructor creating a zero volume axis aligned bounding
+         * box.
+         */
+		Bounds();
+		
+		/**
+         * Constructor creating a zero volume axis aligned bounding
+         * box around a certain point.
+         * @param[in] point Center of the box as 3D vector
+         */
+		Bounds(const glm::vec3& point);
+
+        /**
+         * Constructor creating an axis aligned bounding box with given
+         * boundaries, defined through minimum and maximum values.
+         * @param[in] min Minimum values of the box as 3D vector
+         * @param[in] max Maximum values of the box as 3D vector
+         */
+		Bounds(const glm::vec3& min, const glm::vec3& max);
+
+        /**
+         * Destructor of an axis aligned bounding box.
+         */
+		~Bounds() = default;
+
+        /**
+         * Copy-constructor of an axis aligned bounding box.
+         * @param[in] other Other box as Bounds
+         */
+		Bounds(const Bounds& other) = default;
+
+        /**
+         * Move-constructor of an axis aligned bounding box.
+         * @param[in,out] other Other box as Bounds
+         */
+        Bounds(Bounds&& other) = default;
+
+        /**
+         * Copy-operator of an axis aligned bounding box.
+         * @param[in] other Other box as Bounds
+         * @return Reference to this box
+         */
+		Bounds& operator=(const Bounds& other) = default;
+
+        /**
+         * Move-operator of an axis aligned bounding box.
+         * @param[in,out] other Other box as Bounds
+         * @return Reference to this box
+         */
+		Bounds& operator=(Bounds&& other) = default;
+
+        /**
+         * Set and replace the minimum values of the box
+         * via a 3D vector.
+         * @param[in] min New minimum values of the box as 3D vector
+         */
+		void setMin(const glm::vec3& min);
+
+        /**
+         * Return the current minimum values of the box as
+         * 3D vector.
+         * @return Minimum values of the box as 3D vector
+         */
+		[[nodiscard]]
+		const glm::vec3& getMin() const;
+
+        /**
+         * Set and replace the maximum values of the box
+         * via a 3D vector.
+         * @param[in] max New maximum values of the box as 3D vector
+         */
+		void setMax(const glm::vec3& max);
+
+        /**
+         * Return the current maximum values of the box as
+         * 3D vector.
+         * @return Maximum values of the box as 3D vector
+         */
+		[[nodiscard]]
+		const glm::vec3& getMax() const;
+
+        /**
+         * Set the new center of the box by moving it and keeping
+         * its current volume.
+         * @param[in] center New center as 3D vector
+         */
+		void setCenter(const glm::vec3& center);
+
+        /**
+         * Return the current center of the box as 3D vector.
+         * @return Center as 3D vector
+         */
+		[[nodiscard]]
+		glm::vec3 getCenter() const;
+
+        /**
+         * Set the new size of the box by scaling it from its center,
+         * keeping the center position but replacing its volume.
+         * @param[in] size New size as 3D vector
+         */
+		void setSize(const glm::vec3& size);
+
+        /**
+         * Return the current size of the box as 3D vector.
+         * @return Size as 3D vector
+         */
+		[[nodiscard]]
+		glm::vec3 getSize() const;
+
+        /**
+         * Return a fixed size array containing all corners of
+         * the box as 3D vectors in absolute positions.
+         * @return Array of corners as 3D vectors
+         */
+		[[nodiscard]]
+		std::array<glm::vec3, 8> getCorners() const;
+
+        /**
+         * Resize the box to include a given point, provided
+         * in absolute position as 3D vector.
+         * @param[in] point Point as 3D vector
+         */
+		void extend(const glm::vec3& point);
+
+        /**
+         * Return if a given point, provided in absolute position,
+         * is inside this box or not.
+         * @param[in] point Point as 3D vector
+         * @return true if the point is inside, otherwise false
+         */
+		[[nodiscard]]
+		bool contains(const glm::vec3& point) const;
+
+        /**
+         * Return if a given other axis aligned bounding box is
+         * inside this box or not.
+         * @param[in] other Other box as Bounds
+         * @return true if the box is inside, otherwise false
+         */
+		[[nodiscard]]
+		bool contains(const Bounds& other) const;
+
+        /**
+         * Return if a given other axis aligned bounding box is
+         * intersecting this box or not.
+         * @param[in] other Other box as Bounds
+         * @return true if the boxes are intersecting, otherwise false
+         */
+		[[nodiscard]]
+		bool intersects(const Bounds& other) const;
+
+        /**
+         * Return if the defined bounding box is valid (min <= max).
+         * @return true if the box is valid, otherwise false
+         */
+		[[nodiscard]]
+		explicit operator bool() const;
+
+        /**
+         * Return if the defined bounding box is invalid (min > max).
+         * @return true if the box is invalid, otherwise false
+         */
+		[[nodiscard]]
+		bool operator!() const;
+	
+	};
+
+    /**
+     * Stream-operator of the axis aligned bounding box to print
+     * it as readable output to logs or the console.
+     * @param[out] out Output stream
+     * @param[in] bounds Axis aligned bounding box
+     * @return Accessed output stream
+     */
+	std::ostream& operator << (std::ostream& out, const Bounds& bounds);
+
+    /** @} */
+	
+}
diff --git a/modules/scene/include/vkcv/scene/Frustum.hpp b/modules/scene/include/vkcv/scene/Frustum.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..e40092240614a5ee76fe3f5aa5a43db98d39970a
--- /dev/null
+++ b/modules/scene/include/vkcv/scene/Frustum.hpp
@@ -0,0 +1,45 @@
+#pragma once
+
+#include <glm/mat4x4.hpp>
+#include "vkcv/scene/Bounds.hpp"
+
+namespace vkcv::scene {
+
+    /**
+     * @addtogroup vkcv_scene
+     * @{
+     */
+	
+	/**
+     * Transform a given axis aligned bounding box into frustum coordinates
+     * using a given 4x4 transformation matrix to return a new axis aligned
+     * bounding box containing that transformed box.
+     * @param[in] transform Frustum defining 4x4 matrix
+     * @param[in] bounds Axis aligned bounding box
+     * @return Bounds containing box in frustum coordinates
+     */
+	Bounds transformBounds(const glm::mat4& transform, const Bounds& bounds);
+
+    /**
+     * Transform a given axis aligned bounding box into frustum coordinates
+     * using a given 4x4 transformation matrix to return a new axis aligned
+     * bounding box containing that transformed box.
+     * @param[in] transform Frustum defining 4x4 matrix
+     * @param[in] bounds Axis aligned bounding box
+     * @param[out] negative_w Flag if w-coordinate is negative
+     * @return Bounds containing box in frustum coordinates
+     */
+	Bounds transformBounds(const glm::mat4& transform, const Bounds& bounds, bool& negative_w);
+
+    /**
+     * Check if an axis aligned bounding box is intersecting a given frustum
+     * defined by a 4x4 transformation matrix.
+     * @param[in] transform Frustum defining 4x4 matrix
+     * @param[in] bounds Axis aligned bounding box
+     * @return true if the box and frustum are intersecting, otherwise false
+     */
+	bool checkFrustum(const glm::mat4& transform, const Bounds& bounds);
+
+    /** @} */
+	
+}
diff --git a/modules/scene/include/vkcv/scene/Mesh.hpp b/modules/scene/include/vkcv/scene/Mesh.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..059eb6bcabb3cecb287b3a5bbdba86029fe97c9f
--- /dev/null
+++ b/modules/scene/include/vkcv/scene/Mesh.hpp
@@ -0,0 +1,146 @@
+#pragma once
+
+#include <glm/mat4x4.hpp>
+
+#include <vkcv/asset/asset_loader.hpp>
+#include <vkcv/camera/Camera.hpp>
+
+#include "MeshPart.hpp"
+
+namespace vkcv::scene {
+
+    /**
+     * @addtogroup vkcv_scene
+     * @{
+     */
+
+    /**
+     * An event function type to be called on per drawcall recording level to adjust data
+     * like push constants with provided matrices.
+     */
+	typedef typename event_function<const glm::mat4&, const glm::mat4&, PushConstants&, vkcv::Drawcall&>::type RecordMeshDrawcallFunction;
+	
+	class Node;
+
+    /**
+     * A class to represent a whole mesh to render.
+     */
+	class Mesh {
+		friend class Node;
+		
+	private:
+        /**
+         * Parent scene of the mesh.
+         */
+		Scene& m_scene;
+
+        /**
+         * List of the meshes parts.
+         */
+		std::vector<MeshPart> m_parts;
+
+        /**
+         * List of the meshes drawcalls to render.
+         */
+		std::vector<InstanceDrawcall> m_drawcalls;
+
+        /**
+         * Local transformation matrix of the mesh.
+         */
+		glm::mat4 m_transform;
+
+        /**
+         * Axis aligned bounding box of the mesh.
+         */
+		Bounds m_bounds;
+
+        /**
+         * Constructor of a new mesh with a given scene as parent.
+         *
+         * @param[in,out] scene Scene
+         */
+		explicit Mesh(Scene& scene);
+
+        /**
+         * Load mesh data from a scene structure from the asset loader
+         * creating and loading all mesh parts being required.
+         *
+         * @param[in] scene Scene structure from asset loader
+         * @param[in] mesh Mesh structure from asset loader
+         * @param[in] types Primitive type order of vertex attributes
+         */
+		void load(const asset::Scene& scene,
+				  const asset::Mesh& mesh,
+				  const std::vector<asset::PrimitiveType>& types);
+
+        /**
+         * Record drawcalls of the mesh, equally to all its visible parts.
+         *
+         * @param[in] viewProjection View-transformation and projection as 4x4 matrix
+         * @param[out] pushConstants Structure to store push constants per drawcall
+         * @param[out] drawcalls List of drawcall structures
+         * @param[in] record Drawcall recording event function
+         */
+		void recordDrawcalls(const glm::mat4& viewProjection,
+							 PushConstants& pushConstants,
+							 std::vector<InstanceDrawcall>& drawcalls,
+							 const RecordMeshDrawcallFunction& record);
+
+        /**
+         * Return the amount of drawcalls of the mesh
+         * as sum of all its parts.
+         *
+         * @return Amount of drawcalls
+         */
+		[[nodiscard]]
+		size_t getDrawcallCount() const;
+	
+	public:
+        /**
+         * Destructor of a mesh.
+         */
+		~Mesh();
+
+        /**
+         * Copy-constructor of a mesh.
+         *
+         * @param[in] other Other mesh instance
+         */
+		Mesh(const Mesh& other) = default;
+
+        /**
+         * Move-constructor of a mesh.
+         *
+         * @param[in,out] other Other mesh instance
+         */
+        Mesh(Mesh&& other) = default;
+
+        /**
+         * Copy-operator of a mesh.
+         *
+         * @param[in] other Other mesh instance
+         * @return Reference to this mesh instance
+         */
+		Mesh& operator=(const Mesh& other);
+
+        /**
+         * Move-operator of a mesh.
+         *
+         * @param[in,out] other Other mesh instance
+         * @return Reference to this mesh instance
+         */
+		Mesh& operator=(Mesh&& other) noexcept;
+
+        /**
+         * Return the axis aligned bounding box of the mesh.
+         *
+         * @return Axis aligned bounding box of this mesh
+         */
+		[[nodiscard]]
+		const Bounds& getBounds() const;
+	
+	};
+
+    /** @} */
+	
+}
diff --git a/modules/scene/include/vkcv/scene/MeshPart.hpp b/modules/scene/include/vkcv/scene/MeshPart.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..72fd6151f64c31377fb9f38b3c8dedae44905d4d
--- /dev/null
+++ b/modules/scene/include/vkcv/scene/MeshPart.hpp
@@ -0,0 +1,145 @@
+#pragma once
+
+#include <vector>
+
+#include <vkcv/Buffer.hpp>
+#include <vkcv/asset/asset_loader.hpp>
+#include <vkcv/material/Material.hpp>
+
+#include "Bounds.hpp"
+
+namespace vkcv::scene {
+
+    /**
+     * @addtogroup vkcv_scene
+     * @{
+     */
+	
+	class Scene;
+	class Mesh;
+
+    /**
+     * A class to represent a group of vertices to render
+     * a part of a mesh.
+     */
+	class MeshPart {
+		friend class Mesh;
+	
+	private:
+        /**
+         * Parent scene of the mesh part.
+         */
+		Scene& m_scene;
+
+        /**
+         * The vertex data containing its part of the mesh.
+         */
+		VertexData m_data;
+
+        /**
+         * Axis aligned bounding box of the mesh part.
+         */
+		Bounds m_bounds;
+
+        /**
+         * The index of the material used to render
+         * this part of the mesh.
+         */
+		size_t m_materialIndex;
+
+        /**
+         * Constructor of a new mesh part with a given scene as parent.
+         *
+         * @param[in,out] scene Scene
+         */
+		explicit MeshPart(Scene& scene);
+
+        /**
+         * Load vertex and index data from structures provided by the asset loader
+         * and add a matching drawcall to the list if the loaded mesh part is valid.
+         *
+         * @param[in] scene Scene structure from asset loader
+         * @param[in] vertexGroup Vertex group structure from asset loader
+         * @param[in] types Primitive type order of vertex attributes
+         * @param[out] drawcalls List of drawcalls
+         */
+		void load(const asset::Scene& scene,
+				  const asset::VertexGroup& vertexGroup,
+				  const std::vector<asset::PrimitiveType>& types,
+				  std::vector<InstanceDrawcall>& drawcalls);
+	
+	public:
+        /**
+         * Destructor of a mesh part.
+         */
+		~MeshPart();
+
+        /**
+         * Copy-constructor of a mesh part.
+         *
+         * @param[in] other Other mesh part
+         */
+		MeshPart(const MeshPart& other);
+
+        /**
+         * Move-constructor of a mesh part.
+         *
+         * @param[in,out] other Other mesh part
+         */
+        MeshPart(MeshPart&& other) noexcept;
+
+        /**
+         * Copy-operator of a mesh part.
+         *
+         * @param[in] other Other mesh part
+         * @return Reference to this mesh part
+         */
+		MeshPart& operator=(const MeshPart& other);
+
+        /**
+         * Move-operator of a mesh part.
+         *
+         * @param[in,out] other Other mesh part
+         * @return Reference to this mesh part
+         */
+        MeshPart& operator=(MeshPart&& other) noexcept;
+
+        /**
+         * Get the material used by this specific part of
+         * the mesh for rendering.
+         *
+         * @return Material
+         */
+		[[nodiscard]]
+		const material::Material& getMaterial() const;
+
+        /**
+         * Return the axis aligned bounding box of this
+         * specific part of the mesh.
+         *
+         * @return Axis aligned bounding box of this mesh part
+         */
+		[[nodiscard]]
+		const Bounds& getBounds() const;
+
+        /**
+         * Return the status if this part of the mesh is valid
+         * as boolean value.
+         *
+         * @return true if the mesh part is valid, otherwise false
+         */
+		explicit operator bool() const;
+
+        /**
+         * Return the status if this part of the mesh is invalid
+         * as boolean value.
+         *
+         * @return true if the mesh part is invalid, otherwise false
+         */
+		bool operator!() const;
+		
+	};
+
+    /** @} */
+
+}
diff --git a/modules/scene/include/vkcv/scene/Node.hpp b/modules/scene/include/vkcv/scene/Node.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..de40adcfb5e9c71c233465b44ad07c5bb30b2a78
--- /dev/null
+++ b/modules/scene/include/vkcv/scene/Node.hpp
@@ -0,0 +1,176 @@
+#pragma once
+
+#include <vector>
+
+#include <vkcv/asset/asset_loader.hpp>
+#include <vkcv/camera/Camera.hpp>
+
+#include "Bounds.hpp"
+#include "Mesh.hpp"
+
+namespace vkcv::scene {
+
+    /**
+     * @addtogroup vkcv_scene
+     * @{
+     */
+	
+	class Scene;
+
+    /**
+     * A class to represent a graph node in a scene graph.
+     */
+	class Node {
+		friend class Scene;
+		
+	private:
+        /**
+         * Parent scene of the node.
+         */
+		Scene& m_scene;
+
+        /**
+         * List of meshes added the node.
+         */
+		std::vector<Mesh> m_meshes;
+
+        /**
+         * List of child nodes added the node.
+         */
+		std::vector<Node> m_nodes;
+
+        /**
+         * Axis aligned bounding box of the node.
+         */
+		Bounds m_bounds;
+
+        /**
+         * Constructor of a new node with a given scene as parent.
+         *
+         * @param[in,out] scene Scene
+         */
+		explicit Node(Scene& scene);
+
+        /**
+         * Add a given mesh to this node for drawcall recording.
+         *
+         * @param[in] mesh Mesh
+         */
+		void addMesh(const Mesh& mesh);
+
+        /**
+         * Load and add a mesh from a scene preloaded with the asset loader.
+         *
+         * @param[in] asset_scene Scene structure from asset loader
+         * @param[in] asset_mesh Mesh structure from asset loader
+         * @param[in] types Primitive type order of vertex attributes
+         */
+		void loadMesh(const asset::Scene& asset_scene,
+					  const asset::Mesh& asset_mesh,
+					  const std::vector<asset::PrimitiveType>& types);
+
+        /**
+         * Record drawcalls of all meshes of this node and its child nodes.
+         *
+         * @param[in] viewProjection View-transformation and projection as 4x4 matrix
+         * @param[out] pushConstants Structure to store push constants per drawcall
+         * @param[out] drawcalls List of drawcall structures
+         * @param[in] record Drawcall recording event function
+         */
+		void recordDrawcalls(const glm::mat4& viewProjection,
+							 PushConstants& pushConstants,
+							 std::vector<InstanceDrawcall>& drawcalls,
+							 const RecordMeshDrawcallFunction& record);
+
+        /**
+         * Splits child nodes into tree based graphs of nodes
+         * until all nodes contain an amount of meshes below
+         * a given maximum.
+         *
+         * @param[in] maxMeshesPerNode Maximum amount of meshes per node
+         */
+		void splitMeshesToSubNodes(size_t maxMeshesPerNode);
+
+        /**
+         * Return the sum of drawcalls in the graph of this node.
+         *
+         * @return Amount of drawcalls
+         */
+		[[nodiscard]]
+		size_t getDrawcallCount() const;
+
+        /**
+         * Add a new node as child to the scene graph with this node
+         * as parent and return its index.
+         *
+         * @return Index of the new node
+         */
+		size_t addNode();
+
+        /**
+         * Get a reference to the child node with a given index.
+         *
+         * @param[in] index Valid index of a child node
+         * @return Matching child node
+         */
+		Node& getNode(size_t index);
+
+        /**
+         * Get a const reference to the child node with a given index.
+         *
+         * @param[in] index Valid index of a child node
+         * @return Matching child node
+         */
+		[[nodiscard]]
+		const Node& getNode(size_t index) const;
+	
+	public:
+        /**
+         * Destructor of a scene node.
+         */
+		~Node();
+
+        /**
+         * Copy-constructor of a scene node.
+         *
+         * @param[in] other Other scene node
+         */
+		Node(const Node& other) = default;
+
+        /**
+         * Move-constructor of a scene node.
+         *
+         * @param[in,out] other Other scene node
+         */
+		Node(Node&& other) = default;
+
+        /**
+         * Copy-operator of a scene node.
+         *
+         * @param[in] other Other scene node
+         * @return Reference of this node
+         */
+		Node& operator=(const Node& other);
+
+        /**
+         * Move-operator of a scene node.
+         *
+         * @param[in,out] other Other scene node
+         * @return Reference of this node
+         */
+		Node& operator=(Node&& other) noexcept;
+
+        /**
+         * Return the axis aligned bounding box of the
+         * scene node.
+         *
+         * @return Axis aligned bounding box of this node
+         */
+		[[nodiscard]]
+		const Bounds& getBounds() const;
+		
+	};
+
+    /** @} */
+	
+}
diff --git a/modules/scene/include/vkcv/scene/Scene.hpp b/modules/scene/include/vkcv/scene/Scene.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d6db062c823f1d8746179bc0fde61535c16f8fe8
--- /dev/null
+++ b/modules/scene/include/vkcv/scene/Scene.hpp
@@ -0,0 +1,216 @@
+#pragma once
+
+#include <filesystem>
+#include <mutex>
+
+#include <vkcv/Core.hpp>
+#include <vkcv/Event.hpp>
+#include <vkcv/asset/asset_loader.hpp>
+#include <vkcv/camera/Camera.hpp>
+#include <vkcv/material/Material.hpp>
+
+#include "Node.hpp"
+
+namespace vkcv::scene {
+
+    /**
+     * @defgroup vkcv_scene Scene Module
+     * A module to manage basic scene rendering with CPU-side frustum culling.
+     * @{
+     */
+
+    /**
+     * A class to represent a scene graph.
+     */
+	class Scene {
+		friend class MeshPart;
+		
+	private:
+        /**
+         * A nested structure to manage material storage.
+         */
+		struct Material {
+            /**
+             * The amount of active usages of the material.
+             */
+			size_t m_usages;
+
+            /**
+             * The actual material data.
+             */
+			material::Material m_data;
+		};
+
+        /**
+         * A pointer to the current Core instance.
+         */
+		Core* m_core;
+
+        /**
+         * A list of currently used materials.
+         */
+		std::vector<Material> m_materials;
+
+        /**
+         * A list of nodes in the first level of the scene graph.
+         */
+		std::vector<Node> m_nodes;
+
+        /**
+         * Constructor of a scene instance with a given Core instance.
+         *
+         * @param[in,out] core Pointer to valid Core instance
+         */
+		explicit Scene(Core* core);
+
+        /**
+         * Add a new node to the first level of the scene graph with
+         * this scene as parent and return its index.
+         *
+         * @return Index of the new node
+         */
+		size_t addNode();
+
+        /**
+         * Get a reference to the first-level node with a given index.
+         *
+         * @param[in] index Valid index of a first-level node
+         * @return Matching first-level node
+         */
+		Node& getNode(size_t index);
+
+        /**
+        * Get a const reference to the first-level node with a given index.
+         *
+        * @param[in] index Valid index of a first-level node
+        * @return Matching first-level node
+        */
+        [[nodiscard]]
+		const Node& getNode(size_t index) const;
+
+        /**
+         * Increase the amount of usages for a certain material via its index.
+         *
+         * @param[in] index Index of a material
+         */
+		void increaseMaterialUsage(size_t index);
+
+        /**
+         * Decrease the amount of usages for a certain material via its index.
+         *
+         * @param[in] index Index of a material
+         */
+		void decreaseMaterialUsage(size_t index);
+
+        /**
+         * Load a material from a scene preloaded with the asset loader via
+         * its index.
+         *
+         * @param[in] index Valid index of a material
+         * @param[in] scene Scene structure from asset loader
+         * @param[in] material Material structure from asset loader
+         */
+		void loadMaterial(size_t index, const asset::Scene& scene,
+						  const asset::Material& material);
+		
+	public:
+        /**
+         * Destructor of a scene instance.
+         */
+		~Scene();
+
+        /**
+         * Copy-constructor of a scene instance.
+         *
+         * @param[in] other Other scene instance
+         */
+		Scene(const Scene& other);
+
+        /**
+         * Move-constructor of a scene instance.
+         *
+         * @param[in,out] other Other scene instance
+         */
+        Scene(Scene&& other) noexcept;
+
+        /**
+         * Copy-operator of a scene instance.
+         *
+         * @param[in] other Other scene instance
+         * @return Reference to this scene
+         */
+		Scene& operator=(const Scene& other);
+
+        /**
+         * Move-operator of a scene instance.
+         *
+         * @param[in,out] other Other scene instance
+         * @return Reference to this scene
+         */
+        Scene& operator=(Scene&& other) noexcept;
+
+        /**
+         * Return the amount of materials managed by this scene.
+         *
+         * @return Amount of materials
+         */
+        [[nodiscard]]
+		size_t getMaterialCount() const;
+
+        /**
+         * Get the material data by its certain index.
+         * The material can still be invalid if it was not loaded properly.
+         *
+         * @param[in] index Valid index of material
+         * @return Material
+         */
+		[[nodiscard]]
+		const material::Material& getMaterial(size_t index) const;
+
+        /**
+         * Record drawcalls of all meshes of this scene with CPU-side frustum culling.
+         *
+         * @param cmdStream Command stream handle
+         * @param camera Scene viewing camera
+         * @param pass Render pass handle
+         * @param pipeline Graphics pipeline handle
+         * @param pushConstantsSizePerDrawcall Size of push constants per drawcall
+         * @param record Drawcall recording event function
+         * @param renderTargets Actual render targets
+         * @param windowHandle Window handle to use
+         */
+		void recordDrawcalls(CommandStreamHandle       		  &cmdStream,
+							 const camera::Camera			  &camera,
+							 const PassHandle                 &pass,
+							 const GraphicsPipelineHandle     &pipeline,
+							 size_t							  pushConstantsSizePerDrawcall,
+							 const RecordMeshDrawcallFunction &record,
+							 const std::vector<ImageHandle>   &renderTargets,
+							 const WindowHandle               &windowHandle);
+
+        /**
+         * Instantiation function to create a new scene instance.
+         *
+         * @param[in,out] core Current Core instance
+         * @return New scene instance
+         */
+		static Scene create(Core& core);
+
+        /**
+         * Load function to create a new scene instance with materials and meshes
+         * loaded from a file using the asset loader.
+         *
+         * @param[in,out] core Current Core instance
+         * @param[in] path Path of a valid file to load via asset loader
+         * @param[in] types Primitive type order of vertex attributes
+         * @return New scene instance
+         */
+		static Scene load(Core& core,
+						  const std::filesystem::path &path,
+						  const std::vector<asset::PrimitiveType>& types);
+		
+	};
+
+    /** @} */
+	
+}
\ No newline at end of file
diff --git a/modules/scene/src/vkcv/scene/Bounds.cpp b/modules/scene/src/vkcv/scene/Bounds.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9614208d2d523117eef09e1432ff755005b5c51f
--- /dev/null
+++ b/modules/scene/src/vkcv/scene/Bounds.cpp
@@ -0,0 +1,131 @@
+
+#include "vkcv/scene/Bounds.hpp"
+
+namespace vkcv::scene {
+	
+	Bounds::Bounds() :
+	m_min(glm::vec3(0)),
+	m_max(glm::vec3(0)) {}
+	
+	Bounds::Bounds(const glm::vec3 &point) :
+	m_min(point),
+	m_max(point)
+	{}
+	
+	Bounds::Bounds(const glm::vec3 &min, const glm::vec3 &max) :
+	m_min(min),
+	m_max(max)
+	{}
+
+	void Bounds::setMin(const glm::vec3 &min) {
+		m_min = min;
+	}
+	
+	const glm::vec3 & Bounds::getMin() const {
+		return m_min;
+	}
+	
+	void Bounds::setMax(const glm::vec3 &max) {
+		m_max = max;
+	}
+	
+	const glm::vec3 & Bounds::getMax() const {
+		return m_max;
+	}
+	
+	void Bounds::setCenter(const glm::vec3 &center) {
+		const glm::vec3 size = getSize();
+		m_min = center - size / 2.0f;
+		m_max = center + size / 2.0f;
+	}
+	
+	glm::vec3 Bounds::getCenter() const {
+		return (m_min + m_max) / 2.0f;
+	}
+	
+	void Bounds::setSize(const glm::vec3 &size) {
+		const glm::vec3 center = getCenter();
+		m_min = center - size / 2.0f;
+		m_max = center + size / 2.0f;
+	}
+	
+	glm::vec3 Bounds::getSize() const {
+		return (m_max - m_min);
+	}
+	
+	std::array<glm::vec3, 8> Bounds::getCorners() const {
+		return {
+			m_min,
+			glm::vec3(m_min[0], m_min[1], m_max[2]),
+			glm::vec3(m_min[0], m_max[1], m_min[2]),
+			glm::vec3(m_min[0], m_max[1], m_max[2]),
+			glm::vec3(m_max[0], m_min[1], m_min[2]),
+			glm::vec3(m_max[0], m_min[1], m_max[2]),
+			glm::vec3(m_max[0], m_max[1], m_min[2]),
+			m_max
+		};
+	}
+	
+	void Bounds::extend(const glm::vec3 &point) {
+		m_min = glm::vec3(
+				std::min(m_min[0], point[0]),
+				std::min(m_min[1], point[1]),
+				std::min(m_min[2], point[2])
+		);
+		
+		m_max = glm::vec3(
+				std::max(m_max[0], point[0]),
+				std::max(m_max[1], point[1]),
+				std::max(m_max[2], point[2])
+		);
+	}
+	
+	bool Bounds::contains(const glm::vec3 &point) const {
+		return (
+				(point[0] >= m_min[0]) && (point[0] <= m_max[0]) &&
+				(point[1] >= m_min[1]) && (point[1] <= m_max[1]) &&
+				(point[2] >= m_min[2]) && (point[2] <= m_max[2])
+		);
+	}
+	
+	bool Bounds::contains(const Bounds &other) const {
+		return (
+				(other.m_min[0] >= m_min[0]) && (other.m_max[0] <= m_max[0]) &&
+				(other.m_min[1] >= m_min[1]) && (other.m_max[1] <= m_max[1]) &&
+				(other.m_min[2] >= m_min[2]) && (other.m_max[2] <= m_max[2])
+		);
+	}
+	
+	bool Bounds::intersects(const Bounds &other) const {
+		return (
+				(other.m_max[0] >= m_min[0]) && (other.m_min[0] <= m_max[0]) &&
+				(other.m_max[1] >= m_min[1]) && (other.m_min[1] <= m_max[1]) &&
+				(other.m_max[2] >= m_min[2]) && (other.m_min[2] <= m_max[2])
+		);
+	}
+	
+	Bounds::operator bool() const {
+		return (
+				(m_min[0] <= m_max[0]) &&
+				(m_min[1] <= m_max[1]) &&
+				(m_min[2] <= m_max[2])
+		);
+	}
+	
+	bool Bounds::operator!() const {
+		return (
+				(m_min[0] > m_max[0]) ||
+				(m_min[1] > m_max[1]) ||
+				(m_min[2] > m_max[2])
+		);
+	}
+	
+	std::ostream& operator << (std::ostream& out, const Bounds& bounds) {
+		const auto& min = bounds.getMin();
+		const auto& max = bounds.getMax();
+		
+		return out << "[Bounds: (" << min[0] << ", " << min[1] << ", " << min[2] << ") ("
+								   << max[0] << ", " << max[1] << ", " << max[2] << ") ]";
+	}
+	
+}
diff --git a/modules/scene/src/vkcv/scene/Frustum.cpp b/modules/scene/src/vkcv/scene/Frustum.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..10af3bc44e4f585f990577bd9d95ecfade7acca8
--- /dev/null
+++ b/modules/scene/src/vkcv/scene/Frustum.cpp
@@ -0,0 +1,68 @@
+
+#include "vkcv/scene/Frustum.hpp"
+
+namespace vkcv::scene {
+	
+	static glm::vec3 transformPoint(const glm::mat4& transform, const glm::vec3& point, bool& negative_w) {
+		const glm::vec4 position = transform * glm::vec4(point, 1.0f);
+		
+		/*
+		 * We divide by the absolute of the 4th coorditnate because
+		 * clipping is weird and points have to move to the other
+		 * side of the camera.
+		 *
+		 * We also need to collect if the 4th coordinate was negative
+		 * to know if all corners are behind the camera. So these can
+		 * be culled as well
+		 */
+		const float perspective = std::abs(position[3]);
+		negative_w &= (position[3] < 0.0f);
+		return glm::vec3(
+				position[0] / perspective,
+				position[1] / perspective,
+				position[2] / perspective
+		);
+	}
+	
+	Bounds transformBounds(const glm::mat4& transform, const Bounds& bounds) {
+		const auto corners = bounds.getCorners();
+		
+		Bounds result (glm::vec3(transform * glm::vec4(corners[0], 1.0f)));
+		
+		for (size_t j = 1; j < corners.size(); j++) {
+			result.extend(glm::vec3(transform * glm::vec4(corners[j], 1.0f)));
+		}
+		
+		return result;
+	}
+	
+	Bounds transformBounds(const glm::mat4& transform, const Bounds& bounds, bool& negative_w) {
+		const auto corners = bounds.getCorners();
+		negative_w = true;
+		
+		Bounds result (transformPoint(transform, corners[0], negative_w));
+		
+		for (size_t j = 1; j < corners.size(); j++) {
+			result.extend(transformPoint(transform, corners[j], negative_w));
+		}
+		
+		return result;
+	}
+	
+	bool checkFrustum(const glm::mat4& transform, const Bounds& bounds) {
+		static Bounds frustum (
+				glm::vec3(-1.0f, -1.0f, -0.0f),
+				glm::vec3(+1.0f, +1.0f, +1.0f)
+		);
+		
+		bool negative_w;
+		auto box = transformBounds(transform, bounds, negative_w);
+		
+		if (negative_w) {
+			return false;
+		} else {
+			return box.intersects(frustum);
+		}
+	}
+
+}
diff --git a/modules/scene/src/vkcv/scene/Mesh.cpp b/modules/scene/src/vkcv/scene/Mesh.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3c6249fb556c9c2d7e08b41636718be9ef80f829
--- /dev/null
+++ b/modules/scene/src/vkcv/scene/Mesh.cpp
@@ -0,0 +1,134 @@
+
+#include "vkcv/scene/Mesh.hpp"
+#include "vkcv/scene/Scene.hpp"
+#include "vkcv/scene/Frustum.hpp"
+
+namespace vkcv::scene {
+	
+	Mesh::Mesh(Scene& scene) :
+	m_scene(scene) {}
+	
+	static glm::mat4 arrayTo4x4Matrix(const std::array<float,16>& array){
+		glm::mat4 matrix;
+		
+		for (int i = 0; i < 4; i++){
+			for (int j = 0; j < 4; j++){
+				matrix[i][j] = array[j * 4 + i];
+			}
+		}
+		
+		return matrix;
+	}
+	
+	void Mesh::load(const asset::Scene &scene,
+					const asset::Mesh &mesh,
+					const std::vector<asset::PrimitiveType>& types) {
+		m_parts.clear();
+		m_drawcalls.clear();
+		
+		m_transform = arrayTo4x4Matrix(mesh.modelMatrix);
+		
+		for (const auto& vertexGroupIndex : mesh.vertexGroups) {
+			if ((vertexGroupIndex < 0) || (vertexGroupIndex >= scene.vertexGroups.size())) {
+				continue;
+			}
+			
+			MeshPart part (m_scene);
+			part.load(scene, scene.vertexGroups[vertexGroupIndex], types, m_drawcalls);
+			
+			if (!part) {
+				continue;
+			}
+			
+			auto bounds = transformBounds(m_transform, part.getBounds());
+			
+			if (m_parts.empty()) {
+				m_bounds = bounds;
+			} else {
+				m_bounds.extend(bounds.getMin());
+				m_bounds.extend(bounds.getMax());
+			}
+			
+			m_parts.push_back(part);
+		}
+	}
+	
+	Mesh::~Mesh() {
+		m_drawcalls.clear();
+		m_parts.clear();
+	}
+	
+	Mesh &Mesh::operator=(const Mesh &other) {
+		if (&other == this) {
+			return *this;
+		}
+		
+		m_parts.resize(other.m_parts.size(), MeshPart(m_scene));
+		
+		for (size_t i = 0; i < m_parts.size(); i++) {
+			m_parts[i] = other.m_parts[i];
+		}
+		
+		m_drawcalls = std::vector<InstanceDrawcall>(other.m_drawcalls);
+		m_transform = other.m_transform;
+		m_bounds = other.m_bounds;
+		
+		return *this;
+	}
+	
+	Mesh &Mesh::operator=(Mesh &&other) noexcept {
+		m_parts.resize(other.m_parts.size(), MeshPart(m_scene));
+		
+		for (size_t i = 0; i < m_parts.size(); i++) {
+			m_parts[i] = std::move(other.m_parts[i]);
+		}
+		
+		m_drawcalls = std::move(other.m_drawcalls);
+		m_transform = other.m_transform;
+		m_bounds = other.m_bounds;
+		
+		return *this;
+	}
+	
+	void Mesh::recordDrawcalls(const glm::mat4& viewProjection,
+							   PushConstants& pushConstants,
+							   std::vector<InstanceDrawcall>& drawcalls,
+							   const RecordMeshDrawcallFunction& record) {
+		const glm::mat4 transform = viewProjection * m_transform;
+		
+		if (!checkFrustum(viewProjection, m_bounds)) {
+			return;
+		}
+		
+		if (m_drawcalls.size() == 1) {
+			drawcalls.push_back(m_drawcalls[0]);
+			
+			if (record) {
+				record(transform, m_transform, pushConstants, drawcalls.back());
+			}
+		} else {
+			for (size_t i = 0; i < m_parts.size(); i++) {
+				const MeshPart& part = m_parts[i];
+				
+				if (!checkFrustum(transform, part.getBounds())) {
+					continue;
+				}
+				
+				drawcalls.push_back(m_drawcalls[i]);
+				
+				if (record) {
+					record(transform, m_transform, pushConstants, drawcalls.back());
+				}
+			}
+		}
+	}
+	
+	size_t Mesh::getDrawcallCount() const {
+		return m_drawcalls.size();
+	}
+	
+	const Bounds& Mesh::getBounds() const {
+		return m_bounds;
+	}
+
+}
diff --git a/modules/scene/src/vkcv/scene/MeshPart.cpp b/modules/scene/src/vkcv/scene/MeshPart.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..959148b0c82f9ddc4068e669f56f9936f5462055
--- /dev/null
+++ b/modules/scene/src/vkcv/scene/MeshPart.cpp
@@ -0,0 +1,161 @@
+
+#include "vkcv/scene/MeshPart.hpp"
+#include "vkcv/scene/Scene.hpp"
+
+#include <vkcv/Buffer.hpp>
+
+namespace vkcv::scene {
+	
+	MeshPart::MeshPart(Scene& scene) :
+	m_scene(scene),
+	m_data(),
+	m_bounds(),
+	m_materialIndex(std::numeric_limits<size_t>::max()) {}
+	
+	void MeshPart::load(const asset::Scene& scene,
+						const asset::VertexGroup &vertexGroup,
+						const std::vector<asset::PrimitiveType>& types,
+						std::vector<InstanceDrawcall>& drawcalls) {
+		Core& core = *(m_scene.m_core);
+		
+		auto vertexBuffer = buffer<uint8_t>(
+				core, BufferType::VERTEX, vertexGroup.vertexBuffer.data.size()
+		);
+		
+		vertexBuffer.fill(vertexGroup.vertexBuffer.data);
+		
+		m_data = VertexData(
+				asset::loadVertexBufferBindings(
+						vertexGroup.vertexBuffer.attributes,
+						vertexBuffer.getHandle(),
+						types
+				)
+		);
+		
+		if (!vertexGroup.indexBuffer.data.empty()) {
+			auto indexBuffer = buffer<uint8_t>(
+					core, BufferType::INDEX, vertexGroup.indexBuffer.data.size()
+			);
+			
+			indexBuffer.fill(vertexGroup.indexBuffer.data);
+			
+			IndexBitCount indexBitCount;
+			switch (vertexGroup.indexBuffer.type) {
+				case asset::IndexType::UINT8:
+					indexBitCount = IndexBitCount::Bit8;
+					break;
+				case asset::IndexType::UINT16:
+					indexBitCount = IndexBitCount::Bit16;
+					break;
+				case asset::IndexType::UINT32:
+					indexBitCount = IndexBitCount::Bit32;
+					break;
+				default:
+					indexBitCount = IndexBitCount::Bit16;
+					vkcv_log(LogLevel::WARNING, "Unsupported index type!");
+					break;
+			}
+			
+			m_data.setIndexBuffer(indexBuffer.getHandle(), indexBitCount);
+			m_data.setCount(vertexGroup.numIndices);
+		} else {
+			m_data.setCount(vertexGroup.numVertices);
+		}
+		
+		m_bounds.setMin(glm::vec3(
+				vertexGroup.min.x,
+				vertexGroup.min.y,
+				vertexGroup.min.z
+		));
+		
+		m_bounds.setMax(glm::vec3(
+				vertexGroup.max.x,
+				vertexGroup.max.y,
+				vertexGroup.max.z
+		));
+		
+		if ((vertexGroup.materialIndex >= 0) &&
+			(vertexGroup.materialIndex < scene.materials.size())) {
+			m_materialIndex = vertexGroup.materialIndex;
+			
+			if (!getMaterial()) {
+				m_scene.loadMaterial(m_materialIndex, scene, scene.materials[vertexGroup.materialIndex]);
+			}
+			
+			m_scene.increaseMaterialUsage(m_materialIndex);
+		} else {
+			m_materialIndex = std::numeric_limits<size_t>::max();
+		}
+		
+		if (*this) {
+			const auto& material = getMaterial();
+			
+			InstanceDrawcall drawcall (m_data);
+			drawcall.useDescriptorSet(0, material.getDescriptorSet());
+			drawcalls.push_back(drawcall);
+		}
+	}
+	
+	MeshPart::~MeshPart() {
+		m_scene.decreaseMaterialUsage(m_materialIndex);
+	}
+	
+	MeshPart::MeshPart(const MeshPart &other) :
+			m_scene(other.m_scene),
+			m_data(other.m_data),
+			m_bounds(other.m_bounds),
+			m_materialIndex(other.m_materialIndex) {
+		m_scene.increaseMaterialUsage(m_materialIndex);
+	}
+	
+	MeshPart::MeshPart(MeshPart &&other) noexcept :
+			m_scene(other.m_scene),
+			m_data(other.m_data),
+			m_bounds(other.m_bounds),
+			m_materialIndex(other.m_materialIndex) {
+		m_scene.increaseMaterialUsage(m_materialIndex);
+	}
+	
+	MeshPart &MeshPart::operator=(const MeshPart &other) {
+		if (&other == this) {
+			return *this;
+		}
+		
+		m_data = other.m_data;
+		m_bounds = other.m_bounds;
+		m_materialIndex = other.m_materialIndex;
+		
+		return *this;
+	}
+	
+	MeshPart &MeshPart::operator=(MeshPart &&other) noexcept {
+		m_data = other.m_data;
+		m_bounds = other.m_bounds;
+		m_materialIndex = other.m_materialIndex;
+		
+		return *this;
+	}
+	
+	const material::Material & MeshPart::getMaterial() const {
+		return m_scene.getMaterial(m_materialIndex);
+	}
+	
+	MeshPart::operator bool() const {
+		return (
+				(getMaterial()) &&
+				(m_data.getCount() > 0)
+		);
+	}
+	
+	bool MeshPart::operator!() const {
+		return (
+				(!getMaterial()) ||
+				(m_data.getCount() <= 0)
+		);
+	}
+	
+	const Bounds &MeshPart::getBounds() const {
+		return m_bounds;
+	}
+	
+}
diff --git a/modules/scene/src/vkcv/scene/Node.cpp b/modules/scene/src/vkcv/scene/Node.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f5234dd461537c491c0fa5580c78fbfec695a310
--- /dev/null
+++ b/modules/scene/src/vkcv/scene/Node.cpp
@@ -0,0 +1,191 @@
+
+#include "vkcv/scene/Node.hpp"
+#include "vkcv/scene/Scene.hpp"
+#include "vkcv/scene/Frustum.hpp"
+
+#include <algorithm>
+
+namespace vkcv::scene {
+	
+	Node::Node(Scene& scene) :
+	m_scene(scene),
+	m_meshes(),
+	m_nodes(),
+	m_bounds() {}
+	
+	Node::~Node() {
+		m_nodes.clear();
+		m_meshes.clear();
+	}
+	
+	Node &Node::operator=(const Node &other) {
+		if (&other == this) {
+			return *this;
+		}
+		
+		m_meshes.resize(other.m_meshes.size(), Mesh(m_scene));
+		
+		for (size_t i = 0; i < m_meshes.size(); i++) {
+			m_meshes[i] = other.m_meshes[i];
+		}
+		
+		m_nodes.resize(other.m_nodes.size(), Node(m_scene));
+		
+		for (size_t i = 0; i < m_nodes.size(); i++) {
+			m_nodes[i] = other.m_nodes[i];
+		}
+		
+		m_bounds = other.m_bounds;
+		
+		return *this;
+	}
+	
+	Node &Node::operator=(Node &&other) noexcept {
+		m_meshes.resize(other.m_meshes.size(), Mesh(m_scene));
+		
+		for (size_t i = 0; i < m_meshes.size(); i++) {
+			m_meshes[i] = std::move(other.m_meshes[i]);
+		}
+		
+		m_nodes.resize(other.m_nodes.size(), Node(m_scene));
+		
+		for (size_t i = 0; i < m_nodes.size(); i++) {
+			m_nodes[i] = std::move(other.m_nodes[i]);
+		}
+		
+		m_bounds = other.m_bounds;
+		
+		return *this;
+	}
+	
+	void Node::addMesh(const Mesh& mesh) {
+		if (m_meshes.empty()) {
+			m_bounds = mesh.getBounds();
+		} else {
+			m_bounds.extend(mesh.getBounds().getMin());
+			m_bounds.extend(mesh.getBounds().getMax());
+		}
+		
+		m_meshes.push_back(mesh);
+	}
+	
+	void Node::loadMesh(const asset::Scene &asset_scene,
+						const asset::Mesh &asset_mesh,
+						const std::vector<asset::PrimitiveType>& types) {
+		Mesh mesh (m_scene);
+		mesh.load(asset_scene, asset_mesh, types);
+		addMesh(mesh);
+	}
+	
+	size_t Node::addNode() {
+		const Node node (m_scene);
+		const size_t index = m_nodes.size();
+		m_nodes.push_back(node);
+		return index;
+	}
+	
+	Node& Node::getNode(size_t index) {
+		return m_nodes[index];
+	}
+	
+	const Node& Node::getNode(size_t index) const {
+		return m_nodes[index];
+	}
+	
+	void Node::recordDrawcalls(const glm::mat4& viewProjection,
+							   PushConstants& pushConstants,
+							   std::vector<InstanceDrawcall>& drawcalls,
+							   const RecordMeshDrawcallFunction& record) {
+		if (!checkFrustum(viewProjection, m_bounds)) {
+			return;
+		}
+		
+		for (auto& mesh : m_meshes) {
+			mesh.recordDrawcalls(viewProjection, pushConstants, drawcalls, record);
+		}
+		
+		for (auto& node : m_nodes) {
+			node.recordDrawcalls(viewProjection, pushConstants, drawcalls, record);
+		}
+	}
+	
+	void Node::splitMeshesToSubNodes(size_t maxMeshesPerNode) {
+		if (m_meshes.size() <= maxMeshesPerNode) {
+			return;
+		}
+		
+		const auto split = m_bounds.getCenter();
+		int axis = 0;
+		
+		const auto size = m_bounds.getSize();
+		
+		if (size[1] > size[0]) {
+			if (size[2] > size[1]) {
+				axis = 2;
+			} else {
+				axis = 1;
+			}
+		} else
+		if (size[2] > size[0]) {
+			axis = 2;
+		}
+		
+		std::vector<size_t> left_meshes;
+		std::vector<size_t> right_meshes;
+		
+		for (size_t i = 0; i < m_meshes.size(); i++) {
+			const auto& bounds = m_meshes[i].getBounds();
+			
+			if (bounds.getMax()[axis] <= split[axis]) {
+				left_meshes.push_back(i);
+			} else
+			if (bounds.getMin()[axis] >= split[axis]) {
+				right_meshes.push_back(i);
+			}
+		}
+		
+		if ((left_meshes.empty()) || (right_meshes.empty())) {
+			return;
+		}
+		
+		const size_t left = addNode();
+		const size_t right = addNode();
+		
+		for (size_t i : left_meshes) {
+			getNode(left).addMesh(m_meshes[i]);
+		}
+		
+		for (size_t i : right_meshes) {
+			getNode(right).addMesh(m_meshes[i]);
+			left_meshes.push_back(i);
+		}
+		
+		std::sort(left_meshes.begin(), left_meshes.end(), std::greater());
+		
+		for (size_t i : left_meshes) {
+			m_meshes.erase(m_meshes.begin() + static_cast<long>(i));
+		}
+		
+		getNode(left).splitMeshesToSubNodes(maxMeshesPerNode);
+		getNode(right).splitMeshesToSubNodes(maxMeshesPerNode);
+	}
+	
+	size_t Node::getDrawcallCount() const {
+		size_t count = 0;
+		
+		for (auto& mesh : m_meshes) {
+			count += mesh.getDrawcallCount();
+		}
+		
+		for (auto& node : m_nodes) {
+			count += node.getDrawcallCount();
+		}
+		
+		return count;
+	}
+	
+	const Bounds& Node::getBounds() const {
+		return m_bounds;
+	}
+
+}
diff --git a/modules/scene/src/vkcv/scene/Scene.cpp b/modules/scene/src/vkcv/scene/Scene.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dfe3ac54e6f63922218edd607432d9b291dcc168
--- /dev/null
+++ b/modules/scene/src/vkcv/scene/Scene.cpp
@@ -0,0 +1,361 @@
+
+#include "vkcv/scene/Scene.hpp"
+
+#include <vkcv/Image.hpp>
+#include <vkcv/Logger.hpp>
+#include <vkcv/Sampler.hpp>
+#include <vkcv/asset/asset_loader.hpp>
+
+#include <vkcv/algorithm/SinglePassDownsampler.hpp>
+
+namespace vkcv::scene {
+	
+	Scene::Scene(Core* core) :
+	m_core(core),
+	m_materials(),
+	m_nodes() {}
+	
+	Scene::~Scene() {
+		m_nodes.clear();
+		m_materials.clear();
+	}
+	
+	Scene::Scene(const Scene &other) :
+	m_core(other.m_core),
+	m_materials(other.m_materials),
+	m_nodes() {
+		m_nodes.resize(other.m_nodes.size(), Node(*this));
+		
+		for (size_t i = 0; i < m_nodes.size(); i++) {
+			m_nodes[i] = other.m_nodes[i];
+		}
+	}
+	
+	Scene::Scene(Scene &&other) noexcept :
+	m_core(other.m_core),
+	m_materials(other.m_materials),
+	m_nodes() {
+		m_nodes.resize(other.m_nodes.size(), Node(*this));
+		
+		for (size_t i = 0; i < m_nodes.size(); i++) {
+			m_nodes[i] = std::move(other.m_nodes[i]);
+		}
+	}
+	
+	Scene &Scene::operator=(const Scene &other) {
+		if (&other == this) {
+			return *this;
+		}
+		
+		m_core = other.m_core;
+		m_materials = std::vector<Material>(other.m_materials);
+		
+		m_nodes.resize(other.m_nodes.size(), Node(*this));
+		
+		for (size_t i = 0; i < m_nodes.size(); i++) {
+			m_nodes[i] = other.m_nodes[i];
+		}
+		
+		return *this;
+	}
+	
+	Scene &Scene::operator=(Scene &&other) noexcept {
+		m_core = other.m_core;
+		m_materials = std::move(other.m_materials);
+		
+		m_nodes.resize(other.m_nodes.size(), Node(*this));
+		
+		for (size_t i = 0; i < m_nodes.size(); i++) {
+			m_nodes[i] = std::move(other.m_nodes[i]);
+		}
+		
+		return *this;
+	}
+	
+	size_t Scene::addNode() {
+		const Node node (*this);
+		const size_t index = m_nodes.size();
+		m_nodes.push_back(node);
+		return index;
+	}
+	
+	Node& Scene::getNode(size_t index) {
+		return m_nodes[index];
+	}
+	
+	const Node& Scene::getNode(size_t index) const {
+		return m_nodes[index];
+	}
+	
+	void Scene::increaseMaterialUsage(size_t index) {
+		if (index < m_materials.size()) {
+			m_materials[index].m_usages++;
+		}
+	}
+	
+	void Scene::decreaseMaterialUsage(size_t index) {
+		if ((index < m_materials.size()) && (m_materials[index].m_usages > 0)) {
+			m_materials[index].m_usages--;
+		}
+	}
+	
+	size_t Scene::getMaterialCount() const {
+		return m_materials.size();
+	}
+	
+	const material::Material & Scene::getMaterial(size_t index) const {
+		static material::Material noMaterial;
+		
+		if (index >= m_materials.size()) {
+			return noMaterial;
+		}
+		
+		return m_materials[index].m_data;
+	}
+	
+	void Scene::recordDrawcalls(CommandStreamHandle       		 &cmdStream,
+								const camera::Camera			 &camera,
+								const PassHandle                 &pass,
+								const GraphicsPipelineHandle     &pipeline,
+								size_t							 pushConstantsSizePerDrawcall,
+								const RecordMeshDrawcallFunction &record,
+								const std::vector<ImageHandle>   &renderTargets,
+								const WindowHandle               &windowHandle) {
+		m_core->recordBeginDebugLabel(cmdStream, "vkcv::scene::Scene", {
+			0.0f, 1.0f, 0.0f, 1.0f
+		});
+		
+		PushConstants pushConstants (pushConstantsSizePerDrawcall);
+		std::vector<InstanceDrawcall> drawcalls;
+		size_t count = 0;
+		
+		const glm::mat4 viewProjection = camera.getMVP();
+		
+		for (auto& node : m_nodes) {
+			count += node.getDrawcallCount();
+			node.recordDrawcalls(viewProjection, pushConstants, drawcalls, record);
+		}
+		
+		vkcv_log(LogLevel::RAW_INFO, "Frustum culling: %lu / %lu", drawcalls.size(), count);
+		
+		m_core->recordDrawcallsToCmdStream(
+				cmdStream,
+				pipeline,
+				pushConstants,
+				drawcalls,
+				renderTargets,
+				windowHandle
+		);
+		
+		m_core->recordEndDebugLabel(cmdStream);
+	}
+	
+	Scene Scene::create(Core& core) {
+		return Scene(&core);
+	}
+	
+	static void loadImage(Core& core, const asset::Scene& asset_scene,
+						  const asset::Texture& asset_texture,
+						  const vk::Format& format,
+						  ImageHandle& image, SamplerHandle& sampler) {
+		const asset::Sampler* asset_sampler = nullptr;
+		
+		if ((asset_texture.sampler >= 0) && (asset_texture.sampler < asset_scene.samplers.size())) {
+			asset_sampler = &(asset_scene.samplers[asset_texture.sampler]);
+		}
+		
+		Image img = vkcv::image(core, format, asset_texture.w, asset_texture.h, 1, true);
+		img.fill(asset_texture.data.data());
+		image = img.getHandle();
+		
+		SamplerFilterType magFilter = SamplerFilterType::LINEAR;
+		SamplerFilterType minFilter = SamplerFilterType::LINEAR;
+		SamplerMipmapMode mipmapMode = SamplerMipmapMode::LINEAR;
+		SamplerAddressMode addressMode = SamplerAddressMode::REPEAT;
+		
+		float mipLodBias = 0.0f;
+		
+		if (asset_sampler) {
+			switch (asset_sampler->magFilter) {
+				case VK_FILTER_NEAREST:
+					magFilter = SamplerFilterType::NEAREST;
+					break;
+				case VK_FILTER_LINEAR:
+					magFilter = SamplerFilterType::LINEAR;
+					break;
+				default:
+					break;
+			}
+			
+			switch (asset_sampler->minFilter) {
+				case VK_FILTER_NEAREST:
+					minFilter = SamplerFilterType::NEAREST;
+					break;
+				case VK_FILTER_LINEAR:
+					minFilter = SamplerFilterType::LINEAR;
+					break;
+				default:
+					break;
+			}
+			
+			switch (asset_sampler->mipmapMode) {
+				case VK_SAMPLER_MIPMAP_MODE_NEAREST:
+					mipmapMode = SamplerMipmapMode::NEAREST;
+					break;
+				case VK_SAMPLER_MIPMAP_MODE_LINEAR:
+					mipmapMode = SamplerMipmapMode::LINEAR;
+					break;
+				default:
+					break;
+			}
+			
+			switch (asset_sampler->addressModeU) {
+				case VK_SAMPLER_ADDRESS_MODE_REPEAT:
+					addressMode = SamplerAddressMode::REPEAT;
+					break;
+				case VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT:
+					addressMode = SamplerAddressMode::MIRRORED_REPEAT;
+					break;
+				case VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE:
+					addressMode = SamplerAddressMode::CLAMP_TO_EDGE;
+					break;
+				case VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE:
+					addressMode = SamplerAddressMode::MIRROR_CLAMP_TO_EDGE;
+					break;
+				case VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER:
+					addressMode = SamplerAddressMode::CLAMP_TO_BORDER;
+					break;
+				default:
+					break;
+			}
+			
+			mipLodBias = asset_sampler->minLOD;
+		}
+		
+		sampler = core.createSampler(
+				magFilter,
+				minFilter,
+				mipmapMode,
+				addressMode,
+				mipLodBias
+		);
+	}
+	
+	void Scene::loadMaterial(size_t index, const asset::Scene& scene,
+							 const asset::Material& material) {
+		if (index >= m_materials.size()) {
+			return;
+		}
+		
+		ImageHandle diffuseImg;
+		SamplerHandle diffuseSmp;
+		
+		if ((material.baseColor >= 0) && (material.baseColor < scene.textures.size())) {
+			loadImage(*m_core, scene, scene.textures[material.baseColor], vk::Format::eR8G8B8A8Srgb,
+					  diffuseImg, diffuseSmp);
+		}
+		
+		ImageHandle normalImg;
+		SamplerHandle normalSmp;
+		
+		if ((material.normal >= 0) && (material.normal < scene.textures.size())) {
+			loadImage(*m_core, scene, scene.textures[material.normal], vk::Format::eR8G8B8A8Unorm,
+					  normalImg, normalSmp);
+		}
+		
+		ImageHandle metalRoughImg;
+		SamplerHandle metalRoughSmp;
+		
+		if ((material.metalRough >= 0) && (material.metalRough < scene.textures.size())) {
+			loadImage(*m_core, scene, scene.textures[material.metalRough], vk::Format::eR8G8B8A8Unorm,
+					  metalRoughImg, metalRoughSmp);
+		}
+		
+		ImageHandle occlusionImg;
+		SamplerHandle occlusionSmp;
+		
+		if ((material.occlusion >= 0) && (material.occlusion < scene.textures.size())) {
+			loadImage(*m_core, scene, scene.textures[material.occlusion], vk::Format::eR8G8B8A8Unorm,
+					  occlusionImg, occlusionSmp);
+		}
+		
+		ImageHandle emissionImg;
+		SamplerHandle emissionSmp;
+		
+		if ((material.emissive >= 0) && (material.emissive < scene.textures.size())) {
+			loadImage(*m_core, scene, scene.textures[material.emissive], vk::Format::eR8G8B8A8Srgb,
+					  emissionImg, emissionSmp);
+		}
+		
+		const float colorFactors [4] = {
+				material.baseColorFactor.r,
+				material.baseColorFactor.g,
+				material.baseColorFactor.b,
+				material.baseColorFactor.a
+		};
+		
+		const float emissionFactors[4] = {
+				material.emissiveFactor.r,
+				material.emissiveFactor.g,
+				material.emissiveFactor.b,
+				0.0f
+		};
+		
+		m_materials[index].m_data = material::Material::createPBR(
+				*m_core,
+				diffuseImg, diffuseSmp,
+				normalImg, normalSmp,
+				metalRoughImg, metalRoughSmp,
+				occlusionImg, occlusionSmp,
+				emissionImg, emissionSmp,
+				colorFactors,
+				material.normalScale,
+				material.metallicFactor,
+				material.roughnessFactor,
+				material.occlusionStrength,
+				emissionFactors
+		);
+	}
+	
+	Scene Scene::load(Core& core,
+					  const std::filesystem::path &path,
+					  const std::vector<asset::PrimitiveType>& types) {
+		asset::Scene asset_scene;
+		
+		if (!asset::loadScene(path.string(), asset_scene)) {
+			vkcv_log(LogLevel::ERROR, "Scene could not be loaded (%s)", path.c_str());
+			return create(core);
+		}
+		
+		Scene scene = create(core);
+		
+		for (const auto& material : asset_scene.materials) {
+			scene.m_materials.push_back({
+				0, material::Material()
+			});
+		}
+		
+		const size_t root = scene.addNode();
+		
+		for (const auto& mesh : asset_scene.meshes) {
+			scene.getNode(root).loadMesh(asset_scene, mesh, types);
+		}
+		
+		vkcv::SamplerHandle sampler = samplerLinear(core);
+		
+		const vkcv::FeatureManager& featureManager = core.getContext().getFeatureManager();
+		
+		vkcv::algorithm::SinglePassDownsampler spdDownsampler (core, sampler);
+		auto mipStream = core.createCommandStream(vkcv::QueueType::Graphics);
+		
+		for (auto& material : scene.m_materials) {
+			material.m_data.recordMipChainGeneration(mipStream, spdDownsampler);
+		}
+		
+		core.submitCommandStream(mipStream, false);
+		
+		scene.getNode(root).splitMeshesToSubNodes(128);
+		return scene;
+	}
+	
+}
diff --git a/modules/shader_compiler/CMakeLists.txt b/modules/shader_compiler/CMakeLists.txt
index 4b674ec41ed4ea5f42dc73187c212e6a69952cec..63182aaf34ad9d09d3f9a3dca7360cbed1875c04 100644
--- a/modules/shader_compiler/CMakeLists.txt
+++ b/modules/shader_compiler/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.16)
 project(vkcv_shader_compiler)
 
 # setting c++ standard for the module
-set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD 20)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
 set(vkcv_shader_compiler_source ${PROJECT_SOURCE_DIR}/src)
@@ -10,12 +10,15 @@ set(vkcv_shader_compiler_include ${PROJECT_SOURCE_DIR}/include)
 
 # Add source and header files to the module
 set(vkcv_shader_compiler_sources
+		${vkcv_shader_compiler_include}/vkcv/shader/Compiler.hpp
+		${vkcv_shader_compiler_source}/vkcv/shader/Compiler.cpp
+		
 		${vkcv_shader_compiler_include}/vkcv/shader/GLSLCompiler.hpp
 		${vkcv_shader_compiler_source}/vkcv/shader/GLSLCompiler.cpp
 )
 
 # adding source files to the module
-add_library(vkcv_shader_compiler STATIC ${vkcv_shader_compiler_sources})
+add_library(vkcv_shader_compiler ${vkcv_build_attribute} ${vkcv_shader_compiler_sources})
 
 # Setup some path variables to load libraries
 set(vkcv_shader_compiler_lib lib)
@@ -28,7 +31,15 @@ include(config/GLSLANG.cmake)
 target_link_libraries(vkcv_shader_compiler ${vkcv_shader_compiler_libraries} vkcv)
 
 # including headers of dependencies and the VkCV framework
-target_include_directories(vkcv_shader_compiler SYSTEM BEFORE PRIVATE ${vkcv_shader_compiler_includes} ${vkcv_include})
+target_include_directories(vkcv_shader_compiler SYSTEM BEFORE PRIVATE ${vkcv_shader_compiler_includes} ${vkcv_include} ${vkcv_includes})
 
 # add the own include directory for public headers
 target_include_directories(vkcv_shader_compiler BEFORE PUBLIC ${vkcv_shader_compiler_include})
+
+if (vkcv_parent_scope)
+	list(APPEND vkcv_modules_includes ${vkcv_shader_compiler_include})
+	list(APPEND vkcv_modules_libraries vkcv_shader_compiler)
+	
+	set(vkcv_modules_includes ${vkcv_modules_includes} PARENT_SCOPE)
+	set(vkcv_modules_libraries ${vkcv_modules_libraries} PARENT_SCOPE)
+endif()
diff --git a/modules/shader_compiler/README.md b/modules/shader_compiler/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..3b64e10e5eb8de251de3ce2695e499e6e15f746b
--- /dev/null
+++ b/modules/shader_compiler/README.md
@@ -0,0 +1,15 @@
+# Shader-Compiler
+
+A VkCV module to compile shaders at runtime
+
+## Build
+
+### Dependencies (required):
+
+| Name of dependency | Used as submodule |
+|----------------------------------------------------|---|
+| [Glslang](https://github.com/KhronosGroup/glslang/)   | ✅ |
+
+## Docs
+
+Here is a [link](https://userpages.uni-koblenz.de/~vkcv/doc/group__vkcv__shader.html) to this module.
diff --git a/modules/shader_compiler/config/GLSLANG.cmake b/modules/shader_compiler/config/GLSLANG.cmake
index 50b9fd46bd0db9421c632aa0b80fb8df7e3f2123..98bd45497f9d7ed5196dbed486921f8e6ada12da 100644
--- a/modules/shader_compiler/config/GLSLANG.cmake
+++ b/modules/shader_compiler/config/GLSLANG.cmake
@@ -1,5 +1,7 @@
 
-if (EXISTS "${vkcv_shader_compiler_lib_path}/glslang")
+use_git_submodule("${vkcv_shader_compiler_lib_path}/glslang" glslang_status)
+
+if (${glslang_status})
 	set(SKIP_GLSLANG_INSTALL ON CACHE INTERNAL "")
 	set(ENABLE_SPVREMAPPER OFF CACHE INTERNAL "")
 	set(ENABLE_GLSLANG_BINARIES OFF CACHE INTERNAL "")
@@ -23,6 +25,4 @@ if (EXISTS "${vkcv_shader_compiler_lib_path}/glslang")
 	
 	list(APPEND vkcv_shader_compiler_libraries glslang SPIRV)
 	list(APPEND vkcv_shader_compiler_includes ${vkcv_shader_compiler_lib})
-else()
-	message(WARNING "GLSLANG is required..! Update the submodules!")
 endif ()
diff --git a/modules/shader_compiler/include/vkcv/shader/Compiler.hpp b/modules/shader_compiler/include/vkcv/shader/Compiler.hpp
index d7b7af7178531aea358cecbc8b86a29527173014..f62190753ec440c7d732551b6229276c5a42cfc1 100644
--- a/modules/shader_compiler/include/vkcv/shader/Compiler.hpp
+++ b/modules/shader_compiler/include/vkcv/shader/Compiler.hpp
@@ -1,17 +1,101 @@
 #pragma once
 
+#include <filesystem>
+#include <string>
+#include <unordered_map>
+
 #include <vkcv/Event.hpp>
+#include <vkcv/ShaderStage.hpp>
+#include <vkcv/ShaderProgram.hpp>
 
 namespace vkcv::shader {
-	
+
+    /**
+     * @defgroup vkcv_shader Shader Compiler Module
+     * A module to use runtime shader compilation.
+     * @{
+     */
+
+    /**
+     * An event function type to be called on compilation completion.
+     */
 	typedef typename event_function<ShaderStage, const std::filesystem::path&>::type ShaderCompiledFunction;
 	
+	/**
+     * An event function type to be called on program compilation completion.
+     */
+	typedef typename event_function<ShaderProgram&>::type ShaderProgramCompiledFunction;
+	
+	/**
+     * An abstract class to handle runtime shader compilation.
+     */
 	class Compiler {
 	private:
+	protected:
+        /**
+         * A map containing macros for shader compilation.
+         */
+		std::unordered_map<std::string, std::string> m_defines;
+		
 	public:
+        /**
+         * Compile a shader from source for a target stage with a custom shader
+         * include path and an event function called if the compilation completes.
+         * @param[in] shaderStage Shader pipeline stage
+         * @param[in] shaderSource Source of shader
+         * @param[in] compiled Shader compilation event
+         * @param[in] includePath Include path for shaders
+         * @return Result if the compilation succeeds
+         */
+		virtual bool compileSource(ShaderStage shaderStage, const char* shaderSource,
+								   const ShaderCompiledFunction& compiled,
+								   const std::filesystem::path& includePath) = 0;
+
+        /**
+         * Compile a shader from a specific file path for a target stage with
+         * a custom shader include path and an event function called if the
+         * compilation completes.
+         * @param[in] shaderStage Shader pipeline stage
+         * @param[in] shaderPath Filepath of shader
+         * @param[in] compiled Shader compilation event
+         * @param[in] includePath Include path for shaders
+         * @param[in] update Flag to update shaders during runtime
+         */
 		virtual void compile(ShaderStage shaderStage, const std::filesystem::path& shaderPath,
-							 const ShaderCompiledFunction& compiled, bool update = false) = 0;
+							 const ShaderCompiledFunction& compiled,
+							 const std::filesystem::path& includePath, bool update) = 0;
+		
+		/**
+         * Compile a shader program from a specific map of given file paths for
+         * target pipeline stages with a custom shader include path and an event
+         * function called if the compilation completes.
+         * @param[in,out] program Shader program
+         * @param[in] stages Shader pipeline stages
+         * @param[in] compiled Shader program compilation event
+         * @param[in] includePath Include path for shaders
+         * @param[in] update Flag to update shaders during runtime
+         */
+		void compileProgram(ShaderProgram& program,
+							const std::unordered_map<ShaderStage, const std::filesystem::path>& stages,
+							const ShaderProgramCompiledFunction& compiled,
+							const std::filesystem::path& includePath = "", bool update = false);
+		
+		/**
+         * Return the definition value of a macro for shader compilation.
+         * @param[in] name Macro definition name
+         * @return Macro definition value
+         */
+		std::string getDefine(const std::string& name) const;
+
+        /**
+         * Set a macro for shader compilation.
+         * @param[in] name Macro definition name
+         * @param[in] value Macro definition value
+         */
+		void setDefine(const std::string& name, const std::string& value);
 		
 	};
+
+    /** @} */
 	
 }
diff --git a/modules/shader_compiler/include/vkcv/shader/GLSLCompiler.hpp b/modules/shader_compiler/include/vkcv/shader/GLSLCompiler.hpp
index 7105d93a0c3e153bf3abe1d624d0c13c6f09ac6d..face4cba350122f22feee6bae878397dccf4a070 100644
--- a/modules/shader_compiler/include/vkcv/shader/GLSLCompiler.hpp
+++ b/modules/shader_compiler/include/vkcv/shader/GLSLCompiler.hpp
@@ -6,23 +6,94 @@
 #include "Compiler.hpp"
 
 namespace vkcv::shader {
-	
-	class GLSLCompiler {
+
+    /**
+     * @addtogroup vkcv_shader
+     * @{
+     */
+     
+	enum class GLSLCompileTarget {
+		SUBGROUP_OP,
+		RAY_TRACING,
+		
+		UNKNOWN
+	};
+
+    /**
+     * A class to handle GLSL runtime shader compilation.
+     */
+	class GLSLCompiler : public Compiler {
 	private:
-	public:
-		GLSLCompiler();
+		GLSLCompileTarget m_target;
 		
+	public:
+        /**
+         * The constructor of a runtime GLSL shader compiler instance.
+         *
+         * @param[in] target Compile target (optional)
+         */
+		GLSLCompiler(GLSLCompileTarget target = GLSLCompileTarget::UNKNOWN);
+
+        /**
+         * The copy-constructor of a runtime GLSL shader compiler instance.
+         * @param[in] other Other instance of a GLSL shader compiler instance
+         */
 		GLSLCompiler(const GLSLCompiler& other);
+
+        /**
+         * The move-constructor of a runtime GLSL shader compiler instance.
+         * @param[out] other Other instance of a GLSL shader compiler instance
+         */
 		GLSLCompiler(GLSLCompiler&& other) = default;
-	
+
+        /**
+         * The destructor of a runtime GLSL shader compiler instance.
+         */
 		~GLSLCompiler();
-		
+
+        /**
+         * The copy-operator of a runtime GLSL shader compiler instance.
+         * @param[in] other Other instance of a GLSL shader compiler instance
+         * @return Reference to this instance
+         */
 		GLSLCompiler& operator=(const GLSLCompiler& other);
-		GLSLCompiler& operator=(GLSLCompiler&& other) = default;
-		
+
+        /**
+         * The copy-operator of a runtime GLSL shader compiler instance.
+         * @param[out] other Other instance of a GLSL shader compiler instance
+         * @return Reference to this instance
+         */
+        GLSLCompiler& operator=(GLSLCompiler&& other) = default;
+
+        /**
+         * Compile a GLSL shader from source for a target stage with a custom shader
+         * include path and an event function called if the compilation completes.
+         * @param[in] shaderStage Shader pipeline stage
+         * @param[in] shaderSource Source of shader
+         * @param[in] compiled Shader compilation event
+         * @param[in] includePath Include path for shaders
+         * @return Result if the compilation succeeds
+         */
+		bool compileSource(ShaderStage shaderStage, const char* shaderSource,
+						   const ShaderCompiledFunction& compiled,
+						   const std::filesystem::path& includePath) override;
+
+        /**
+         * Compile a GLSL shader from a specific file path for a target stage with
+         * a custom shader include path and an event function called if the
+         * compilation completes.
+         * @param[in] shaderStage Shader pipeline stage
+         * @param[in] shaderPath Filepath of shader
+         * @param[in] compiled Shader compilation event
+         * @param[in] includePath Include path for shaders
+         * @param[in] update Flag to update shaders during runtime
+         */
 		void compile(ShaderStage shaderStage, const std::filesystem::path& shaderPath,
-					 const ShaderCompiledFunction& compiled, bool update = false);
+					 const ShaderCompiledFunction& compiled,
+					 const std::filesystem::path& includePath = "", bool update = false) override;
 		
 	};
+
+    /** @} */
 	
 }
diff --git a/modules/shader_compiler/lib/glslang b/modules/shader_compiler/lib/glslang
index fe15158676657bf965e41c32e15ae5db7ea2ab6a..5755de46b07e4374c05fb1081f65f7ae1f8cca81 160000
--- a/modules/shader_compiler/lib/glslang
+++ b/modules/shader_compiler/lib/glslang
@@ -1 +1 @@
-Subproject commit fe15158676657bf965e41c32e15ae5db7ea2ab6a
+Subproject commit 5755de46b07e4374c05fb1081f65f7ae1f8cca81
diff --git a/modules/shader_compiler/src/vkcv/shader/Compiler.cpp b/modules/shader_compiler/src/vkcv/shader/Compiler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dcf9e5167c2b2fdd251e4e245a8ea7a6d7e62712
--- /dev/null
+++ b/modules/shader_compiler/src/vkcv/shader/Compiler.cpp
@@ -0,0 +1,48 @@
+
+#include "vkcv/shader/Compiler.hpp"
+
+namespace vkcv::shader {
+	
+	void Compiler::compileProgram(ShaderProgram& program,
+								  const std::unordered_map<ShaderStage, const std::filesystem::path>& stages,
+								  const ShaderProgramCompiledFunction& compiled,
+								  const std::filesystem::path& includePath, bool update) {
+		std::vector<std::pair<ShaderStage, const std::filesystem::path>> stageList;
+		size_t i;
+		
+		stageList.reserve(stages.size());
+		for (const auto& stage : stages) {
+			stageList.push_back(stage);
+		}
+		
+		/* Compile a shader programs stages in parallel to improve performance */
+		#pragma omp parallel for shared(stageList, includePath, update) private(i)
+		for (i = 0; i < stageList.size(); i++) {
+			const auto& stage = stageList[i];
+			
+			compile(
+				stage.first,
+				stage.second,
+				[&program](ShaderStage shaderStage, const std::filesystem::path& path) {
+					#pragma omp critical
+					program.addShader(shaderStage, path);
+				},
+				includePath,
+				update
+			);
+		}
+		
+		if (compiled) {
+			compiled(program);
+		}
+	}
+	
+	std::string Compiler::getDefine(const std::string &name) const {
+		return m_defines.at(name);
+	}
+	
+	void Compiler::setDefine(const std::string &name, const std::string &value) {
+		m_defines[name] = value;
+	}
+	
+}
diff --git a/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp b/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp
index ec358188b8e871da6f4d62ffd397f32bfb795ee2..78a82113f89bd54914471d64fee6e11700548431 100644
--- a/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp
+++ b/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp
@@ -2,16 +2,20 @@
 #include "vkcv/shader/GLSLCompiler.hpp"
 
 #include <fstream>
+#include <sstream>
 #include <glslang/SPIRV/GlslangToSpv.h>
 #include <glslang/StandAlone/DirStackFileIncluder.h>
 
+#include <vkcv/File.hpp>
 #include <vkcv/Logger.hpp>
 
 namespace vkcv::shader {
 	
 	static uint32_t s_CompilerCount = 0;
 	
-	GLSLCompiler::GLSLCompiler() {
+	GLSLCompiler::GLSLCompiler(GLSLCompileTarget target) :
+		Compiler(),
+		m_target(target) {
 		if (s_CompilerCount == 0) {
 			glslang::InitializeProcess();
 		}
@@ -19,7 +23,7 @@ namespace vkcv::shader {
 		s_CompilerCount++;
 	}
 	
-	GLSLCompiler::GLSLCompiler(const GLSLCompiler &other) {
+	GLSLCompiler::GLSLCompiler(const GLSLCompiler &other) : Compiler(other) {
 		s_CompilerCount++;
 	}
 	
@@ -50,6 +54,22 @@ namespace vkcv::shader {
 				return EShLangFragment;
 			case ShaderStage::COMPUTE:
 				return EShLangCompute;
+			case ShaderStage::TASK:
+				return EShLangTaskNV;
+			case ShaderStage::MESH:
+				return EShLangMeshNV;
+			case ShaderStage::RAY_GEN:
+			    return EShLangRayGen;
+			case ShaderStage::RAY_CLOSEST_HIT:
+			    return EShLangClosestHit;
+			case ShaderStage::RAY_MISS:
+			    return EShLangMiss;
+			case ShaderStage::RAY_INTERSECTION:
+				return EShLangIntersect;
+			case ShaderStage::RAY_ANY_HIT:
+				return EShLangAnyHit;
+			case ShaderStage::RAY_CALLABLE:
+				return EShLangCallable;
 			default:
 				return EShLangCount;
 		}
@@ -197,22 +217,57 @@ namespace vkcv::shader {
 		return true;
 	}
 	
-	void GLSLCompiler::compile(ShaderStage shaderStage, const std::filesystem::path &shaderPath,
-							   const ShaderCompiledFunction& compiled, bool update) {
+	bool GLSLCompiler::compileSource(ShaderStage shaderStage, const char* shaderSource,
+									 const ShaderCompiledFunction &compiled,
+									 const std::filesystem::path& includePath) {
 		const EShLanguage language = findShaderLanguage(shaderStage);
 		
 		if (language == EShLangCount) {
-			vkcv_log(LogLevel::ERROR, "Shader stage not supported (%s)", shaderPath.string().c_str());
-			return;
+			vkcv_log(LogLevel::ERROR, "Shader stage not supported");
+			return false;
 		}
 		
-		const std::vector<char> code = readShaderCode(shaderPath);
-		
 		glslang::TShader shader (language);
+		switch (m_target) {
+			case GLSLCompileTarget::SUBGROUP_OP:
+				shader.setEnvClient(glslang::EShClientVulkan,glslang::EShTargetVulkan_1_1);
+				shader.setEnvTarget(glslang::EShTargetSpv,glslang::EShTargetSpv_1_3);
+				break;
+			case GLSLCompileTarget::RAY_TRACING:
+				shader.setEnvClient(glslang::EShClientVulkan,glslang::EShTargetVulkan_1_2);
+				shader.setEnvTarget(glslang::EShTargetSpv,glslang::EShTargetSpv_1_4);
+				break;
+			default:
+				break;
+		}
+
 		glslang::TProgram program;
+		std::string source (shaderSource);
+		
+		if (!m_defines.empty()) {
+			std::ostringstream defines;
+			for (const auto& define : m_defines) {
+				defines << "#define " << define.first << " " << define.second << std::endl;
+			}
+
+			size_t pos = source.find("#version") + 8;
+			if (pos >= source.length()) {
+				pos = 0;
+			}
+			
+			const size_t epos = source.find_last_of("#extension", pos) + 10;
+			if (epos < source.length()) {
+				pos = epos;
+			}
+			
+			const auto defines_str = defines.str();
+			
+			pos = source.find('\n', pos) + 1;
+			source = source.insert(pos, defines_str);
+		}
 		
 		const char *shaderStrings [1];
-		shaderStrings[0] = code.data();
+		shaderStrings[0] = source.c_str();
 		
 		shader.setStrings(shaderStrings, 1);
 		
@@ -222,51 +277,53 @@ namespace vkcv::shader {
 		const auto messages = (EShMessages)(
 			EShMsgSpvRules |
 			EShMsgVulkanRules
-			);
+		);
 
 		std::string preprocessedGLSL;
 
 		DirStackFileIncluder includer;
-		includer.pushExternalLocalDirectory(shaderPath.parent_path().string());
+		includer.pushExternalLocalDirectory(includePath.string());
 
-		if (!shader.preprocess(&resources, 100, ENoProfile, false, false, messages, &preprocessedGLSL, includer)) {
-			vkcv_log(LogLevel::ERROR, "Shader parsing failed {\n%s\n%s\n} (%s)",
-				shader.getInfoLog(), shader.getInfoDebugLog(), shaderPath.string().c_str());
-			return;
+		if (!shader.preprocess(&resources, 100, ENoProfile,
+							   false, false,
+							   messages, &preprocessedGLSL, includer)) {
+			vkcv_log(LogLevel::ERROR, "Shader preprocessing failed {\n%s\n%s\n}",
+				shader.getInfoLog(), shader.getInfoDebugLog());
+			return false;
 		}
 		
 		const char* preprocessedCString = preprocessedGLSL.c_str();
 		shader.setStrings(&preprocessedCString, 1);
 
 		if (!shader.parse(&resources, 100, false, messages)) {
-			vkcv_log(LogLevel::ERROR, "Shader parsing failed {\n%s\n%s\n} (%s)",
-					 shader.getInfoLog(), shader.getInfoDebugLog(), shaderPath.string().c_str());
-			return;
+			vkcv_log(LogLevel::ERROR, "Shader parsing failed {\n%s\n%s\n}",
+					 shader.getInfoLog(), shader.getInfoDebugLog());
+			return false;
 		}
 		
 		program.addShader(&shader);
 		
 		if (!program.link(messages)) {
-			vkcv_log(LogLevel::ERROR, "Shader linking failed {\n%s\n%s\n} (%s)",
-					 shader.getInfoLog(), shader.getInfoDebugLog(), shaderPath.string().c_str());
-			return;
+			vkcv_log(LogLevel::ERROR, "Shader linking failed {\n%s\n%s\n}",
+					 shader.getInfoLog(), shader.getInfoDebugLog());
+			return false;
 		}
 		
 		const glslang::TIntermediate* intermediate = program.getIntermediate(language);
 		
 		if (!intermediate) {
-			vkcv_log(LogLevel::ERROR, "No valid intermediate representation (%s)", shaderPath.string().c_str());
-			return;
+			vkcv_log(LogLevel::ERROR, "No valid intermediate representation");
+			return false;
 		}
 		
 		std::vector<uint32_t> spirv;
 		glslang::GlslangToSpv(*intermediate, spirv);
 		
-		const std::filesystem::path tmp_path (std::tmpnam(nullptr));
+		const std::filesystem::path tmp_path = generateTemporaryFilePath();
 		
 		if (!writeSpirvCode(tmp_path, spirv)) {
-			vkcv_log(LogLevel::ERROR, "Spir-V could not be written to disk (%s)", shaderPath.string().c_str());
-			return;
+			vkcv_log(LogLevel::ERROR, "Spir-V could not be written to disk");
+			return false;
 		}
 		
 		if (compiled) {
@@ -274,6 +331,24 @@ namespace vkcv::shader {
 		}
 		
 		std::filesystem::remove(tmp_path);
+		return true;
+	}
+	
+	void GLSLCompiler::compile(ShaderStage shaderStage, const std::filesystem::path &shaderPath,
+							   const ShaderCompiledFunction& compiled,
+							   const std::filesystem::path& includePath, bool update) {
+		const std::vector<char> code = readShaderCode(shaderPath);
+		bool result;
+		
+		if (!includePath.empty()) {
+			result = compileSource(shaderStage, code.data(), compiled, includePath);
+		} else {
+			result = compileSource(shaderStage, code.data(), compiled, shaderPath.parent_path());
+		}
+		
+		if (!result) {
+			vkcv_log(LogLevel::ERROR, "Shader compilation failed: (%s)", shaderPath.string().c_str());
+		}
 		
 		if (update) {
 			// TODO: Shader hot compilation during runtime
diff --git a/modules/testing/CMakeLists.txt b/modules/testing/CMakeLists.txt
index a22e547646fd4ef59860245d51365b98df59b578..60f5a82732b9e095f05b0d6a551b825a6d05f3dd 100644
--- a/modules/testing/CMakeLists.txt
+++ b/modules/testing/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.16)
 project(vkcv_testing)
 
 # setting c++ standard for the project
-set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD 20)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
 set(vkcv_testing_source ${PROJECT_SOURCE_DIR}/src)
@@ -14,8 +14,15 @@ set(vkcv_testing_sources
 )
 
 # adding source files to the project
-add_library(vkcv_testing STATIC ${vkcv_testing_sources})
+add_library(vkcv_testing ${vkcv_build_attribute} ${vkcv_testing_sources})
 
 # add the own include directory for public headers
 target_include_directories(vkcv_testing BEFORE PUBLIC ${vkcv_testing_include})
 
+if (vkcv_parent_scope)
+	list(APPEND vkcv_modules_includes ${vkcv_testing_include})
+	list(APPEND vkcv_modules_libraries vkcv_testing)
+	
+	set(vkcv_modules_includes ${vkcv_modules_includes} PARENT_SCOPE)
+	set(vkcv_modules_libraries ${vkcv_modules_libraries} PARENT_SCOPE)
+endif()
diff --git a/modules/upscaling/CMakeLists.txt b/modules/upscaling/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..0a5ee06dd695a3f3596c238bc0ef4383c011cf24
--- /dev/null
+++ b/modules/upscaling/CMakeLists.txt
@@ -0,0 +1,62 @@
+cmake_minimum_required(VERSION 3.16)
+project(vkcv_upscaling)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(vkcv_upscaling_source ${PROJECT_SOURCE_DIR}/src)
+set(vkcv_upscaling_include ${PROJECT_SOURCE_DIR}/include)
+
+set(vkcv_upscaling_sources
+		${vkcv_upscaling_include}/vkcv/upscaling/Upscaling.hpp
+		${vkcv_upscaling_source}/vkcv/upscaling/Upscaling.cpp
+		
+		${vkcv_upscaling_include}/vkcv/upscaling/BilinearUpscaling.hpp
+		${vkcv_upscaling_source}/vkcv/upscaling/BilinearUpscaling.cpp
+		
+		${vkcv_upscaling_include}/vkcv/upscaling/FSRUpscaling.hpp
+		${vkcv_upscaling_source}/vkcv/upscaling/FSRUpscaling.cpp
+		
+		${vkcv_upscaling_include}/vkcv/upscaling/NISUpscaling.hpp
+		${vkcv_upscaling_source}/vkcv/upscaling/NISUpscaling.cpp
+		
+		${vkcv_upscaling_include}/vkcv/upscaling/FSR2Upscaling.hpp
+		${vkcv_upscaling_source}/vkcv/upscaling/FSR2Upscaling.cpp
+)
+
+# Setup some path variables to load libraries
+set(vkcv_upscaling_lib lib)
+set(vkcv_upscaling_lib_path ${PROJECT_SOURCE_DIR}/${vkcv_upscaling_lib})
+
+# Check and load FidelityFX_FSR
+include(config/FidelityFX_FSR.cmake)
+
+# Check and load FidelityFX_FSR2
+include(config/FidelityFX_FSR2.cmake)
+
+# Check and load NVIDIAImageScaling
+include(config/NVIDIAImageScaling.cmake)
+
+# Add compile definitions depending on the build context of the module
+add_compile_definitions(${vkcv_upscaling_definitions})
+
+# adding source files to the project
+add_library(vkcv_upscaling ${vkcv_build_attribute} ${vkcv_upscaling_sources})
+
+# link the required libraries to the module
+target_link_libraries(vkcv_upscaling ${vkcv_upscaling_libraries} vkcv vkcv_shader_compiler)
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(vkcv_upscaling SYSTEM BEFORE PRIVATE ${vkcv_upscaling_includes} ${vkcv_include} ${vkcv_includes} ${vkcv_shader_compiler_include})
+
+# add the own include directory for public headers
+target_include_directories(vkcv_upscaling BEFORE PUBLIC ${vkcv_upscaling_include})
+
+if (vkcv_parent_scope)
+	list(APPEND vkcv_modules_includes ${vkcv_upscaling_include})
+	list(APPEND vkcv_modules_libraries vkcv_upscaling)
+	
+	set(vkcv_modules_includes ${vkcv_modules_includes} PARENT_SCOPE)
+	set(vkcv_modules_libraries ${vkcv_modules_libraries} PARENT_SCOPE)
+endif()
diff --git a/modules/upscaling/README.md b/modules/upscaling/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..9c4bed679f5a7a756e8b25edc85360672f8fabd0
--- /dev/null
+++ b/modules/upscaling/README.md
@@ -0,0 +1,17 @@
+# Upscaling
+
+A VkCV module to upscale images in realtime
+
+## Build
+
+### Dependencies (required):
+
+| Name of dependency                                                           | Used as submodule |
+|------------------------------------------------------------------------------|---|
+| [FidelityFX-FSR](https://github.com/GPUOpen-Effects/FidelityFX-FSR/)         | ✅ |
+| [NVIDIAImageScaling](https://github.com/NVIDIAGameWorks/NVIDIAImageScaling/) | ✅ |
+| [FidelityFX-FSR2](https://github.com/GPUOpen-Effects/FidelityFX-FSR2/)       | ✅ |
+
+## Docs
+
+Here is a [link](https://userpages.uni-koblenz.de/~vkcv/doc/group__vkcv__upscaling.html) to this module.
diff --git a/modules/upscaling/config/FidelityFX_FSR.cmake b/modules/upscaling/config/FidelityFX_FSR.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..780f1d441abe2a567db7491ce5e2f4821a396b89
--- /dev/null
+++ b/modules/upscaling/config/FidelityFX_FSR.cmake
@@ -0,0 +1,18 @@
+
+use_git_submodule("${vkcv_upscaling_lib_path}/FidelityFX-FSR" ffx_fsr_status)
+
+if (${ffx_fsr_status})
+	include_shader(${vkcv_upscaling_lib_path}/FidelityFX-FSR/ffx-fsr/ffx_a.h ${vkcv_upscaling_include} ${vkcv_upscaling_source})
+	include_shader(${vkcv_upscaling_lib_path}/FidelityFX-FSR/ffx-fsr/ffx_fsr1.h ${vkcv_upscaling_include} ${vkcv_upscaling_source})
+	include_shader(${vkcv_upscaling_lib_path}/FidelityFX-FSR/sample/src/VK/FSR_Pass.glsl ${vkcv_upscaling_include} ${vkcv_upscaling_source})
+	
+	list(APPEND vkcv_upscaling_includes ${vkcv_upscaling_lib}/FidelityFX-FSR/ffx-fsr)
+	
+	list(APPEND vkcv_upscaling_sources ${vkcv_upscaling_source}/ffx_a.h.cxx)
+	list(APPEND vkcv_upscaling_sources ${vkcv_upscaling_source}/ffx_fsr1.h.cxx)
+	list(APPEND vkcv_upscaling_sources ${vkcv_upscaling_source}/FSR_Pass.glsl.cxx)
+	
+	list(APPEND vkcv_upscaling_sources ${vkcv_upscaling_include}/ffx_a.h.hxx)
+	list(APPEND vkcv_upscaling_sources ${vkcv_upscaling_include}/ffx_fsr1.h.hxx)
+	list(APPEND vkcv_upscaling_sources ${vkcv_upscaling_include}/FSR_Pass.glsl.hxx)
+endif ()
diff --git a/modules/upscaling/config/FidelityFX_FSR2.cmake b/modules/upscaling/config/FidelityFX_FSR2.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..1c1cb1339a7e7f93cb42994c3532f3128ad206ac
--- /dev/null
+++ b/modules/upscaling/config/FidelityFX_FSR2.cmake
@@ -0,0 +1,30 @@
+
+set(vkcv_upscaling_fsr2_override ON)
+
+if (WIN32)
+	set(vkcv_upscaling_fsr2_override OFF)
+else()
+	find_program(wine_program "wine")
+	
+	if (EXISTS ${wine_program})
+		set(vkcv_upscaling_fsr2_override OFF)
+	endif()
+endif()
+
+if (vkcv_upscaling_fsr2_override)
+	list(APPEND vkcv_upscaling_definitions VKCV_OVERRIDE_FSR2_WITH_FSR1=1)
+else()
+	use_git_submodule("${vkcv_upscaling_lib_path}/FidelityFX-FSR2" ffx_fsr2_status)
+	
+	if (${ffx_fsr2_status})
+		set(FFX_FSR2_API_DX12 OFF CACHE INTERNAL "")
+		set(FFX_FSR2_API_VK ON CACHE INTERNAL "")
+		
+		add_subdirectory(${vkcv_upscaling_lib}/FidelityFX-FSR2/src/ffx-fsr2-api)
+		
+		list(APPEND vkcv_upscaling_libraries ${FFX_FSR2_API} ${FFX_FSR2_API_VK})
+		
+		list(APPEND vkcv_upscaling_includes ${vkcv_upscaling_lib}/FidelityFX-FSR2/src/ffx-fsr2-api)
+		list(APPEND vkcv_upscaling_includes ${vkcv_upscaling_lib}/FidelityFX-FSR2/src/ffx-fsr2-api/vk)
+	endif ()
+endif()
\ No newline at end of file
diff --git a/modules/upscaling/config/NVIDIAImageScaling.cmake b/modules/upscaling/config/NVIDIAImageScaling.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..083891c67e369dc783c79f5ac4386ae15e6e3261
--- /dev/null
+++ b/modules/upscaling/config/NVIDIAImageScaling.cmake
@@ -0,0 +1,15 @@
+
+use_git_submodule("${vkcv_upscaling_lib_path}/NVIDIAImageScaling" nvidia_nis_status)
+
+if (${nvidia_nis_status})
+	include_shader(${vkcv_upscaling_lib_path}/NVIDIAImageScaling/NIS/NIS_Scaler.h ${vkcv_upscaling_include} ${vkcv_upscaling_source})
+	include_shader(${vkcv_upscaling_lib_path}/NVIDIAImageScaling/NIS/NIS_Main.glsl ${vkcv_upscaling_include} ${vkcv_upscaling_source})
+	
+	list(APPEND vkcv_upscaling_includes ${vkcv_upscaling_lib}/NVIDIAImageScaling/NIS)
+	
+	list(APPEND vkcv_upscaling_sources ${vkcv_upscaling_source}/NIS_Scaler.h.cxx)
+	list(APPEND vkcv_upscaling_sources ${vkcv_upscaling_source}/NIS_Main.glsl.cxx)
+	
+	list(APPEND vkcv_upscaling_sources ${vkcv_upscaling_include}/NIS_Scaler.h.hxx)
+	list(APPEND vkcv_upscaling_sources ${vkcv_upscaling_include}/NIS_Main.glsl.hxx)
+endif ()
diff --git a/modules/upscaling/include/vkcv/upscaling/BilinearUpscaling.hpp b/modules/upscaling/include/vkcv/upscaling/BilinearUpscaling.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a3f29276aa95e17e11ce2a044721d47d558dd869
--- /dev/null
+++ b/modules/upscaling/include/vkcv/upscaling/BilinearUpscaling.hpp
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "Upscaling.hpp"
+
+namespace vkcv::upscaling {
+
+    /**
+     * @addtogroup vkcv_upscaling
+     * @{
+     */
+
+    /**
+     * A class to handle upscaling via bilinear interpolation.
+     */
+	class BilinearUpscaling : public Upscaling {
+	private:
+	public:
+        /**
+         * Constructor to create instance for bilinear upscaling.
+         *
+         * @param[in,out] core Reference to a Core instance
+         */
+		explicit BilinearUpscaling(Core& core);
+
+        /**
+         * Record the commands of the bilinear upscaling instance to
+         * scale the image of the input handle to the resolution of
+         * the output image handle via bilinear interpolation.
+         *
+         * @param[in] cmdStream Command stream handle to record commands
+         * @param[in] input Input image handle
+         * @param[in] output Output image handle
+         */
+		void recordUpscaling(const CommandStreamHandle& cmdStream,
+							 const ImageHandle& input,
+							 const ImageHandle& output) override;
+	
+	};
+
+    /** @} */
+
+}
diff --git a/modules/upscaling/include/vkcv/upscaling/FSR2Upscaling.hpp b/modules/upscaling/include/vkcv/upscaling/FSR2Upscaling.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..447f16ed6bc3f84862c252db2103c7734659b5d2
--- /dev/null
+++ b/modules/upscaling/include/vkcv/upscaling/FSR2Upscaling.hpp
@@ -0,0 +1,244 @@
+#pragma once
+
+#include "Upscaling.hpp"
+
+#include <memory>
+#include <vector>
+
+#ifdef VKCV_OVERRIDE_FSR2_WITH_FSR1
+#include "FSRUpscaling.hpp"
+#else
+struct FfxFsr2ContextDescription;
+struct FfxFsr2Context;
+#endif
+
+namespace vkcv::upscaling {
+
+	/**
+     * @addtogroup vkcv_upscaling
+     * @{
+     */
+
+	/**
+     * Enum to set the mode of quality for
+     * FSR2 upscaling.
+     */
+	enum class FSR2QualityMode : int {
+		/**
+		 * Don't upscale anything.
+		 */
+		NONE = 0,
+		
+		/**
+		 * High quality of FSR upscaling:
+		 * 1.5x per dimension
+		 */
+		QUALITY = 2,
+		
+		/**
+		 * Medium quality of FSR upscaling:
+		 * 1.7x per dimension
+		 */
+		BALANCED = 3,
+		
+		/**
+		 * Low quality of FSR upscaling:
+		 * 2.0x per dimension
+		 */
+		PERFORMANCE = 4,
+		
+		/**
+         * Lowest quality of FSR upscaling:
+         * 3.0x per dimension
+         */
+		ULTRA_PERFORMANCE = 5,
+	};
+	
+	/**
+     * Calculates the internal resolution for actual rendering if
+     * a specific mode of quality is used for upscaling with FSR2.
+     *
+     * @param[in] mode Mode of quality
+     * @param[in] outputWidth Final resolution width
+     * @param[in] outputHeight Final resolution height
+     * @param[out] inputWidth Internal resolution width
+     * @param[out] inputHeight Internal resolution height
+     */
+	void getFSR2Resolution(FSR2QualityMode mode,
+						   uint32_t outputWidth, uint32_t outputHeight,
+						   uint32_t &inputWidth, uint32_t &inputHeight);
+	
+	/**
+	 * Returns the matching negative lod bias to reduce artifacts
+	 * upscaling with FSR2 under a given mode of quality.
+	 *
+	 * @param mode Mode of quality
+	 * @return Lod bias
+	 */
+	float getFSR2LodBias(FSR2QualityMode mode);
+
+	/**
+     * A class to handle upscaling via FidelityFX Super Resolution.
+     * https://github.com/GPUOpen-Effects/FidelityFX-FSR2
+     */
+	class FSR2Upscaling : public Upscaling {
+	private:
+#ifdef VKCV_OVERRIDE_FSR2_WITH_FSR1
+		std::unique_ptr<FSRUpscaling> m_fsr1;
+#else
+		std::vector<char> m_scratchBuffer;
+		
+		std::unique_ptr<FfxFsr2ContextDescription> m_description;
+		std::unique_ptr<FfxFsr2Context> m_context;
+		
+		ImageHandle m_depth;
+		ImageHandle m_velocity;
+		
+		uint32_t m_frameIndex;
+		
+		float m_frameDeltaTime;
+		bool m_reset;
+		
+		float m_near;
+		float m_far;
+		float m_fov;
+		
+		/**
+		* Current state of HDR support.
+		*/
+		bool m_hdr;
+		
+		/**
+		 * Sharpness will improve the upscaled image quality with
+		 * a factor between 0.0f for no sharpening and 1.0f for
+		 * maximum sharpening.
+		 *
+		 * The default value for sharpness should be 0.875f.
+		 *
+		 * Beware that 0.0f or any negative value of sharpness will
+		 * disable the sharpening pass completely.
+		 */
+		float m_sharpness;
+		
+		void createFSR2Context(uint32_t displayWidth,
+							   uint32_t displayHeight,
+							   uint32_t renderWidth,
+							   uint32_t renderHeight);
+		
+		void destroyFSR2Context();
+#endif
+		
+	public:
+		/**
+         * Constructor to create an instance for FSR upscaling.
+         *
+         * @param[in,out] core Reference to a Core instance
+         */
+		explicit FSR2Upscaling(Core& core);
+		
+		/**
+		 * Destructor to free the instance for FSR upscaling.
+		 */
+		~FSR2Upscaling();
+		
+		/**
+		 * Update the upscaling instance with current frame
+		 * delta time and whether the temporal data needs to
+		 * be reset (for example because the camera switched).
+		 *
+		 * @param[in] deltaTime Current frame delta time
+		 * @param[in] reset Reset temporal frame data
+		 */
+		void update(float deltaTime, bool reset = false);
+		
+		/**
+		 * Calculates the jitter offset for the projection
+		 * matrix of the camera to use in the current frame.
+		 *
+		 * @param[in] renderWidth Render resolution width
+		 * @param[in] renderHeight Render resolution height
+		 * @param[out] jitterOffsetX Jitter offset x-coordinate
+		 * @param[out] jitterOffsetY Jitter offset y-coordinate
+		 */
+		void calcJitterOffset(uint32_t renderWidth,
+							 uint32_t renderHeight,
+							 float& jitterOffsetX,
+							 float& jitterOffsetY) const;
+		
+		/**
+		 * Bind the depth buffer image to use with the FSR2
+		 * upscaling instance for utilizing depth information.
+		 *
+		 * @param[in] depthInput Depth input image handle
+		 */
+		void bindDepthBuffer(const ImageHandle& depthInput);
+		
+		/**
+		 * Bind the velocity buffer image to use with the FSR2
+		 * upscaling instance for utilizing 2D motion vectors.
+		 *
+		 * @param[in] velocityInput Velocity input image handle
+		 */
+		void bindVelocityBuffer(const ImageHandle& velocityInput);
+		
+		/**
+		 * Record the commands of the FSR2 upscaling instance to
+		 * scale the image of the input handle to the resolution of
+		 * the output image handle via FidelityFX Super Resolution.
+		 *
+		 * @param[in] cmdStream Command stream handle to record commands
+		 * @param[in] colorInput Color input image handle
+		 * @param[in] output Output image handle
+		 */
+		void recordUpscaling(const CommandStreamHandle& cmdStream,
+							 const ImageHandle& colorInput,
+							 const ImageHandle& output) override;
+		
+		/**
+		 * Set the required camera values for the FSR2 upscaling
+		 * instance including near- and far-plane as well as
+		 * the FOV angle vertical.
+		 *
+		 * @param[in] near Camera near plane
+		 * @param[in] far Camera far plane
+		 * @param[in] fov Camera field of view angle vertical
+		 */
+		void setCamera(float near, float far, float fov);
+		
+		/**
+         * Checks if HDR support is enabled and returns the status as boolean.
+         *
+         * @return true if HDR is supported, otherwise false
+         */
+		[[nodiscard]]
+		bool isHdrEnabled() const;
+		
+		/**
+		 * Changes the status of HDR support of the FSR upscaling instance.
+		 *
+		 * @param[in] enabled New status of HDR support
+		 */
+		void setHdrEnabled(bool enabled);
+		
+		/**
+         * Returns the amount of sharpness the FSR upscaling instance is using.
+         *
+         * @return The amount of sharpness
+         */
+		[[nodiscard]]
+		float getSharpness() const;
+		
+		/**
+		 * Changes the amount of sharpness of the FSR upscaling instance.
+		 * The new sharpness value is restricted by 0.0f as lower and 1.0f
+		 * as upper boundary.
+		 *
+		 * @param[in] sharpness New sharpness value
+		 */
+		void setSharpness(float sharpness);
+		
+	};
+	
+	/** @} */
+
+}
\ No newline at end of file
diff --git a/modules/upscaling/include/vkcv/upscaling/FSRUpscaling.hpp b/modules/upscaling/include/vkcv/upscaling/FSRUpscaling.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..5a4c59ef02c2f17ed017535ca79b834e7391546a
--- /dev/null
+++ b/modules/upscaling/include/vkcv/upscaling/FSRUpscaling.hpp
@@ -0,0 +1,239 @@
+#pragma once
+
+#include "Upscaling.hpp"
+
+#include <vkcv/Buffer.hpp>
+#include <vkcv/ShaderProgram.hpp>
+
+namespace vkcv::upscaling {
+
+    /**
+     * @addtogroup vkcv_upscaling
+     * @{
+     */
+
+    /**
+     * Enum to set the mode of quality for
+     * FSR upscaling.
+     */
+	enum class FSRQualityMode : int {
+        /**
+         * Don't upscale anything.
+         */
+		NONE = 0,
+
+        /**
+         * Highest quality of FSR upscaling:
+         * 1.3x per dimension
+         */
+		ULTRA_QUALITY = 1,
+
+        /**
+         * High quality of FSR upscaling:
+         * 1.5x per dimension
+         */
+		QUALITY = 2,
+
+        /**
+         * Medium quality of FSR upscaling:
+         * 1.7x per dimension
+         */
+		BALANCED = 3,
+
+        /**
+         * Low quality of FSR upscaling:
+         * 2.0x per dimension
+         */
+		PERFORMANCE = 4,
+	};
+
+    /**
+     * Calculates the internal resolution for actual rendering if
+     * a specific mode of quality is used for upscaling with FSR.
+     *
+     * @param[in] mode Mode of quality
+     * @param[in] outputWidth Final resolution width
+     * @param[in] outputHeight Final resolution height
+     * @param[out] inputWidth Internal resolution width
+     * @param[out] inputHeight Internal resolution height
+     */
+	void getFSRResolution(FSRQualityMode mode,
+						  uint32_t outputWidth, uint32_t outputHeight,
+						  uint32_t &inputWidth, uint32_t &inputHeight);
+
+    /**
+     * Returns the matching negative lod bias to reduce artifacts
+     * upscaling with FSR under a given mode of quality.
+     *
+     * @param mode Mode of quality
+     * @return Lod bias
+     */
+	float getFSRLodBias(FSRQualityMode mode);
+
+    /**
+     * A structure to exchange required configuration
+     * with the shaders used by FSR upscaling.
+     */
+	struct FSRConstants {
+        /**
+         * 0th FSR constant.
+         */
+		uint32_t Const0 [4];
+
+        /**
+         * 1st FSR constant.
+         */
+        uint32_t Const1 [4];
+
+        /**
+         * 2nd FSR constant.
+         */
+        uint32_t Const2 [4];
+
+        /**
+         * 3rd FSR constant.
+         */
+        uint32_t Const3 [4];
+
+        /**
+         * 4th FSR constant.
+         */
+        uint32_t Sample [4];
+	};
+
+    /**
+     * A class to handle upscaling via FidelityFX Super Resolution.
+     * https://github.com/GPUOpen-Effects/FidelityFX-FSR
+     */
+	class FSRUpscaling : public Upscaling {
+	private:
+        /**
+         * The EASU compute pipeline of the FSR upscaling.
+         */
+		ComputePipelineHandle m_easuPipeline;
+
+        /**
+         * The RCAS compute pipeline of the FSR upscaling.
+         */
+		ComputePipelineHandle m_rcasPipeline;
+
+        /**
+         * The descriptor set layout of the EASU pipeline.
+         */
+		DescriptorSetLayoutHandle m_easuDescriptorSetLayout;
+
+        /**
+         * The descriptor set for the EASU pipeline.
+         */
+		DescriptorSetHandle m_easuDescriptorSet;
+
+        /**
+         * The descriptor set layout of the RCAS pipeline.
+         */
+		DescriptorSetLayoutHandle m_rcasDescriptorSetLayout;
+
+        /**
+         * The descriptor set for the RCAS pipeline.
+         */
+		DescriptorSetHandle m_rcasDescriptorSet;
+
+        /**
+         * The buffer template to handle FSR constants for
+         * the EASU pipeline.
+         */
+		Buffer<FSRConstants> m_easuConstants;
+
+        /**
+         * The buffer template to handle FSR constants for
+         * the RCAS pipeline.
+         */
+		Buffer<FSRConstants> m_rcasConstants;
+
+        /**
+         * The image handle to store the intermidiate state of
+         * the FSR upscaling.
+         */
+		ImageHandle m_intermediateImage;
+
+        /**
+         * The sampler handle to use for accessing the images
+         * in the FSR upscaling process.
+         */
+		SamplerHandle m_sampler;
+
+        /**
+         * Current state of HDR support.
+         */
+		bool m_hdr;
+		
+		/**
+		 * Sharpness will calculate the rcasAttenuation value
+		 * which should be between 0.0f and 2.0f (default: 0.25f).
+		 *
+		 * rcasAttenuation = (1.0f - sharpness) * 2.0f
+		 *
+		 * So the default value for sharpness should be 0.875f.
+		 *
+		 * Beware that 0.0f or any negative value of sharpness will
+		 * disable the rcas pass completely.
+		 */
+		float m_sharpness;
+	
+	public:
+        /**
+         * Constructor to create instance for FSR upscaling.
+         *
+         * @param[in,out] core Reference to a Core instance
+         */
+		explicit FSRUpscaling(Core& core);
+
+        /**
+         * Record the commands of the FSR upscaling instance to
+         * scale the image of the input handle to the resolution of
+         * the output image handle via FidelityFX Super Resolution.
+         *
+         * @param[in] cmdStream Command stream handle to record commands
+         * @param[in] input Input image handle
+         * @param[in] output Output image handle
+         */
+		void recordUpscaling(const CommandStreamHandle& cmdStream,
+							 const ImageHandle& input,
+							 const ImageHandle& output) override;
+
+        /**
+         * Checks if HDR support is enabled and returns the status as boolean.
+         *
+         * @return true if HDR is supported, otherwise false
+         */
+		[[nodiscard]]
+		bool isHdrEnabled() const;
+
+        /**
+         * Changes the status of HDR support of the FSR upscaling instance.
+         *
+         * @param[in] enabled New status of HDR support
+         */
+		void setHdrEnabled(bool enabled);
+
+        /**
+         * Returns the amount of sharpness the FSR upscaling instance is using.
+         *
+         * @return The amount of sharpness
+         */
+		[[nodiscard]]
+		float getSharpness() const;
+
+        /**
+         * Changes the amount of sharpness of the FSR upscaling instance.
+         * The new sharpness value is restricted by 0.0f as lower and 1.0f
+         * as upper boundary.
+         *
+         * @param[in] sharpness New sharpness value
+         */
+		void setSharpness(float sharpness);
+		
+	};
+
+    /** @} */
+
+}
diff --git a/modules/upscaling/include/vkcv/upscaling/NISUpscaling.hpp b/modules/upscaling/include/vkcv/upscaling/NISUpscaling.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..62fadf8332afd78ad7cb9dbe1eadda75de35d1c3
--- /dev/null
+++ b/modules/upscaling/include/vkcv/upscaling/NISUpscaling.hpp
@@ -0,0 +1,135 @@
+#pragma once
+
+#include "Upscaling.hpp"
+
+#include <vkcv/Buffer.hpp>
+#include <vkcv/ShaderProgram.hpp>
+
+namespace vkcv::upscaling {
+	
+	/**
+     * @addtogroup vkcv_upscaling
+     * @{
+     */
+	
+	/**
+	 * A class to handle upscaling via NVIDIA Image Scaling.
+	 * https://github.com/NVIDIAGameWorks/NVIDIAImageScaling
+	 */
+	class NISUpscaling : public Upscaling {
+	private:
+		/**
+         * The compute pipeline of the NIS upscaling.
+         */
+		ComputePipelineHandle m_scalerPipeline;
+		
+		/**
+		 * The descriptor set layout of the upscaling pipeline.
+		 */
+		DescriptorSetLayoutHandle m_scalerDescriptorSetLayout;
+		
+		/**
+		 * The descriptor set for the upscaling pipeline.
+		 */
+		DescriptorSetHandle m_scalerDescriptorSet;
+		
+		/**
+		 * The buffer template to handle NIS constants for
+         * the upscaling pipeline.
+		 */
+		Buffer<uint8_t> m_scalerConstants;
+		
+		/**
+		 * The sampler handle to use for accessing the images
+         * in the NIS upscaling process.
+		 */
+		SamplerHandle m_sampler;
+		
+		/**
+		 * The image handle to store the upscaling coefficients.
+		 */
+		ImageHandle m_coefScaleImage;
+		
+		/**
+		 * The image handle to store the USM coefficients.
+		 */
+		ImageHandle m_coefUsmImage;
+		
+		/**
+		 * The amount of pixels per block width.
+		 */
+		uint32_t m_blockWidth;
+		
+		/**
+		 *  The amount of pixels per block height.
+		 */
+		uint32_t m_blockHeight;
+		
+		/**
+		 * Current state of HDR support.
+		 */
+		bool m_hdr;
+		
+		/**
+		 * The current value of sharpness.
+		 */
+		float m_sharpness;
+		
+	public:
+		/**
+		 * Constructor to create instance for NIS upscaling.
+		 *
+		 * @param[in,out] core Reference to a Core instance
+		 */
+		explicit NISUpscaling(Core &core);
+		
+		/**
+         * Record the commands of the NIS upscaling instance to
+         * scale the image of the input handle to the resolution of
+         * the output image handle via NVIDIA Image Scaling.
+         *
+         * @param[in] cmdStream Command stream handle to record commands
+         * @param[in] input Input image handle
+         * @param[in] output Output image handle
+         */
+		void recordUpscaling(const CommandStreamHandle &cmdStream,
+							 const ImageHandle &input,
+							 const ImageHandle &output) override;
+		
+		/**
+         * Checks if HDR support is enabled and returns the status as boolean.
+         *
+         * @return true if HDR is supported, otherwise false
+         */
+		[[nodiscard]]
+		bool isHdrEnabled() const;
+		
+		/**
+         * Changes the status of HDR support of the NIS upscaling instance.
+         *
+         * @param[in] enabled New status of HDR support
+         */
+		void setHdrEnabled(bool enabled);
+		
+		/**
+         * Returns the amount of sharpness the NIS upscaling instance is using.
+         *
+         * @return The amount of sharpness
+         */
+		[[nodiscard]]
+		float getSharpness() const;
+		
+		/**
+         * Changes the amount of sharpness of the NIS upscaling instance.
+         * The new sharpness value is restricted by 0.0f as lower and 1.0f
+         * as upper boundary.
+         *
+         * @param[in] sharpness New sharpness value
+         */
+		void setSharpness(float sharpness);
+		
+	};
+	
+	/** @} */
+	
+}
diff --git a/modules/upscaling/include/vkcv/upscaling/Upscaling.hpp b/modules/upscaling/include/vkcv/upscaling/Upscaling.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..06e35208744734f903c8a48aa6abcd9478685c1e
--- /dev/null
+++ b/modules/upscaling/include/vkcv/upscaling/Upscaling.hpp
@@ -0,0 +1,51 @@
+#pragma once
+
+#include <vkcv/Core.hpp>
+#include <vkcv/Handles.hpp>
+
+namespace vkcv::upscaling {
+
+    /**
+     * @defgroup vkcv_upscaling Upscaling Module
+     * A module to upscale an image from an internal resolution to a final resolution in realtime.
+     * @{
+     */
+
+    /**
+     * An abstract class to handle upscaling of images in realtime.
+     */
+	class Upscaling {
+	protected:
+        /**
+         * Reference to the current Core instance.
+         */
+		Core& m_core;
+	
+	public:
+        /**
+         * Constructor to create an upscaling instance.
+         *
+         * @param[in,out] core Reference to a Core instance
+         */
+		explicit Upscaling(Core& core);
+		
+		~Upscaling() = default;
+
+        /**
+         * Record the commands of the given upscaling instance to
+         * scale the image of the input handle to the resolution of
+         * the output image handle.
+         *
+         * @param[in] cmdStream Command stream handle to record commands
+         * @param[in] input Input image handle
+         * @param[in] output Output image handle
+         */
+		virtual void recordUpscaling(const CommandStreamHandle& cmdStream,
+									 const ImageHandle& input,
+							 		 const ImageHandle& output) = 0;
+	
+	};
+
+    /** @} */
+	
+}
diff --git a/modules/upscaling/lib/FidelityFX-FSR b/modules/upscaling/lib/FidelityFX-FSR
new file mode 160000
index 0000000000000000000000000000000000000000..a21ffb8f6c13233ba336352bdff293894c706575
--- /dev/null
+++ b/modules/upscaling/lib/FidelityFX-FSR
@@ -0,0 +1 @@
+Subproject commit a21ffb8f6c13233ba336352bdff293894c706575
diff --git a/modules/upscaling/lib/FidelityFX-FSR2 b/modules/upscaling/lib/FidelityFX-FSR2
new file mode 160000
index 0000000000000000000000000000000000000000..59950a85247baa4e099537324912a1f0e3a7b5d5
--- /dev/null
+++ b/modules/upscaling/lib/FidelityFX-FSR2
@@ -0,0 +1 @@
+Subproject commit 59950a85247baa4e099537324912a1f0e3a7b5d5
diff --git a/modules/upscaling/lib/NVIDIAImageScaling b/modules/upscaling/lib/NVIDIAImageScaling
new file mode 160000
index 0000000000000000000000000000000000000000..35e13ba316c98eeecf16f37eae70ce88019911f6
--- /dev/null
+++ b/modules/upscaling/lib/NVIDIAImageScaling
@@ -0,0 +1 @@
+Subproject commit 35e13ba316c98eeecf16f37eae70ce88019911f6
diff --git a/modules/upscaling/src/vkcv/upscaling/BilinearUpscaling.cpp b/modules/upscaling/src/vkcv/upscaling/BilinearUpscaling.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..54df1829006964b30cc1831dc7115e9d5d222a51
--- /dev/null
+++ b/modules/upscaling/src/vkcv/upscaling/BilinearUpscaling.cpp
@@ -0,0 +1,19 @@
+
+#include "vkcv/upscaling/BilinearUpscaling.hpp"
+
+namespace vkcv::upscaling {
+	
+	BilinearUpscaling::BilinearUpscaling(Core &core) : Upscaling(core) {}
+	
+	void BilinearUpscaling::recordUpscaling(const CommandStreamHandle &cmdStream, const ImageHandle &input,
+											const ImageHandle &output) {
+		m_core.recordBeginDebugLabel(cmdStream, "vkcv::upscaling::BilinearUpscaling", {
+			0.0f, 0.0f, 1.0f, 1.0f
+		});
+		
+		m_core.recordBlitImage(cmdStream, input, output, SamplerFilterType::LINEAR);
+		
+		m_core.recordEndDebugLabel(cmdStream);
+	}
+
+}
diff --git a/modules/upscaling/src/vkcv/upscaling/FSR2Upscaling.cpp b/modules/upscaling/src/vkcv/upscaling/FSR2Upscaling.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e523a11db3512f30ab1f7df421da9b7838a1ea5b
--- /dev/null
+++ b/modules/upscaling/src/vkcv/upscaling/FSR2Upscaling.cpp
@@ -0,0 +1,399 @@
+
+#include "vkcv/upscaling/FSR2Upscaling.hpp"
+
+#include <cmath>
+
+#ifndef VKCV_OVERRIDE_FSR2_WITH_FSR1
+#ifndef _MSVC_LANG
+#define FFX_GCC
+#endif
+
+#include <ffx_fsr2.h>
+#include <ffx_fsr2_vk.h>
+
+#ifdef FFX_GCC
+#undef FFX_GCC
+#endif
+#endif
+
+namespace vkcv::upscaling {
+	
+	void getFSR2Resolution(FSR2QualityMode mode,
+						   uint32_t outputWidth, uint32_t outputHeight,
+						   uint32_t &inputWidth, uint32_t &inputHeight) {
+		float scale;
+		
+		switch (mode) {
+			case FSR2QualityMode::QUALITY:
+				scale = 1.5f;
+				break;
+			case FSR2QualityMode::BALANCED:
+				scale = 1.7f;
+				break;
+			case FSR2QualityMode::PERFORMANCE:
+				scale = 2.0f;
+				break;
+			case FSR2QualityMode::ULTRA_PERFORMANCE:
+				scale = 3.0f;
+				break;
+			default:
+				scale = 1.0f;
+				break;
+		}
+		
+		inputWidth = static_cast<uint32_t>(
+				std::round(static_cast<float>(outputWidth) / scale)
+		);
+		
+		inputHeight = static_cast<uint32_t>(
+				std::round(static_cast<float>(outputHeight) / scale)
+		);
+	}
+	
+	float getFSR2LodBias(FSR2QualityMode mode) {
+		switch (mode) {
+			case FSR2QualityMode::QUALITY:
+				return -1.58f;
+			case FSR2QualityMode::BALANCED:
+				return -1.76f;
+			case FSR2QualityMode::PERFORMANCE:
+				return -2.0f;
+			case FSR2QualityMode::ULTRA_PERFORMANCE:
+				return -2.58f;
+			default:
+				return 0.0f;
+		}
+	}
+	
+#ifndef VKCV_OVERRIDE_FSR2_WITH_FSR1
+	void FSR2Upscaling::createFSR2Context(uint32_t displayWidth,
+									 uint32_t displayHeight,
+									 uint32_t renderWidth,
+									 uint32_t renderHeight) {
+		m_description->displaySize.width = displayWidth;
+		m_description->displaySize.height = displayHeight;
+		
+		m_description->maxRenderSize.width = renderWidth;
+		m_description->maxRenderSize.height = renderHeight;
+		
+		m_description->flags = FFX_FSR2_ENABLE_AUTO_EXPOSURE;
+		
+		if (m_hdr) {
+			m_description->flags |= FFX_FSR2_ENABLE_HIGH_DYNAMIC_RANGE;
+		}
+		
+		if ((m_description->displaySize.width * m_description->displaySize.height <= 1) ||
+			(m_description->maxRenderSize.width * m_description->maxRenderSize.height <= 1)) {
+			return;
+		}
+		
+		if (!m_context) {
+			m_context.reset(new FfxFsr2Context());
+		}
+		
+		memset(m_context.get(), 0, sizeof(*m_context));
+		assert(ffxFsr2ContextCreate(m_context.get(), m_description.get()) == FFX_OK);
+	}
+	
+	void FSR2Upscaling::destroyFSR2Context() {
+		m_core.getContext().getDevice().waitIdle();
+		
+		if (m_context) {
+			assert(ffxFsr2ContextDestroy(m_context.get()) == FFX_OK);
+			m_context.reset(nullptr);
+		}
+		
+		m_frameIndex = 0;
+	}
+	
+	FSR2Upscaling::FSR2Upscaling(Core &core) :
+	Upscaling(core),
+	m_scratchBuffer(),
+	
+	m_description(new FfxFsr2ContextDescription()),
+	m_context(nullptr),
+	
+	m_depth(),
+	m_velocity(),
+	
+	m_frameIndex(0),
+	
+	m_frameDeltaTime(0.0f),
+	m_reset(false),
+	
+	m_near(0.0f),
+	m_far(0.0f),
+	m_fov(0.0f),
+	
+	m_hdr(false),
+	m_sharpness(0.875f) {
+		const auto& physicalDevice = core.getContext().getPhysicalDevice();
+		
+		memset(m_description.get(), 0, sizeof(*m_description));
+		
+		m_scratchBuffer.resize(ffxFsr2GetScratchMemorySizeVK(physicalDevice));
+		
+		assert(ffxFsr2GetInterfaceVK(
+				&(m_description->callbacks),
+				m_scratchBuffer.data(),
+				m_scratchBuffer.size(),
+				physicalDevice,
+				vkGetDeviceProcAddr
+		) == FFX_OK);
+		
+		m_description->device = ffxGetDeviceVK(core.getContext().getDevice());
+		
+		createFSR2Context(1, 1, 1, 1);
+	}
+	
+	FSR2Upscaling::~FSR2Upscaling() {
+		destroyFSR2Context();
+		
+		m_scratchBuffer.clear();
+		m_description->callbacks.scratchBuffer = nullptr;
+	}
+#else
+	FSR2Upscaling::FSR2Upscaling(vkcv::Core &core) :
+	Upscaling(core), m_fsr1(new FSRUpscaling(m_core)) {}
+	
+	FSR2Upscaling::~FSR2Upscaling() {}
+#endif
+	
+	void FSR2Upscaling::update(float deltaTime, bool reset) {
+#ifndef VKCV_OVERRIDE_FSR2_WITH_FSR1
+		if (reset) {
+			m_frameIndex = 0;
+		}
+		
+		m_frameDeltaTime = deltaTime;
+		m_reset = reset;
+#endif
+	}
+	
+	void FSR2Upscaling::calcJitterOffset(uint32_t renderWidth,
+										 uint32_t renderHeight,
+										 float &jitterOffsetX,
+										 float &jitterOffsetY) const {
+#ifndef VKCV_OVERRIDE_FSR2_WITH_FSR1
+		const int32_t phaseCount = ffxFsr2GetJitterPhaseCount(
+				static_cast<int32_t>(renderWidth),
+				static_cast<int32_t>(renderHeight)
+		);
+		
+		const int32_t phaseIndex = (static_cast<int32_t>(m_frameIndex) % phaseCount);
+		
+		assert(ffxFsr2GetJitterOffset(
+				&jitterOffsetX,
+				&jitterOffsetY,
+				phaseIndex,
+				phaseCount
+		) == FFX_OK);
+		
+		jitterOffsetX *= +2.0f / renderWidth;
+		jitterOffsetY *= -2.0f / renderHeight;
+#else
+		jitterOffsetX = 0.0f;
+		jitterOffsetY = 0.0f;
+#endif
+	}
+	
+	void FSR2Upscaling::bindDepthBuffer(const ImageHandle &depthInput) {
+#ifndef VKCV_OVERRIDE_FSR2_WITH_FSR1
+		m_depth = depthInput;
+#endif
+	}
+	
+	void FSR2Upscaling::bindVelocityBuffer(const ImageHandle &velocityInput) {
+#ifndef VKCV_OVERRIDE_FSR2_WITH_FSR1
+		m_velocity = velocityInput;
+#endif
+	}
+	
+	void FSR2Upscaling::recordUpscaling(const CommandStreamHandle &cmdStream,
+										const ImageHandle &colorInput,
+										const ImageHandle &output) {
+#ifndef VKCV_OVERRIDE_FSR2_WITH_FSR1
+		m_core.recordBeginDebugLabel(cmdStream, "vkcv::upscaling::FSR2Upscaling", {
+				1.0f, 0.05f, 0.05f, 1.0f
+		});
+		
+		m_core.prepareImageForSampling(cmdStream, output);
+		
+		FfxFsr2DispatchDescription dispatch;
+		memset(&dispatch, 0, sizeof(dispatch));
+		
+		const uint32_t inputWidth = m_core.getImageWidth(colorInput);
+		const uint32_t inputHeight = m_core.getImageHeight(colorInput);
+		
+		const uint32_t outputWidth = m_core.getImageWidth(output);
+		const uint32_t outputHeight = m_core.getImageHeight(output);
+		
+		if ((m_description->displaySize.width != outputWidth) ||
+			(m_description->displaySize.height != outputHeight) ||
+			(m_description->maxRenderSize.width < inputWidth) ||
+			(m_description->maxRenderSize.height < inputHeight) ||
+			(m_hdr != ((m_description->flags & FFX_FSR2_ENABLE_HIGH_DYNAMIC_RANGE) != 0))) {
+			destroyFSR2Context();
+			
+			createFSR2Context(
+					outputWidth,
+					outputHeight,
+					inputWidth,
+					inputHeight
+			);
+		}
+		
+		if (m_context) {
+			const bool sharpeningEnabled = (
+					(m_sharpness > +0.0f) &&
+					((inputWidth < outputWidth) || (inputHeight < outputHeight))
+			);
+			
+			dispatch.color = ffxGetTextureResourceVK(
+					m_context.get(),
+					m_core.getVulkanImage(colorInput),
+					m_core.getVulkanImageView(colorInput),
+					inputWidth,
+					inputHeight,
+					static_cast<VkFormat>(m_core.getImageFormat(colorInput))
+			);
+			
+			dispatch.depth = ffxGetTextureResourceVK(
+					m_context.get(),
+					m_core.getVulkanImage(m_depth),
+					m_core.getVulkanImageView(m_depth),
+					m_core.getImageWidth(m_depth),
+					m_core.getImageHeight(m_depth),
+					static_cast<VkFormat>(m_core.getImageFormat(m_depth))
+			);
+			
+			dispatch.motionVectors = ffxGetTextureResourceVK(
+					m_context.get(),
+					m_core.getVulkanImage(m_velocity),
+					m_core.getVulkanImageView(m_velocity),
+					m_core.getImageWidth(m_velocity),
+					m_core.getImageHeight(m_velocity),
+					static_cast<VkFormat>(m_core.getImageFormat(m_velocity))
+			);
+			
+			dispatch.exposure = ffxGetTextureResourceVK(
+					m_context.get(),
+					nullptr,
+					nullptr,
+					1,
+					1,
+					VK_FORMAT_UNDEFINED
+			);
+			
+			dispatch.reactive = ffxGetTextureResourceVK(
+					m_context.get(),
+					nullptr,
+					nullptr,
+					1,
+					1,
+					VK_FORMAT_UNDEFINED
+			);
+			
+			dispatch.transparencyAndComposition = ffxGetTextureResourceVK(
+					m_context.get(),
+					nullptr,
+					nullptr,
+					1,
+					1,
+					VK_FORMAT_UNDEFINED
+			);
+			
+			dispatch.output = ffxGetTextureResourceVK(
+					m_context.get(),
+					m_core.getVulkanImage(output),
+					m_core.getVulkanImageView(output),
+					outputWidth,
+					outputHeight,
+					static_cast<VkFormat>(m_core.getImageFormat(output))
+			);
+			
+			calcJitterOffset(
+					inputWidth,
+					inputHeight,
+					dispatch.jitterOffset.x,
+					dispatch.jitterOffset.y
+			);
+			
+			dispatch.motionVectorScale.x = static_cast<float>(+2.0f);
+			dispatch.motionVectorScale.y = static_cast<float>(-2.0f);
+			
+			dispatch.renderSize.width = inputWidth;
+			dispatch.renderSize.height = inputHeight;
+			
+			dispatch.enableSharpening = sharpeningEnabled;
+			dispatch.sharpness = m_sharpness;
+			
+			dispatch.frameTimeDelta = m_frameDeltaTime * 1000.0f; // from seconds to milliseconds
+			dispatch.preExposure = 1.0f;
+			dispatch.reset = m_reset;
+			
+			dispatch.cameraNear = m_near;
+			dispatch.cameraFar = m_far;
+			dispatch.cameraFovAngleVertical = m_fov;
+			
+			m_core.recordCommandsToStream(cmdStream, [&](const vk::CommandBuffer& cmdBuffer) {
+				dispatch.commandList = ffxGetCommandListVK(cmdBuffer);
+				
+				assert(ffxFsr2ContextDispatch(
+						m_context.get(),
+						&dispatch
+				) == FFX_OK);
+				
+				m_frameIndex++;
+				m_reset = false;
+			}, nullptr);
+		}
+		
+		m_core.updateImageLayoutManual(output, vk::ImageLayout::eGeneral);
+		m_core.recordEndDebugLabel(cmdStream);
+#else
+		m_fsr1->recordUpscaling(cmdStream, colorInput, output);
+#endif
+	}
+	
+	void FSR2Upscaling::setCamera(float near, float far, float fov) {
+#ifndef VKCV_OVERRIDE_FSR2_WITH_FSR1
+		m_near = near;
+		m_far = far;
+		m_fov = fov;
+#endif
+	}
+	
+	bool FSR2Upscaling::isHdrEnabled() const {
+#ifdef VKCV_OVERRIDE_FSR2_WITH_FSR1
+		return m_fsr1->isHdrEnabled();
+#else
+		return m_hdr;
+#endif
+	}
+	
+	void FSR2Upscaling::setHdrEnabled(bool enabled) {
+#ifdef VKCV_OVERRIDE_FSR2_WITH_FSR1
+		m_fsr1->setHdrEnabled(true);
+#else
+		m_hdr = enabled;
+#endif
+	}
+	
+	float FSR2Upscaling::getSharpness() const {
+#ifdef VKCV_OVERRIDE_FSR2_WITH_FSR1
+		return m_fsr1->getSharpness();
+#else
+		return m_sharpness;
+#endif
+	}
+	
+	void FSR2Upscaling::setSharpness(float sharpness) {
+#ifdef VKCV_OVERRIDE_FSR2_WITH_FSR1
+		m_fsr1->setSharpness(sharpness);
+#else
+		m_sharpness = (sharpness < 0.0f ? 0.0f : (sharpness > 1.0f ? 1.0f : sharpness));
+#endif
+	}
+	
+}
\ No newline at end of file
diff --git a/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp b/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bacb4ce0814ff0caec0c3d4d353ecf5019e0eda4
--- /dev/null
+++ b/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp
@@ -0,0 +1,399 @@
+
+#include "vkcv/upscaling/FSRUpscaling.hpp"
+
+#include <cstdint>
+#include <cmath>
+
+#define A_CPU 1
+#include <ffx_a.h>
+#include <ffx_fsr1.h>
+
+#include "ffx_a.h.hxx"
+#include "ffx_fsr1.h.hxx"
+#include "FSR_Pass.glsl.hxx"
+
+#include <vkcv/File.hpp>
+#include <vkcv/Logger.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+
+namespace vkcv::upscaling {
+	
+	void getFSRResolution(FSRQualityMode mode,
+						  uint32_t outputWidth, uint32_t outputHeight,
+						  uint32_t &inputWidth, uint32_t &inputHeight) {
+		float scale;
+		
+		switch (mode) {
+			case FSRQualityMode::ULTRA_QUALITY:
+				scale = 1.3f;
+				break;
+			case FSRQualityMode::QUALITY:
+				scale = 1.5f;
+				break;
+			case FSRQualityMode::BALANCED:
+				scale = 1.7f;
+				break;
+			case FSRQualityMode::PERFORMANCE:
+				scale = 2.0f;
+				break;
+			default:
+				scale = 1.0f;
+				break;
+		}
+		
+		inputWidth = static_cast<uint32_t>(
+				std::round(static_cast<float>(outputWidth) / scale)
+		);
+		
+		inputHeight = static_cast<uint32_t>(
+				std::round(static_cast<float>(outputHeight) / scale)
+		);
+	}
+	
+	float getFSRLodBias(FSRQualityMode mode) {
+		switch (mode) {
+			case FSRQualityMode::ULTRA_QUALITY:
+				return -0.38f;
+			case FSRQualityMode::QUALITY:
+				return -0.58f;
+			case FSRQualityMode::BALANCED:
+				return -0.79f;
+			case FSRQualityMode::PERFORMANCE:
+				return -1.0f;
+			default:
+				return 0.0f;
+		}
+	}
+	
+	static DescriptorBindings getDescriptorBindings() {
+		DescriptorBindings descriptorBindings = {};
+
+	    auto binding_0 = DescriptorBinding {
+	            0,
+	            DescriptorType::UNIFORM_BUFFER_DYNAMIC,
+	            1,
+	            ShaderStage::COMPUTE,
+				false
+		};
+
+	    auto binding_1 = DescriptorBinding {
+				1,
+				DescriptorType::IMAGE_SAMPLED,
+				1,
+				ShaderStage::COMPUTE,
+				false
+		};
+
+	    auto binding_2 = DescriptorBinding{
+				2,
+				DescriptorType::IMAGE_STORAGE,
+				1,
+				ShaderStage::COMPUTE,
+				false
+		};
+
+	    auto binding_3 = DescriptorBinding{
+				3,
+				DescriptorType::SAMPLER,
+				1,
+				ShaderStage::COMPUTE
+		};
+
+	    descriptorBindings.insert(std::make_pair(0, binding_0));
+	    descriptorBindings.insert(std::make_pair(1, binding_1));
+	    descriptorBindings.insert(std::make_pair(2, binding_2));
+	    descriptorBindings.insert(std::make_pair(3, binding_3));
+
+	    return descriptorBindings;
+	}
+	
+	static bool writeShaderCode(const std::filesystem::path &shaderPath, const std::string& code) {
+		std::ofstream file (shaderPath.string(), std::ios::out);
+		
+		if (!file.is_open()) {
+			vkcv_log(LogLevel::ERROR, "The file could not be opened (%s)", shaderPath.string().c_str());
+			return false;
+		}
+		
+		file.seekp(0);
+		file.write(code.c_str(), static_cast<std::streamsize>(code.length()));
+		file.close();
+		
+		return true;
+	}
+	
+	static bool compileFSRShader(vkcv::shader::GLSLCompiler& compiler,
+								 const shader::ShaderCompiledFunction& compiled) {
+		std::filesystem::path directory = generateTemporaryDirectoryPath();
+		
+		if (!std::filesystem::create_directory(directory)) {
+			vkcv_log(LogLevel::ERROR, "The directory could not be created (%s)", directory.string().c_str());
+			return false;
+		}
+		
+		if (!writeShaderCode(directory / "ffx_a.h", FFX_A_H_SHADER)) {
+			return false;
+		}
+		
+		if (!writeShaderCode(directory / "ffx_fsr1.h", FFX_FSR1_H_SHADER)) {
+			return false;
+		}
+		
+		return compiler.compileSource(
+				vkcv::ShaderStage::COMPUTE,
+				FSR_PASS_GLSL_SHADER.c_str(),
+				[&directory, &compiled] (vkcv::ShaderStage shaderStage,
+										 const std::filesystem::path& path) {
+				if (compiled) {
+					compiled(shaderStage, path);
+				}
+				
+				std::filesystem::remove_all(directory);
+			}, directory
+		);
+	}
+	
+	FSRUpscaling::FSRUpscaling(Core& core) :
+	Upscaling(core),
+	m_easuPipeline(),
+	m_rcasPipeline(),
+
+	m_easuDescriptorSetLayout(m_core.createDescriptorSetLayout(getDescriptorBindings())),
+	m_easuDescriptorSet(m_core.createDescriptorSet(m_easuDescriptorSetLayout)),
+
+	m_rcasDescriptorSetLayout(m_core.createDescriptorSetLayout(getDescriptorBindings())),
+	m_rcasDescriptorSet(m_core.createDescriptorSet(m_rcasDescriptorSetLayout)),
+
+	m_easuConstants(buffer<FSRConstants>(
+			m_core,
+			BufferType::UNIFORM,
+			1,
+			BufferMemoryType::HOST_VISIBLE
+	)),
+	m_rcasConstants(buffer<FSRConstants>(
+			m_core,
+			BufferType::UNIFORM,
+			1,
+			BufferMemoryType::HOST_VISIBLE
+	)),
+	m_intermediateImage(),
+	m_sampler(m_core.createSampler(
+			SamplerFilterType::LINEAR,
+			SamplerFilterType::LINEAR,
+			SamplerMipmapMode::NEAREST,
+			SamplerAddressMode::CLAMP_TO_EDGE
+	)),
+	
+	m_hdr(false),
+	m_sharpness(0.875f) {
+		vkcv::shader::GLSLCompiler easuCompiler;
+		vkcv::shader::GLSLCompiler rcasCompiler;
+		
+		const auto& featureManager = m_core.getContext().getFeatureManager();
+		
+		const bool float16Support = (
+				featureManager.checkFeatures<vk::PhysicalDeviceFloat16Int8FeaturesKHR>(
+						vk::StructureType::ePhysicalDeviceShaderFloat16Int8FeaturesKHR,
+						[](const vk::PhysicalDeviceFloat16Int8FeaturesKHR& features) {
+							return features.shaderFloat16;
+						}
+				) &&
+				featureManager.checkFeatures<vk::PhysicalDevice16BitStorageFeaturesKHR>(
+						vk::StructureType::ePhysicalDevice16BitStorageFeaturesKHR,
+						[](const vk::PhysicalDevice16BitStorageFeaturesKHR& features) {
+							return features.storageBuffer16BitAccess;
+						}
+				)
+		);
+		
+		if (!float16Support) {
+			easuCompiler.setDefine("SAMPLE_SLOW_FALLBACK", "1");
+			rcasCompiler.setDefine("SAMPLE_SLOW_FALLBACK", "1");
+		}
+		
+		easuCompiler.setDefine("SAMPLE_EASU", "1");
+		rcasCompiler.setDefine("SAMPLE_RCAS", "1");
+		
+		{
+			ShaderProgram program;
+			compileFSRShader(easuCompiler, [&program](vkcv::ShaderStage shaderStage,
+				const std::filesystem::path& path) {
+				program.addShader(shaderStage, path);
+			});
+
+			m_easuPipeline = m_core.createComputePipeline({program,{
+				m_easuDescriptorSetLayout
+			}});
+
+			
+			DescriptorWrites writes;
+			writes.writeUniformBuffer(
+					0, m_easuConstants.getHandle(),true
+			);
+			
+			writes.writeSampler(3, m_sampler);
+			
+			m_core.writeDescriptorSet(m_easuDescriptorSet, writes);
+		}
+		
+		{
+			ShaderProgram program;
+			compileFSRShader(rcasCompiler, [&program](vkcv::ShaderStage shaderStage,
+					const std::filesystem::path& path) {
+				program.addShader(shaderStage, path);
+			});
+
+			m_rcasPipeline = m_core.createComputePipeline({ program, {
+				m_rcasDescriptorSetLayout
+			}});
+
+			DescriptorWrites writes;
+			writes.writeUniformBuffer(
+					0, m_rcasConstants.getHandle(),true
+			);
+			
+			writes.writeSampler(3, m_sampler);
+			
+			m_core.writeDescriptorSet(m_rcasDescriptorSet, writes);
+		}
+	}
+	
+	void FSRUpscaling::recordUpscaling(const CommandStreamHandle& cmdStream,
+									   const ImageHandle& input,
+									   const ImageHandle& output) {
+		m_core.recordBeginDebugLabel(cmdStream, "vkcv::upscaling::FSRUpscaling", {
+			1.0f, 0.0f, 0.0f, 1.0f
+		});
+		
+		const uint32_t inputWidth = m_core.getImageWidth(input);
+		const uint32_t inputHeight = m_core.getImageHeight(input);
+		
+		const uint32_t outputWidth = m_core.getImageWidth(output);
+		const uint32_t outputHeight = m_core.getImageHeight(output);
+		
+		if ((!m_intermediateImage) ||
+			(outputWidth != m_core.getImageWidth(m_intermediateImage)) ||
+			(outputHeight != m_core.getImageHeight(m_intermediateImage))) {
+			ImageConfig imageConfig (outputWidth, outputHeight);
+			imageConfig.setSupportingStorage(true);
+			
+			m_intermediateImage = m_core.createImage(
+					m_core.getImageFormat(output),
+					imageConfig
+			);
+			
+			m_core.prepareImageForStorage(cmdStream, m_intermediateImage);
+		}
+		
+		const bool rcasEnabled = (
+				(m_sharpness > +0.0f) &&
+				((inputWidth < outputWidth) || (inputHeight < outputHeight))
+		);
+		
+		{
+			FSRConstants consts = {};
+			
+			FsrEasuCon(
+					consts.Const0, consts.Const1, consts.Const2, consts.Const3,
+					static_cast<AF1>(inputWidth), static_cast<AF1>(inputHeight),
+					static_cast<AF1>(inputWidth), static_cast<AF1>(inputHeight),
+					static_cast<AF1>(outputWidth), static_cast<AF1>(outputHeight)
+			);
+			
+			consts.Sample[0] = (((m_hdr) && (!rcasEnabled)) ? 1 : 0);
+			
+			m_easuConstants.fill(&consts);
+		}
+		
+		static const uint32_t threadGroupWorkRegionDim = 16;
+		
+		DispatchSize dispatch = dispatchInvocations(
+				DispatchSize(outputWidth, outputHeight),
+				DispatchSize(threadGroupWorkRegionDim, threadGroupWorkRegionDim)
+		);
+		
+		m_core.recordBufferMemoryBarrier(cmdStream, m_easuConstants.getHandle());
+		
+		if (rcasEnabled) {
+			{
+				DescriptorWrites writes;
+				writes.writeSampledImage(1, input);
+				writes.writeStorageImage(2, m_intermediateImage);
+				
+				m_core.writeDescriptorSet(m_easuDescriptorSet, writes);
+			}
+			{
+				DescriptorWrites writes;
+				writes.writeSampledImage(1, m_intermediateImage);
+				writes.writeStorageImage(2, output);
+				
+				m_core.writeDescriptorSet(m_rcasDescriptorSet, writes);
+			}
+			
+			m_core.recordComputeDispatchToCmdStream(
+					cmdStream,
+					m_easuPipeline,
+					dispatch,
+					{ useDescriptorSet(0, m_easuDescriptorSet, { 0 }) },
+					PushConstants(0)
+			);
+			
+			{
+				FSRConstants consts = {};
+				
+				FsrRcasCon(consts.Const0, (1.0f - m_sharpness) * 2.0f);
+				consts.Sample[0] = (m_hdr ? 1 : 0);
+				
+				m_rcasConstants.fill(&consts);
+			}
+			
+			m_core.recordBufferMemoryBarrier(cmdStream, m_rcasConstants.getHandle());
+			m_core.prepareImageForSampling(cmdStream, m_intermediateImage);
+			
+			m_core.recordComputeDispatchToCmdStream(
+					cmdStream,
+					m_rcasPipeline,
+					dispatch,
+					{ useDescriptorSet(0,m_rcasDescriptorSet, { 0 }) },
+					PushConstants(0)
+			);
+			
+			m_core.prepareImageForStorage(cmdStream, m_intermediateImage);
+		} else {
+			{
+				DescriptorWrites writes;
+				writes.writeSampledImage(1, input);
+				writes.writeStorageImage(2, output);
+				
+				m_core.writeDescriptorSet(m_easuDescriptorSet, writes);
+			}
+			
+			m_core.recordComputeDispatchToCmdStream(
+					cmdStream,
+					m_easuPipeline,
+					dispatch,
+					{ useDescriptorSet(0, m_easuDescriptorSet, { 0 }) },
+					PushConstants(0)
+			);
+		}
+		
+		m_core.recordEndDebugLabel(cmdStream);
+	}
+	
+	bool FSRUpscaling::isHdrEnabled() const {
+		return m_hdr;
+	}
+	
+	void FSRUpscaling::setHdrEnabled(bool enabled) {
+		m_hdr = enabled;
+	}
+	
+	float FSRUpscaling::getSharpness() const {
+		return m_sharpness;
+	}
+	
+	void FSRUpscaling::setSharpness(float sharpness) {
+		m_sharpness = (sharpness < 0.0f ? 0.0f : (sharpness > 1.0f ? 1.0f : sharpness));
+	}
+	
+}
diff --git a/modules/upscaling/src/vkcv/upscaling/NISUpscaling.cpp b/modules/upscaling/src/vkcv/upscaling/NISUpscaling.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8c3a973e6914f4cb9b31eef1acf805e6448237eb
--- /dev/null
+++ b/modules/upscaling/src/vkcv/upscaling/NISUpscaling.cpp
@@ -0,0 +1,281 @@
+
+#include "vkcv/upscaling/NISUpscaling.hpp"
+
+#include <NIS_Config.h>
+
+#include "NIS_Main.glsl.hxx"
+#include "NIS_Scaler.h.hxx"
+
+#include <vkcv/File.hpp>
+#include <vkcv/Image.hpp>
+#include <vkcv/Logger.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+
+namespace vkcv::upscaling {
+	
+	static DescriptorBindings getDescriptorBindings() {
+		DescriptorBindings descriptorBindings = {};
+		
+		auto binding_0 = DescriptorBinding {
+				0,
+				DescriptorType::UNIFORM_BUFFER_DYNAMIC,
+				1,
+				ShaderStage::COMPUTE,
+				false
+		};
+		
+		auto binding_1 = DescriptorBinding{
+				1,
+				DescriptorType::SAMPLER,
+				1,
+				ShaderStage::COMPUTE
+		};
+		
+		auto binding_2 = DescriptorBinding {
+				2,
+				DescriptorType::IMAGE_SAMPLED,
+				1,
+				ShaderStage::COMPUTE,
+				false
+		};
+		
+		auto binding_3 = DescriptorBinding{
+				3,
+				DescriptorType::IMAGE_STORAGE,
+				1,
+				ShaderStage::COMPUTE,
+				false
+		};
+		
+		auto binding_4 = DescriptorBinding {
+				4,
+				DescriptorType::IMAGE_SAMPLED,
+				1,
+				ShaderStage::COMPUTE,
+				false
+		};
+		
+		auto binding_5 = DescriptorBinding {
+				5,
+				DescriptorType::IMAGE_SAMPLED,
+				1,
+				ShaderStage::COMPUTE,
+				false
+		};
+		
+		descriptorBindings.insert(std::make_pair(0, binding_0));
+		descriptorBindings.insert(std::make_pair(1, binding_1));
+		descriptorBindings.insert(std::make_pair(2, binding_2));
+		descriptorBindings.insert(std::make_pair(3, binding_3));
+		descriptorBindings.insert(std::make_pair(4, binding_4));
+		descriptorBindings.insert(std::make_pair(5, binding_5));
+		
+		return descriptorBindings;
+	}
+	
+	static ImageHandle createFilterImage(Core &core,
+										 const void* data) {
+		const size_t rowPitch = kFilterSize * 2;
+		const size_t imageSize = rowPitch * kPhaseCount;
+		
+		Image image = vkcv::image(
+				core,
+				vk::Format::eR16G16B16A16Sfloat,
+				kFilterSize / 4,
+				kPhaseCount
+		);
+		
+		image.fill(data, imageSize);
+		return image.getHandle();
+	}
+	
+	static bool writeShaderCode(const std::filesystem::path &shaderPath, const std::string& code) {
+		std::ofstream file (shaderPath.string(), std::ios::out);
+		
+		if (!file.is_open()) {
+			vkcv_log(LogLevel::ERROR, "The file could not be opened (%s)", shaderPath.string().c_str());
+			return false;
+		}
+		
+		file.seekp(0);
+		file.write(code.c_str(), static_cast<std::streamsize>(code.length()));
+		file.close();
+		
+		return true;
+	}
+	
+	static bool compileNISShader(vkcv::shader::GLSLCompiler& compiler,
+								 const shader::ShaderCompiledFunction& compiled) {
+		std::filesystem::path directory = generateTemporaryDirectoryPath();
+		
+		if (!std::filesystem::create_directory(directory)) {
+			vkcv_log(LogLevel::ERROR, "The directory could not be created (%s)", directory.string().c_str());
+			return false;
+		}
+		
+		if (!writeShaderCode(directory / "NIS_Scaler.h", NIS_SCALER_H_SHADER)) {
+			return false;
+		}
+		
+		return compiler.compileSource(
+				vkcv::ShaderStage::COMPUTE,
+				NIS_MAIN_GLSL_SHADER.c_str(),
+				[&directory, &compiled] (vkcv::ShaderStage shaderStage,
+										 const std::filesystem::path& path) {
+				if (compiled) {
+					compiled(shaderStage, path);
+				}
+				
+				std::filesystem::remove_all(directory);
+			}, directory
+		);
+	}
+	
+	NISUpscaling::NISUpscaling(Core &core) :
+	Upscaling(core),
+	m_scalerPipeline(),
+	
+	m_scalerDescriptorSetLayout(m_core.createDescriptorSetLayout(getDescriptorBindings())),
+	m_scalerDescriptorSet(m_core.createDescriptorSet(m_scalerDescriptorSetLayout)),
+	
+	m_scalerConstants(buffer<uint8_t>(
+			m_core,
+			BufferType::UNIFORM,
+			sizeof(NISConfig),
+			BufferMemoryType::HOST_VISIBLE
+	)),
+	m_sampler(m_core.createSampler(
+			SamplerFilterType::LINEAR,
+			SamplerFilterType::LINEAR,
+			SamplerMipmapMode::NEAREST,
+			SamplerAddressMode::CLAMP_TO_EDGE
+	)),
+	
+	m_coefScaleImage(createFilterImage(m_core, coef_scale_fp16)),
+	m_coefUsmImage(createFilterImage(m_core, coef_usm_fp16)),
+	
+	m_blockWidth(0),
+	m_blockHeight(0),
+	
+	m_hdr(false),
+	m_sharpness(0.875f) {
+		vkcv::shader::GLSLCompiler scalerCompiler;
+		
+		scalerCompiler.setDefine("NIS_SCALER", "1");
+		scalerCompiler.setDefine("NIS_GLSL", "1");
+		
+		NISOptimizer optimizer (true, NISGPUArchitecture::NVIDIA_Generic);
+		
+		m_blockWidth = optimizer.GetOptimalBlockWidth();
+		m_blockHeight = optimizer.GetOptimalBlockHeight();
+		
+		scalerCompiler.setDefine("NIS_BLOCK_WIDTH", std::to_string(m_blockWidth));
+		scalerCompiler.setDefine("NIS_BLOCK_HEIGHT", std::to_string(m_blockHeight));
+		
+		const uint32_t threadGroupSize = optimizer.GetOptimalThreadGroupSize();
+		
+		scalerCompiler.setDefine("NIS_THREAD_GROUP_SIZE", std::to_string(threadGroupSize));
+		
+		{
+			ShaderProgram program;
+			compileNISShader(scalerCompiler, [&program](vkcv::ShaderStage shaderStage,
+														const std::filesystem::path& path) {
+				program.addShader(shaderStage, path);
+			});
+			
+			m_scalerPipeline = m_core.createComputePipeline({program,{
+					m_scalerDescriptorSetLayout
+			}});
+			
+			
+			DescriptorWrites writes;
+			writes.writeUniformBuffer(
+					0, m_scalerConstants.getHandle(), true
+			);
+			
+			writes.writeSampler(1, m_sampler);
+			writes.writeSampledImage(4, m_coefScaleImage);
+			writes.writeSampledImage(5, m_coefUsmImage);
+			
+			m_core.writeDescriptorSet(m_scalerDescriptorSet, writes);
+		}
+	}
+	
+	void NISUpscaling::recordUpscaling(const CommandStreamHandle &cmdStream,
+									   const ImageHandle &input,
+									   const ImageHandle &output) {
+		m_core.recordBeginDebugLabel(cmdStream, "vkcv::upscaling::NISUpscaling", {
+				0.0f, 1.0f, 0.0f, 1.0f
+		});
+		
+		const uint32_t inputWidth = m_core.getImageWidth(input);
+		const uint32_t inputHeight = m_core.getImageHeight(input);
+		
+		const uint32_t outputWidth = m_core.getImageWidth(output);
+		const uint32_t outputHeight = m_core.getImageHeight(output);
+		
+		NISConfig config {};
+		NVScalerUpdateConfig(
+				config,
+				m_sharpness,
+				0, 0,
+				inputWidth,
+				inputHeight,
+				inputWidth,
+				inputHeight,
+				0, 0,
+				outputWidth,
+				outputHeight,
+				outputWidth,
+				outputHeight,
+				m_hdr? NISHDRMode::PQ : NISHDRMode::None
+		);
+		
+		m_scalerConstants.fill(
+				reinterpret_cast<uint8_t*>(&config),
+				sizeof(config)
+		);
+		
+		DispatchSize dispatch = dispatchInvocations(
+				DispatchSize(outputWidth, outputHeight),
+				DispatchSize(m_blockWidth, m_blockHeight)
+		);
+		
+		m_core.recordBufferMemoryBarrier(cmdStream, m_scalerConstants.getHandle());
+		
+		{
+			DescriptorWrites writes;
+			writes.writeSampledImage(2, input);
+			writes.writeStorageImage(3, output);
+			
+			m_core.writeDescriptorSet(m_scalerDescriptorSet, writes);
+		}
+		
+		m_core.recordComputeDispatchToCmdStream(
+				cmdStream,
+				m_scalerPipeline,
+				dispatch,
+				{ useDescriptorSet(0, m_scalerDescriptorSet, { 0 }) },
+				PushConstants(0)
+		);
+		
+		m_core.recordEndDebugLabel(cmdStream);
+	}
+	
+	bool NISUpscaling::isHdrEnabled() const {
+		return m_hdr;
+	}
+	
+	void NISUpscaling::setHdrEnabled(bool enabled) {
+		m_hdr = enabled;
+	}
+	
+	float NISUpscaling::getSharpness() const {
+		return m_sharpness;
+	}
+	
+	void NISUpscaling::setSharpness(float sharpness) {
+		m_sharpness = (sharpness < 0.0f ? 0.0f : (sharpness > 1.0f ? 1.0f : sharpness));
+	}
+
+}
diff --git a/modules/upscaling/src/vkcv/upscaling/Upscaling.cpp b/modules/upscaling/src/vkcv/upscaling/Upscaling.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b0c3dee9b1c799c0e1f07b59b03d3ad46bd453ed
--- /dev/null
+++ b/modules/upscaling/src/vkcv/upscaling/Upscaling.cpp
@@ -0,0 +1,8 @@
+
+#include "vkcv/upscaling/Upscaling.hpp"
+
+namespace vkcv::upscaling {
+	
+	Upscaling::Upscaling(Core &core) : m_core(core) {}
+	
+}
diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt
index 34fbcb0cf8dd3f1d34efd2cc8424994c7da76e32..3860ec7a2135a9ecc39db6e0f7a1ddd167e3b391 100644
--- a/projects/CMakeLists.txt
+++ b/projects/CMakeLists.txt
@@ -1,7 +1,20 @@
 
+include(${vkcv_config_ext}/Project.cmake)
+
 # Add new projects/examples here:
-add_subdirectory(bloom)
+add_subdirectory(bindless_textures)
+add_subdirectory(fire_works)
 add_subdirectory(first_triangle)
 add_subdirectory(first_mesh)
 add_subdirectory(first_scene)
+add_subdirectory(head_demo)
+add_subdirectory(indirect_dispatch)
+add_subdirectory(indirect_draw)
+add_subdirectory(mesh_shader)
+add_subdirectory(mpm)
+add_subdirectory(particle_simulation)
+add_subdirectory(path_tracer)
+add_subdirectory(ray_tracer)
+add_subdirectory(rtx_ambient_occlusion)
+add_subdirectory(sph)
 add_subdirectory(voxelization)
\ No newline at end of file
diff --git a/projects/bindless_textures/.gitignore b/projects/bindless_textures/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..cd45650aee8ab49ad568556452dde2d9d51d5f13
--- /dev/null
+++ b/projects/bindless_textures/.gitignore
@@ -0,0 +1 @@
+bindless_textures
\ No newline at end of file
diff --git a/projects/bindless_textures/CMakeLists.txt b/projects/bindless_textures/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..86a93e04427d7ff103bda1dbd5a4602ff337d639
--- /dev/null
+++ b/projects/bindless_textures/CMakeLists.txt
@@ -0,0 +1,15 @@
+cmake_minimum_required(VERSION 3.16)
+project(bindless_textures)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+# adding source files to the project
+add_project(bindless_textures src/main.cpp)
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(bindless_textures SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(bindless_textures vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera vkcv_shader_compiler)
diff --git a/projects/bindless_textures/README.md b/projects/bindless_textures/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..0c5498551ae55802010bbb4295d35ec2ba43978a
--- /dev/null
+++ b/projects/bindless_textures/README.md
@@ -0,0 +1,16 @@
+# Bindless textures
+An example project to show usage of bindless descriptor indexing with the VkCV framework
+
+![Screenshot](../../screenshots/bindless_textures.png)
+
+## Details
+
+The project utilizes a Vulkan extension to access an array of descriptors with arbitrary size. The 
+size does not need to be known during shader compilation and can be adjusted during runtime when
+creating the graphics pipeline.
+
+## Extensions
+
+Here is a list of the used extensions:
+
+ - [VK_EXT_descriptor_indexing](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_descriptor_indexing.html)
diff --git a/projects/bindless_textures/resources/cube/Grass001_1K_AmbientOcclusion.jpg b/projects/bindless_textures/resources/cube/Grass001_1K_AmbientOcclusion.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2217fb53744f9166232a40b78ee9518e04b0ded5
--- /dev/null
+++ b/projects/bindless_textures/resources/cube/Grass001_1K_AmbientOcclusion.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f6c88f8f3facd9e91f8dd160bd8c4a602e433872ed18e08015a9fa9dfff889de
+size 901465
diff --git a/projects/bindless_textures/resources/cube/Grass001_1K_Color.jpg b/projects/bindless_textures/resources/cube/Grass001_1K_Color.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b8aa1533ae5a023a5fc8457f30ed60efe3bda32d
--- /dev/null
+++ b/projects/bindless_textures/resources/cube/Grass001_1K_Color.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:878b8fe4747d9ce693220edea1915de550e8f14d402d26a0915f162d40f84e91
+size 1763328
diff --git a/projects/bindless_textures/resources/cube/Grass001_1K_Displacement.jpg b/projects/bindless_textures/resources/cube/Grass001_1K_Displacement.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..89789cba150eea7c7abbdc1851090f6021af978a
--- /dev/null
+++ b/projects/bindless_textures/resources/cube/Grass001_1K_Displacement.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1e8300e1107bee7e681059d9da0a7e3ca422977e8b6f496e16452a4c94b3d385
+size 912347
diff --git a/projects/bindless_textures/resources/cube/Grass001_1K_Normal.jpg b/projects/bindless_textures/resources/cube/Grass001_1K_Normal.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3163d6391592ace10446cb71141a2192e63e9660
--- /dev/null
+++ b/projects/bindless_textures/resources/cube/Grass001_1K_Normal.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:684426b49e841e267f12f06dc955b5b1d01b3ba3659bea0c5d73be889700929f
+size 2336471
diff --git a/projects/bindless_textures/resources/cube/Grass001_1K_Roughness.jpg b/projects/bindless_textures/resources/cube/Grass001_1K_Roughness.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..10e6ac33badf2a4795766a66546a62c67eb8b558
--- /dev/null
+++ b/projects/bindless_textures/resources/cube/Grass001_1K_Roughness.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d952ffb098faf9ac5eb25134eabac08f0c65d927a448b5e7b4f9c72510cfcbe0
+size 822659
diff --git a/projects/first_mesh/resources/Szene/boards2_vcyc_jpg.jpg b/projects/bindless_textures/resources/cube/boards2_vcyc_jpg.jpg
similarity index 100%
rename from projects/first_mesh/resources/Szene/boards2_vcyc_jpg.jpg
rename to projects/bindless_textures/resources/cube/boards2_vcyc_jpg.jpg
diff --git a/projects/first_mesh/resources/cube/cube.bin b/projects/bindless_textures/resources/cube/cube.bin
similarity index 100%
rename from projects/first_mesh/resources/cube/cube.bin
rename to projects/bindless_textures/resources/cube/cube.bin
diff --git a/projects/first_mesh/resources/cube/cube.blend b/projects/bindless_textures/resources/cube/cube.blend
similarity index 100%
rename from projects/first_mesh/resources/cube/cube.blend
rename to projects/bindless_textures/resources/cube/cube.blend
diff --git a/projects/first_mesh/resources/cube/cube.blend1 b/projects/bindless_textures/resources/cube/cube.blend1
similarity index 100%
rename from projects/first_mesh/resources/cube/cube.blend1
rename to projects/bindless_textures/resources/cube/cube.blend1
diff --git a/projects/first_mesh/resources/cube/cube.glb b/projects/bindless_textures/resources/cube/cube.glb
similarity index 100%
rename from projects/first_mesh/resources/cube/cube.glb
rename to projects/bindless_textures/resources/cube/cube.glb
diff --git a/projects/first_mesh/resources/cube/cube.gltf b/projects/bindless_textures/resources/cube/cube.gltf
similarity index 100%
rename from projects/first_mesh/resources/cube/cube.gltf
rename to projects/bindless_textures/resources/cube/cube.gltf
diff --git a/projects/bindless_textures/resources/shaders/shader.frag b/projects/bindless_textures/resources/shaders/shader.frag
new file mode 100644
index 0000000000000000000000000000000000000000..c855eb407944c415dc4055716aa64a531c830ef3
--- /dev/null
+++ b/projects/bindless_textures/resources/shaders/shader.frag
@@ -0,0 +1,16 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_EXT_nonuniform_qualifier : enable
+
+layout(location = 0) in vec3 passNormal;
+layout(location = 1) in vec2 passUV;
+layout(location = 2) in flat int passTextureIndex;
+
+layout(location = 0) out vec3 outColor;
+
+layout(set=0, binding=0) uniform sampler    textureSampler;
+layout(set=0, binding=1) uniform texture2D  materialTextures[];
+
+void main()	{
+	outColor =  texture(sampler2D(materialTextures[nonuniformEXT(passTextureIndex)], textureSampler), passUV).rgb;
+}
\ No newline at end of file
diff --git a/projects/bindless_textures/resources/shaders/shader.vert b/projects/bindless_textures/resources/shaders/shader.vert
new file mode 100644
index 0000000000000000000000000000000000000000..6bc918c6a186dcfb965719cd1e08cb448a49b44e
--- /dev/null
+++ b/projects/bindless_textures/resources/shaders/shader.vert
@@ -0,0 +1,43 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 inPosition;
+layout(location = 1) in vec3 inNormal;
+layout(location = 2) in vec2 inUV;
+
+layout(location = 0) out vec3 passNormal;
+layout(location = 1) out vec2 passUV;
+layout(location = 2) out flat int passTextureIndex;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+};
+
+void main()
+{
+	gl_Position = mvp * vec4(inPosition, 1.0);
+	passNormal  = inNormal;
+    passUV      = inUV;
+
+    passTextureIndex = (gl_VertexIndex / 4);
+
+    /*
+    if(inNormal.x > 0.9)
+        passTextureIndex = 0;
+
+    if(inNormal.x < -0.9)
+        passTextureIndex = 1;
+
+    if(inNormal.y > 0.9)
+        passTextureIndex = 2;
+
+    if(inNormal.y < -0.9)
+        passTextureIndex = 3;
+
+    if(inNormal.z > 0.9)
+        passTextureIndex = 4;
+
+    if(inNormal.z < -0.9)
+        passTextureIndex = 5;
+    */
+}
\ No newline at end of file
diff --git a/projects/bindless_textures/src/main.cpp b/projects/bindless_textures/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5021de5ca081f009ab39806c5648662f86482418
--- /dev/null
+++ b/projects/bindless_textures/src/main.cpp
@@ -0,0 +1,254 @@
+#include <iostream>
+#include <vkcv/Buffer.hpp>
+#include <vkcv/Core.hpp>
+#include <vkcv/Image.hpp>
+#include <vkcv/Pass.hpp>
+#include <vkcv/Sampler.hpp>
+#include <GLFW/glfw3.h>
+#include <vkcv/camera/CameraManager.hpp>
+#include <chrono>
+#include <vkcv/asset/asset_loader.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+
+int main(int argc, const char** argv) {
+	const std::string applicationName = "Bindless Textures";
+
+	vkcv::Features features;
+	features.requireExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
+    features.requireExtensionFeature<vk::PhysicalDeviceDescriptorIndexingFeatures>(
+            VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME, [](vk::PhysicalDeviceDescriptorIndexingFeatures &features) {
+                features.setShaderInputAttachmentArrayDynamicIndexing(true);
+                features.setShaderUniformTexelBufferArrayDynamicIndexing(true);
+                features.setShaderStorageTexelBufferArrayDynamicIndexing(true);
+                features.setShaderUniformBufferArrayNonUniformIndexing(true);
+                features.setShaderSampledImageArrayNonUniformIndexing(true);
+                features.setShaderStorageBufferArrayNonUniformIndexing(true);
+                features.setShaderStorageImageArrayNonUniformIndexing(true);
+                features.setShaderInputAttachmentArrayNonUniformIndexing(true);
+                features.setShaderUniformTexelBufferArrayNonUniformIndexing(true);
+                features.setShaderStorageTexelBufferArrayNonUniformIndexing(true);
+
+                features.setDescriptorBindingUniformBufferUpdateAfterBind(true);
+                features.setDescriptorBindingSampledImageUpdateAfterBind(true);
+                features.setDescriptorBindingStorageImageUpdateAfterBind(true);
+                features.setDescriptorBindingStorageBufferUpdateAfterBind(true);
+                features.setDescriptorBindingUniformTexelBufferUpdateAfterBind(true);
+                features.setDescriptorBindingStorageTexelBufferUpdateAfterBind(true);
+
+                features.setDescriptorBindingUpdateUnusedWhilePending(true);
+                features.setDescriptorBindingPartiallyBound(true);
+                features.setDescriptorBindingVariableDescriptorCount(true);
+                features.setRuntimeDescriptorArray(true);
+            }
+	);
+
+	vkcv::Core core = vkcv::Core::create(
+		applicationName,
+		VK_MAKE_VERSION(0, 0, 1),
+		{ vk::QueueFlagBits::eGraphics ,vk::QueueFlagBits::eCompute , vk::QueueFlagBits::eTransfer },
+		features
+	);
+
+	vkcv::WindowHandle windowHandle = core.createWindow(applicationName, 800, 600, true);
+	vkcv::Window& window = core.getWindow(windowHandle);
+
+	vkcv::asset::Scene mesh;
+
+	// TEST DATA
+	std::vector<vkcv::Image> texturesArray;
+    const std::string grassPaths[5] = { "resources/cube/Grass001_1K_AmbientOcclusion.jpg",
+                                        "resources/cube/Grass001_1K_Color.jpg",
+                                        "resources/cube/Grass001_1K_Displacement.jpg",
+                                        "resources/cube/Grass001_1K_Normal.jpg",
+                                        "resources/cube/Grass001_1K_Roughness.jpg" };
+	
+    for (const auto &path : grassPaths) {
+        std::filesystem::path grassPath(path);
+        vkcv::asset::Texture grassTexture = vkcv::asset::loadTexture(grassPath);
+
+        vkcv::Image texture = vkcv::image(
+				core,
+				vk::Format::eR8G8B8A8Srgb,
+				grassTexture.width,
+				grassTexture.height
+		);
+		
+        texture.fill(grassTexture.data.data());
+        texturesArray.push_back(texture);
+    }
+
+	const char* path = argc > 1 ? argv[1] : "resources/cube/cube.gltf";
+	int result = vkcv::asset::loadScene(path, mesh);
+
+	if (result == 1) {
+		std::cout << "Mesh loading successful!" << std::endl;
+	} else {
+		std::cerr << "Mesh loading failed: " << result << std::endl;
+		return 1;
+	}
+
+	assert(!mesh.vertexGroups.empty());
+	auto vertexBuffer = vkcv::buffer<uint8_t>(
+			core,
+			vkcv::BufferType::VERTEX,
+			mesh.vertexGroups[0].vertexBuffer.data.size(),
+			vkcv::BufferMemoryType::DEVICE_LOCAL
+	);
+	
+	vertexBuffer.fill(mesh.vertexGroups[0].vertexBuffer.data);
+
+	auto indexBuffer = vkcv::buffer<uint8_t>(
+			core,
+			vkcv::BufferType::INDEX,
+			mesh.vertexGroups[0].indexBuffer.data.size(),
+			vkcv::BufferMemoryType::DEVICE_LOCAL
+	);
+	
+	indexBuffer.fill(mesh.vertexGroups[0].indexBuffer.data);
+	
+	vkcv::PassHandle firstMeshPass = vkcv::passSwapchain(
+			core,
+			window.getSwapchain(),
+			{ vk::Format::eUndefined, vk::Format::eD32Sfloat }
+	);
+
+	if (!firstMeshPass) {
+		std::cerr << "Error. Could not create renderpass. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+	vkcv::ShaderProgram firstMeshProgram;
+	vkcv::shader::GLSLCompiler compiler;
+	
+	compiler.compileProgram(firstMeshProgram, {
+		{ vkcv::ShaderStage::VERTEX, "resources/shaders/shader.vert" },
+		{ vkcv::ShaderStage::FRAGMENT, "resources/shaders/shader.frag" }
+	}, nullptr);
+	
+	const auto vertexBufferBindings = vkcv::asset::loadVertexBufferBindings(
+			mesh.vertexGroups[0].vertexBuffer.attributes,
+			vertexBuffer.getHandle(),
+			{
+					vkcv::asset::PrimitiveType::POSITION,
+					vkcv::asset::PrimitiveType::NORMAL,
+					vkcv::asset::PrimitiveType::TEXCOORD_0
+			}
+	);
+	
+	std::vector<vkcv::VertexBinding> bindings = vkcv::createVertexBindings(
+			firstMeshProgram.getVertexAttachments()
+	);
+	
+	const vkcv::VertexLayout firstMeshLayout { bindings };
+	const std::unordered_map<uint32_t, vkcv::DescriptorBinding> &descriptorBindings = firstMeshProgram.getReflectedDescriptors().at(0);
+
+    std::unordered_map<uint32_t, vkcv::DescriptorBinding> adjustedBindings = descriptorBindings;
+    adjustedBindings[1].descriptorCount = 6;
+
+    vkcv::DescriptorSetLayoutHandle descriptorSetLayout = core.createDescriptorSetLayout(adjustedBindings);
+	vkcv::DescriptorSetHandle descriptorSet = core.createDescriptorSet(descriptorSetLayout);
+
+	vkcv::GraphicsPipelineHandle firstMeshPipeline = core.createGraphicsPipeline(
+			vkcv::GraphicsPipelineConfig(
+					firstMeshProgram,
+					firstMeshPass,
+					{ firstMeshLayout },
+					{ descriptorSetLayout }
+			)
+	);
+	
+	if (!firstMeshPipeline) {
+		std::cerr << "Error. Could not create graphics pipeline. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+	
+	if (mesh.textures.empty()) {
+		std::cerr << "Error. No textures found. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+	
+	{
+		vkcv::asset::Texture &tex = mesh.textures[0];
+		vkcv::Image texture = vkcv::image(core, vk::Format::eR8G8B8A8Srgb, tex.w, tex.h);
+		texture.fill(tex.data.data());
+		texturesArray.push_back(texture);
+	}
+	
+	auto downsampleStream = core.createCommandStream(vkcv::QueueType::Graphics);
+	
+	for (auto& texture : texturesArray) {
+		texture.recordMipChainGeneration(downsampleStream, core.getDownsampler());
+	}
+	
+	core.submitCommandStream(downsampleStream, false);
+
+	vkcv::SamplerHandle sampler = vkcv::samplerLinear(core);
+	vkcv::DescriptorWrites setWrites;
+	
+	for(uint32_t i = 0; i < 6; i++) {
+		setWrites.writeSampledImage(
+				1,
+				texturesArray[i].getHandle(),
+				0,
+				false,
+				i
+		);
+	}
+
+	setWrites.writeSampler(0, sampler);
+
+	core.writeDescriptorSet(descriptorSet, setWrites);
+
+	vkcv::ImageHandle depthBuffer;
+
+	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+	
+	vkcv::VertexData vertexData (vertexBufferBindings);
+	vertexData.setIndexBuffer(indexBuffer.getHandle());
+	vertexData.setCount(mesh.vertexGroups[0].numIndices);
+	
+	vkcv::InstanceDrawcall drawcall (vertexData);
+	drawcall.useDescriptorSet(0, descriptorSet);
+
+    vkcv::camera::CameraManager cameraManager(window);
+    auto camHandle0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+	auto camHandle1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+	
+	cameraManager.getCamera(camHandle0).setPosition(glm::vec3(0, 0, -3));
+	cameraManager.getCamera(camHandle1).setPosition(glm::vec3(0, 0, -3));
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
+		if ((!depthBuffer) ||
+			(swapchainWidth != core.getImageWidth(depthBuffer)) ||
+			(swapchainHeight != core.getImageHeight(depthBuffer))) {
+			depthBuffer = core.createImage(
+					vk::Format::eD32Sfloat,
+					vkcv::ImageConfig(swapchainWidth, swapchainHeight)
+			);
+		}
+
+		cameraManager.update(dt);
+        glm::mat4 mvp = cameraManager.getActiveCamera().getMVP();
+
+		vkcv::PushConstants pushConstants = vkcv::pushConstants<glm::mat4>();
+		pushConstants.appendDrawcall(mvp);
+
+		const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer };
+		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+
+		core.recordDrawcallsToCmdStream(
+			cmdStream,
+			firstMeshPipeline,
+			pushConstants,
+			{ drawcall },
+			renderTargets,
+			windowHandle
+		);
+		
+		core.prepareSwapchainImageForPresent(cmdStream);
+		core.submitCommandStream(cmdStream);
+	});
+	
+	return 0;
+}
diff --git a/projects/bloom/.gitignore b/projects/bloom/.gitignore
deleted file mode 100644
index 3643183e0628e666abab193e1dd1d92c1774ac61..0000000000000000000000000000000000000000
--- a/projects/bloom/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-bloom
\ No newline at end of file
diff --git a/projects/bloom/CMakeLists.txt b/projects/bloom/CMakeLists.txt
deleted file mode 100644
index 8171938e7cb430aacce5562af44f628c11c97c54..0000000000000000000000000000000000000000
--- a/projects/bloom/CMakeLists.txt
+++ /dev/null
@@ -1,32 +0,0 @@
-cmake_minimum_required(VERSION 3.16)
-project(bloom)
-
-# setting c++ standard for the project
-set(CMAKE_CXX_STANDARD 17)
-set(CMAKE_CXX_STANDARD_REQUIRED ON)
-
-# this should fix the execution path to load local files from the project
-set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
-
-# adding source files to the project
-add_executable(bloom src/main.cpp)
-
-target_sources(bloom PRIVATE
-		src/BloomAndFlares.cpp
-		src/BloomAndFlares.hpp)
-
-# this should fix the execution path to load local files from the project (for MSVC)
-if(MSVC)
-	set_target_properties(bloom PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-	set_target_properties(bloom PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-    
-    # in addition to setting the output directory, the working directory has to be set
-	# by default visual studio sets the working directory to the build directory, when using the debugger
-	set_target_properties(bloom PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-endif()
-
-# including headers of dependencies and the VkCV framework
-target_include_directories(bloom SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include})
-
-# linking with libraries from all dependencies and the VkCV framework
-target_link_libraries(bloom vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera vkcv_shader_compiler)
diff --git a/projects/bloom/resources/shaders/comp.spv b/projects/bloom/resources/shaders/comp.spv
deleted file mode 100644
index 85c7e74cfc0a89917bf6dd1a7ec449368274c1d3..0000000000000000000000000000000000000000
Binary files a/projects/bloom/resources/shaders/comp.spv and /dev/null differ
diff --git a/projects/bloom/resources/shaders/composite.comp b/projects/bloom/resources/shaders/composite.comp
deleted file mode 100644
index 190bed0657d70e0217bf654820d0b2b2c58f12c2..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/shaders/composite.comp
+++ /dev/null
@@ -1,38 +0,0 @@
-#version 450
-#extension GL_ARB_separate_shader_objects : enable
-
-layout(set=0, binding=0) uniform texture2D                          blurImage;
-layout(set=0, binding=1) uniform texture2D                          lensImage;
-layout(set=0, binding=2) uniform sampler                            linearSampler;
-layout(set=0, binding=3, r11f_g11f_b10f) uniform image2D            colorBuffer;
-
-layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
-
-
-void main()
-{
-    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(colorBuffer)))){
-        return;
-    }
-
-    ivec2 pixel_coord   = ivec2(gl_GlobalInvocationID.xy);
-    vec2  pixel_size    = vec2(1.0f) / textureSize(sampler2D(blurImage, linearSampler), 0);
-    vec2  UV            = pixel_coord.xy * pixel_size;
-
-    vec4 composite_color = vec4(0.0f);
-
-    vec3 blur_color   = texture(sampler2D(blurImage, linearSampler), UV).rgb;
-    vec3 lens_color   = texture(sampler2D(lensImage, linearSampler), UV).rgb;
-    vec3 main_color   = imageLoad(colorBuffer, pixel_coord).rgb;
-
-    // composite blur and lens features
-    float bloom_weight = 0.25f;
-    float lens_weight  = 0.25f;
-    float main_weight = 1 - (bloom_weight + lens_weight);
-
-    composite_color.rgb = blur_color * bloom_weight +
-                          lens_color * lens_weight  +
-                          main_color * main_weight;
-
-    imageStore(colorBuffer, pixel_coord, composite_color);
-}
\ No newline at end of file
diff --git a/projects/bloom/resources/shaders/gammaCorrection.comp b/projects/bloom/resources/shaders/gammaCorrection.comp
deleted file mode 100644
index f89ad167c846cca8e80f69d33eda83bd6ed00d46..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/shaders/gammaCorrection.comp
+++ /dev/null
@@ -1,20 +0,0 @@
-#version 440
-
-layout(set=0, binding=0, r11f_g11f_b10f)    uniform image2D inImage;
-layout(set=0, binding=1, rgba8)             uniform image2D outImage;
-
-
-layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
-
-void main(){
-
-    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(inImage)))){
-        return;
-    }
-    ivec2 uv = ivec2(gl_GlobalInvocationID.xy);
-    vec3 linearColor = imageLoad(inImage, uv).rgb;
-    // cheap Reinhard tone mapping
-    linearColor = linearColor/(linearColor + 1.0f);
-    vec3 gammaCorrected = pow(linearColor, vec3(1.f / 2.2f));
-    imageStore(outImage, uv, vec4(gammaCorrected, 0.f));
-}
\ No newline at end of file
diff --git a/projects/bloom/resources/shaders/perMeshResources.inc b/projects/bloom/resources/shaders/perMeshResources.inc
deleted file mode 100644
index 95e4fb7c27009965659d14a9c72acfec950c37e3..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/shaders/perMeshResources.inc
+++ /dev/null
@@ -1,2 +0,0 @@
-layout(set=1, binding=0) uniform texture2D  albedoTexture;
-layout(set=1, binding=1) uniform sampler    textureSampler;
\ No newline at end of file
diff --git a/projects/bloom/resources/shaders/shader.frag b/projects/bloom/resources/shaders/shader.frag
deleted file mode 100644
index 3e95b4508f112c1ed9aa4a7050a98fa789dccd09..0000000000000000000000000000000000000000
--- a/projects/bloom/resources/shaders/shader.frag
+++ /dev/null
@@ -1,45 +0,0 @@
-#version 450
-#extension GL_ARB_separate_shader_objects : enable
-#extension GL_GOOGLE_include_directive : enable
-
-#include "perMeshResources.inc"
-
-layout(location = 0) in vec3 passNormal;
-layout(location = 1) in vec2 passUV;
-layout(location = 2) in vec3 passPos;
-
-layout(location = 0) out vec3 outColor;
-
-layout(set=0, binding=0) uniform sunBuffer {
-    vec3 L; float padding;
-    mat4 lightMatrix;
-};
-layout(set=0, binding=1) uniform texture2D  shadowMap;
-layout(set=0, binding=2) uniform sampler    shadowMapSampler;
-
-float shadowTest(vec3 worldPos){
-    vec4 lightPos = lightMatrix * vec4(worldPos, 1);
-    lightPos /= lightPos.w;
-    lightPos.xy = lightPos.xy * 0.5 + 0.5;
-    
-    if(any(lessThan(lightPos.xy, vec2(0))) || any(greaterThan(lightPos.xy, vec2(1)))){
-        return 1;
-    }
-    
-    lightPos.z = clamp(lightPos.z, 0, 1);
-    
-    float shadowMapSample = texture(sampler2D(shadowMap, shadowMapSampler), lightPos.xy).r;
-    float bias = 0.01f;
-    shadowMapSample += bias;
-    return shadowMapSample < lightPos.z ? 0 : 1;
-}
-
-void main()	{
-    vec3 N = normalize(passNormal);
-    vec3 sunColor = vec3(10);
-    vec3 sun = sunColor * clamp(dot(N, L), 0, 1);
-    sun *= shadowTest(passPos);
-    vec3 ambient = vec3(0.05);
-    vec3 albedo = texture(sampler2D(albedoTexture, textureSampler), passUV).rgb;
-	outColor = albedo * (sun + ambient);
-}
\ No newline at end of file
diff --git a/projects/bloom/src/BloomAndFlares.cpp b/projects/bloom/src/BloomAndFlares.cpp
deleted file mode 100644
index 6f26db9de0f2c8334b6dd7e5dd6cf4b6f48baedc..0000000000000000000000000000000000000000
--- a/projects/bloom/src/BloomAndFlares.cpp
+++ /dev/null
@@ -1,274 +0,0 @@
-#include "BloomAndFlares.hpp"
-#include <vkcv/shader/GLSLCompiler.hpp>
-
-BloomAndFlares::BloomAndFlares(
-        vkcv::Core *p_Core,
-        vk::Format colorBufferFormat,
-        uint32_t width,
-        uint32_t height) :
-
-        p_Core(p_Core),
-        m_ColorBufferFormat(colorBufferFormat),
-        m_Width(width),
-        m_Height(height),
-        m_LinearSampler(p_Core->createSampler(vkcv::SamplerFilterType::LINEAR,
-                                              vkcv::SamplerFilterType::LINEAR,
-                                              vkcv::SamplerMipmapMode::LINEAR,
-                                              vkcv::SamplerAddressMode::CLAMP_TO_EDGE)),
-        m_Blur(p_Core->createImage(colorBufferFormat, width, height, 1, true, true, false)),
-        m_LensFeatures(p_Core->createImage(colorBufferFormat, width, height, 1, false, true, false))
-{
-    vkcv::shader::GLSLCompiler compiler;
-
-    // DOWNSAMPLE
-    vkcv::ShaderProgram dsProg;
-    compiler.compile(vkcv::ShaderStage::COMPUTE,
-                     "resources/shaders/downsample.comp",
-                     [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path)
-                     {
-                         dsProg.addShader(shaderStage, path);
-                     });
-    for(uint32_t mipLevel = 0; mipLevel < m_Blur.getMipCount(); mipLevel++)
-    {
-		m_DownsampleDescSets.push_back(
-                p_Core->createDescriptorSet(dsProg.getReflectedDescriptors()[0]));
-    }
-    m_DownsamplePipe = p_Core->createComputePipeline(
-            dsProg, { p_Core->getDescriptorSet(m_DownsampleDescSets[0]).layout });
-
-    // UPSAMPLE
-    vkcv::ShaderProgram usProg;
-    compiler.compile(vkcv::ShaderStage::COMPUTE,
-                     "resources/shaders/upsample.comp",
-                     [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path)
-                     {
-                         usProg.addShader(shaderStage, path);
-                     });
-    for(uint32_t mipLevel = 0; mipLevel < m_Blur.getMipCount(); mipLevel++)
-    {
-        m_UpsampleDescSets.push_back(
-                p_Core->createDescriptorSet(usProg.getReflectedDescriptors()[0]));
-    }
-    m_UpsamplePipe = p_Core->createComputePipeline(
-            usProg, { p_Core->getDescriptorSet(m_UpsampleDescSets[0]).layout });
-
-    // LENS FEATURES
-    vkcv::ShaderProgram lensProg;
-    compiler.compile(vkcv::ShaderStage::COMPUTE,
-                     "resources/shaders/lensFlares.comp",
-                     [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path)
-                     {
-                         lensProg.addShader(shaderStage, path);
-                     });
-    m_LensFlareDescSet = p_Core->createDescriptorSet(lensProg.getReflectedDescriptors()[0]);
-    m_LensFlarePipe = p_Core->createComputePipeline(
-            lensProg, { p_Core->getDescriptorSet(m_LensFlareDescSet).layout });
-
-    // COMPOSITE
-    vkcv::ShaderProgram compProg;
-    compiler.compile(vkcv::ShaderStage::COMPUTE,
-                     "resources/shaders/composite.comp",
-                     [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path)
-                     {
-                         compProg.addShader(shaderStage, path);
-                     });
-    m_CompositeDescSet = p_Core->createDescriptorSet(compProg.getReflectedDescriptors()[0]);
-    m_CompositePipe = p_Core->createComputePipeline(
-            compProg, { p_Core->getDescriptorSet(m_CompositeDescSet).layout });
-}
-
-void BloomAndFlares::execDownsamplePipe(const vkcv::CommandStreamHandle &cmdStream,
-                                        const vkcv::ImageHandle &colorAttachment)
-{
-    auto dispatchCountX  = static_cast<float>(m_Width)  / 8.0f;
-    auto dispatchCountY = static_cast<float>(m_Height) / 8.0f;
-    // blur dispatch
-    uint32_t initialDispatchCount[3] = {
-            static_cast<uint32_t>(glm::ceil(dispatchCountX)),
-            static_cast<uint32_t>(glm::ceil(dispatchCountY)),
-            1
-    };
-
-    // downsample dispatch of original color attachment
-    p_Core->prepareImageForSampling(cmdStream, colorAttachment);
-    p_Core->prepareImageForStorage(cmdStream, m_Blur.getHandle());
-
-    vkcv::DescriptorWrites initialDownsampleWrites;
-    initialDownsampleWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, colorAttachment)};
-    initialDownsampleWrites.samplerWrites      = {vkcv::SamplerDescriptorWrite(1, m_LinearSampler)};
-    initialDownsampleWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, m_Blur.getHandle(), 0) };
-    p_Core->writeDescriptorSet(m_DownsampleDescSets[0], initialDownsampleWrites);
-
-    p_Core->recordComputeDispatchToCmdStream(
-            cmdStream,
-            m_DownsamplePipe,
-            initialDispatchCount,
-            {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_DownsampleDescSets[0]).vulkanHandle)},
-            vkcv::PushConstantData(nullptr, 0));
-
-    // downsample dispatches of blur buffer's mip maps
-    float mipDispatchCountX = dispatchCountX;
-    float mipDispatchCountY = dispatchCountY;
-    for(uint32_t mipLevel = 1; mipLevel < m_DownsampleDescSets.size(); mipLevel++)
-    {
-        // mip descriptor writes
-        vkcv::DescriptorWrites mipDescriptorWrites;
-        mipDescriptorWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, m_Blur.getHandle(), mipLevel - 1, true)};
-        mipDescriptorWrites.samplerWrites      = {vkcv::SamplerDescriptorWrite(1, m_LinearSampler)};
-        mipDescriptorWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, m_Blur.getHandle(), mipLevel) };
-        p_Core->writeDescriptorSet(m_DownsampleDescSets[mipLevel], mipDescriptorWrites);
-
-        // mip dispatch calculation
-        mipDispatchCountX  /= 2.0f;
-        mipDispatchCountY /= 2.0f;
-
-        uint32_t mipDispatchCount[3] = {
-                static_cast<uint32_t>(glm::ceil(mipDispatchCountX)),
-                static_cast<uint32_t>(glm::ceil(mipDispatchCountY)),
-                1
-        };
-
-        if(mipDispatchCount[0] == 0)
-            mipDispatchCount[0] = 1;
-        if(mipDispatchCount[1] == 0)
-            mipDispatchCount[1] = 1;
-
-        // mip blur dispatch
-        p_Core->recordComputeDispatchToCmdStream(
-                cmdStream,
-                m_DownsamplePipe,
-                mipDispatchCount,
-                {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_DownsampleDescSets[mipLevel]).vulkanHandle)},
-                vkcv::PushConstantData(nullptr, 0));
-
-        // image barrier between mips
-        p_Core->recordImageMemoryBarrier(cmdStream, m_Blur.getHandle());
-    }
-}
-
-void BloomAndFlares::execUpsamplePipe(const vkcv::CommandStreamHandle &cmdStream)
-{
-    // upsample dispatch
-    p_Core->prepareImageForStorage(cmdStream, m_Blur.getHandle());
-
-    const uint32_t upsampleMipLevels = std::min(
-    		static_cast<uint32_t>(m_UpsampleDescSets.size() - 1),
-    		static_cast<uint32_t>(5)
-	);
-
-    // upsample dispatch for each mip map
-    for(uint32_t mipLevel = upsampleMipLevels; mipLevel > 0; mipLevel--)
-    {
-        // mip descriptor writes
-        vkcv::DescriptorWrites mipUpsampleWrites;
-        mipUpsampleWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, m_Blur.getHandle(), mipLevel, true)};
-        mipUpsampleWrites.samplerWrites      = {vkcv::SamplerDescriptorWrite(1, m_LinearSampler)};
-        mipUpsampleWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, m_Blur.getHandle(), mipLevel - 1) };
-        p_Core->writeDescriptorSet(m_UpsampleDescSets[mipLevel], mipUpsampleWrites);
-
-        auto mipDivisor = glm::pow(2.0f, static_cast<float>(mipLevel) - 1.0f);
-
-        auto upsampleDispatchX  = static_cast<float>(m_Width) / mipDivisor;
-        auto upsampleDispatchY = static_cast<float>(m_Height) / mipDivisor;
-        upsampleDispatchX /= 8.0f;
-        upsampleDispatchY /= 8.0f;
-
-        const uint32_t upsampleDispatchCount[3] = {
-                static_cast<uint32_t>(glm::ceil(upsampleDispatchX)),
-                static_cast<uint32_t>(glm::ceil(upsampleDispatchY)),
-                1
-        };
-
-        p_Core->recordComputeDispatchToCmdStream(
-                cmdStream,
-                m_UpsamplePipe,
-                upsampleDispatchCount,
-                {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_UpsampleDescSets[mipLevel]).vulkanHandle)},
-                vkcv::PushConstantData(nullptr, 0)
-        );
-        // image barrier between mips
-        p_Core->recordImageMemoryBarrier(cmdStream, m_Blur.getHandle());
-    }
-}
-
-void BloomAndFlares::execLensFeaturePipe(const vkcv::CommandStreamHandle &cmdStream)
-{
-    // lens feature generation descriptor writes
-    p_Core->prepareImageForSampling(cmdStream, m_Blur.getHandle());
-    p_Core->prepareImageForStorage(cmdStream, m_LensFeatures.getHandle());
-
-    vkcv::DescriptorWrites lensFeatureWrites;
-    lensFeatureWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, m_Blur.getHandle(), 0)};
-    lensFeatureWrites.samplerWrites = {vkcv::SamplerDescriptorWrite(1, m_LinearSampler)};
-    lensFeatureWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, m_LensFeatures.getHandle(), 0)};
-    p_Core->writeDescriptorSet(m_LensFlareDescSet, lensFeatureWrites);
-
-    auto dispatchCountX  = static_cast<float>(m_Width)  / 8.0f;
-    auto dispatchCountY = static_cast<float>(m_Height) / 8.0f;
-    // lens feature generation dispatch
-    uint32_t lensFeatureDispatchCount[3] = {
-            static_cast<uint32_t>(glm::ceil(dispatchCountX)),
-            static_cast<uint32_t>(glm::ceil(dispatchCountY)),
-            1
-    };
-    p_Core->recordComputeDispatchToCmdStream(
-            cmdStream,
-            m_LensFlarePipe,
-            lensFeatureDispatchCount,
-            {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_LensFlareDescSet).vulkanHandle)},
-            vkcv::PushConstantData(nullptr, 0));
-}
-
-void BloomAndFlares::execCompositePipe(const vkcv::CommandStreamHandle &cmdStream,
-                                       const vkcv::ImageHandle &colorAttachment)
-{
-    p_Core->prepareImageForSampling(cmdStream, m_Blur.getHandle());
-    p_Core->prepareImageForSampling(cmdStream, m_LensFeatures.getHandle());
-    p_Core->prepareImageForStorage(cmdStream, colorAttachment);
-
-    // bloom composite descriptor write
-    vkcv::DescriptorWrites compositeWrites;
-    compositeWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, m_Blur.getHandle()),
-                                          vkcv::SampledImageDescriptorWrite(1, m_LensFeatures.getHandle())};
-    compositeWrites.samplerWrites = {vkcv::SamplerDescriptorWrite(2, m_LinearSampler)};
-    compositeWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(3, colorAttachment)};
-    p_Core->writeDescriptorSet(m_CompositeDescSet, compositeWrites);
-
-    float dispatchCountX = static_cast<float>(m_Width)  / 8.0f;
-    float dispatchCountY = static_cast<float>(m_Height) / 8.0f;
-
-    uint32_t compositeDispatchCount[3] = {
-            static_cast<uint32_t>(glm::ceil(dispatchCountX)),
-            static_cast<uint32_t>(glm::ceil(dispatchCountY)),
-            1
-    };
-
-    // bloom composite dispatch
-    p_Core->recordComputeDispatchToCmdStream(
-            cmdStream,
-            m_CompositePipe,
-            compositeDispatchCount,
-            {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_CompositeDescSet).vulkanHandle)},
-            vkcv::PushConstantData(nullptr, 0));
-}
-
-void BloomAndFlares::execWholePipeline(const vkcv::CommandStreamHandle &cmdStream,
-                                       const vkcv::ImageHandle &colorAttachment)
-{
-    execDownsamplePipe(cmdStream, colorAttachment);
-    execUpsamplePipe(cmdStream);
-    execLensFeaturePipe(cmdStream);
-    execCompositePipe(cmdStream, colorAttachment);
-}
-
-void BloomAndFlares::updateImageDimensions(uint32_t width, uint32_t height)
-{
-    m_Width  = width;
-    m_Height = height;
-
-    p_Core->getContext().getDevice().waitIdle();
-    m_Blur = p_Core->createImage(m_ColorBufferFormat, m_Width, m_Height, 1, true, true, false);
-    m_LensFeatures = p_Core->createImage(m_ColorBufferFormat, m_Width, m_Height, 1, false, true, false);
-}
-
-
diff --git a/projects/bloom/src/BloomAndFlares.hpp b/projects/bloom/src/BloomAndFlares.hpp
deleted file mode 100644
index 756b1ca154ea5232df04eb09a88bb743c5bd28aa..0000000000000000000000000000000000000000
--- a/projects/bloom/src/BloomAndFlares.hpp
+++ /dev/null
@@ -1,47 +0,0 @@
-#pragma once
-#include <vkcv/Core.hpp>
-#include <glm/glm.hpp>
-
-class BloomAndFlares{
-public:
-    BloomAndFlares(vkcv::Core *p_Core,
-                   vk::Format colorBufferFormat,
-                   uint32_t width,
-                   uint32_t height);
-
-    void execWholePipeline(const vkcv::CommandStreamHandle &cmdStream, const vkcv::ImageHandle &colorAttachment);
-
-    void updateImageDimensions(uint32_t width, uint32_t height);
-
-private:
-    vkcv::Core *p_Core;
-
-    vk::Format m_ColorBufferFormat;
-    uint32_t m_Width;
-    uint32_t m_Height;
-
-    vkcv::SamplerHandle m_LinearSampler;
-    vkcv::Image m_Blur;
-    vkcv::Image m_LensFeatures;
-
-
-    vkcv::PipelineHandle                     m_DownsamplePipe;
-    std::vector<vkcv::DescriptorSetHandle>   m_DownsampleDescSets; // per mip desc set
-
-    vkcv::PipelineHandle                     m_UpsamplePipe;
-    std::vector<vkcv::DescriptorSetHandle>   m_UpsampleDescSets;   // per mip desc set
-
-    vkcv::PipelineHandle                     m_LensFlarePipe;
-    vkcv::DescriptorSetHandle                m_LensFlareDescSet;
-
-    vkcv::PipelineHandle                     m_CompositePipe;
-    vkcv::DescriptorSetHandle                m_CompositeDescSet;
-
-    void execDownsamplePipe(const vkcv::CommandStreamHandle &cmdStream, const vkcv::ImageHandle &colorAttachment);
-    void execUpsamplePipe(const vkcv::CommandStreamHandle &cmdStream);
-    void execLensFeaturePipe(const vkcv::CommandStreamHandle &cmdStream);
-    void execCompositePipe(const vkcv::CommandStreamHandle &cmdStream, const vkcv::ImageHandle &colorAttachment);
-};
-
-
-
diff --git a/projects/bloom/src/main.cpp b/projects/bloom/src/main.cpp
deleted file mode 100644
index 7a17a51f1c7d638575c0b5aafcdca49b589533ef..0000000000000000000000000000000000000000
--- a/projects/bloom/src/main.cpp
+++ /dev/null
@@ -1,419 +0,0 @@
-#include <iostream>
-#include <vkcv/Core.hpp>
-#include <GLFW/glfw3.h>
-#include <vkcv/camera/CameraManager.hpp>
-#include <chrono>
-#include <vkcv/asset/asset_loader.hpp>
-#include <vkcv/shader/GLSLCompiler.hpp>
-#include <vkcv/Logger.hpp>
-#include "BloomAndFlares.hpp"
-#include <glm/glm.hpp>
-
-int main(int argc, const char** argv) {
-	const char* applicationName = "Bloom";
-
-	uint32_t windowWidth = 1920;
-	uint32_t windowHeight = 1080;
-	
-	vkcv::Window window = vkcv::Window::create(
-		applicationName,
-		windowWidth,
-		windowHeight,
-		true
-	);
-
-    vkcv::camera::CameraManager cameraManager(window);
-    uint32_t camIndex = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
-    uint32_t camIndex2 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
-    
-    cameraManager.getCamera(camIndex).setPosition(glm::vec3(0.f, 0.f, 3.f));
-    cameraManager.getCamera(camIndex).setNearFar(0.1f, 30.0f);
-	cameraManager.getCamera(camIndex).setYaw(180.0f);
-	
-	cameraManager.getCamera(camIndex2).setNearFar(0.1f, 30.0f);
-
-	vkcv::Core core = vkcv::Core::create(
-		window,
-		applicationName,
-		VK_MAKE_VERSION(0, 0, 1),
-		{ vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },
-		{},
-		{ "VK_KHR_swapchain" }
-	);
-
-	const char* path = argc > 1 ? argv[1] : "resources/Sponza/Sponza.gltf";
-	vkcv::asset::Scene scene;
-	int result = vkcv::asset::loadScene(path, scene);
-
-	if (result == 1) {
-		std::cout << "Scene loading successful!" << std::endl;
-	}
-	else {
-		std::cout << "Scene loading failed: " << result << std::endl;
-		return 1;
-	}
-
-	// build index and vertex buffers
-	assert(!scene.vertexGroups.empty());
-	std::vector<std::vector<uint8_t>> vBuffers;
-	std::vector<std::vector<uint8_t>> iBuffers;
-
-	std::vector<vkcv::VertexBufferBinding> vBufferBindings;
-	std::vector<std::vector<vkcv::VertexBufferBinding>> vertexBufferBindings;
-	std::vector<vkcv::asset::VertexAttribute> vAttributes;
-
-	for (int i = 0; i < scene.vertexGroups.size(); i++) {
-
-		vBuffers.push_back(scene.vertexGroups[i].vertexBuffer.data);
-		iBuffers.push_back(scene.vertexGroups[i].indexBuffer.data);
-
-		auto& attributes = scene.vertexGroups[i].vertexBuffer.attributes;
-
-		std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) {
-			return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type);
-		});
-	}
-
-	std::vector<vkcv::Buffer<uint8_t>> vertexBuffers;
-	for (const vkcv::asset::VertexGroup& group : scene.vertexGroups) {
-		vertexBuffers.push_back(core.createBuffer<uint8_t>(
-			vkcv::BufferType::VERTEX,
-			group.vertexBuffer.data.size()));
-		vertexBuffers.back().fill(group.vertexBuffer.data);
-	}
-
-	std::vector<vkcv::Buffer<uint8_t>> indexBuffers;
-	for (const auto& dataBuffer : iBuffers) {
-		indexBuffers.push_back(core.createBuffer<uint8_t>(
-			vkcv::BufferType::INDEX,
-			dataBuffer.size()));
-		indexBuffers.back().fill(dataBuffer);
-	}
-
-	int vertexBufferIndex = 0;
-	for (const auto& vertexGroup : scene.vertexGroups) {
-		for (const auto& attribute : vertexGroup.vertexBuffer.attributes) {
-			vAttributes.push_back(attribute);
-			vBufferBindings.push_back(vkcv::VertexBufferBinding(attribute.offset, vertexBuffers[vertexBufferIndex].getVulkanHandle()));
-		}
-		vertexBufferBindings.push_back(vBufferBindings);
-		vBufferBindings.clear();
-		vertexBufferIndex++;
-	}
-
-	const vk::Format colorBufferFormat = vk::Format::eB10G11R11UfloatPack32;
-	const vkcv::AttachmentDescription color_attachment(
-		vkcv::AttachmentOperation::STORE,
-		vkcv::AttachmentOperation::CLEAR,
-		colorBufferFormat
-	);
-	
-	const vk::Format depthBufferFormat = vk::Format::eD32Sfloat;
-	const vkcv::AttachmentDescription depth_attachment(
-		vkcv::AttachmentOperation::STORE,
-		vkcv::AttachmentOperation::CLEAR,
-		depthBufferFormat
-	);
-
-	vkcv::PassConfig forwardPassDefinition({ color_attachment, depth_attachment });
-	vkcv::PassHandle forwardPass = core.createPass(forwardPassDefinition);
-
-	vkcv::shader::GLSLCompiler compiler;
-
-	vkcv::ShaderProgram forwardProgram;
-	compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/shader.vert"), 
-		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
-		forwardProgram.addShader(shaderStage, path);
-	});
-	compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/shader.frag"),
-		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
-		forwardProgram.addShader(shaderStage, path);
-	});
-
-	const std::vector<vkcv::VertexAttachment> vertexAttachments = forwardProgram.getVertexAttachments();
-
-	std::vector<vkcv::VertexBinding> vertexBindings;
-	for (size_t i = 0; i < vertexAttachments.size(); i++) {
-		vertexBindings.push_back(vkcv::VertexBinding(i, { vertexAttachments[i] }));
-	}
-	const vkcv::VertexLayout vertexLayout (vertexBindings);
-
-	// shadow map
-	vkcv::SamplerHandle shadowSampler = core.createSampler(
-		vkcv::SamplerFilterType::NEAREST,
-		vkcv::SamplerFilterType::NEAREST,
-		vkcv::SamplerMipmapMode::NEAREST,
-		vkcv::SamplerAddressMode::CLAMP_TO_EDGE
-	);
-	const vk::Format shadowMapFormat = vk::Format::eD16Unorm;
-	const uint32_t shadowMapResolution = 1024;
-	const vkcv::Image shadowMap = core.createImage(shadowMapFormat, shadowMapResolution, shadowMapResolution, 1);
-
-	// light info buffer
-	struct LightInfo {
-		glm::vec3 direction;
-		float padding;
-		glm::mat4 lightMatrix;
-	};
-	LightInfo lightInfo;
-	vkcv::Buffer lightBuffer = core.createBuffer<LightInfo>(vkcv::BufferType::UNIFORM, sizeof(glm::vec3));
-
-	vkcv::DescriptorSetHandle forwardShadingDescriptorSet = 
-		core.createDescriptorSet({ forwardProgram.getReflectedDescriptors()[0] });
-
-	vkcv::DescriptorWrites forwardDescriptorWrites;
-	forwardDescriptorWrites.uniformBufferWrites = { vkcv::UniformBufferDescriptorWrite(0, lightBuffer.getHandle()) };
-	forwardDescriptorWrites.sampledImageWrites  = { vkcv::SampledImageDescriptorWrite(1, shadowMap.getHandle()) };
-	forwardDescriptorWrites.samplerWrites       = { vkcv::SamplerDescriptorWrite(2, shadowSampler) };
-	core.writeDescriptorSet(forwardShadingDescriptorSet, forwardDescriptorWrites);
-
-	vkcv::SamplerHandle colorSampler = core.createSampler(
-		vkcv::SamplerFilterType::LINEAR,
-		vkcv::SamplerFilterType::LINEAR,
-		vkcv::SamplerMipmapMode::LINEAR,
-		vkcv::SamplerAddressMode::REPEAT
-	);
-
-	// prepare per mesh descriptor sets
-	std::vector<vkcv::DescriptorSetHandle> perMeshDescriptorSets;
-	std::vector<vkcv::Image> sceneImages;
-	for (const auto& vertexGroup : scene.vertexGroups) {
-		perMeshDescriptorSets.push_back(core.createDescriptorSet(forwardProgram.getReflectedDescriptors()[1]));
-
-		const auto& material = scene.materials[vertexGroup.materialIndex];
-
-		int baseColorIndex = material.baseColor;
-		if (baseColorIndex < 0) {
-			vkcv_log(vkcv::LogLevel::WARNING, "Material lacks base color");
-			baseColorIndex = 0;
-		}
-
-		vkcv::asset::Texture& sceneTexture = scene.textures[baseColorIndex];
-
-		sceneImages.push_back(core.createImage(vk::Format::eR8G8B8A8Srgb, sceneTexture.w, sceneTexture.h));
-		sceneImages.back().fill(sceneTexture.data.data());
-
-		vkcv::DescriptorWrites setWrites;
-		setWrites.sampledImageWrites = {
-			vkcv::SampledImageDescriptorWrite(0, sceneImages.back().getHandle())
-		};
-		setWrites.samplerWrites = {
-			vkcv::SamplerDescriptorWrite(1, colorSampler),
-		};
-		core.writeDescriptorSet(perMeshDescriptorSets.back(), setWrites);
-	}
-
-	const vkcv::PipelineConfig forwardPipelineConfig {
-		forwardProgram,
-		windowWidth,
-		windowHeight,
-		forwardPass,
-		vertexLayout,
-		{	core.getDescriptorSet(forwardShadingDescriptorSet).layout, 
-			core.getDescriptorSet(perMeshDescriptorSets[0]).layout },
-		true
-	};
-	
-	vkcv::PipelineHandle forwardPipeline = core.createGraphicsPipeline(forwardPipelineConfig);
-	
-	if (!forwardPipeline) {
-		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
-		return EXIT_FAILURE;
-	}
-
-	vkcv::ImageHandle depthBuffer       = core.createImage(depthBufferFormat, windowWidth, windowHeight).getHandle();
-	vkcv::ImageHandle colorBuffer       = core.createImage(colorBufferFormat, windowWidth, windowHeight, 1, false, true, true).getHandle();
-
-	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
-
-	vkcv::ShaderProgram shadowShader;
-	compiler.compile(vkcv::ShaderStage::VERTEX, "resources/shaders/shadow.vert",
-		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
-		shadowShader.addShader(shaderStage, path);
-	});
-	compiler.compile(vkcv::ShaderStage::FRAGMENT, "resources/shaders/shadow.frag",
-		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
-		shadowShader.addShader(shaderStage, path);
-	});
-
-	const std::vector<vkcv::AttachmentDescription> shadowAttachments = {
-		vkcv::AttachmentDescription(vkcv::AttachmentOperation::STORE, vkcv::AttachmentOperation::CLEAR, shadowMapFormat)
-	};
-	const vkcv::PassConfig shadowPassConfig(shadowAttachments);
-	const vkcv::PassHandle shadowPass = core.createPass(shadowPassConfig);
-	const vkcv::PipelineConfig shadowPipeConfig{
-		shadowShader,
-		shadowMapResolution,
-		shadowMapResolution,
-		shadowPass,
-		vertexLayout,
-		{},
-		false
-	};
-	const vkcv::PipelineHandle shadowPipe = core.createGraphicsPipeline(shadowPipeConfig);
-
-	std::vector<std::array<glm::mat4, 2>> mainPassMatrices;
-	std::vector<glm::mat4> mvpLight;
-
-	// gamma correction compute shader
-	vkcv::ShaderProgram gammaCorrectionProgram;
-	compiler.compile(vkcv::ShaderStage::COMPUTE, "resources/shaders/gammaCorrection.comp",
-		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
-		gammaCorrectionProgram.addShader(shaderStage, path);
-	});
-	vkcv::DescriptorSetHandle gammaCorrectionDescriptorSet = core.createDescriptorSet(gammaCorrectionProgram.getReflectedDescriptors()[0]);
-	vkcv::PipelineHandle gammaCorrectionPipeline = core.createComputePipeline(gammaCorrectionProgram,
-		{ core.getDescriptorSet(gammaCorrectionDescriptorSet).layout });
-
-    BloomAndFlares baf(&core, colorBufferFormat, windowWidth, windowHeight);
-
-
-	// model matrices per mesh
-	std::vector<glm::mat4> modelMatrices;
-	modelMatrices.resize(scene.vertexGroups.size(), glm::mat4(1.f));
-	for (const auto& mesh : scene.meshes) {
-		const glm::mat4 m = *reinterpret_cast<const glm::mat4*>(&mesh.modelMatrix[0]);
-		for (const auto& vertexGroupIndex : mesh.vertexGroups) {
-			modelMatrices[vertexGroupIndex] = m;
-		}
-	}
-
-	// prepare drawcalls
-	std::vector<vkcv::Mesh> meshes;
-	for (int i = 0; i < scene.vertexGroups.size(); i++) {
-		vkcv::Mesh mesh(
-			vertexBufferBindings[i], 
-			indexBuffers[i].getVulkanHandle(), 
-			scene.vertexGroups[i].numIndices);
-		meshes.push_back(mesh);
-	}
-
-	std::vector<vkcv::DrawcallInfo> drawcalls;
-	std::vector<vkcv::DrawcallInfo> shadowDrawcalls;
-	for (int i = 0; i < meshes.size(); i++) {
-		drawcalls.push_back(vkcv::DrawcallInfo(meshes[i], { 
-			vkcv::DescriptorSetUsage(0, core.getDescriptorSet(forwardShadingDescriptorSet).vulkanHandle),
-			vkcv::DescriptorSetUsage(1, core.getDescriptorSet(perMeshDescriptorSets[i]).vulkanHandle) }));
-		shadowDrawcalls.push_back(vkcv::DrawcallInfo(meshes[i], {}));
-	}
-
-	auto start = std::chrono::system_clock::now();
-	const auto appStartTime = start;
-	while (window.isWindowOpen()) {
-		vkcv::Window::pollEvents();
-
-		uint32_t swapchainWidth, swapchainHeight;
-		if (!core.beginFrame(swapchainWidth, swapchainHeight)) {
-			continue;
-		}
-
-		if ((swapchainWidth != windowWidth) || ((swapchainHeight != windowHeight))) {
-			depthBuffer = core.createImage(depthBufferFormat, swapchainWidth, swapchainHeight).getHandle();
-			colorBuffer = core.createImage(colorBufferFormat, swapchainWidth, swapchainHeight, 1, false, true, true).getHandle();
-
-			baf.updateImageDimensions(swapchainWidth, swapchainHeight);
-
-			windowWidth = swapchainWidth;
-			windowHeight = swapchainHeight;
-		}
-
-		auto end = std::chrono::system_clock::now();
-		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
-
-		start = end;
-		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
-
-		auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - appStartTime);
-		
-		const float sunTheta = 0.0001f * static_cast<float>(duration.count());
-		lightInfo.direction = glm::normalize(glm::vec3(std::cos(sunTheta), 1, std::sin(sunTheta)));
-
-		const float shadowProjectionSize = 20.f;
-		glm::mat4 projectionLight = glm::ortho(
-			-shadowProjectionSize,
-			shadowProjectionSize,
-			-shadowProjectionSize,
-			shadowProjectionSize,
-			-shadowProjectionSize,
-			shadowProjectionSize);
-
-		glm::mat4 vulkanCorrectionMatrix(1.f);
-		vulkanCorrectionMatrix[2][2] = 0.5;
-		vulkanCorrectionMatrix[3][2] = 0.5;
-		projectionLight = vulkanCorrectionMatrix * projectionLight;
-
-		const glm::mat4 viewLight = glm::lookAt(glm::vec3(0), -lightInfo.direction, glm::vec3(0, -1, 0));
-
-		lightInfo.lightMatrix = projectionLight * viewLight;
-		lightBuffer.fill({ lightInfo });
-
-		const glm::mat4 viewProjectionCamera = cameraManager.getActiveCamera().getMVP();
-
-		mainPassMatrices.clear();
-		mvpLight.clear();
-		for (const auto& m : modelMatrices) {
-			mainPassMatrices.push_back({ viewProjectionCamera * m, m });
-			mvpLight.push_back(lightInfo.lightMatrix * m);
-		}
-
-		vkcv::PushConstantData pushConstantData((void*)mainPassMatrices.data(), 2 * sizeof(glm::mat4));
-		const std::vector<vkcv::ImageHandle> renderTargets = { colorBuffer, depthBuffer };
-
-		const vkcv::PushConstantData shadowPushConstantData((void*)mvpLight.data(), sizeof(glm::mat4));
-
-		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
-
-		// shadow map
-		core.recordDrawcallsToCmdStream(
-			cmdStream,
-			shadowPass,
-			shadowPipe,
-			shadowPushConstantData,
-			shadowDrawcalls,
-			{ shadowMap.getHandle() });
-		core.prepareImageForSampling(cmdStream, shadowMap.getHandle());
-
-		// main pass
-		core.recordDrawcallsToCmdStream(
-			cmdStream,
-            forwardPass,
-            forwardPipeline,
-			pushConstantData,
-			drawcalls,
-			renderTargets);
-
-        const uint32_t gammaCorrectionLocalGroupSize = 8;
-        const uint32_t gammaCorrectionDispatchCount[3] = {
-                static_cast<uint32_t>(glm::ceil(static_cast<float>(windowWidth) / static_cast<float>(gammaCorrectionLocalGroupSize))),
-                static_cast<uint32_t>(glm::ceil(static_cast<float>(windowHeight) / static_cast<float>(gammaCorrectionLocalGroupSize))),
-                1
-        };
-
-        baf.execWholePipeline(cmdStream, colorBuffer);
-
-        core.prepareImageForStorage(cmdStream, swapchainInput);
-        
-        // gamma correction descriptor write
-        vkcv::DescriptorWrites gammaCorrectionDescriptorWrites;
-        gammaCorrectionDescriptorWrites.storageImageWrites = {
-                vkcv::StorageImageDescriptorWrite(0, colorBuffer),
-                vkcv::StorageImageDescriptorWrite(1, swapchainInput) };
-        core.writeDescriptorSet(gammaCorrectionDescriptorSet, gammaCorrectionDescriptorWrites);
-
-        // gamma correction dispatch
-        core.recordComputeDispatchToCmdStream(
-			cmdStream, 
-			gammaCorrectionPipeline, 
-			gammaCorrectionDispatchCount,
-			{ vkcv::DescriptorSetUsage(0, core.getDescriptorSet(gammaCorrectionDescriptorSet).vulkanHandle) },
-			vkcv::PushConstantData(nullptr, 0));
-
-		// present and end
-		core.prepareSwapchainImageForPresent(cmdStream);
-		core.submitCommandStream(cmdStream);
-
-		core.endFrame();
-	}
-	
-	return 0;
-}
diff --git a/projects/cmd_sync_test/src/main.cpp b/projects/cmd_sync_test/src/main.cpp
deleted file mode 100644
index eccc0af7331dc140f3a15ddf12c5645e685abc90..0000000000000000000000000000000000000000
--- a/projects/cmd_sync_test/src/main.cpp
+++ /dev/null
@@ -1,317 +0,0 @@
-#include <iostream>
-#include <vkcv/Core.hpp>
-#include <GLFW/glfw3.h>
-#include <vkcv/camera/CameraManager.hpp>
-#include <chrono>
-#include <vkcv/asset/asset_loader.hpp>
-
-int main(int argc, const char** argv) {
-	const char* applicationName = "First Mesh";
-
-	uint32_t windowWidth = 800;
-	uint32_t windowHeight = 600;
-	
-	vkcv::Window window = vkcv::Window::create(
-		applicationName,
-		windowWidth,
-		windowHeight,
-		true
-	);
-
-    vkcv::camera::CameraManager cameraManager(window);
-    uint32_t camIndex = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
-    uint32_t camIndex2 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
-    
-    cameraManager.getCamera(camIndex).setPosition(glm::vec3(0.f, 0.f, 3.f));
-    cameraManager.getCamera(camIndex).setNearFar(0.1f, 30.0f);
-	cameraManager.getCamera(camIndex).setYaw(180.0f);
-	
-	cameraManager.getCamera(camIndex2).setNearFar(0.1f, 30.0f);
-
-	window.initEvents();
-
-	vkcv::Core core = vkcv::Core::create(
-		window,
-		applicationName,
-		VK_MAKE_VERSION(0, 0, 1),
-		{ vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },
-		{},
-		{ "VK_KHR_swapchain" }
-	);
-
-	vkcv::asset::Scene mesh;
-
-	const char* path = argc > 1 ? argv[1] : "resources/cube/cube.gltf";
-	int result = vkcv::asset::loadScene(path, mesh);
-
-	if (result == 1) {
-		std::cout << "Mesh loading successful!" << std::endl;
-	}
-	else {
-		std::cout << "Mesh loading failed: " << result << std::endl;
-		return 1;
-	}
-
-	assert(mesh.vertexGroups.size() > 0);
-	auto vertexBuffer = core.createBuffer<uint8_t>(
-			vkcv::BufferType::VERTEX,
-			mesh.vertexGroups[0].vertexBuffer.data.size(),
-			vkcv::BufferMemoryType::DEVICE_LOCAL
-	);
-	
-	vertexBuffer.fill(mesh.vertexGroups[0].vertexBuffer.data);
-
-	auto indexBuffer = core.createBuffer<uint8_t>(
-			vkcv::BufferType::INDEX,
-			mesh.vertexGroups[0].indexBuffer.data.size(),
-			vkcv::BufferMemoryType::DEVICE_LOCAL
-	);
-	
-	indexBuffer.fill(mesh.vertexGroups[0].indexBuffer.data);
-	
-	auto& attributes = mesh.vertexGroups[0].vertexBuffer.attributes;
-	
-	std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) {
-		return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type);
-	});
-
-	const std::vector<vkcv::VertexBufferBinding> vertexBufferBindings = {
-		vkcv::VertexBufferBinding(attributes[0].offset, vertexBuffer.getVulkanHandle()),
-		vkcv::VertexBufferBinding(attributes[1].offset, vertexBuffer.getVulkanHandle()),
-		vkcv::VertexBufferBinding(attributes[2].offset, vertexBuffer.getVulkanHandle()) };
-
-	const vkcv::Mesh loadedMesh(vertexBufferBindings, indexBuffer.getVulkanHandle(), mesh.vertexGroups[0].numIndices);
-
-	// an example attachment for passes that output to the window
-	const vkcv::AttachmentDescription present_color_attachment(
-		vkcv::AttachmentOperation::STORE,
-		vkcv::AttachmentOperation::CLEAR,
-		core.getSwapchain().getFormat()
-	);
-	
-	const vkcv::AttachmentDescription depth_attachment(
-			vkcv::AttachmentOperation::STORE,
-			vkcv::AttachmentOperation::CLEAR,
-			vk::Format::eD32Sfloat
-	);
-
-	vkcv::PassConfig firstMeshPassDefinition({ present_color_attachment, depth_attachment });
-	vkcv::PassHandle firstMeshPass = core.createPass(firstMeshPassDefinition);
-
-	if (!firstMeshPass) {
-		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
-		return EXIT_FAILURE;
-	}
-
-	vkcv::ShaderProgram firstMeshProgram{};
-    firstMeshProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/vert.spv"));
-    firstMeshProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/frag.spv"));
-
-    const std::vector<vkcv::VertexAttachment> vertexAttachments = firstMeshProgram.getVertexAttachments();
-    
-    std::vector<vkcv::VertexBinding> bindings;
-    for (size_t i = 0; i < vertexAttachments.size(); i++) {
-		bindings.push_back(vkcv::VertexBinding(i, { vertexAttachments[i] }));
-    }
-    
-    const vkcv::VertexLayout firstMeshLayout (bindings);
-
-	std::vector<vkcv::DescriptorBinding> descriptorBindings = { firstMeshProgram.getReflectedDescriptors()[0] };
-	vkcv::DescriptorSetHandle descriptorSet = core.createDescriptorSet(descriptorBindings);
-
-	const vkcv::PipelineConfig firstMeshPipelineConfig {
-        firstMeshProgram,
-		windowWidth,
-		windowHeight,
-        firstMeshPass,
-        firstMeshLayout,
-		{ core.getDescriptorSet(descriptorSet).layout },
-		true
-	};
-	
-	vkcv::PipelineHandle firstMeshPipeline = core.createGraphicsPipeline(firstMeshPipelineConfig);
-	
-	if (!firstMeshPipeline) {
-		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
-		return EXIT_FAILURE;
-	}
-	
-	//vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, mesh.texture_hack.w, mesh.texture_hack.h);
-	//texture.fill(mesh.texture_hack.img);
-    vkcv::asset::Texture &tex = mesh.textures[0];
-    vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, tex.w, tex.h);
-    texture.fill(tex.data.data());
-
-	vkcv::SamplerHandle sampler = core.createSampler(
-		vkcv::SamplerFilterType::LINEAR,
-		vkcv::SamplerFilterType::LINEAR,
-		vkcv::SamplerMipmapMode::LINEAR,
-		vkcv::SamplerAddressMode::REPEAT
-	);
-
-    vkcv::SamplerHandle shadowSampler = core.createSampler(
-        vkcv::SamplerFilterType::NEAREST,
-        vkcv::SamplerFilterType::NEAREST,
-        vkcv::SamplerMipmapMode::NEAREST,
-        vkcv::SamplerAddressMode::CLAMP_TO_EDGE
-    );
-
-	vkcv::ImageHandle depthBuffer = core.createImage(vk::Format::eD32Sfloat, windowWidth, windowHeight).getHandle();
-
-	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
-
-	const vkcv::DescriptorSetUsage descriptorUsage(0, core.getDescriptorSet(descriptorSet).vulkanHandle);
-
-	const std::vector<glm::vec3> instancePositions = {
-		glm::vec3( 0.f, -2.f, 0.f),
-		glm::vec3( 3.f,  0.f, 0.f),
-		glm::vec3(-3.f,  0.f, 0.f),
-		glm::vec3( 0.f,  2.f, 0.f),
-		glm::vec3( 0.f, -5.f, 0.f)
-	};
-
-	std::vector<glm::mat4> modelMatrices;
-	std::vector<vkcv::DrawcallInfo> drawcalls;
-	std::vector<vkcv::DrawcallInfo> shadowDrawcalls;
-	for (const auto& position : instancePositions) {
-		modelMatrices.push_back(glm::translate(glm::mat4(1.f), position));
-		drawcalls.push_back(vkcv::DrawcallInfo(loadedMesh, { descriptorUsage }));
-		shadowDrawcalls.push_back(vkcv::DrawcallInfo(loadedMesh, {}));
-	}
-
-	modelMatrices.back() *= glm::scale(glm::mat4(1.f), glm::vec3(10.f, 1.f, 10.f));
-
-	std::vector<std::array<glm::mat4, 2>> mainPassMatrices;
-	std::vector<glm::mat4> mvpLight;
-
-	vkcv::ShaderProgram shadowShader;
-	shadowShader.addShader(vkcv::ShaderStage::VERTEX, "resources/shaders/shadow_vert.spv");
-	shadowShader.addShader(vkcv::ShaderStage::FRAGMENT, "resources/shaders/shadow_frag.spv");
-
-	const vk::Format shadowMapFormat = vk::Format::eD16Unorm;
-	const std::vector<vkcv::AttachmentDescription> shadowAttachments = {
-		vkcv::AttachmentDescription(vkcv::AttachmentOperation::STORE, vkcv::AttachmentOperation::CLEAR, shadowMapFormat)
-	};
-	const vkcv::PassConfig shadowPassConfig(shadowAttachments);
-	const vkcv::PassHandle shadowPass = core.createPass(shadowPassConfig);
-
-	const uint32_t shadowMapResolution = 1024;
-	const vkcv::Image shadowMap = core.createImage(shadowMapFormat, shadowMapResolution, shadowMapResolution, 1);
-	const vkcv::PipelineConfig shadowPipeConfig {
-		shadowShader, 
-		shadowMapResolution, 
-		shadowMapResolution, 
-		shadowPass,
-        firstMeshLayout,
-		{},
-		false
-	};
-	
-	const vkcv::PipelineHandle shadowPipe = core.createGraphicsPipeline(shadowPipeConfig);
-
-	struct LightInfo {
-		glm::vec3 direction;
-		float padding;
-		glm::mat4 lightMatrix;
-	};
-	LightInfo lightInfo;
-	vkcv::Buffer lightBuffer = core.createBuffer<LightInfo>(vkcv::BufferType::UNIFORM, sizeof(glm::vec3));
-
-	vkcv::DescriptorWrites setWrites;
-	setWrites.sampledImageWrites    = { 
-        vkcv::SampledImageDescriptorWrite(0, texture.getHandle()),
-        vkcv::SampledImageDescriptorWrite(3, shadowMap.getHandle()) };
-	setWrites.samplerWrites         = { 
-        vkcv::SamplerDescriptorWrite(1, sampler), 
-        vkcv::SamplerDescriptorWrite(4, shadowSampler) };
-    setWrites.uniformBufferWrites   = { vkcv::UniformBufferDescriptorWrite(2, lightBuffer.getHandle()) };
-	core.writeDescriptorSet(descriptorSet, setWrites);
-
-	auto start = std::chrono::system_clock::now();
-	const auto appStartTime = start;
-	while (window.isWindowOpen()) {
-		window.pollEvents();
-		
-		uint32_t swapchainWidth, swapchainHeight;
-		if (!core.beginFrame(swapchainWidth, swapchainHeight)) {
-			continue;
-		}
-		
-		if ((swapchainWidth != windowWidth) || ((swapchainHeight != windowHeight))) {
-			depthBuffer = core.createImage(vk::Format::eD32Sfloat, swapchainWidth, swapchainHeight).getHandle();
-			
-			windowWidth = swapchainWidth;
-			windowHeight = swapchainHeight;
-		}
-		
-		auto end = std::chrono::system_clock::now();
-		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
-		
-		start = end;
-		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
-
-		auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - appStartTime);
-		
-		const float sunTheta = 0.001f * static_cast<float>(duration.count());
-		lightInfo.direction = glm::normalize(glm::vec3(std::cos(sunTheta), 1, std::sin(sunTheta)));
-
-		const float shadowProjectionSize = 5.f;
-		glm::mat4 projectionLight = glm::ortho(
-			-shadowProjectionSize,
-			shadowProjectionSize,
-			-shadowProjectionSize,
-			shadowProjectionSize,
-			-shadowProjectionSize,
-			shadowProjectionSize);
-		
-		glm::mat4 vulkanCorrectionMatrix(1.f);
-		vulkanCorrectionMatrix[2][2] = 0.5;
-		vulkanCorrectionMatrix[3][2] = 0.5;
-		projectionLight = vulkanCorrectionMatrix * projectionLight;
-
-		const glm::mat4 viewLight = glm::lookAt(glm::vec3(0), -lightInfo.direction, glm::vec3(0, -1, 0));
-
-		lightInfo.lightMatrix = projectionLight * viewLight;
-		lightBuffer.fill({ lightInfo });
-
-		const glm::mat4 viewProjectionCamera = cameraManager.getActiveCamera().getMVP();
-
-		mainPassMatrices.clear();
-		mvpLight.clear();
-		for (const auto& m : modelMatrices) {
-			mainPassMatrices.push_back({ viewProjectionCamera * m, m });
-			mvpLight.push_back(lightInfo.lightMatrix* m);
-		}
-
-		vkcv::PushConstantData pushConstantData((void*)mainPassMatrices.data(), 2 * sizeof(glm::mat4));
-		const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer };
-
-		vkcv::PushConstantData shadowPushConstantData((void*)mvpLight.data(), sizeof(glm::mat4));
-
-		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
-
-		core.recordDrawcallsToCmdStream(
-			cmdStream,
-			shadowPass,
-			shadowPipe,
-			shadowPushConstantData,
-			shadowDrawcalls,
-			{ shadowMap.getHandle() });
-
-		core.prepareImageForSampling(cmdStream, shadowMap.getHandle());
-
-		core.recordDrawcallsToCmdStream(
-			cmdStream,
-            firstMeshPass,
-            firstMeshPipeline,
-			pushConstantData,
-			drawcalls,
-			renderTargets);
-		core.prepareSwapchainImageForPresent(cmdStream);
-		core.submitCommandStream(cmdStream);
-
-		core.endFrame();
-	}
-	
-	return 0;
-}
diff --git a/projects/fire_works/.gitignore b/projects/fire_works/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..a991f1c077c11db780beb6e3d01c5bd561336690
--- /dev/null
+++ b/projects/fire_works/.gitignore
@@ -0,0 +1 @@
+fire_works
diff --git a/projects/fire_works/CMakeLists.txt b/projects/fire_works/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..7f9fd1fdd30bff0b331138821f10035f4b7dc64d
--- /dev/null
+++ b/projects/fire_works/CMakeLists.txt
@@ -0,0 +1,27 @@
+cmake_minimum_required(VERSION 3.16)
+project(fire_works)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+# adding source files to the project
+add_project(fire_works
+		src/main.cpp)
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(fire_works SYSTEM BEFORE PRIVATE
+		${vkcv_include}
+		${vkcv_includes}
+		${vkcv_camera_include}
+		${vkcv_gui_include}
+		${vkcv_shader_compiler_include}
+		${vkcv_effects_include})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(fire_works
+		vkcv
+		vkcv_camera
+		vkcv_gui
+		vkcv_shader_compiler
+		vkcv_effects)
diff --git a/projects/fire_works/README.md b/projects/fire_works/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..0357b60bed448a813e0b5812b8716542adebb31a
--- /dev/null
+++ b/projects/fire_works/README.md
@@ -0,0 +1,11 @@
+# Fire works
+An example project to show a particle simulation via compute shader to visualize virtual firework
+
+![Screenshot](../../screenshots/fire_works.png)
+
+## Details
+
+The project uses many different compute shaders to calculate different steps of the particles
+lifecycle. Particles can dynamically be spread to different events for explosions on the GPU. The 
+particles will then be rendered as volumetric smoke and geometric smoke using geometry shader 
+invocations.
diff --git a/projects/fire_works/shaders/add.comp b/projects/fire_works/shaders/add.comp
new file mode 100644
index 0000000000000000000000000000000000000000..737a1f3fd69e237f7692be8a6b36b1e14f1fd50a
--- /dev/null
+++ b/projects/fire_works/shaders/add.comp
@@ -0,0 +1,64 @@
+#version 440
+#extension GL_GOOGLE_include_directive : enable
+
+layout(set=0, binding=0) uniform texture2D voxelTexture;
+layout(set=0, binding=1) uniform sampler voxelSampler;
+
+layout(set=0, binding=2, rgba16f) restrict readonly uniform image2D inParticles;
+layout(set=0, binding=3, rgba16f) restrict readonly uniform image2D inSmoke;
+layout(set=0, binding=4, rgba16f) restrict readonly uniform image2D inTrails;
+layout(set=0, binding=5, rgba16f) restrict writeonly uniform image2D outImage;
+
+layout(set=1, binding=0, std430) readonly buffer randomBuffer {
+    float randomData [];
+};
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+#include "physics.inc"
+#include "smoke.inc"
+
+#define NUM_VOXEL_SAMPLES 32
+
+shared vec2 sc_data [NUM_VOXEL_SAMPLES];
+
+void main() {
+    const float localRadian = 0.25f * pi * randomData[gl_LocalInvocationIndex % randomData.length()];
+
+    sc_data[gl_LocalInvocationIndex % NUM_VOXEL_SAMPLES] = vec2(
+        sin(localRadian), cos(localRadian)
+    );
+
+    memoryBarrierShared();
+    barrier();
+
+    const ivec2 res = imageSize(outImage);
+
+    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, res))){
+        return;
+    }
+
+    ivec2 uv = ivec2(gl_GlobalInvocationID.xy);
+
+    vec4 outParticles = imageLoad(inParticles, uv);
+    vec4 outSmoke = imageLoad(inSmoke, uv);
+    vec4 outTrails = imageLoad(inTrails, uv);
+
+    vec2 pos = (vec2(uv) + vec2(0.5f)) / vec2(res);
+
+    vec4 outSamples = texture(sampler2D(voxelTexture, voxelSampler), pos);
+
+    vec4 result = vec4(0.0f);
+
+    result = smokeBlend(result, outParticles);
+    result = smokeBlend(result, outTrails);
+    result = smokeBlend(result, outSmoke);
+    result = smokeBlend(result, outSamples * 0.1f);
+
+    result.r = clamp(result.r, 0, 1);
+    result.g = clamp(result.g, 0, 1);
+    result.b = clamp(result.b, 0, 1);
+    result.a = clamp(result.a, 0, 1);
+
+    imageStore(outImage, uv, result);
+}
\ No newline at end of file
diff --git a/projects/fire_works/shaders/clear.comp b/projects/fire_works/shaders/clear.comp
new file mode 100644
index 0000000000000000000000000000000000000000..4668538c4a38aefec868f1817a126b5278f8fd78
--- /dev/null
+++ b/projects/fire_works/shaders/clear.comp
@@ -0,0 +1,25 @@
+#version 440
+#extension GL_GOOGLE_include_directive : enable
+
+#include "physics.inc"
+#include "voxel.inc"
+
+layout(set=0, binding=0, r32ui) restrict writeonly uniform uimage3D voxelRed;
+layout(set=0, binding=1, r32ui) restrict writeonly uniform uimage3D voxelGreen;
+layout(set=0, binding=2, r32ui) restrict writeonly uniform uimage3D voxelBlue;
+layout(set=0, binding=3, r32ui) restrict writeonly uniform uimage3D voxelDensity;
+
+layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
+
+void main() {
+    if(any(greaterThanEqual(gl_GlobalInvocationID.xyz, imageSize(voxelDensity)))){
+        return;
+    }
+
+    ivec3 pos = ivec3(gl_GlobalInvocationID.xyz);
+
+    voxel_write(voxelRed, pos, 0.0f);
+    voxel_write(voxelGreen, pos, 0.0f);
+    voxel_write(voxelBlue, pos, 0.0f);
+    voxel_write(voxelDensity, pos, mediumDensity);
+}
\ No newline at end of file
diff --git a/projects/fire_works/shaders/event.inc b/projects/fire_works/shaders/event.inc
new file mode 100644
index 0000000000000000000000000000000000000000..a2ab170c0894da68638b0e61e4af90a7a5fcdd64
--- /dev/null
+++ b/projects/fire_works/shaders/event.inc
@@ -0,0 +1,21 @@
+#ifndef EVENT_INC
+#define EVENT_INC
+
+struct event_t {
+	vec3 direction;
+	float startTime;
+	vec3 color;
+	float velocity;
+	
+	uint count;
+	uint index;
+	uint parent;
+	uint continuous;
+	
+	float lifetime;
+	float mass;
+	float size;
+	uint contCount;
+};
+
+#endif // EVENT_INC
\ No newline at end of file
diff --git a/projects/fire_works/shaders/fluid.comp b/projects/fire_works/shaders/fluid.comp
new file mode 100644
index 0000000000000000000000000000000000000000..076a280739e542c0579a7b80eaeaf4993df13edf
--- /dev/null
+++ b/projects/fire_works/shaders/fluid.comp
@@ -0,0 +1,80 @@
+#version 450 core
+#extension GL_GOOGLE_include_directive : enable
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
+
+#include "physics.inc"
+#include "voxel.inc"
+
+layout(set=0, binding=0) uniform texture3D voxelTexture;
+layout(set=0, binding=1) uniform sampler voxelSampler;
+layout(set=0, binding=2, rgba16) restrict writeonly uniform image3D fluidImage;
+
+vec4 getDataFrom(vec3 position, vec3 offset) {
+    return texture(
+        sampler3D(
+            voxelTexture,
+            voxelSampler
+        ),
+        position + offset
+    );
+}
+
+shared vec4 cachedData [4][4][4];
+
+void storeCachedData(vec3 position) {
+    uvec3 localId = gl_LocalInvocationID;
+    cachedData[localId.x][localId.y][localId.z] = getDataFrom(position, vec3(0));
+}
+
+vec4 getCachedData() {
+    uvec3 localId = gl_LocalInvocationID;
+    return cachedData[localId.x][localId.y][localId.z];
+}
+
+vec4 loadCachedData(vec3 position, ivec3 offset, ivec3 size) {
+    uvec3 localId = gl_LocalInvocationID;
+    ivec3 index = ivec3(localId) + offset;
+
+    // TOO SPECIAL FOR GPU TO WORK..!
+    //if ((any(lessThan(index, ivec3(0)))) || (any(greaterThan(index, ivec3(gl_WorkGroupSize))))) {
+    return getDataFrom(position, vec3(offset) / vec3(size));
+    //} else {
+    //    return cachedData[index.x][index.y][index.z];
+    //}
+}
+
+void main() {
+    uvec3 id = gl_GlobalInvocationID;
+    ivec3 size = imageSize(fluidImage);
+
+    if (any(greaterThanEqual(id, size))) {
+        return;
+    }
+
+    vec3 position = (vec3(id) + vec3(0.5f)) / vec3(size);
+
+    storeCachedData(position);
+    memoryBarrierShared();
+    barrier();
+
+    vec4 extData [6];
+
+    extData[0] = loadCachedData(position, ivec3(+1, 0, 0), size);
+    extData[1] = loadCachedData(position, ivec3(-1, 0, 0), size);
+    extData[2] = loadCachedData(position, ivec3(0, +1, 0), size);
+    extData[3] = loadCachedData(position, ivec3(0, -1, 0), size);
+    extData[4] = loadCachedData(position, ivec3(0, 0, +1), size);
+    extData[5] = loadCachedData(position, ivec3(0, 0, -1), size);
+
+    vec4 data = vec4(0);
+
+    for (uint i = 0; i < 6; i++) {
+        data += extData[i];
+    }
+
+    data = mix(getCachedData(), (data / 6), flowRate);
+
+    imageStore(fluidImage, ivec3(id), data);
+}
diff --git a/projects/fire_works/shaders/generation.comp b/projects/fire_works/shaders/generation.comp
new file mode 100644
index 0000000000000000000000000000000000000000..eb585236d993099350d0c0e6b97adcbfd28fdf57
--- /dev/null
+++ b/projects/fire_works/shaders/generation.comp
@@ -0,0 +1,179 @@
+#version 450 core
+#extension GL_GOOGLE_include_directive : enable
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(local_size_x = 256) in;
+
+#include "physics.inc"
+#include "particle.inc"
+
+layout(set=0, binding=0, std430) buffer particleBuffer {
+    particle_t particles [];
+};
+
+layout(set=0, binding=1, std430) readonly buffer particleBufferCopy {
+    particle_t particlesCopy [];
+};
+
+layout(set=1, binding=0, std430) readonly buffer randomBuffer {
+    float randomData [];
+};
+
+#include "event.inc"
+
+layout(set=1, binding=1, std430) buffer eventBuffer {
+    event_t events [];
+};
+
+layout(set=1, binding=2, std430) buffer startIndexBuffer {
+    uint startIndex [];
+};
+
+#include "smoke.inc"
+
+layout(set=2, binding=0, std430) writeonly buffer smokeBuffer {
+    smoke_t smokes [];
+};
+
+layout(set=2, binding=1, std430) buffer smokeIndexBuffer {
+    uint smokeIndex;
+    uint trailIndex;
+    uint pointIndex;
+};
+
+#include "trail.inc"
+
+layout(set=3, binding=0, std430) writeonly buffer trailBuffer {
+    trail_t trails [];
+};
+
+#include "point.inc"
+
+layout(set=3, binding=1, std430) readonly buffer pointBuffer {
+    point_t points [];
+};
+
+layout( push_constant ) uniform constants{
+    float t;
+    float dt;
+};
+
+void main() {
+    uint id = gl_GlobalInvocationID.x;
+
+    if (id >= particles.length()) {
+        return;
+    }
+
+    float lifetime = particles[id].lifetime;
+
+    if (lifetime > 0.0f) {
+        return;
+    }
+
+    uint event_id = events.length();
+    uint index = 0;
+
+    for (uint i = 0; i < events.length(); i++) {
+        const float start = events[i].startTime;
+
+        if ((events[i].continuous < 1) && (t < start)) {
+            continue;
+        }
+
+        index = atomicAdd(events[i].index, 1);
+
+        if (events[i].continuous < 1) {
+            if (events[i].count > index) {
+                event_id = i;
+                break;
+            } else {
+                atomicAdd(events[i].index, -1);
+            }
+        } else {
+            if (events[i].continuous > index){
+                event_id = i;
+                break;
+            } else {
+                if (events[i].contCount > 0) {
+                    atomicAdd(events[i].contCount, -1);
+                    events[i].index = 0;
+                }
+
+                atomicAdd(events[i].index, -1);
+            }
+        }
+    }
+
+    if (event_id >= events.length()) {
+        return;
+    }
+
+    lifetime = events[event_id].lifetime * (1.0f + 0.1f * randomData[(id + 1) % randomData.length()]);
+
+    vec3 direction;
+    if (dot(events[event_id].direction, events[event_id].direction) <= 0.0f) {
+        direction = vec3(
+            randomData[(id * 3 + 0) % randomData.length()],
+            randomData[(id * 3 + 1) % randomData.length()],
+            randomData[(id * 3 + 2) % randomData.length()]
+        );
+    } else {
+        direction = events[event_id].direction;
+    }
+
+    vec3 color = normalize(events[event_id].color);
+    const float v = events[event_id].velocity;
+
+    vec3 velocity = vec3(0.0f);
+    float size = events[event_id].size;
+
+    const uint pid = events[event_id].parent;
+
+    if (pid < events.length()) {
+        const uint spawnCount = events[pid].count;
+        const uint spawnId = startIndex[pid] + (id % spawnCount);
+
+        if (spawnId < particlesCopy.length()) {
+            particles[id].position = particlesCopy[spawnId].position;
+            velocity += particlesCopy[spawnId].velocity;
+            size = particlesCopy[spawnId].size;
+        }
+    }
+
+    if ((0 == index) && (events[event_id].continuous < 1)) {
+        const uint sid = atomicAdd(smokeIndex, 1) % smokes.length();
+
+        smokes[sid].position = particles[id].position;
+        smokes[sid].size = size * (1.0f + friction);
+        smokes[sid].velocity = velocity;
+        smokes[sid].scaling = v;
+        smokes[sid].color = mix(color, vec3(1.0f), 0.5f);
+        smokes[sid].eventID = event_id;
+    }
+
+    velocity += normalize(direction) * v * (1.0f + 0.1f * randomData[(id + 2) % randomData.length()]);;
+
+    const float split = pow(1.0f / events[event_id].count, 1.0f / 3.0f);
+
+    particles[id].lifetime = lifetime;
+    particles[id].velocity = velocity;
+    particles[id].size = size * split;
+    particles[id].color = color;
+    particles[id].mass = events[event_id].mass / events[event_id].count;
+    particles[id].eventId = event_id;
+
+    {
+        const uint tid = atomicAdd(trailIndex, 1) % trails.length();
+        const uint trailLen = 96 + int(randomData[(tid + id) % randomData.length()] * 32);
+
+        const uint startIndex = atomicAdd(pointIndex, trailLen) % points.length();
+
+        trails[tid].particleIndex = id;
+        trails[tid].startIndex = startIndex;
+        trails[tid].endIndex = (startIndex + trailLen - 1) % points.length();
+        trails[tid].useCount = 0;
+        trails[tid].color = mix(color, vec3(1.0f), 0.75f);
+        trails[tid].lifetime = lifetime + (dt * trailLen) * 0.5f;
+    }
+}
diff --git a/projects/fire_works/shaders/motion.comp b/projects/fire_works/shaders/motion.comp
new file mode 100644
index 0000000000000000000000000000000000000000..51d6fc5f60f2eff380376fead6cc189af68b52e6
--- /dev/null
+++ b/projects/fire_works/shaders/motion.comp
@@ -0,0 +1,49 @@
+#version 450 core
+#extension GL_GOOGLE_include_directive : enable
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(local_size_x = 256) in;
+
+#include "physics.inc"
+#include "particle.inc"
+
+layout(set=0, binding=0, std430) coherent buffer particleBuffer {
+    particle_t particles [];
+};
+
+layout( push_constant ) uniform constants{
+    float t;
+    float dt;
+};
+
+void main() {
+    uint id = gl_GlobalInvocationID.x;
+
+    if (id >= particles.length()) {
+        return;
+    }
+
+    vec3 position = particles[id].position;
+    float lifetime = particles[id].lifetime;
+    vec3 velocity = particles[id].velocity;
+
+    if (lifetime > dt) {
+        lifetime -= dt;
+    } else {
+        lifetime = 0.0f;
+    }
+
+    const float fading = 1.0f / (1.0f + friction);
+
+    position = position + velocity * dt;
+
+    if (particles[id].mass > 0){
+        velocity = velocity * fading + vec3(0.0f, -g, 0.0f) * dt;
+    } else {
+        velocity = velocity * fading;
+    }
+
+    particles[id].position = position;
+    particles[id].lifetime = lifetime;
+    particles[id].velocity = velocity;
+}
diff --git a/projects/fire_works/shaders/particle.frag b/projects/fire_works/shaders/particle.frag
new file mode 100644
index 0000000000000000000000000000000000000000..173c83ef5d7a79bfd8446c5586b7a2f487fe7546
--- /dev/null
+++ b/projects/fire_works/shaders/particle.frag
@@ -0,0 +1,21 @@
+#version 450
+
+layout(location = 0) in vec2 passPos;
+layout(location = 1) in flat vec3 passColor;
+layout(location = 2) in flat float passLifetime;
+
+layout(location = 0) out vec4 outColor;
+
+void main()	{
+    if (passLifetime <= 0.0f) {
+        discard;
+    }
+
+    const float value = length(passPos);
+
+    if (value < 0.5f) {
+        outColor = vec4(passColor, 1.0f - max(value * 2.0f, 0.0f));
+    } else {
+        discard;
+    }
+}
\ No newline at end of file
diff --git a/projects/fire_works/shaders/particle.inc b/projects/fire_works/shaders/particle.inc
new file mode 100644
index 0000000000000000000000000000000000000000..488c9349d72ac04d48e523d5c9a97057bdfb1f78
--- /dev/null
+++ b/projects/fire_works/shaders/particle.inc
@@ -0,0 +1,15 @@
+#ifndef PARTICLE_INC
+#define PARTICLE_INC
+
+struct particle_t {
+	vec3 position;
+	float lifetime;
+	vec3 velocity;
+	float size;
+	vec3 color;
+	float mass;
+	vec3 pad0;
+	uint eventId;
+};
+
+#endif // PARTICLE_INC
\ No newline at end of file
diff --git a/projects/fire_works/shaders/particle.vert b/projects/fire_works/shaders/particle.vert
new file mode 100644
index 0000000000000000000000000000000000000000..ff0e862ebcf0f66c3412e9ce04b610cea760151d
--- /dev/null
+++ b/projects/fire_works/shaders/particle.vert
@@ -0,0 +1,40 @@
+#version 450
+#extension GL_GOOGLE_include_directive : enable
+
+#include "particle.inc"
+
+layout(set=0, binding=0, std430) readonly buffer particleBuffer {
+    particle_t particles [];
+};
+
+layout(location = 0) in vec2 vertexPos;
+
+layout(location = 0) out vec2 passPos;
+layout(location = 1) out flat vec3 passColor;
+layout(location = 2) out flat float passLifetime;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+    uint width;
+    uint height;
+};
+
+void main()	{
+    vec3 position = particles[gl_InstanceIndex].position;
+    float lifetime = particles[gl_InstanceIndex].lifetime;
+    float size = particles[gl_InstanceIndex].size;
+    vec3 color = particles[gl_InstanceIndex].color;
+
+    if (width > height) {
+        passPos = vertexPos * vec2(1.0f * width / height, 1.0f);
+    } else {
+        passPos = vertexPos * vec2(1.0f, 1.0f * height / width);
+    }
+
+    passColor = color;
+    passLifetime = lifetime;
+
+    // align particle to face camera
+    gl_Position = mvp * vec4(position, 1);      // transform position into projected view space
+    gl_Position.xy += vertexPos * size * 2.0f;  // move position directly in view space
+}
\ No newline at end of file
diff --git a/projects/fire_works/shaders/physics.inc b/projects/fire_works/shaders/physics.inc
new file mode 100644
index 0000000000000000000000000000000000000000..e14c62e33a6fd42ebfa8d31591394989de68a8d2
--- /dev/null
+++ b/projects/fire_works/shaders/physics.inc
@@ -0,0 +1,13 @@
+#ifndef PHYSICS_INC
+#define PHYSICS_INC
+
+const float pi = 3.14159f;
+
+const float g = 9.81f;
+const float friction = 0.004f;
+
+const float flowRate = 0.75f;
+const float mediumDensity = 0.0002f;
+const float trailWidth = 0.25f;
+
+#endif // PHYSICS_INC
\ No newline at end of file
diff --git a/projects/fire_works/shaders/point.inc b/projects/fire_works/shaders/point.inc
new file mode 100644
index 0000000000000000000000000000000000000000..54663c1e2eae4c641fbe5485d4c0ac41cb323df6
--- /dev/null
+++ b/projects/fire_works/shaders/point.inc
@@ -0,0 +1,11 @@
+#ifndef POINT_INC
+#define POINT_INC
+
+struct point_t {
+    vec3 position;
+    float size;
+	vec3 velocity;
+	float scaling;
+};
+
+#endif // POINT_INC
\ No newline at end of file
diff --git a/projects/fire_works/shaders/sample.comp b/projects/fire_works/shaders/sample.comp
new file mode 100644
index 0000000000000000000000000000000000000000..1eef49df639cfbc9f35c53ace21b1ce50ab62459
--- /dev/null
+++ b/projects/fire_works/shaders/sample.comp
@@ -0,0 +1,39 @@
+#version 440
+#extension GL_GOOGLE_include_directive : enable
+
+#include "voxel.inc"
+#include "smoke.inc"
+
+layout(set=0, binding=0, rgba16) restrict readonly uniform image3D voxelImage;
+layout(set=1, binding=0, rgba16f) restrict writeonly uniform image2D outImage;
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+void main() {
+    const ivec2 res = imageSize(outImage);
+
+    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, res))){
+        return;
+    }
+
+    ivec2 uv = ivec2(gl_GlobalInvocationID.xy);
+
+    const ivec3 voxelRes = imageSize(voxelImage);
+
+    vec4 voxel = vec4(0.0f);
+
+    for (int i = 0; i < voxelRes.z; i++) {
+        const ivec3 voxelPos = ivec3(uv, i);
+
+        vec4 data = imageLoad(voxelImage, voxelPos);
+
+        voxel = smokeBlend(voxel, data);
+    }
+
+    voxel.r = clamp(voxel.r, 0, 1);
+    voxel.g = clamp(voxel.g, 0, 1);
+    voxel.b = clamp(voxel.b, 0, 1);
+    voxel.a = clamp(voxel.a, 0, 1);
+
+    imageStore(outImage, uv, voxel);
+}
\ No newline at end of file
diff --git a/projects/fire_works/shaders/scale.comp b/projects/fire_works/shaders/scale.comp
new file mode 100644
index 0000000000000000000000000000000000000000..44cfb8e48ad078f5ee8f63785b513a20f27ef542
--- /dev/null
+++ b/projects/fire_works/shaders/scale.comp
@@ -0,0 +1,40 @@
+#version 450 core
+#extension GL_GOOGLE_include_directive : enable
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(local_size_x = 256) in;
+
+#include "physics.inc"
+#include "smoke.inc"
+
+layout(set=0, binding=0, std430) buffer smokeBuffer {
+    smoke_t smokes [];
+};
+
+layout( push_constant ) uniform constants{
+    float t;
+    float dt;
+};
+
+void main() {
+    uint id = gl_GlobalInvocationID.x;
+
+    if (id >= smokes.length()) {
+        return;
+    }
+
+    vec3 position = smokes[id].position;
+    float size = smokes[id].size;
+    vec3 velocity = smokes[id].velocity;
+
+    const float scaling = smokes[id].scaling;
+    const float fading = 1.0f / (1.0f + friction);
+
+    position = position + velocity * dt;
+    velocity = velocity * fading + vec3(0.0f, 0.2f, 0.0f) * dt; //smoke is lighter than air right? + vec3(0.0f, -g, 0.0f) * dt;
+    size = size + scaling * dt;
+
+    smokes[id].position = position;
+    smokes[id].size = size;
+    smokes[id].velocity = velocity;
+}
diff --git a/projects/fire_works/shaders/smoke.frag b/projects/fire_works/shaders/smoke.frag
new file mode 100644
index 0000000000000000000000000000000000000000..8ded98ac5fbae554959a83df5bbf7451d286e832
--- /dev/null
+++ b/projects/fire_works/shaders/smoke.frag
@@ -0,0 +1,57 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+#include "physics.inc"
+#include "smoke.inc"
+
+layout(location = 0) in vec3 passPos;
+layout(location = 1) in vec3 passDir;
+layout(location = 2) in vec3 passColor;
+layout(location = 3) in float passDensity;
+layout(location = 4) in flat int passSmokeIndex;
+
+layout(location = 0) out vec4 outColor;
+
+layout(set=1, binding=0, std430) readonly buffer randomBuffer {
+    float randomData [];
+};
+
+#define NUM_SMOKE_SAMPLES 16
+
+void main()	{
+    if (passDensity <= mediumDensity) {
+        discard;
+    }
+
+    vec3 start = passPos;
+    vec3 end = start + normalize(passDir) * 3.5f;
+
+    vec4 result = vec4(0);
+
+    for (uint i = 0; i < NUM_SMOKE_SAMPLES; i++) {
+        vec3 position = (
+            end + (start - end) * i / (NUM_SMOKE_SAMPLES - 1)
+        );
+
+        vec4 data = vec4(passColor, passDensity);
+
+        float fallOff = max(1.0f - length(position), 0.0f);
+
+        const uint randomIndex = (passSmokeIndex * NUM_SMOKE_SAMPLES + i) % randomData.length();
+        const float alpha = (1.0f + randomData[randomIndex] * 0.1f) * data.a * fallOff;
+
+        result = smokeBlend(result, vec4(data.rgb, alpha));
+    }
+
+    result.r = clamp(result.r, 0, 1);
+    result.g = clamp(result.g, 0, 1);
+    result.b = clamp(result.b, 0, 1);
+    result.a = clamp(result.a, 0, 1);
+
+    if (result.a < 1.0f) {
+        outColor = result;
+    } else {
+        discard;
+    }
+}
\ No newline at end of file
diff --git a/projects/fire_works/shaders/smoke.inc b/projects/fire_works/shaders/smoke.inc
new file mode 100644
index 0000000000000000000000000000000000000000..8886a788273cd5fa5c871749e0c9012f2722ed41
--- /dev/null
+++ b/projects/fire_works/shaders/smoke.inc
@@ -0,0 +1,31 @@
+#ifndef SMOKE_INC
+#define SMOKE_INC
+
+struct smoke_t {
+	vec3 position;
+	float size;
+	vec3 velocity;
+	float scaling;
+	vec3 color;
+	uint eventID;
+};
+
+float smokeDensity(float size) {
+	if (size > 0.0f) {
+		return 0.025f / size;
+	} else {
+		return 0.0f;
+	}
+}
+
+vec4 smokeBlend(vec4 dst, vec4 src) {
+	const float f = max(1.0f - dst.a, 0.0f);
+	const float a = clamp(0.0f, 1.0f, src.a);
+
+	return vec4(
+		dst.rgb + src.rgb * a * f,
+		dst.a + a * f
+	);
+}
+
+#endif // SMOKE_INC
\ No newline at end of file
diff --git a/projects/fire_works/shaders/smoke.vert b/projects/fire_works/shaders/smoke.vert
new file mode 100644
index 0000000000000000000000000000000000000000..634117f79789797cd68e00cac3dbd59fb6515f79
--- /dev/null
+++ b/projects/fire_works/shaders/smoke.vert
@@ -0,0 +1,38 @@
+#version 450
+#extension GL_GOOGLE_include_directive : enable
+
+#include "smoke.inc"
+
+layout(set=0, binding=0, std430) readonly buffer smokeBuffer {
+    smoke_t smokes [];
+};
+
+layout(location = 0) in vec3 vertexPos;
+
+layout(location = 0) out vec3 passPos;
+layout(location = 1) out vec3 passDir;
+layout(location = 2) out vec3 passColor;
+layout(location = 3) out float passDensity;
+layout(location = 4) out flat int passSmokeIndex;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+    vec3 camera;
+};
+
+void main()	{
+    vec3 position = smokes[gl_InstanceIndex].position;
+    float size = smokes[gl_InstanceIndex].size;
+    vec3 color = smokes[gl_InstanceIndex].color;
+
+    vec3 pos = position + vertexPos * size;
+
+    passPos = vertexPos;
+    passDir = pos - camera;
+    passColor = color;
+    passDensity = smokeDensity(size);
+    passSmokeIndex = gl_InstanceIndex;
+
+    // transform position into projected view space
+    gl_Position = mvp * vec4(pos, 1);
+}
\ No newline at end of file
diff --git a/projects/fire_works/shaders/tonemapping.comp b/projects/fire_works/shaders/tonemapping.comp
new file mode 100644
index 0000000000000000000000000000000000000000..5e6cc8412a77939888fc8e961ea5e9ef29534a81
--- /dev/null
+++ b/projects/fire_works/shaders/tonemapping.comp
@@ -0,0 +1,21 @@
+#version 440
+
+layout(set=0, binding=0, rgba16f) readonly uniform image2D inImage;
+layout(set=0, binding=1, rgba8) writeonly uniform image2D outImage;
+
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+void main() {
+    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(inImage)))){
+        return;
+    }
+
+    ivec2 uv            = ivec2(gl_GlobalInvocationID.xy);
+    vec3 linearColor    = imageLoad(inImage, uv).rgb;
+
+    vec3 tonemapped     = linearColor / (dot(linearColor, vec3(0.21, 0.71, 0.08)) + 1); // reinhard tonemapping
+    vec3 gammaCorrected = pow(tonemapped, vec3(1.f / 2.2f));
+
+    imageStore(outImage, uv, vec4(gammaCorrected, 0.f));
+}
\ No newline at end of file
diff --git a/projects/fire_works/shaders/trail.comp b/projects/fire_works/shaders/trail.comp
new file mode 100644
index 0000000000000000000000000000000000000000..8e811a5251948e979fcacc0d2c1e95e27411fa87
--- /dev/null
+++ b/projects/fire_works/shaders/trail.comp
@@ -0,0 +1,98 @@
+#version 450 core
+#extension GL_GOOGLE_include_directive : enable
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(local_size_x = 256) in;
+
+#include "physics.inc"
+#include "particle.inc"
+
+layout(set=0, binding=0, std430) readonly buffer particleBuffer {
+    particle_t particles [];
+};
+
+#include "trail.inc"
+
+layout(set=1, binding=0, std430) coherent buffer trailBuffer {
+    trail_t trails [];
+};
+
+#include "point.inc"
+
+layout(set=1, binding=1, std430) buffer pointBuffer {
+    point_t points [];
+};
+
+layout( push_constant ) uniform constants{
+    float t;
+    float dt;
+};
+
+void main() {
+    uint id = gl_GlobalInvocationID.x;
+
+    if (id >= trails.length()) {
+        return;
+    }
+
+    const uint particleIndex = trails[id].particleIndex;
+    const uint startIndex = trails[id].startIndex;
+    const uint endIndex = trails[id].endIndex;
+
+    uint useCount = trails[id].useCount;
+    float lifetime = trails[id].lifetime;
+
+    if (lifetime > dt) {
+        lifetime -= dt;
+    } else {
+        lifetime = 0.0f;
+    }
+
+    const uint available = (endIndex - startIndex) % points.length();
+
+    float trailLife = dt * available;
+    float fading = 1.0f / (1.0f + friction);
+
+    if (lifetime <= trailLife) {
+        fading *= (lifetime / trailLife);
+
+        if (useCount > 0) {
+            useCount--;
+        }
+    } else
+    if (available > useCount) {
+        useCount++;
+    }
+
+    for (uint i = useCount; i > 1; i--) {
+        const uint x = (startIndex + (i - 1)) % points.length();
+        const uint y = (startIndex + (i - 2)) % points.length();
+
+        vec3 position = points[y].position;
+        float size = points[y].size;
+        vec3 velocity = points[y].velocity;
+
+        const float scaling = points[y].scaling;
+
+        size = size * fading + scaling * dt;
+
+        points[x].position = position;
+        points[x].size = size;
+        points[x].velocity = velocity;
+        points[x].scaling = scaling;
+    }
+
+    vec3 position = particles[particleIndex].position;
+    float size = particles[particleIndex].size;
+    vec3 velocity = particles[particleIndex].velocity;
+
+    const float trailFactor = mediumDensity / friction;
+
+    points[startIndex].position = position * fading;
+    points[startIndex].size = trailWidth * size * fading;
+    points[startIndex].velocity = velocity * fading;
+    points[startIndex].scaling = trailFactor * length(velocity) * fading;
+
+    trails[id].useCount = useCount;
+    trails[id].lifetime = lifetime;
+}
diff --git a/projects/fire_works/shaders/trail.geom b/projects/fire_works/shaders/trail.geom
new file mode 100644
index 0000000000000000000000000000000000000000..027943473d05cddc9db6019c7ee36771fcb91d2e
--- /dev/null
+++ b/projects/fire_works/shaders/trail.geom
@@ -0,0 +1,96 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+#define INSTANCE_LEN (16)
+
+layout(points) in;
+layout (triangle_strip, max_vertices = (INSTANCE_LEN * 2)) out;
+layout(invocations = 8) in;
+
+#include "physics.inc"
+#include "point.inc"
+
+layout(set=0, binding=1, std430) readonly buffer pointBuffer {
+    point_t points [];
+};
+
+layout(location = 0) in vec3 geomColor [1];
+layout(location = 1) in uint geomTrailIndex [1];
+layout(location = 2) in vec3 geomTrailColor [1];
+layout(location = 3) in uint geomStartIndex [1];
+layout(location = 4) in uint geomUseCount [1];
+
+layout(location = 0) out vec3 passPos;
+layout(location = 1) out vec3 passDir;
+layout(location = 2) out vec3 passColor;
+layout(location = 3) out float passDensity;
+layout(location = 4) out flat int passSmokeIndex;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+    vec3 camera;
+};
+
+void main() {
+    const vec3 color = geomColor[0];
+    const uint id = geomTrailIndex[0];
+
+    const vec3 trailColor = geomTrailColor[0];
+
+    const uint startIndex = geomStartIndex[0];
+    const uint useCount = geomUseCount[0];
+
+    const uint indexOffset = (gl_InvocationID * (INSTANCE_LEN - 1));
+    const uint instanceIndex = startIndex + indexOffset;
+
+    uint count = min(INSTANCE_LEN, useCount);
+
+    if ((indexOffset >= useCount) && (indexOffset + INSTANCE_LEN > useCount)) {
+        count = indexOffset - useCount;
+    }
+
+    if (count <= 1) {
+        return;
+    }
+
+    const float trailFactor = mediumDensity / friction;
+
+    for (uint i = 0; i < count; i++) {
+        const float u = float(indexOffset + i + 1) / float(useCount);
+
+        const uint index = (instanceIndex + i) % points.length();
+
+        const vec3 position = points[index].position;
+        const float size = points[index].size;
+        const vec3 velocity = points[index].velocity;
+
+        const vec3 dir = normalize(cross(abs(velocity), position - camera));
+
+        vec3 offset = dir * size;
+        float density = trailFactor * (1.0f - u * u) / size;
+
+        const vec3 p0 = position - offset;
+        const vec3 p1 = position + offset;
+
+        passPos = vec3(u, -1.0f, -1.0f);
+        passDir = vec3(-0.1f * u, +0.2f, 2.0f);
+        passColor = mix(color, trailColor, u);
+        passDensity = density;
+        passSmokeIndex = int(id);
+
+        gl_Position = mvp * vec4(p0, 1);
+        EmitVertex();
+
+        passPos = vec3(u, +1.0f, -1.0f);
+        passDir = vec3(-0.1f * u, -0.2f, 2.0f);
+        passColor = mix(color, trailColor, u);
+        passDensity = density;
+        passSmokeIndex = int(id);
+
+        gl_Position = mvp * vec4(p1, 1);
+        EmitVertex();
+    }
+
+    EndPrimitive();
+}
\ No newline at end of file
diff --git a/projects/fire_works/shaders/trail.inc b/projects/fire_works/shaders/trail.inc
new file mode 100644
index 0000000000000000000000000000000000000000..75cc49ad4038f28f27890e088fe4dd4e511d1f6b
--- /dev/null
+++ b/projects/fire_works/shaders/trail.inc
@@ -0,0 +1,13 @@
+#ifndef TRAIL_INC
+#define TRAIL_INC
+
+struct trail_t {
+    uint particleIndex;
+    uint startIndex;
+    uint endIndex;
+	uint useCount;
+	vec3 color;
+    float lifetime;
+};
+
+#endif // TRAIL_INC
\ No newline at end of file
diff --git a/projects/fire_works/shaders/trail.vert b/projects/fire_works/shaders/trail.vert
new file mode 100644
index 0000000000000000000000000000000000000000..871beb9544c33ec790d079a050b4780efbec363f
--- /dev/null
+++ b/projects/fire_works/shaders/trail.vert
@@ -0,0 +1,40 @@
+#version 450
+#extension GL_GOOGLE_include_directive : enable
+
+#include "trail.inc"
+
+layout(set=0, binding=0, std430) readonly buffer trailBuffer {
+    trail_t trails [];
+};
+
+#include "particle.inc"
+
+layout(set=2, binding=0, std430) readonly buffer particleBuffer {
+    particle_t particles [];
+};
+
+layout(location = 0) out vec3 geomColor;
+layout(location = 1) out uint geomTrailIndex;
+layout(location = 2) out vec3 geomTrailColor;
+layout(location = 3) out uint geomStartIndex;
+layout(location = 4) out uint geomUseCount;
+
+void main()	{
+    const uint particleIndex = trails[gl_InstanceIndex].particleIndex;
+    const float lifetime = trails[gl_InstanceIndex].lifetime;
+
+    geomColor = particles[particleIndex].color;
+    geomTrailIndex = gl_InstanceIndex;
+    geomTrailColor = trails[gl_InstanceIndex].color;
+    geomStartIndex = trails[gl_InstanceIndex].startIndex;
+
+    const uint useCount = trails[gl_InstanceIndex].useCount;
+
+    if (lifetime > 0.0f) {
+        geomUseCount = useCount;
+    } else {
+        geomUseCount = 0;
+    }
+
+    gl_Position = vec4(gl_InstanceIndex, lifetime, useCount, 0.0f);
+}
\ No newline at end of file
diff --git a/projects/fire_works/shaders/voxel.comp b/projects/fire_works/shaders/voxel.comp
new file mode 100644
index 0000000000000000000000000000000000000000..459c06f381c5c7c9cc26074cd22ecf2869e30350
--- /dev/null
+++ b/projects/fire_works/shaders/voxel.comp
@@ -0,0 +1,36 @@
+#version 450 core
+#extension GL_GOOGLE_include_directive : enable
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
+
+#include "physics.inc"
+#include "voxel.inc"
+
+layout(set=0, binding=0, r32ui) restrict readonly uniform uimage3D voxelRed;
+layout(set=0, binding=1, r32ui) restrict readonly uniform uimage3D voxelGreen;
+layout(set=0, binding=2, r32ui) restrict readonly uniform uimage3D voxelBlue;
+layout(set=0, binding=3, r32ui) restrict readonly uniform uimage3D voxelDensity;
+
+layout(set=1, binding=0, rgba16) restrict writeonly uniform image3D voxelImage;
+
+void main() {
+    ivec3 pos = ivec3(gl_GlobalInvocationID);
+    ivec3 size = imageSize(voxelImage);
+
+    if (any(greaterThanEqual(pos, size))) {
+        return;
+    }
+
+    const float red = voxel_read(voxelRed, pos);
+    const float green = voxel_read(voxelGreen, pos);
+    const float blue = voxel_read(voxelBlue, pos);
+    const float density = voxel_read(voxelDensity, pos);
+
+    imageStore(voxelImage, pos, vec4(
+        red,
+        green,
+        blue,
+        density
+    ));
+}
diff --git a/projects/fire_works/shaders/voxel.inc b/projects/fire_works/shaders/voxel.inc
new file mode 100644
index 0000000000000000000000000000000000000000..9e2c895e4a0c688262de7404039aefe6650191bd
--- /dev/null
+++ b/projects/fire_works/shaders/voxel.inc
@@ -0,0 +1,18 @@
+#ifndef VOXEL_INC
+#define VOXEL_INC
+
+#define VOXEL_NORM_VALUE 0xFF
+
+#define voxel_add(img, pos, value) imageAtomicAdd(img, ivec3((imageSize(img) - ivec3(1)) * pos), uint(VOXEL_NORM_VALUE * value))
+
+#define voxel_write(img, pos, value) imageStore(img, pos, uvec4(VOXEL_NORM_VALUE * value));
+#define voxel_read(img, pos) imageLoad(img, pos).r / float(VOXEL_NORM_VALUE);
+
+// https://stackoverflow.com/questions/51108596/linearize-depth
+float linearize_depth(float d,float zNear,float zFar) {
+    return zNear * zFar / (zFar + d * (zNear - zFar));
+}
+
+#define voxel_pos(pos) vec3((pos.xy + vec2(1.0f)) * 0.5f, linearize_depth(pos.y, 0.1f, 50.0f))
+
+#endif // VOXEL_INC
\ No newline at end of file
diff --git a/projects/fire_works/shaders/voxel_particle.comp b/projects/fire_works/shaders/voxel_particle.comp
new file mode 100644
index 0000000000000000000000000000000000000000..12b68eed09746f38286a5b1f1b4e9e56d4bfe105
--- /dev/null
+++ b/projects/fire_works/shaders/voxel_particle.comp
@@ -0,0 +1,59 @@
+#version 450
+#extension GL_GOOGLE_include_directive : enable
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(local_size_x = 256) in;
+
+#include "physics.inc"
+#include "particle.inc"
+
+layout(set=0, binding=0, std430) readonly buffer particleBuffer {
+    particle_t particles [];
+};
+
+#include "voxel.inc"
+
+layout(set=1, binding=0, r32ui) uniform uimage3D voxelRed;
+layout(set=1, binding=1, r32ui) uniform uimage3D voxelGreen;
+layout(set=1, binding=2, r32ui) uniform uimage3D voxelBlue;
+layout(set=1, binding=3, r32ui) uniform uimage3D voxelDensity;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+};
+
+void main() {
+    uint id = gl_GlobalInvocationID.x;
+
+    if (id >= particles.length()) {
+        return;
+    }
+
+    vec3 position = particles[id].position;
+    float lifetime = particles[id].lifetime;
+
+    if (lifetime <= 0.0f) {
+        return;
+    }
+
+    vec4 cs_pos = mvp * vec4(position, 1);
+
+    if (abs(cs_pos.w) <= 0.0f) {
+        return;
+    }
+
+    vec3 ndc_pos = cs_pos.xyz / cs_pos.w;
+    vec3 pos = voxel_pos(ndc_pos);
+
+    if ((any(greaterThanEqual(pos, vec3(1.5f)))) || (any(lessThanEqual(pos, vec3(-0.5f))))) {
+        return;
+    }
+
+    float size = particles[id].size;
+    vec3 color = particles[id].color;
+
+    voxel_add(voxelRed, pos, color.r);
+    voxel_add(voxelGreen, pos, color.g);
+    voxel_add(voxelBlue, pos, color.b);
+    voxel_add(voxelDensity, pos, 1.0f);
+}
diff --git a/projects/fire_works/shaders/voxel_smoke.comp b/projects/fire_works/shaders/voxel_smoke.comp
new file mode 100644
index 0000000000000000000000000000000000000000..de216ab29a6671e1547600d072edaf510b775154
--- /dev/null
+++ b/projects/fire_works/shaders/voxel_smoke.comp
@@ -0,0 +1,72 @@
+#version 450 core
+#extension GL_GOOGLE_include_directive : enable
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(local_size_x = 256) in;
+
+#include "physics.inc"
+#include "smoke.inc"
+
+layout(set=0, binding=0, std430) readonly buffer smokeBuffer {
+    smoke_t smokes [];
+};
+
+#include "voxel.inc"
+
+layout(set=1, binding=0, r32ui) uniform uimage3D voxelRed;
+layout(set=1, binding=1, r32ui) uniform uimage3D voxelGreen;
+layout(set=1, binding=2, r32ui) uniform uimage3D voxelBlue;
+layout(set=1, binding=3, r32ui) uniform uimage3D voxelDensity;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+};
+
+#define NUM_SMOKE_SAMPLES 4
+
+void main() {
+    uint id = gl_GlobalInvocationID.x;
+
+    if (id >= smokes.length()) {
+        return;
+    }
+
+    vec3 position = smokes[id].position;
+    float size = smokes[id].size;
+
+    const float density = smokeDensity(size);
+
+    if (density <= mediumDensity) {
+        return;
+    }
+
+    vec3 offset = vec3(-size);
+
+    for (;offset.x <= size; offset.x += size / NUM_SMOKE_SAMPLES) {
+        for (;offset.y <= size; offset.y += size / NUM_SMOKE_SAMPLES) {
+            for (;offset.z <= size; offset.z += size / NUM_SMOKE_SAMPLES) {
+                vec4 cs_pos = mvp * vec4(position + offset, 1);
+
+                if (abs(cs_pos.w) <= 0.0f) {
+                    return;
+                }
+
+                vec3 ndc_pos = cs_pos.xyz / cs_pos.w;
+                vec3 pos = voxel_pos(ndc_pos);
+
+                if ((any(greaterThanEqual(pos, vec3(1.5f)))) || (any(lessThanEqual(pos, vec3(-0.5f))))) {
+                    return;
+                }
+
+                vec3 color = smokes[id].color;
+
+                float local_density = density * max(1.0f - length(offset / size), 0.0f);
+
+                voxel_add(voxelRed, pos, color.r);
+                voxel_add(voxelGreen, pos, color.g);
+                voxel_add(voxelBlue, pos, color.b);
+                voxel_add(voxelDensity, pos, local_density);
+            }
+        }
+    }
+}
diff --git a/projects/fire_works/shaders/voxel_trail.comp b/projects/fire_works/shaders/voxel_trail.comp
new file mode 100644
index 0000000000000000000000000000000000000000..56971a1719075e0dd9a6a5bb6b52e89c11f8489c
--- /dev/null
+++ b/projects/fire_works/shaders/voxel_trail.comp
@@ -0,0 +1,87 @@
+#version 450 core
+#extension GL_GOOGLE_include_directive : enable
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(local_size_x = 256) in;
+
+#include "physics.inc"
+
+#include "trail.inc"
+
+layout(set=0, binding=0, std430) coherent buffer trailBuffer {
+    trail_t trails [];
+};
+
+#include "point.inc"
+
+layout(set=0, binding=1, std430) buffer pointBuffer {
+    point_t points [];
+};
+
+#include "voxel.inc"
+
+layout(set=1, binding=0, r32ui) uniform uimage3D voxelRed;
+layout(set=1, binding=1, r32ui) uniform uimage3D voxelGreen;
+layout(set=1, binding=2, r32ui) uniform uimage3D voxelBlue;
+layout(set=1, binding=3, r32ui) uniform uimage3D voxelDensity;
+
+#include "smoke.inc"
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+};
+
+void main() {
+    uint id = gl_GlobalInvocationID.x;
+
+    if (id >= trails.length()) {
+        return;
+    }
+
+    const uint particleIndex = trails[id].particleIndex;
+    const uint startIndex = trails[id].startIndex;
+
+    uint useCount = trails[id].useCount;
+
+    if (useCount <= 0) {
+        return;
+    }
+
+    vec3 color = trails[id].color;
+    float lifetime = trails[id].lifetime;
+
+    if (lifetime <= 0.0f) {
+        return;
+    }
+
+    for (uint i = 0; i < useCount; i++) {
+        const uint x = (startIndex + i) % points.length();
+
+        vec3 position = points[x].position;
+        float size = points[x].size;
+
+        const float density = smokeDensity(size);
+
+        if (density <= mediumDensity) {
+            break;
+        }
+
+        vec4 cs_pos = mvp * vec4(position, 1);
+
+        if (abs(cs_pos.w) <= 0.0f) {
+            return;
+        }
+
+        vec3 ndc_pos = cs_pos.xyz / cs_pos.w;
+        vec3 pos = voxel_pos(ndc_pos);
+
+        if ((any(greaterThanEqual(pos, vec3(1.5f)))) || (any(lessThanEqual(pos, vec3(-0.5f))))) {
+            continue;
+        }
+
+        voxel_add(voxelRed, pos, color.r);
+        voxel_add(voxelGreen, pos, color.g);
+        voxel_add(voxelBlue, pos, color.b);
+        voxel_add(voxelDensity, pos, density);
+    }
+}
diff --git a/projects/fire_works/src/main.cpp b/projects/fire_works/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..51e29ed5b13182a20e5a069247222a48fe0c90ef
--- /dev/null
+++ b/projects/fire_works/src/main.cpp
@@ -0,0 +1,1386 @@
+
+#include <array>
+
+#include <vkcv/Buffer.hpp>
+#include <vkcv/Core.hpp>
+#include <vkcv/Image.hpp>
+#include <vkcv/Pass.hpp>
+#include <vkcv/Sampler.hpp>
+
+#include <vkcv/camera/CameraManager.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+#include <vkcv/gui/GUI.hpp>
+#include <vkcv/effects/BloomAndFlaresEffect.hpp>
+
+struct particle_t {
+	glm::vec3 position;
+	float lifetime;
+	glm::vec3 velocity;
+	float size;
+	glm::vec3 color;
+	float mass;
+	glm::vec3 pad0;
+	uint32_t eventId;
+};
+
+struct event_t {
+	glm::vec3 direction;
+	float startTime;
+	glm::vec3 color;
+	float velocity;
+	
+	uint32_t count;
+	uint32_t index;
+	uint32_t parent;
+	uint32_t continuous;
+	
+	float lifetime;
+	float mass;
+	float size;
+	uint32_t contCount;
+};
+
+struct smoke_t {
+	glm::vec3 position;
+	float size;
+	glm::vec3 velocity;
+	float scaling;
+	glm::vec3 color;
+	float eventID;
+};
+
+struct trail_t {
+	uint32_t particleIndex;
+	uint32_t startIndex;
+	uint32_t endIndex;
+	uint32_t useCount;
+	glm::vec3 color;
+	float lifetime;
+};
+
+struct point_t {
+	glm::vec3 position;
+	float size;
+	glm::vec3 velocity;
+	float scaling;
+};
+
+struct draw_particles_t {
+	glm::mat4 mvp;
+	uint32_t width;
+	uint32_t height;
+};
+
+struct draw_smoke_t {
+	glm::mat4 mvp;
+	glm::vec3 camera;
+};
+
+#define PARTICLE_COUNT (1024)
+#define SMOKE_COUNT (512)
+#define TRAIL_COUNT (2048)
+#define RANDOM_DATA_LENGTH (4096)
+#define POINT_COUNT (2048 * 256)
+
+void InitializeParticles(std::vector<particle_t> &particles) {
+	for (size_t i = 0; i < particles.size(); i++) {
+		particle_t particle;
+		particle.position = glm::vec3(2.0f * (std::rand() % RAND_MAX) / RAND_MAX - 1.0f,
+									  2.0f * (std::rand() % RAND_MAX) / RAND_MAX - 1.0f,
+									  2.0f * (std::rand() % RAND_MAX) / RAND_MAX - 1.0f);
+
+		particle.lifetime = 0.0f;
+		particle.velocity = glm::vec3(0.0f);
+		particle.size = 0.01f;
+		particle.color = glm::vec3(1.0f, 0.0f, 0.0f);
+
+		particles [i] = particle;
+	}
+}
+
+void InitializeFireworkEvents(std::vector<event_t>& events) {
+	events.push_back({
+		glm::vec3(0, 1, 0), 0.5f, glm::vec3(0.0f, 1.0f, 0.0f), 12.5f,
+		1, 0, UINT_MAX, 0,
+		1.0f, 1.0f, 0.5f, 0
+	});
+
+	events.push_back({
+		glm::vec3(0.0f), 1.5f, glm::vec3(0.0f, 1.0f, 1.0f), 10.0f,
+		100, 0, 0, 0,
+		10.0f, 1.0f, 0.0f, 0
+	});
+
+	events.push_back({
+		glm::vec3(0.5, 1, 0), 0.25f, glm::vec3(0.0f, 1.5f, 0.0f), 15.0f,
+		1, 0, UINT_MAX, 0,
+		0.5f, 1.0f, 0.5f, 0
+	});
+
+	events.push_back({
+		glm::vec3(0.0f), 0.75f, glm::vec3(0.0f, 1.5f, 1.0f), 8.0f,
+		150, 0, 2, 0,
+		10.0f, 1.0f, 0.0f, 0
+	});
+
+	events.push_back({
+		glm::vec3(-2.5, 3, 0.5), 1.0f, glm::vec3(246.0f, 189.0f, 255.0f), 12.5f,
+		1, 0, UINT_MAX, 0,
+		1.0f, 1.0f, 0.5f, 0
+	});
+
+	events.push_back({
+		glm::vec3(0.0f), 2.0f, glm::vec3(235.0f, 137.0f, 250.0f), 8.0f,
+		75, 0, 4, 0,
+		10.0f, 1.0f, 0.0f, 0
+	});
+}
+
+void InitializeSparklerEvents(std::vector<event_t> &events) {
+	events.push_back({
+		glm::vec3(0, 1, 0), 0.0f, glm::vec3(251.0f, 255.0f, 145.0f), 1.0f,
+		1, 0, UINT_MAX, 0,
+		8.0f, 0.0f, 0.5f, 0
+	});
+
+	events.push_back({
+		glm::vec3(0.0f), 0.0f, glm::vec3(251.0f, 255.0f, 145.0f), 10.0f,
+		1000, 1, 0, 10,
+		0.5f, -1.0f, 0.0f, 100
+	});
+}
+
+void InitializeNestedFireworkEvents(std::vector<event_t>& events) {
+	events.push_back({
+		glm::vec3(0, 2, 0), 0.0f, glm::vec3(0.0f, 1.0f, 0.0f), 12.5f,
+		1, 0, UINT_MAX, 0,
+		1.0f, 1.0f, 0.5f, 0
+	});
+
+	events.push_back({
+		glm::vec3(0.0f), 0.9f, glm::vec3(0.0f, 1.0f, 1.0f), 7.0f,
+		100, 0, 0, 0,
+		10.1f, 1.0f, 0.0f, 0
+	});
+
+	events.push_back({
+		glm::vec3(0.0f), 2.0f, glm::vec3(0.0f, 0.0f, 0.0f), 10.0f,
+		100, 0, 1, 0,
+		10.0f, 1.0f, 0.0f, 0
+	});
+
+	events.push_back({
+		glm::vec3(0.0f), 1.0f, glm::vec3(42.0f,0.0f, 1.0f), 12.5f,
+		100, 0, 1, 0,
+		1.0f, 1.0f, 0.5f, 0
+	});
+
+	events.push_back({
+		glm::vec3(0.0f), 1.5f, glm::vec3(42.0f, 0.0f, 1.0f), 10.0f,
+		100, 0, 3, 0,
+		10.0f, 1.0f, 0.0f, 0
+	});
+
+	events.push_back({
+		glm::vec3(0.0f), 2.0f, glm::vec3(42.0f, 0.0f, 1.0f), 10.0f,
+		100, 0, 4, 0,
+		10.0f, 1.0f, 0.0f, 0
+	});
+}
+
+void ChangeColor(std::vector<event_t>& events, glm::vec3 color) {
+	for (size_t i = 0; i < events.size(); i++) {
+		events [i].color = color;
+	}
+}
+
+int main(int argc, const char **argv) {
+	vkcv::Features features;
+	
+	features.requireExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
+	features.requireFeature([](vk::PhysicalDeviceFeatures& features) {
+		features.setGeometryShader(true);
+	});
+	
+	vkcv::Core core = vkcv::Core::create(
+		"Firework",
+		VK_MAKE_VERSION(0, 0, 1),
+		{vk::QueueFlagBits::eTransfer, vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute},
+		features
+	);
+	
+	vkcv::WindowHandle windowHandle = core.createWindow("Firework", 800, 600, true);
+	vkcv::Window& window = core.getWindow (windowHandle);
+	vkcv::camera::CameraManager cameraManager (window);
+	
+	auto trackballHandle = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+	auto pilotHandle = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+	
+	cameraManager.getCamera(trackballHandle).setCenter(glm::vec3(0.0f, 0.0f, 0.0f));   // set camera to look at the center of the particle volume
+	cameraManager.getCamera(trackballHandle).setNearFar(0.1f, 50.0f);
+	cameraManager.getCamera(trackballHandle).setPosition(glm::vec3(0, 0, 25));
+	
+	cameraManager.getCamera(pilotHandle).setNearFar(0.1f, 50.0f);
+	cameraManager.getCamera(pilotHandle).setPosition(glm::vec3(0, 0, 25));
+	
+	cameraManager.setActiveCamera(pilotHandle);
+	
+	vkcv::gui::GUI gui (core, windowHandle);
+	vkcv::shader::GLSLCompiler compiler;
+	
+	vkcv::DescriptorBindings descriptorBindings0;
+	vkcv::DescriptorBinding binding0 {
+		0,
+		vkcv::DescriptorType::STORAGE_BUFFER,
+		1,
+		vkcv::ShaderStage::VERTEX | vkcv::ShaderStage::COMPUTE,
+		false,
+		false
+	};
+	vkcv::DescriptorBinding binding1 { 
+		1,     
+		vkcv::DescriptorType::STORAGE_BUFFER,
+									   
+		1,     
+		vkcv::ShaderStage::COMPUTE,
+		false, 
+		false 
+	};
+	
+	descriptorBindings0.insert(std::make_pair(0, binding0));
+	descriptorBindings0.insert(std::make_pair(1, binding1));
+	
+	vkcv::DescriptorSetLayoutHandle descriptorSetLayout = core.createDescriptorSetLayout(descriptorBindings0);
+	vkcv::DescriptorSetHandle descriptorSet = core.createDescriptorSet(descriptorSetLayout);
+	
+	vkcv::ShaderProgram generationShader;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "shaders/generation.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		generationShader.addShader(shaderStage, path);
+	});
+	
+	auto generationBindings = generationShader.getReflectedDescriptors().at(1);
+	generationBindings[0].shaderStages |= vkcv::ShaderStage::FRAGMENT;
+	
+	vkcv::DescriptorSetLayoutHandle generationDescriptorLayout = core.createDescriptorSetLayout(
+		generationBindings
+	);
+	
+	vkcv::DescriptorSetHandle generationDescriptorSet = core.createDescriptorSet(generationDescriptorLayout);
+	
+	vkcv::DescriptorBindings descriptorBindings1;
+	
+	descriptorBindings1.insert(std::make_pair(0, binding0));
+	descriptorBindings1.insert(std::make_pair(1, binding1));
+	
+	vkcv::DescriptorSetLayoutHandle smokeDescriptorLayout = core.createDescriptorSetLayout(descriptorBindings1);
+	vkcv::DescriptorSetHandle smokeDescriptorSet = core.createDescriptorSet(smokeDescriptorLayout);
+	
+	vkcv::DescriptorBindings descriptorBindings2;
+	vkcv::DescriptorBinding binding2 {
+		1,
+		vkcv::DescriptorType::STORAGE_BUFFER,
+		1,
+		vkcv::ShaderStage::GEOMETRY | vkcv::ShaderStage::COMPUTE,
+		false,
+		false
+	};
+	
+	descriptorBindings2.insert(std::make_pair(0, binding0));
+	descriptorBindings2.insert(std::make_pair(1, binding2));
+	
+	vkcv::DescriptorSetLayoutHandle trailDescriptorLayout = core.createDescriptorSetLayout(
+		descriptorBindings2
+	);
+	
+	vkcv::DescriptorSetHandle trailDescriptorSet = core.createDescriptorSet(trailDescriptorLayout);
+	
+	vkcv::ComputePipelineHandle generationPipeline = core.createComputePipeline({
+		generationShader,
+		{
+          descriptorSetLayout,
+          generationDescriptorLayout,
+          smokeDescriptorLayout,
+          trailDescriptorLayout
+		}
+	});
+	
+	vkcv::ShaderProgram trailShader;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "shaders/trail.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		trailShader.addShader(shaderStage, path);
+	});
+	
+	vkcv::ComputePipelineHandle trailComputePipeline = core.createComputePipeline({
+		trailShader,
+		{ descriptorSetLayout, trailDescriptorLayout }
+	});
+	
+	vkcv::ShaderProgram scaleShader;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "shaders/scale.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		scaleShader.addShader(shaderStage, path);
+	});
+	
+	vkcv::ComputePipelineHandle scalePipeline = core.createComputePipeline({
+		scaleShader,
+		{ smokeDescriptorLayout }
+	});
+	
+	auto swapchainHandle = core.getWindow(windowHandle).getSwapchain();
+	auto swapchainExtent = core.getSwapchainExtent(swapchainHandle);
+	
+	const vk::Format colorFormat = vk::Format::eR16G16B16A16Sfloat;
+	
+	std::array<vkcv::ImageHandle, 4> colorBuffers;
+	for (size_t i = 0; i < colorBuffers.size(); i++) {
+		vkcv::ImageConfig colorBufferConfig (
+				swapchainExtent.width,
+				swapchainExtent.height
+		);
+		
+		colorBufferConfig.setSupportingStorage(true);
+		colorBufferConfig.setSupportingColorAttachment(true);
+		
+		colorBuffers[i] = core.createImage(
+				colorFormat,
+				colorBufferConfig
+		);
+	}
+	
+	vkcv::ShaderProgram particleShaderProgram;
+	compiler.compile(vkcv::ShaderStage::VERTEX, "shaders/particle.vert", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		particleShaderProgram.addShader(shaderStage, path);
+	});
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, "shaders/particle.frag", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		particleShaderProgram.addShader(shaderStage, path);
+	});
+	
+	vkcv::ShaderProgram trailShaderProgram;
+	compiler.compile(vkcv::ShaderStage::VERTEX, "shaders/trail.vert", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		trailShaderProgram.addShader(shaderStage, path);
+	});
+	compiler.compile(vkcv::ShaderStage::GEOMETRY, "shaders/trail.geom", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		trailShaderProgram.addShader(shaderStage, path);
+	});
+	
+	vkcv::ShaderProgram smokeShaderProgram;
+	compiler.compile(vkcv::ShaderStage::VERTEX, "shaders/smoke.vert", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		smokeShaderProgram.addShader(shaderStage, path);
+	});
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, "shaders/smoke.frag", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		smokeShaderProgram.addShader(shaderStage, path);
+		trailShaderProgram.addShader(shaderStage, path);
+	});
+	
+	std::vector<particle_t> particles;
+	particles.resize(PARTICLE_COUNT);
+	InitializeParticles(particles);
+	
+	auto particleBuffer = vkcv::buffer<particle_t>(
+			core,
+			vkcv::BufferType::STORAGE,
+			particles.size(),
+			vkcv::BufferMemoryType::DEVICE_LOCAL,
+			true
+	);
+	
+	particleBuffer.fill(particles);
+	
+	auto particleBufferCopy = vkcv::buffer<particle_t>(
+			core,
+			vkcv::BufferType::STORAGE,
+			particles.size()
+	);
+
+	particleBufferCopy.fill(particles);
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeStorageBuffer(0, particleBuffer.getHandle());
+		writes.writeStorageBuffer(1, particleBufferCopy.getHandle());
+		core.writeDescriptorSet(descriptorSet, writes);
+	}
+	
+	std::vector<float> randomData;
+	randomData.reserve(RANDOM_DATA_LENGTH);
+	
+	for (size_t i = 0; i < RANDOM_DATA_LENGTH; i++) {
+		randomData.push_back(
+			2.0f * static_cast<float>(std::rand() % RAND_MAX) / static_cast<float>(RAND_MAX) - 1.0f
+		);
+	}
+	
+	auto randomBuffer = vkcv::buffer<float>(
+			core,
+			vkcv::BufferType::STORAGE,
+			randomData.size()
+	);
+	
+	randomBuffer.fill(randomData);
+	
+	std::vector<event_t> events;
+	InitializeFireworkEvents(events);
+	
+	auto eventBuffer = vkcv::buffer<event_t>(
+			core,
+			vkcv::BufferType::STORAGE,
+			events.size()
+	);
+	
+	eventBuffer.fill(events);
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeStorageBuffer(0, randomBuffer.getHandle());
+		writes.writeStorageBuffer(1, eventBuffer.getHandle());
+		core.writeDescriptorSet(generationDescriptorSet, writes);
+	}
+
+	auto startIndexBuffer = vkcv::buffer<uint32_t>(
+			core,
+			vkcv::BufferType::STORAGE,
+			eventBuffer.getCount()
+	);
+
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeStorageBuffer(2, startIndexBuffer.getHandle());
+		core.writeDescriptorSet(generationDescriptorSet, writes);
+	}
+
+	std::vector<smoke_t> smokes;
+	smokes.reserve(SMOKE_COUNT);
+	
+	for (size_t i = 0; i < SMOKE_COUNT; i++) {
+		smoke_t smoke;
+		smoke.position = glm::vec3(0.0f);
+		smoke.size = 0.0f;
+		
+		smoke.velocity = glm::vec3(0.0f);
+		smoke.scaling = 0.0f;
+		
+		smoke.color = glm::vec3(0.0f);
+		
+		smokes.push_back(smoke);
+	}
+	
+	auto smokeBuffer = vkcv::buffer<smoke_t>(
+			core,
+			vkcv::BufferType::STORAGE,
+			smokes.size()
+	);
+	
+	smokeBuffer.fill(smokes);
+	
+	auto smokeIndexBuffer = vkcv::buffer<uint32_t>(
+			core,
+			vkcv::BufferType::STORAGE,
+			3,
+			vkcv::BufferMemoryType::HOST_VISIBLE
+	);
+	
+	uint32_t* smokeIndices = smokeIndexBuffer.map();
+	memset(smokeIndices, 0, smokeIndexBuffer.getSize());
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeStorageBuffer(0, smokeBuffer.getHandle());
+		writes.writeStorageBuffer(1, smokeIndexBuffer.getHandle());
+		core.writeDescriptorSet(smokeDescriptorSet, writes);
+	}
+	
+	std::vector<trail_t> trails;
+	trails.reserve(TRAIL_COUNT);
+	
+	for (size_t i = 0; i < TRAIL_COUNT; i++) {
+		trail_t trail;
+		
+		trail.particleIndex = 0;
+		trail.startIndex = 0;
+		trail.endIndex = 0;
+		trail.useCount = 0;
+		trail.color = glm::vec3(0.0f);
+		trail.lifetime = 0.0f;
+		
+		trails.push_back(trail);
+	}
+	
+	auto trailBuffer = vkcv::buffer<trail_t>(
+			core,
+			vkcv::BufferType::STORAGE,
+			trails.size()
+	);
+	
+	trailBuffer.fill(trails);
+	
+	std::vector<point_t> points;
+	points.reserve(POINT_COUNT);
+	
+	for (size_t i = 0; i < POINT_COUNT; i++) {
+		point_t point;
+		
+		point.position = glm::vec3(0.0f);
+		point.size = 0.0f;
+		point.velocity = glm::vec3(0.0f);
+		point.scaling = 0.0f;
+		
+		points.push_back(point);
+	}
+	
+	auto pointBuffer = vkcv::buffer<point_t>(
+			core,
+			vkcv::BufferType::STORAGE,
+			points.size()
+	);
+	
+	pointBuffer.fill(points);
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeStorageBuffer(0, trailBuffer.getHandle());
+		writes.writeStorageBuffer(1, pointBuffer.getHandle());
+		core.writeDescriptorSet(trailDescriptorSet, writes);
+	}
+	
+	auto cubePositions = vkcv::buffer<glm::vec3>(
+			core,
+			vkcv::BufferType::VERTEX,
+			8
+	);
+	
+	cubePositions.fill({
+		glm::vec3(-1.0f, -1.0f, -1.0f),
+		glm::vec3(+1.0f, -1.0f, -1.0f),
+		glm::vec3(-1.0f, +1.0f, -1.0f),
+		glm::vec3(+1.0f, +1.0f, -1.0f),
+		glm::vec3(-1.0f, -1.0f, +1.0f),
+		glm::vec3(+1.0f, -1.0f, +1.0f),
+		glm::vec3(-1.0f, +1.0f, +1.0f),
+		glm::vec3(+1.0f, +1.0f, +1.0f)
+	});
+	
+	auto cubeIndices = vkcv::buffer<uint16_t>(
+			core,
+			vkcv::BufferType::INDEX,
+			36
+	);
+	
+	cubeIndices.fill({
+		0, 2, 3,
+		0, 3, 1,
+		1, 3, 7,
+		1, 7, 5,
+		
+		5, 7, 6,
+		5, 6, 4,
+		4, 6, 2,
+		4, 2, 0,
+		
+		2, 6, 7,
+		2, 7, 3,
+		1, 5, 4,
+		1, 4, 0
+	});
+	
+	vkcv::VertexData cubeData ({ vkcv::vertexBufferBinding(cubePositions.getHandle()) });
+	cubeData.setIndexBuffer(cubeIndices.getHandle());
+	cubeData.setCount(cubeIndices.getCount());
+	
+	const std::vector<vkcv::VertexAttachment> vaSmoke = smokeShaderProgram.getVertexAttachments();
+	
+	std::vector<vkcv::VertexBinding> vbSmoke;
+	for (size_t i = 0; i < vaSmoke.size(); i++) {
+		vbSmoke.push_back(vkcv::createVertexBinding(i, { vaSmoke[i] }));
+	}
+	
+	const vkcv::VertexLayout smokeLayout { vbSmoke };
+	
+	vkcv::PassHandle renderPass = vkcv::passFormat(core, colorFormat);
+	
+	vkcv::GraphicsPipelineConfig smokePipelineDefinition (
+		smokeShaderProgram,
+		renderPass,
+		{smokeLayout},
+		{smokeDescriptorLayout, generationDescriptorLayout}
+	);
+	
+	smokePipelineDefinition.setBlendMode(vkcv::BlendMode::Additive);
+	
+	vkcv::GraphicsPipelineHandle smokePipeline = core.createGraphicsPipeline(smokePipelineDefinition);
+	
+	const std::vector<vkcv::VertexAttachment> vaTrail = trailShaderProgram.getVertexAttachments();
+	
+	std::vector<vkcv::VertexBinding> vbTrail;
+	for (size_t i = 0; i < vaTrail.size(); i++) {
+		vbTrail.push_back(vkcv::createVertexBinding(i, { vaTrail[i] }));
+	}
+	
+	const vkcv::VertexLayout trailLayout { vbTrail };
+	
+	vkcv::GraphicsPipelineConfig trailPipelineDefinition (
+		trailShaderProgram,
+		renderPass,
+		{trailLayout},
+		{trailDescriptorLayout, generationDescriptorLayout, descriptorSetLayout}
+	);
+	
+	trailPipelineDefinition.setPrimitiveTopology(vkcv::PrimitiveTopology::PointList);
+	trailPipelineDefinition.setBlendMode(vkcv::BlendMode::Additive);
+	
+	vkcv::GraphicsPipelineHandle trailPipeline = core.createGraphicsPipeline(trailPipelineDefinition);
+	
+	vkcv::InstanceDrawcall drawcallSmoke (cubeData, smokeBuffer.getCount());
+	drawcallSmoke.useDescriptorSet(0, smokeDescriptorSet);
+	drawcallSmoke.useDescriptorSet(1, generationDescriptorSet);
+	
+	auto trianglePositions = vkcv::buffer<glm::vec2>(
+			core,
+			vkcv::BufferType::VERTEX,
+			3
+	);
+	
+	trianglePositions.fill({
+		glm::vec2(-1.0f, -1.0f),
+		glm::vec2(+0.0f, +1.5f),
+		glm::vec2(+1.0f, -1.0f)
+	});
+	
+	auto triangleIndices = vkcv::buffer<uint16_t>(
+			core,
+			vkcv::BufferType::INDEX,
+			3
+	);
+	
+	triangleIndices.fill({
+		0, 1, 2
+	});
+	
+	vkcv::VertexData triangleData ({ vkcv::vertexBufferBinding(trianglePositions.getHandle()) });
+	triangleData.setIndexBuffer(triangleIndices.getHandle());
+	triangleData.setCount(triangleIndices.getCount());
+	
+	vkcv::VertexData trailData;
+	triangleData.setIndexBuffer(triangleIndices.getHandle());
+	trailData.setCount(1);
+	
+	vkcv::InstanceDrawcall drawcallTrail (trailData, trailBuffer.getCount());
+	drawcallTrail.useDescriptorSet(0, trailDescriptorSet);
+	drawcallTrail.useDescriptorSet(1, generationDescriptorSet);
+	drawcallTrail.useDescriptorSet(2, descriptorSet);
+	
+	const std::vector<vkcv::VertexAttachment> vaParticles = particleShaderProgram.getVertexAttachments();
+	
+	std::vector<vkcv::VertexBinding> vbParticles;
+	for (size_t i = 0; i < vaParticles.size(); i++) {
+		vbParticles.push_back(vkcv::createVertexBinding(i, { vaParticles[i] }));
+	}
+
+	const vkcv::VertexLayout particleLayout { vbParticles };
+
+	vkcv::GraphicsPipelineConfig particlePipelineDefinition (
+		particleShaderProgram,
+		renderPass,
+		{particleLayout},
+		{descriptorSetLayout}
+	);
+	
+	particlePipelineDefinition.setBlendMode(vkcv::BlendMode::Additive);
+	
+	vkcv::GraphicsPipelineHandle particlePipeline = core.createGraphicsPipeline(particlePipelineDefinition);
+	
+	vkcv::InstanceDrawcall drawcallParticle (triangleData, particleBuffer.getCount());
+	drawcallParticle.useDescriptorSet(0, descriptorSet);
+	
+	vkcv::ShaderProgram motionShader;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "shaders/motion.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		motionShader.addShader(shaderStage, path);
+	});
+	
+	vkcv::ComputePipelineHandle motionPipeline = core.createComputePipeline({
+		motionShader,
+		{ descriptorSetLayout }
+	});
+	
+	const uint32_t voxelWidth = 160;
+	const uint32_t voxelHeight = 90;
+	const uint32_t voxelDepth = 64;
+	
+	std::vector<uint32_t> zeroVoxel;
+	zeroVoxel.resize(voxelWidth * voxelHeight * voxelDepth, 0);
+	
+	vkcv::ImageConfig voxelImageConfig (
+			voxelWidth,
+			voxelHeight,
+			voxelDepth
+	);
+	
+	voxelImageConfig.setSupportingStorage(true);
+	
+	vkcv::Image voxelRed = vkcv::image(
+			core,
+			vk::Format::eR32Uint,
+			voxelImageConfig
+	);
+	
+	vkcv::Image voxelGreen = vkcv::image(
+			core,
+			vk::Format::eR32Uint,
+			voxelImageConfig
+	);
+	
+	vkcv::Image voxelBlue = vkcv::image(
+			core,
+			vk::Format::eR32Uint,
+			voxelImageConfig
+	);
+	
+	vkcv::Image voxelDensity = vkcv::image(
+			core,
+			vk::Format::eR32Uint,
+			voxelImageConfig
+	);
+	
+	std::array<vkcv::ImageHandle, 2> voxelData {
+		core.createImage(
+			vk::Format::eR16G16B16A16Sfloat,
+			voxelImageConfig
+		),
+		core.createImage(
+			vk::Format::eR16G16B16A16Sfloat,
+			voxelImageConfig
+		)
+	};
+	
+	vkcv::ImageConfig voxelSamplesConfig (
+			voxelWidth,
+			voxelHeight
+	);
+	
+	voxelSamplesConfig.setSupportingStorage(true);
+	
+	vkcv::Image voxelSamples = vkcv::image(
+			core,
+			colorFormat,
+			voxelSamplesConfig
+   	);
+	
+	vkcv::SamplerHandle voxelSampler = vkcv::samplerLinear(core, true);
+	
+	vkcv::ShaderProgram voxelClearShader;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "shaders/clear.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		voxelClearShader.addShader(shaderStage, path);
+	});
+	
+	const auto& voxelBindings = voxelClearShader.getReflectedDescriptors().at(0);
+	auto voxelDescriptorSetLayout = core.createDescriptorSetLayout(voxelBindings);
+	
+	vkcv::ComputePipelineHandle voxelClearPipeline = core.createComputePipeline({
+		voxelClearShader,
+		{ voxelDescriptorSetLayout }
+	});
+	
+	vkcv::ShaderProgram voxelParticleShader;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "shaders/voxel_particle.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		voxelParticleShader.addShader(shaderStage, path);
+	});
+	
+	vkcv::ComputePipelineHandle voxelParticlePipeline = core.createComputePipeline({
+		voxelParticleShader,
+		{ descriptorSetLayout, voxelDescriptorSetLayout }
+	});
+	
+	vkcv::ShaderProgram voxelSmokeShader;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "shaders/voxel_smoke.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		voxelSmokeShader.addShader(shaderStage, path);
+	});
+	
+	vkcv::ComputePipelineHandle voxelSmokePipeline = core.createComputePipeline({
+		voxelSmokeShader,
+		{ smokeDescriptorLayout, voxelDescriptorSetLayout }
+	});
+	
+	vkcv::ShaderProgram voxelTrailShader;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "shaders/voxel_trail.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		voxelTrailShader.addShader(shaderStage, path);
+	});
+	
+	vkcv::ComputePipelineHandle voxelTrailPipeline = core.createComputePipeline({
+		voxelTrailShader,
+		{ trailDescriptorLayout, voxelDescriptorSetLayout }
+	});
+	
+	vkcv::ShaderProgram voxelShader;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "shaders/voxel.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		voxelShader.addShader(shaderStage, path);
+	});
+	
+	const auto& voxelOutBindings = voxelShader.getReflectedDescriptors().at(1);
+	auto voxelOutDescriptorSetLayout = core.createDescriptorSetLayout(voxelOutBindings);
+	
+	vkcv::ComputePipelineHandle voxelPipeline = core.createComputePipeline({
+		voxelShader,
+		{ voxelDescriptorSetLayout, voxelOutDescriptorSetLayout }
+	});
+	
+	vkcv::ShaderProgram fluidShader;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "shaders/fluid.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		fluidShader.addShader(shaderStage, path);
+	});
+	
+	const auto& fluidBindings = fluidShader.getReflectedDescriptors().at(0);
+	auto fluidDescriptorSetLayout = core.createDescriptorSetLayout(fluidBindings);
+	
+	vkcv::ComputePipelineHandle fluidPipeline = core.createComputePipeline({
+		fluidShader,
+		{ fluidDescriptorSetLayout }
+	});
+	
+	vkcv::ShaderProgram voxelSampleShader;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "shaders/sample.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		voxelSampleShader.addShader(shaderStage, path);
+	});
+	
+	const auto& sampleBindings = voxelSampleShader.getReflectedDescriptors().at(1);
+	auto samplesDescriptorSetLayout = core.createDescriptorSetLayout(sampleBindings);
+	
+	vkcv::ComputePipelineHandle voxelSamplePipeline = core.createComputePipeline({
+		voxelSampleShader,
+		{ voxelOutDescriptorSetLayout, samplesDescriptorSetLayout }
+	});
+	
+	auto voxelDescriptorSet = core.createDescriptorSet(voxelDescriptorSetLayout);
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeStorageImage(0, voxelRed.getHandle());
+		writes.writeStorageImage(1, voxelGreen.getHandle());
+		writes.writeStorageImage(2, voxelBlue.getHandle());
+		writes.writeStorageImage(3, voxelDensity.getHandle());
+		core.writeDescriptorSet(voxelDescriptorSet, writes);
+	}
+	
+	auto voxelOutDescriptorSet = core.createDescriptorSet(voxelOutDescriptorSetLayout);
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeStorageImage(0, voxelData[0]);
+		core.writeDescriptorSet(voxelOutDescriptorSet, writes);
+	}
+	
+	std::array<vkcv::DescriptorSetHandle, 2> fluidDescriptorSet {
+		core.createDescriptorSet(fluidDescriptorSetLayout),
+		core.createDescriptorSet(fluidDescriptorSetLayout)
+	};
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeSampledImage(0, voxelData[0]);
+		writes.writeSampler(1, voxelSampler);
+		writes.writeStorageImage(2, voxelData[1]);
+		core.writeDescriptorSet(fluidDescriptorSet[0], writes);
+	}
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeSampledImage(0, voxelData[1]);
+		writes.writeSampler(1, voxelSampler);
+		writes.writeStorageImage(2, voxelData[0]);
+		core.writeDescriptorSet(fluidDescriptorSet[1], writes);
+	}
+	
+	auto samplesDescriptorSet = core.createDescriptorSet(samplesDescriptorSetLayout);
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeStorageImage(0, voxelSamples.getHandle());
+		core.writeDescriptorSet(samplesDescriptorSet, writes);
+	}
+	
+	vkcv::effects::BloomAndFlaresEffect bloomAndFlares (core);
+	bloomAndFlares.setUpsamplingLimit(3);
+	
+	vkcv::ShaderProgram addShader;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "shaders/add.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		addShader.addShader(shaderStage, path);
+	});
+	
+	vkcv::DescriptorSetLayoutHandle addDescriptorLayout = core.createDescriptorSetLayout(addShader.getReflectedDescriptors().at(0));
+	vkcv::DescriptorSetHandle addDescriptor = core.createDescriptorSet(addDescriptorLayout);
+	
+	vkcv::ComputePipelineHandle addPipe = core.createComputePipeline({
+		addShader,
+		{ addDescriptorLayout, generationDescriptorLayout }
+	});
+	
+	vkcv::ShaderProgram tonemappingShader;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "shaders/tonemapping.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		tonemappingShader.addShader(shaderStage, path);
+	});
+	
+	vkcv::DescriptorSetLayoutHandle tonemappingDescriptorLayout = core.createDescriptorSetLayout(tonemappingShader.getReflectedDescriptors().at(0));
+	vkcv::DescriptorSetHandle tonemappingDescriptor = core.createDescriptorSet(tonemappingDescriptorLayout);
+	vkcv::ComputePipelineHandle tonemappingPipe = core.createComputePipeline({
+		tonemappingShader,
+		{ tonemappingDescriptorLayout }
+	});
+	
+	vkcv::ImageHandle swapchainImage = vkcv::ImageHandle::createSwapchainImageHandle();
+	
+	auto start = std::chrono::system_clock::now();
+	auto current = start;
+	
+	while (vkcv::Window::hasOpenWindow()) {
+		vkcv::Window::pollEvents();
+		
+		uint32_t swapchainWidth, swapchainHeight;
+		if (!core.beginFrame(swapchainWidth, swapchainHeight, windowHandle)) {
+			continue;
+		}
+	
+		for (auto& colorBuffer : colorBuffers) {
+			if ((core.getImageWidth(colorBuffer) != swapchainWidth) ||
+				(core.getImageHeight(colorBuffer) != swapchainHeight)) {
+				vkcv::ImageConfig colorBufferConfig (
+						swapchainWidth,
+						swapchainHeight
+				);
+				
+				colorBufferConfig.setSupportingStorage(true);
+				colorBufferConfig.setSupportingColorAttachment(true);
+				
+				colorBuffer = core.createImage(
+					colorFormat,
+					colorBufferConfig
+				);
+			}
+		}
+		
+		auto next = std::chrono::system_clock::now();
+		
+		auto time = std::chrono::duration_cast<std::chrono::microseconds>(next - start);
+		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(next - current);
+		
+		current = next;
+		
+		float time_values [2];
+		time_values[0] = 0.000001f * static_cast<float>(time.count());
+		time_values[1] = 0.000001f * static_cast<float>(deltatime.count());
+		
+		std::cout << time_values[0] << " " << time_values[1] << std::endl;
+		
+		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+		
+		auto voxelDispatchCount = vkcv::dispatchInvocations(
+				vkcv::DispatchSize(voxelWidth, voxelHeight, voxelDepth),
+				vkcv::DispatchSize(4, 4, 4)
+		);
+		
+		core.recordBeginDebugLabel(cmdStream, "Voxel clear", { 0.5f, 0.25f, 0.8f, 1.0f });
+		core.prepareImageForStorage(cmdStream, voxelRed.getHandle());
+		core.prepareImageForStorage(cmdStream, voxelGreen.getHandle());
+		core.prepareImageForStorage(cmdStream, voxelBlue.getHandle());
+		core.prepareImageForStorage(cmdStream, voxelDensity.getHandle());
+		
+		core.recordComputeDispatchToCmdStream(
+			cmdStream,
+			voxelClearPipeline,
+			voxelDispatchCount,
+			{ vkcv::useDescriptorSet(0, voxelDescriptorSet) },
+			vkcv::PushConstants(0)
+		);
+		core.recordEndDebugLabel(cmdStream);
+		
+		core.recordBufferMemoryBarrier(cmdStream, eventBuffer.getHandle());
+		core.recordBufferMemoryBarrier(cmdStream, smokeBuffer.getHandle());
+		core.recordBufferMemoryBarrier(cmdStream, smokeIndexBuffer.getHandle());
+		core.recordBufferMemoryBarrier(cmdStream, particleBuffer.getHandle());
+		core.recordBufferMemoryBarrier(cmdStream, trailBuffer.getHandle());
+		core.recordBufferMemoryBarrier(cmdStream, pointBuffer.getHandle());
+		
+		auto particleDispatchCount = vkcv::dispatchInvocations(particleBuffer.getCount(), 256);
+		
+		vkcv::PushConstants pushConstantsTime (2 * sizeof(float));
+		pushConstantsTime.appendDrawcall(time_values);
+		
+		core.recordBeginDebugLabel(cmdStream, "Generation", { 0.0f, 0.0f, 1.0f, 1.0f });
+		core.recordComputeDispatchToCmdStream(
+			cmdStream,
+			generationPipeline,
+			particleDispatchCount,
+			{
+				vkcv::useDescriptorSet(0, descriptorSet),
+				vkcv::useDescriptorSet(1, generationDescriptorSet),
+				vkcv::useDescriptorSet(2, smokeDescriptorSet),
+				vkcv::useDescriptorSet(3, trailDescriptorSet)
+			},
+			pushConstantsTime
+		);
+		core.recordEndDebugLabel(cmdStream);
+		
+		core.recordBufferMemoryBarrier(cmdStream, smokeBuffer.getHandle());
+		
+		auto smokeDispatchCount = vkcv::dispatchInvocations(smokeBuffer.getCount(), 256);
+		
+		core.recordBeginDebugLabel(cmdStream, "Smoke scaling", { 0.0f, 0.0f, 1.0f, 1.0f });
+		core.recordComputeDispatchToCmdStream(
+			cmdStream,
+			scalePipeline,
+			smokeDispatchCount,
+			{ vkcv::useDescriptorSet(0, smokeDescriptorSet) },
+			pushConstantsTime
+		);
+		core.recordEndDebugLabel(cmdStream);
+		
+		core.recordBufferMemoryBarrier(cmdStream, particleBuffer.getHandle());
+		
+		core.recordBeginDebugLabel(cmdStream, "Particle motion", { 0.0f, 0.0f, 1.0f, 1.0f });
+		core.recordComputeDispatchToCmdStream(
+			cmdStream,
+			motionPipeline,
+			particleDispatchCount,
+			{ vkcv::useDescriptorSet(0, descriptorSet) },
+			pushConstantsTime
+		);
+		core.recordEndDebugLabel(cmdStream);
+		
+		core.recordBufferMemoryBarrier(cmdStream, particleBuffer.getHandle());
+		core.recordBufferMemoryBarrier(cmdStream, trailBuffer.getHandle());
+		core.recordBufferMemoryBarrier(cmdStream, pointBuffer.getHandle());
+		
+		auto trailDispatchCount = vkcv::dispatchInvocations(trailBuffer.getCount(), 256);
+		
+		core.recordBeginDebugLabel(cmdStream, "Trail update", { 0.0f, 0.0f, 1.0f, 1.0f });
+		core.recordComputeDispatchToCmdStream(
+			cmdStream,
+			trailComputePipeline,
+			trailDispatchCount,
+			{
+				vkcv::useDescriptorSet(0, descriptorSet),
+				vkcv::useDescriptorSet(1, trailDescriptorSet)
+			},
+			pushConstantsTime
+		);
+		core.recordEndDebugLabel(cmdStream);
+		
+		cameraManager.update(time_values[1]);
+		
+		const auto& camera = cameraManager.getActiveCamera();
+		
+		core.recordBufferMemoryBarrier(cmdStream, particleBuffer.getHandle());
+		
+		draw_particles_t draw_particles {
+			camera.getMVP(),
+			swapchainWidth,
+			swapchainHeight
+		};
+		
+		vkcv::PushConstants pushConstantsDraw0 (sizeof(draw_particles_t));
+		pushConstantsDraw0.appendDrawcall(draw_particles);
+		
+		core.recordBeginDebugLabel(cmdStream, "Draw particles", { 1.0f, 0.0f, 1.0f, 1.0f });
+		core.recordDrawcallsToCmdStream(
+			cmdStream,
+			particlePipeline,
+			pushConstantsDraw0,
+			{ drawcallParticle },
+			{ colorBuffers[0] },
+			windowHandle
+		);
+		core.recordEndDebugLabel(cmdStream);
+		
+		vkcv::PushConstants pushConstantsVoxel (sizeof(glm::mat4));
+		pushConstantsVoxel.appendDrawcall(camera.getMVP());
+		
+		core.recordBeginDebugLabel(cmdStream, "Particle voxel update", { 1.0f, 0.5f, 0.8f, 1.0f });
+		core.prepareImageForStorage(cmdStream, voxelRed.getHandle());
+		core.prepareImageForStorage(cmdStream, voxelGreen.getHandle());
+		core.prepareImageForStorage(cmdStream, voxelBlue.getHandle());
+		core.prepareImageForStorage(cmdStream, voxelDensity.getHandle());
+		
+		core.recordComputeDispatchToCmdStream(
+			cmdStream,
+			voxelParticlePipeline,
+			particleDispatchCount,
+			{
+				vkcv::useDescriptorSet(0, descriptorSet),
+				vkcv::useDescriptorSet(1, voxelDescriptorSet)
+			},
+			pushConstantsVoxel
+		);
+		core.recordEndDebugLabel(cmdStream);
+		
+		core.recordBufferMemoryBarrier(cmdStream, smokeBuffer.getHandle());
+		
+		draw_smoke_t draw_smoke {
+			camera.getMVP(),
+			camera.getPosition()
+		};
+		
+		core.recordBeginDebugLabel(cmdStream, "Draw smoke", { 1.0f, 0.5f, 1.0f, 1.0f });
+		vkcv::PushConstants pushConstantsDraw1 (sizeof(draw_smoke_t));
+		pushConstantsDraw1.appendDrawcall(draw_smoke);
+		
+		core.recordDrawcallsToCmdStream(
+			cmdStream,
+			smokePipeline,
+			pushConstantsDraw1,
+			{ drawcallSmoke },
+			{ colorBuffers[1] },
+			windowHandle
+		);
+		core.recordEndDebugLabel(cmdStream);
+		
+		core.recordBeginDebugLabel(cmdStream, "Smoke voxel update", { 1.0f, 0.7f, 0.8f, 1.0f });
+		core.prepareImageForStorage(cmdStream, voxelRed.getHandle());
+		core.prepareImageForStorage(cmdStream, voxelGreen.getHandle());
+		core.prepareImageForStorage(cmdStream, voxelBlue.getHandle());
+		core.prepareImageForStorage(cmdStream, voxelDensity.getHandle());
+		
+		core.recordComputeDispatchToCmdStream(
+			cmdStream,
+			voxelSmokePipeline,
+			smokeDispatchCount,
+			{
+				vkcv::useDescriptorSet(0, smokeDescriptorSet),
+				vkcv::useDescriptorSet(1, voxelDescriptorSet)
+			},
+			pushConstantsVoxel
+		);
+		core.recordEndDebugLabel(cmdStream);
+		
+		core.recordBufferMemoryBarrier(cmdStream, trailBuffer.getHandle());
+		core.recordBufferMemoryBarrier(cmdStream, pointBuffer.getHandle());
+		
+		core.recordBeginDebugLabel(cmdStream, "Draw trails", { 0.75f, 0.5f, 1.0f, 1.0f });
+		core.recordDrawcallsToCmdStream(
+			cmdStream,
+			trailPipeline,
+			pushConstantsDraw1,
+			{ drawcallTrail },
+			{ colorBuffers[2] },
+			windowHandle
+		);
+		core.recordEndDebugLabel(cmdStream);
+		
+		core.recordBeginDebugLabel(cmdStream, "Trail voxel update", { 1.0f, 0.9f, 0.8f, 1.0f });
+		core.prepareImageForStorage(cmdStream, voxelRed.getHandle());
+		core.prepareImageForStorage(cmdStream, voxelGreen.getHandle());
+		core.prepareImageForStorage(cmdStream, voxelBlue.getHandle());
+		core.prepareImageForStorage(cmdStream, voxelDensity.getHandle());
+		
+		core.recordComputeDispatchToCmdStream(
+			cmdStream,
+			voxelTrailPipeline,
+			trailDispatchCount,
+			{
+				vkcv::useDescriptorSet(0, trailDescriptorSet),
+				vkcv::useDescriptorSet(1, voxelDescriptorSet)
+			},
+			pushConstantsVoxel
+		);
+		core.recordEndDebugLabel(cmdStream);
+		
+		core.recordBeginDebugLabel(cmdStream, "Combine voxel data", { 0.5f, 0.5f, 0.5f, 1.0f });
+		
+		core.prepareImageForStorage(cmdStream, voxelRed.getHandle());
+		core.prepareImageForStorage(cmdStream, voxelGreen.getHandle());
+		core.prepareImageForStorage(cmdStream, voxelBlue.getHandle());
+		core.prepareImageForStorage(cmdStream, voxelDensity.getHandle());
+		
+		core.prepareImageForStorage(cmdStream, voxelData[0]);
+		
+		core.recordComputeDispatchToCmdStream(
+			cmdStream,
+			voxelPipeline,
+			voxelDispatchCount,
+			{
+				vkcv::useDescriptorSet(0, voxelDescriptorSet),
+				vkcv::useDescriptorSet(1, voxelOutDescriptorSet)
+			},
+			vkcv::PushConstants(0)
+		);
+		
+		core.recordEndDebugLabel(cmdStream);
+		
+		core.recordBeginDebugLabel(cmdStream, "Fluid voxel data", { 0.2f, 0.2f, 0.9f, 1.0f });
+		
+		for (size_t i = 0; i < 8; i++) {
+			core.prepareImageForSampling(cmdStream, voxelData[i % 2]);
+			core.prepareImageForStorage(cmdStream, voxelData[(i + 1) % 2]);
+		
+			core.recordComputeDispatchToCmdStream(
+				cmdStream,
+				fluidPipeline,
+				voxelDispatchCount,
+				{ vkcv::useDescriptorSet(0, fluidDescriptorSet[i % 2]) },
+				vkcv::PushConstants(0)
+			);
+		}
+		
+		core.recordEndDebugLabel(cmdStream);
+		
+		core.recordBeginDebugLabel(cmdStream, "Sample voxels", { 0.5f, 0.5f, 1.0f, 1.0f });
+		
+		core.prepareImageForStorage(cmdStream, voxelData[0]);
+		core.prepareImageForStorage(cmdStream, voxelSamples.getHandle());
+		
+		auto sampleDispatchCount = vkcv::dispatchInvocations(
+				vkcv::DispatchSize(voxelWidth, voxelHeight),
+				vkcv::DispatchSize(8, 8)
+		);
+		
+		core.recordComputeDispatchToCmdStream(
+			cmdStream,
+			voxelSamplePipeline,
+			sampleDispatchCount,
+			{
+				vkcv::useDescriptorSet(0, voxelOutDescriptorSet),
+				vkcv::useDescriptorSet(1, samplesDescriptorSet)
+			},
+			vkcv::PushConstants(0)
+		);
+		
+		core.recordEndDebugLabel(cmdStream);
+		
+		core.recordBeginDebugLabel(cmdStream, "Add rendered images", { 0.5f, 0.5f, 1.0f, 1.0f });
+		
+		vkcv::DescriptorWrites addDescriptorWrites;
+		addDescriptorWrites.writeSampledImage(0, voxelSamples.getHandle());
+		addDescriptorWrites.writeSampler(1, voxelSampler);
+		
+		for (size_t i = 0; i < colorBuffers.size(); i++) {
+			addDescriptorWrites.writeStorageImage(2 + i, colorBuffers[i]);
+			core.prepareImageForStorage(cmdStream, colorBuffers[i]);
+		}
+		
+		core.writeDescriptorSet(addDescriptor, addDescriptorWrites);
+		core.prepareImageForSampling(cmdStream, voxelSamples.getHandle());
+		
+		auto colorDispatchCount = vkcv::dispatchInvocations(
+				vkcv::DispatchSize(swapchainWidth, swapchainHeight),
+				vkcv::DispatchSize(8, 8)
+		);
+		
+		core.recordComputeDispatchToCmdStream(
+			cmdStream,
+			addPipe,
+			colorDispatchCount,
+			{
+				vkcv::useDescriptorSet(0, addDescriptor),
+				vkcv::useDescriptorSet(1, generationDescriptorSet)
+			},
+			vkcv::PushConstants(0)
+		);
+		
+		core.recordEndDebugLabel(cmdStream);
+		
+		bloomAndFlares.recordEffect(cmdStream, colorBuffers.back(), colorBuffers.back());
+		
+		core.recordBeginDebugLabel(cmdStream, "Tonemapping", { 0.0f, 1.0f, 0.0f, 1.0f });
+		core.prepareImageForStorage(cmdStream, colorBuffers.back());
+		core.prepareImageForStorage(cmdStream, swapchainImage);
+		
+		vkcv::DescriptorWrites tonemappingDescriptorWrites;
+		tonemappingDescriptorWrites.writeStorageImage(
+			0, colorBuffers.back()
+		).writeStorageImage(
+			1, swapchainImage
+		);
+		
+		core.writeDescriptorSet(tonemappingDescriptor, tonemappingDescriptorWrites);
+		
+		core.recordComputeDispatchToCmdStream(
+			cmdStream,
+			tonemappingPipe,
+			colorDispatchCount,
+			{ vkcv::useDescriptorSet(0, tonemappingDescriptor) },
+			vkcv::PushConstants(0)
+		);
+		
+		core.recordEndDebugLabel(cmdStream);
+		
+		core.prepareSwapchainImageForPresent(cmdStream);
+		core.submitCommandStream(cmdStream);
+		
+		gui.beginGUI();
+		ImGui::Begin("Settings");
+		
+		bool firework, sparkler, nested;
+		if (ImGui::BeginListBox(" ")) {
+			firework = ImGui::Selectable("Firework");
+			sparkler = ImGui::Selectable("Sparkler");
+			nested = ImGui::Selectable("Nested Firework");
+			
+			ImGui::EndListBox();
+		}
+		
+		bool resetTime = ImGui::Button("Reset");
+		auto color = glm::vec3(0.0f);
+		
+		if (!events.empty()) {
+			color = events[0].color;
+		}
+		
+		bool colorChanged = ImGui::ColorPicker3("Color", (float*) & color);
+		
+		ImGui::End();
+		gui.endGUI();
+		
+		core.endFrame(windowHandle);
+
+		particleBuffer.read(particles);
+		sort(particles.begin(), particles.end(),
+			 [](const particle_t p1, const particle_t p2) {
+				 return p1.eventId < p2.eventId;
+			 });
+
+		std::vector<uint32_t> startingIndex;
+		startingIndex.resize(events.size());
+		uint32_t eventIdCheck = std::numeric_limits<uint32_t>::max();
+		
+		for (size_t i = 0; i < particles.size(); i++) {
+			if (particles[i].eventId != eventIdCheck) {
+				eventIdCheck = particles [i].eventId;
+				if (eventIdCheck < startingIndex.size()) {
+					startingIndex [eventIdCheck] = i;
+				}
+			}
+		}
+
+		startIndexBuffer.fill(startingIndex);
+
+		if (firework) {
+			events.clear();
+			InitializeFireworkEvents(events);
+			resetTime = true;
+		} else if (sparkler) {
+			events.clear();
+			InitializeSparklerEvents(events);
+			resetTime = true;
+		} else if (nested) {
+			events.clear();
+			InitializeNestedFireworkEvents(events);
+			resetTime = true;
+		}
+
+		if (colorChanged) {
+			ChangeColor(events, color);
+			resetTime = true;
+		}
+		
+		if (resetTime) {
+			start = std::chrono::system_clock::now();	
+			InitializeParticles(particles);
+			particleBuffer.fill(particles);
+			eventBuffer.fill(events);
+			smokeBuffer.fill(smokes);
+			trailBuffer.fill(trails);
+			pointBuffer.fill(points);
+			
+			memset(smokeIndices, 0, smokeIndexBuffer.getSize());
+		}
+
+		particleBufferCopy.fill(particles);
+	}
+	
+	smokeIndexBuffer.unmap();
+	return 0;
+}
diff --git a/projects/first_mesh/CMakeLists.txt b/projects/first_mesh/CMakeLists.txt
index eb0f028db38707272f9fbcf61662633f2868eedc..1846c5276210ed6541bdaa9833f072ccc9836b8d 100644
--- a/projects/first_mesh/CMakeLists.txt
+++ b/projects/first_mesh/CMakeLists.txt
@@ -2,27 +2,28 @@ cmake_minimum_required(VERSION 3.16)
 project(first_mesh)
 
 # setting c++ standard for the project
-set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD 20)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
-# this should fix the execution path to load local files from the project
-set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
-
 # adding source files to the project
-add_executable(first_mesh src/main.cpp)
-
-# this should fix the execution path to load local files from the project (for MSVC)
-if(MSVC)
-	set_target_properties(first_mesh PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-	set_target_properties(first_mesh PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-    
-    # in addition to setting the output directory, the working directory has to be set
-	# by default visual studio sets the working directory to the build directory, when using the debugger
-	set_target_properties(first_mesh PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-endif()
+add_project(first_mesh src/main.cpp)
 
 # including headers of dependencies and the VkCV framework
-target_include_directories(first_mesh SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include})
+target_include_directories(first_mesh SYSTEM BEFORE PRIVATE
+		${vkcv_include}
+		${vkcv_includes}
+		${vkcv_asset_loader_include}
+		${vkcv_geometry_include}
+		${vkcv_camera_include}
+		${vkcv_shader_compiler_include}
+)
 
 # linking with libraries from all dependencies and the VkCV framework
-target_link_libraries(first_mesh vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera)
+target_link_libraries(first_mesh
+		vkcv
+		${vkcv_libraries}
+		vkcv_asset_loader
+		vkcv_geometry
+		vkcv_camera
+		vkcv_shader_compiler
+)
diff --git a/projects/first_mesh/README.md b/projects/first_mesh/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..5aec0c2931d7f960df1ff8ea157f12b0c549503f
--- /dev/null
+++ b/projects/first_mesh/README.md
@@ -0,0 +1,10 @@
+# First mesh
+An example project to show a simple mesh can be rendered with the VkCV framework
+
+![Screenshot](../../screenshots/first_mesh.png)
+
+## Details
+
+This project is rather a proof of concept to show that rendering a more complex mesh than a single 
+triangle was indeed possible with the given API. It was used as a benchmark for further API design
+changes.
diff --git a/projects/first_mesh/resources/cube/boards2_vcyc_jpg.jpg b/projects/first_mesh/assets/cube/boards2_vcyc_jpg.jpg
similarity index 100%
rename from projects/first_mesh/resources/cube/boards2_vcyc_jpg.jpg
rename to projects/first_mesh/assets/cube/boards2_vcyc_jpg.jpg
diff --git a/projects/first_mesh/resources/shaders/shader.frag b/projects/first_mesh/assets/shaders/shader.frag
similarity index 100%
rename from projects/first_mesh/resources/shaders/shader.frag
rename to projects/first_mesh/assets/shaders/shader.frag
diff --git a/projects/first_mesh/resources/shaders/shader.vert b/projects/first_mesh/assets/shaders/shader.vert
similarity index 100%
rename from projects/first_mesh/resources/shaders/shader.vert
rename to projects/first_mesh/assets/shaders/shader.vert
diff --git a/projects/first_mesh/resources/Szene/Szene.bin b/projects/first_mesh/resources/Szene/Szene.bin
deleted file mode 100644
index c87d27637516b0bbf864251dd162773f5cc53e06..0000000000000000000000000000000000000000
--- a/projects/first_mesh/resources/Szene/Szene.bin
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:ee4742718f720d589a2a03f5d879f8c50ba9057718d191a43b046eaa9071080d
-size 70328
diff --git a/projects/first_mesh/resources/Szene/Szene.gltf b/projects/first_mesh/resources/Szene/Szene.gltf
deleted file mode 100644
index e5a32b29af5d0a2ac5f497e60b4b92c1873e1df9..0000000000000000000000000000000000000000
--- a/projects/first_mesh/resources/Szene/Szene.gltf
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:75ba834118792ebbacf528a1690c7d04df4b4c8122b9f99a9aa9a9d075d2c86a
-size 7421
diff --git a/projects/first_mesh/resources/Szene/boards2_vcyc.jpg b/projects/first_mesh/resources/Szene/boards2_vcyc.jpg
deleted file mode 100644
index 2636039e272289c0fba3fa2d88a060b857501248..0000000000000000000000000000000000000000
--- a/projects/first_mesh/resources/Szene/boards2_vcyc.jpg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:cca33a6e58ddd1b37a6e6853a9aa0e7b15ca678937119194752393dd2a0a0564
-size 1192476
diff --git a/projects/first_mesh/resources/shaders/compile.bat b/projects/first_mesh/resources/shaders/compile.bat
deleted file mode 100644
index b4521235c40fe5fb163bab874560c2f219b7517f..0000000000000000000000000000000000000000
--- a/projects/first_mesh/resources/shaders/compile.bat
+++ /dev/null
@@ -1,3 +0,0 @@
-%VULKAN_SDK%\Bin32\glslc.exe shader.vert -o vert.spv
-%VULKAN_SDK%\Bin32\glslc.exe shader.frag -o frag.spv
-pause
\ No newline at end of file
diff --git a/projects/first_mesh/resources/shaders/frag.spv b/projects/first_mesh/resources/shaders/frag.spv
deleted file mode 100644
index 087e4e22fb2fcec27d99b3ff2aa1a705fe755796..0000000000000000000000000000000000000000
Binary files a/projects/first_mesh/resources/shaders/frag.spv and /dev/null differ
diff --git a/projects/first_mesh/resources/shaders/vert.spv b/projects/first_mesh/resources/shaders/vert.spv
deleted file mode 100644
index 374c023e14b351eb43cbcda5951cbb8b3d6f96a1..0000000000000000000000000000000000000000
Binary files a/projects/first_mesh/resources/shaders/vert.spv and /dev/null differ
diff --git a/projects/first_mesh/resources/triangle/Triangle.bin b/projects/first_mesh/resources/triangle/Triangle.bin
deleted file mode 100644
index 57f26ad96592b64377e6aa93823d96a94e6c5022..0000000000000000000000000000000000000000
--- a/projects/first_mesh/resources/triangle/Triangle.bin
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:412ebd5f7242c266b4957e7e26be13aa331dbcb7bbb854ab334a2437ae8ed959
-size 104
diff --git a/projects/first_mesh/resources/triangle/Triangle.blend b/projects/first_mesh/resources/triangle/Triangle.blend
deleted file mode 100644
index 2421dc5e1bb029d73a9ec09cc4530c5196851fd7..0000000000000000000000000000000000000000
--- a/projects/first_mesh/resources/triangle/Triangle.blend
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:387e544df173219fbf292a64a6656d1d782bbf71a5a9e9fdef0a308f47b05477
-size 758144
diff --git a/projects/first_mesh/resources/triangle/Triangle.glb b/projects/first_mesh/resources/triangle/Triangle.glb
deleted file mode 100644
index 4148620cd6af0dadbc791aa1c52bb5431a40884b..0000000000000000000000000000000000000000
--- a/projects/first_mesh/resources/triangle/Triangle.glb
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:f4be087a605212d139416b5352a018283b26b99260cbcddb7013a1beeb331227
-size 980
diff --git a/projects/first_mesh/resources/triangle/Triangle.gltf b/projects/first_mesh/resources/triangle/Triangle.gltf
deleted file mode 100644
index a188e6ee16a5e8486cf307c7bda8cfd99bdbeea6..0000000000000000000000000000000000000000
--- a/projects/first_mesh/resources/triangle/Triangle.gltf
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:d5fc354e040f79cff329e919677b194c75e3a522c6406f75c1108ad9575f12ec
-size 2202
diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp
index c559955aebfae863890782d43e61819387817e5d..cc6d93a2efa37f3b3f1d6a45fe03a28ace686226 100644
--- a/projects/first_mesh/src/main.cpp
+++ b/projects/first_mesh/src/main.cpp
@@ -1,217 +1,168 @@
 #include <iostream>
 #include <vkcv/Core.hpp>
-#include <GLFW/glfw3.h>
+#include <vkcv/Image.hpp>
+#include <vkcv/Pass.hpp>
+#include <vkcv/Sampler.hpp>
 #include <vkcv/camera/CameraManager.hpp>
 #include <vkcv/camera/InterpolationLinear.hpp>
 #include <chrono>
-#include <vkcv/asset/asset_loader.hpp>
 
-int main(int argc, const char** argv) {
-	const char* applicationName = "First Mesh";
+#include <vkcv/asset/asset_loader.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
 
-	uint32_t windowWidth = 800;
-	uint32_t windowHeight = 600;
+#include <vkcv/geometry/Cuboid.hpp>
 
-	vkcv::Window window = vkcv::Window::create(
-		applicationName,
-		windowWidth,
-		windowHeight,
-		true
-	);
+int main(int argc, const char** argv) {
+	const std::string applicationName = "First Mesh";
 
 	vkcv::Core core = vkcv::Core::create(
-		window,
 		applicationName,
 		VK_MAKE_VERSION(0, 0, 1),
 		{ vk::QueueFlagBits::eGraphics ,vk::QueueFlagBits::eCompute , vk::QueueFlagBits::eTransfer },
-		{},
-		{ "VK_KHR_swapchain" }
+		{ VK_KHR_SWAPCHAIN_EXTENSION_NAME }
 	);
 
-	vkcv::asset::Scene mesh;
+	vkcv::WindowHandle windowHandle = core.createWindow(applicationName, 800, 600, true);
+	vkcv::Window& window = core.getWindow(windowHandle);
 
-	const char* path = argc > 1 ? argv[1] : "resources/cube/cube.gltf";
-	int result = vkcv::asset::loadScene(path, mesh);
+	vkcv::PassHandle firstMeshPass = vkcv::passSwapchain(
+			core,
+			window.getSwapchain(),
+			{ vk::Format::eUndefined, vk::Format::eD32Sfloat }
+	);
 
-	if (result == 1) {
-		std::cout << "Mesh loading successful!" << std::endl;
-	}
-	else {
-		std::cout << "Mesh loading failed: " << result << std::endl;
-		return 1;
+	if (!firstMeshPass) {
+		std::cerr << "Error. Could not create renderpass. Exiting." << std::endl;
+		return EXIT_FAILURE;
 	}
 
-	assert(!mesh.vertexGroups.empty());
-	auto vertexBuffer = core.createBuffer<uint8_t>(
-			vkcv::BufferType::VERTEX,
-			mesh.vertexGroups[0].vertexBuffer.data.size(),
-			vkcv::BufferMemoryType::DEVICE_LOCAL
-	);
+	vkcv::ShaderProgram firstMeshProgram;
+	vkcv::shader::GLSLCompiler compiler;
 	
-	vertexBuffer.fill(mesh.vertexGroups[0].vertexBuffer.data);
+	compiler.compileProgram(firstMeshProgram, {
+		{ vkcv::ShaderStage::VERTEX, "assets/shaders/shader.vert" },
+		{ vkcv::ShaderStage::FRAGMENT, "assets/shaders/shader.frag" }
+	}, nullptr);
 
-	auto indexBuffer = core.createBuffer<uint8_t>(
-			vkcv::BufferType::INDEX,
-			mesh.vertexGroups[0].indexBuffer.data.size(),
-			vkcv::BufferMemoryType::DEVICE_LOCAL
+	std::vector<vkcv::VertexBinding> bindings = vkcv::createVertexBindings(
+			firstMeshProgram.getVertexAttachments()
 	);
 	
-	indexBuffer.fill(mesh.vertexGroups[0].indexBuffer.data);
+	const vkcv::VertexLayout firstMeshLayout { bindings };
 
-	// an example attachment for passes that output to the window
-	const vkcv::AttachmentDescription present_color_attachment(
-		vkcv::AttachmentOperation::STORE,
-		vkcv::AttachmentOperation::CLEAR,
-		core.getSwapchain().getFormat()
-	);
-	
-	const vkcv::AttachmentDescription depth_attachment(
-			vkcv::AttachmentOperation::STORE,
-			vkcv::AttachmentOperation::CLEAR,
-			vk::Format::eD32Sfloat
-	);
+	// since we only use one descriptor set (namely, desc set 0), directly address it
+	// recreate copies of the bindings and the handles (to check whether they are properly reused instead of actually recreated)
+	const vkcv::DescriptorBindings& set0Bindings = firstMeshProgram.getReflectedDescriptors().at(0);
 
-	vkcv::PassConfig firstMeshPassDefinition({ present_color_attachment, depth_attachment });
-	vkcv::PassHandle firstMeshPass = core.createPass(firstMeshPassDefinition);
+	vkcv::DescriptorSetLayoutHandle setLayoutHandle = core.createDescriptorSetLayout(set0Bindings);
+	vkcv::DescriptorSetLayoutHandle setLayoutHandleCopy = core.createDescriptorSetLayout(set0Bindings);
 
-	if (!firstMeshPass) {
-		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
-		return EXIT_FAILURE;
-	}
-
-	vkcv::ShaderProgram firstMeshProgram{};
-    firstMeshProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/vert.spv"));
-    firstMeshProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/frag.spv"));
+	vkcv::DescriptorSetHandle descriptorSet = core.createDescriptorSet(setLayoutHandle);
 	
-	auto& attributes = mesh.vertexGroups[0].vertexBuffer.attributes;
+	vkcv::GraphicsPipelineHandle firstMeshPipeline = core.createGraphicsPipeline(
+			vkcv::GraphicsPipelineConfig(
+					firstMeshProgram,
+					firstMeshPass,
+					{ firstMeshLayout },
+					{ setLayoutHandle }
+			)
+	);
 	
-	std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) {
-		return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type);
-	});
-
-    const std::vector<vkcv::VertexAttachment> vertexAttachments = firstMeshProgram.getVertexAttachments();
-	std::vector<vkcv::VertexBinding> bindings;
-	for (size_t i = 0; i < vertexAttachments.size(); i++) {
-		bindings.push_back(vkcv::VertexBinding(i, { vertexAttachments[i] }));
+	if (!firstMeshPipeline) {
+		std::cerr << "Error. Could not create graphics pipeline. Exiting." << std::endl;
+		return EXIT_FAILURE;
 	}
 	
-	const vkcv::VertexLayout firstMeshLayout (bindings);
-
-	uint32_t setID = 0;
-	std::vector<vkcv::DescriptorBinding> descriptorBindings = { firstMeshProgram.getReflectedDescriptors()[setID] };
-	vkcv::DescriptorSetHandle descriptorSet = core.createDescriptorSet(descriptorBindings);
-
-	const vkcv::PipelineConfig firstMeshPipelineConfig {
-        firstMeshProgram,
-        UINT32_MAX,
-        UINT32_MAX,
-        firstMeshPass,
-        {firstMeshLayout},
-		{ core.getDescriptorSet(descriptorSet).layout },
-		true
-	};
-	vkcv::PipelineHandle firstMeshPipeline = core.createGraphicsPipeline(firstMeshPipelineConfig);
+	vkcv::asset::Texture tex = vkcv::asset::loadTexture("assets/cube/boards2_vcyc_jpg.jpg");
 	
-	if (!firstMeshPipeline) {
-		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
+	if (tex.data.empty()) {
+		std::cerr << "Error. No texture found. Exiting." << std::endl;
 		return EXIT_FAILURE;
 	}
 	
-	// FIXME There should be a test here to make sure there is at least 1
-	// texture in the mesh.
-	vkcv::asset::Texture &tex = mesh.textures[0];
-	vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, tex.w, tex.h);
+	vkcv::Image texture = vkcv::image(core, vk::Format::eR8G8B8A8Srgb, tex.w, tex.h);
 	texture.fill(tex.data.data());
-	texture.generateMipChainImmediate();
-	texture.switchLayout(vk::ImageLayout::eShaderReadOnlyOptimal);
-
-	vkcv::SamplerHandle sampler = core.createSampler(
-		vkcv::SamplerFilterType::LINEAR,
-		vkcv::SamplerFilterType::LINEAR,
-		vkcv::SamplerMipmapMode::LINEAR,
-		vkcv::SamplerAddressMode::REPEAT
-	);
-
-	const std::vector<vkcv::VertexBufferBinding> vertexBufferBindings = {
-		vkcv::VertexBufferBinding(static_cast<vk::DeviceSize>(attributes[0].offset), vertexBuffer.getVulkanHandle()),
-		vkcv::VertexBufferBinding(static_cast<vk::DeviceSize>(attributes[1].offset), vertexBuffer.getVulkanHandle()),
-		vkcv::VertexBufferBinding(static_cast<vk::DeviceSize>(attributes[2].offset), vertexBuffer.getVulkanHandle()) };
+	
+	{
+		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+		texture.recordMipChainGeneration(cmdStream, core.getDownsampler());
+		core.submitCommandStream(cmdStream, false);
+	}
 
+	vkcv::SamplerHandle sampler = vkcv::samplerLinear(core);
+	
 	vkcv::DescriptorWrites setWrites;
-	setWrites.sampledImageWrites	= { vkcv::SampledImageDescriptorWrite(0, texture.getHandle()) };
-	setWrites.samplerWrites			= { vkcv::SamplerDescriptorWrite(1, sampler) };
+	setWrites.writeSampledImage(0, texture.getHandle());
+	setWrites.writeSampler(1, sampler);
 
 	core.writeDescriptorSet(descriptorSet, setWrites);
-
-	vkcv::ImageHandle depthBuffer = core.createImage(vk::Format::eD32Sfloat, windowWidth, windowHeight, 1, false).getHandle();
+	
+	vkcv::ImageHandle depthBuffer;
 
 	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
-
-	const vkcv::Mesh renderMesh(vertexBufferBindings, indexBuffer.getVulkanHandle(), mesh.vertexGroups[0].numIndices);
-
-	vkcv::DescriptorSetUsage    descriptorUsage(0, core.getDescriptorSet(descriptorSet).vulkanHandle);
-	vkcv::DrawcallInfo          drawcall(renderMesh, { descriptorUsage });
+	
+	vkcv::geometry::Cuboid cube (glm::vec3(0), 1.0f);
+	
+	vkcv::InstanceDrawcall drawcall (cube.generateVertexData(core));
+	drawcall.useDescriptorSet(0, descriptorSet);
 
     vkcv::camera::CameraManager cameraManager(window);
-    uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::NONE);
-	uint32_t camIndex1 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
-	uint32_t camIndex2 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
 	
-	cameraManager.getCamera(camIndex0).setPosition(glm::vec3(0, 0, -3));
-
-	vkcv::camera::InterpolationLinear interp(cameraManager.getCamera(camIndex0));
-
+    auto camHandle0 = cameraManager.addCamera(vkcv::camera::ControllerType::NONE);
+	auto camHandle1 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+	auto camHandle2 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+	
+	cameraManager.getCamera(camHandle1).setPosition(glm::vec3(0, 0, -3));
+	cameraManager.getCamera(camHandle2).setPosition(glm::vec3(0, 0, -3));
+	
+	vkcv::camera::InterpolationLinear interp (cameraManager.getCamera(camHandle0));
+	
 	interp.addPosition(glm::vec3(5,5,-5));
 	interp.addPosition(glm::vec3(0,5,-5));
 	interp.addPosition(glm::vec3(0,-3,-3));
 	interp.addPosition(glm::vec3(3,0,-6));
 	interp.addPosition(glm::vec3(5,5,5));
 	interp.addPosition(glm::vec3(5,5,-5));
-
-    auto start = std::chrono::system_clock::now();
-    
-	while (window.isWindowOpen()) {
-        window.pollEvents();
-		
-		if(window.getHeight() == 0 || window.getWidth() == 0)
-			continue;
-		
-		uint32_t swapchainWidth, swapchainHeight;
-		if (!core.beginFrame(swapchainWidth, swapchainHeight)) {
-			continue;
-		}
-		
-		if ((swapchainWidth != windowWidth) || ((swapchainHeight != windowHeight))) {
-			depthBuffer = core.createImage(vk::Format::eD32Sfloat, swapchainWidth, swapchainHeight).getHandle();
-			
-			windowWidth = swapchainWidth;
-			windowHeight = swapchainHeight;
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
+		if ((!depthBuffer) ||
+			(swapchainWidth != core.getImageWidth(depthBuffer)) ||
+			(swapchainHeight != core.getImageHeight(depthBuffer))) {
+			depthBuffer = core.createImage(
+					vk::Format::eD32Sfloat,
+					vkcv::ImageConfig(
+							swapchainWidth,
+							swapchainHeight
+					)
+			);
 		}
-  
-		auto end = std::chrono::system_clock::now();
-		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
 		
-		start = end;
-		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+		cameraManager.update(dt);
 		interp.updateCamera();
+		
         glm::mat4 mvp = cameraManager.getActiveCamera().getMVP();
 
-		vkcv::PushConstantData pushConstantData((void*)&mvp, sizeof(glm::mat4));
+		vkcv::PushConstants pushConstants = vkcv::pushConstants<glm::mat4>();
+		pushConstants.appendDrawcall(mvp);
 
 		const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer };
 		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
 
 		core.recordDrawcallsToCmdStream(
 			cmdStream,
-			firstMeshPass,
 			firstMeshPipeline,
-			pushConstantData,
+			pushConstants,
 			{ drawcall },
-			renderTargets);
+			renderTargets,
+			windowHandle
+		);
+		
 		core.prepareSwapchainImageForPresent(cmdStream);
 		core.submitCommandStream(cmdStream);
-		core.endFrame();
-	}
+	});
+	
+	core.getContext().getDevice().waitIdle();
 	
 	return 0;
 }
diff --git a/projects/first_scene/CMakeLists.txt b/projects/first_scene/CMakeLists.txt
index 8b90739750011a36b4c1d9e0bff7cba986074228..266630fae0de1d4d9696d454c101bf00085b7599 100644
--- a/projects/first_scene/CMakeLists.txt
+++ b/projects/first_scene/CMakeLists.txt
@@ -2,27 +2,14 @@ cmake_minimum_required(VERSION 3.16)
 project(first_scene)
 
 # setting c++ standard for the project
-set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD 20)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
-# this should fix the execution path to load local files from the project
-set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
-
 # adding source files to the project
-add_executable(first_scene src/main.cpp)
-
-# this should fix the execution path to load local files from the project (for MSVC)
-if(MSVC)
-	set_target_properties(first_scene PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-	set_target_properties(first_scene PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-    
-    # in addition to setting the output directory, the working directory has to be set
-	# by default visual studio sets the working directory to the build directory, when using the debugger
-	set_target_properties(first_scene PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-endif()
+add_project(first_scene src/main.cpp)
 
 # including headers of dependencies and the VkCV framework
-target_include_directories(first_scene SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include})
+target_include_directories(first_scene SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include} ${vkcv_scene_include} ${vkcv_shader_compiler_include} ${vkcv_gui_include})
 
 # linking with libraries from all dependencies and the VkCV framework
-target_link_libraries(first_scene vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera)
+target_link_libraries(first_scene vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera vkcv_scene vkcv_shader_compiler vkcv_gui)
diff --git a/projects/first_scene/README.md b/projects/first_scene/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..be333a773eae88ff3e96fe2b9b004fc3575f386f
--- /dev/null
+++ b/projects/first_scene/README.md
@@ -0,0 +1,10 @@
+# First scene
+An example project to show a whole scene can be rendered with the VkCV framework
+
+![Screenshot](../../screenshots/first_scene.png)
+
+## Details
+
+Similar to the projects to show rendering a single triangle or a simple mesh was possible. This 
+project shows that a whole scene can easily be loaded from any GLTF file and be rendered using the
+modules from the VkCV framework.
diff --git a/projects/bloom/resources/Sponza/Sponza.bin b/projects/first_scene/assets/Sponza/Sponza.bin
similarity index 100%
rename from projects/bloom/resources/Sponza/Sponza.bin
rename to projects/first_scene/assets/Sponza/Sponza.bin
diff --git a/projects/bloom/resources/Sponza/Sponza.gltf b/projects/first_scene/assets/Sponza/Sponza.gltf
similarity index 100%
rename from projects/bloom/resources/Sponza/Sponza.gltf
rename to projects/first_scene/assets/Sponza/Sponza.gltf
diff --git a/projects/first_scene/assets/Sponza/SponzaFloor.bin b/projects/first_scene/assets/Sponza/SponzaFloor.bin
new file mode 100644
index 0000000000000000000000000000000000000000..684251288f35070d2e7d244877fd844cc00ca632
--- /dev/null
+++ b/projects/first_scene/assets/Sponza/SponzaFloor.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:678455aca641cb1f449aa1a5054a7cae132be81c2b333aac283053967da66df0
+size 512
diff --git a/projects/first_scene/assets/Sponza/SponzaFloor.gltf b/projects/first_scene/assets/Sponza/SponzaFloor.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..b45f1c55ef85f2aa1d4bff01df3d9625aa38c809
--- /dev/null
+++ b/projects/first_scene/assets/Sponza/SponzaFloor.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a6deb75441b1138b50a6b0eec05e60df276fe8fb6d58118fdfce2090b6fbe734
+size 3139
diff --git a/projects/bloom/resources/Sponza/background.png b/projects/first_scene/assets/Sponza/background.png
similarity index 100%
rename from projects/bloom/resources/Sponza/background.png
rename to projects/first_scene/assets/Sponza/background.png
diff --git a/projects/bloom/resources/Sponza/chain_texture.png b/projects/first_scene/assets/Sponza/chain_texture.png
similarity index 100%
rename from projects/bloom/resources/Sponza/chain_texture.png
rename to projects/first_scene/assets/Sponza/chain_texture.png
diff --git a/projects/bloom/resources/Sponza/lion.png b/projects/first_scene/assets/Sponza/lion.png
similarity index 100%
rename from projects/bloom/resources/Sponza/lion.png
rename to projects/first_scene/assets/Sponza/lion.png
diff --git a/projects/bloom/resources/Sponza/spnza_bricks_a_diff.png b/projects/first_scene/assets/Sponza/spnza_bricks_a_diff.png
similarity index 100%
rename from projects/bloom/resources/Sponza/spnza_bricks_a_diff.png
rename to projects/first_scene/assets/Sponza/spnza_bricks_a_diff.png
diff --git a/projects/bloom/resources/Sponza/sponza_arch_diff.png b/projects/first_scene/assets/Sponza/sponza_arch_diff.png
similarity index 100%
rename from projects/bloom/resources/Sponza/sponza_arch_diff.png
rename to projects/first_scene/assets/Sponza/sponza_arch_diff.png
diff --git a/projects/bloom/resources/Sponza/sponza_ceiling_a_diff.png b/projects/first_scene/assets/Sponza/sponza_ceiling_a_diff.png
similarity index 100%
rename from projects/bloom/resources/Sponza/sponza_ceiling_a_diff.png
rename to projects/first_scene/assets/Sponza/sponza_ceiling_a_diff.png
diff --git a/projects/bloom/resources/Sponza/sponza_column_a_diff.png b/projects/first_scene/assets/Sponza/sponza_column_a_diff.png
similarity index 100%
rename from projects/bloom/resources/Sponza/sponza_column_a_diff.png
rename to projects/first_scene/assets/Sponza/sponza_column_a_diff.png
diff --git a/projects/bloom/resources/Sponza/sponza_column_b_diff.png b/projects/first_scene/assets/Sponza/sponza_column_b_diff.png
similarity index 100%
rename from projects/bloom/resources/Sponza/sponza_column_b_diff.png
rename to projects/first_scene/assets/Sponza/sponza_column_b_diff.png
diff --git a/projects/bloom/resources/Sponza/sponza_column_c_diff.png b/projects/first_scene/assets/Sponza/sponza_column_c_diff.png
similarity index 100%
rename from projects/bloom/resources/Sponza/sponza_column_c_diff.png
rename to projects/first_scene/assets/Sponza/sponza_column_c_diff.png
diff --git a/projects/bloom/resources/Sponza/sponza_curtain_blue_diff.png b/projects/first_scene/assets/Sponza/sponza_curtain_blue_diff.png
similarity index 100%
rename from projects/bloom/resources/Sponza/sponza_curtain_blue_diff.png
rename to projects/first_scene/assets/Sponza/sponza_curtain_blue_diff.png
diff --git a/projects/bloom/resources/Sponza/sponza_curtain_diff.png b/projects/first_scene/assets/Sponza/sponza_curtain_diff.png
similarity index 100%
rename from projects/bloom/resources/Sponza/sponza_curtain_diff.png
rename to projects/first_scene/assets/Sponza/sponza_curtain_diff.png
diff --git a/projects/bloom/resources/Sponza/sponza_curtain_green_diff.png b/projects/first_scene/assets/Sponza/sponza_curtain_green_diff.png
similarity index 100%
rename from projects/bloom/resources/Sponza/sponza_curtain_green_diff.png
rename to projects/first_scene/assets/Sponza/sponza_curtain_green_diff.png
diff --git a/projects/bloom/resources/Sponza/sponza_details_diff.png b/projects/first_scene/assets/Sponza/sponza_details_diff.png
similarity index 100%
rename from projects/bloom/resources/Sponza/sponza_details_diff.png
rename to projects/first_scene/assets/Sponza/sponza_details_diff.png
diff --git a/projects/bloom/resources/Sponza/sponza_fabric_blue_diff.png b/projects/first_scene/assets/Sponza/sponza_fabric_blue_diff.png
similarity index 100%
rename from projects/bloom/resources/Sponza/sponza_fabric_blue_diff.png
rename to projects/first_scene/assets/Sponza/sponza_fabric_blue_diff.png
diff --git a/projects/bloom/resources/Sponza/sponza_fabric_diff.png b/projects/first_scene/assets/Sponza/sponza_fabric_diff.png
similarity index 100%
rename from projects/bloom/resources/Sponza/sponza_fabric_diff.png
rename to projects/first_scene/assets/Sponza/sponza_fabric_diff.png
diff --git a/projects/bloom/resources/Sponza/sponza_fabric_green_diff.png b/projects/first_scene/assets/Sponza/sponza_fabric_green_diff.png
similarity index 100%
rename from projects/bloom/resources/Sponza/sponza_fabric_green_diff.png
rename to projects/first_scene/assets/Sponza/sponza_fabric_green_diff.png
diff --git a/projects/bloom/resources/Sponza/sponza_flagpole_diff.png b/projects/first_scene/assets/Sponza/sponza_flagpole_diff.png
similarity index 100%
rename from projects/bloom/resources/Sponza/sponza_flagpole_diff.png
rename to projects/first_scene/assets/Sponza/sponza_flagpole_diff.png
diff --git a/projects/bloom/resources/Sponza/sponza_floor_a_diff.png b/projects/first_scene/assets/Sponza/sponza_floor_a_diff.png
similarity index 100%
rename from projects/bloom/resources/Sponza/sponza_floor_a_diff.png
rename to projects/first_scene/assets/Sponza/sponza_floor_a_diff.png
diff --git a/projects/bloom/resources/Sponza/sponza_roof_diff.png b/projects/first_scene/assets/Sponza/sponza_roof_diff.png
similarity index 100%
rename from projects/bloom/resources/Sponza/sponza_roof_diff.png
rename to projects/first_scene/assets/Sponza/sponza_roof_diff.png
diff --git a/projects/bloom/resources/Sponza/sponza_thorn_diff.png b/projects/first_scene/assets/Sponza/sponza_thorn_diff.png
similarity index 100%
rename from projects/bloom/resources/Sponza/sponza_thorn_diff.png
rename to projects/first_scene/assets/Sponza/sponza_thorn_diff.png
diff --git a/projects/bloom/resources/Sponza/vase_dif.png b/projects/first_scene/assets/Sponza/vase_dif.png
similarity index 100%
rename from projects/bloom/resources/Sponza/vase_dif.png
rename to projects/first_scene/assets/Sponza/vase_dif.png
diff --git a/projects/bloom/resources/Sponza/vase_hanging.png b/projects/first_scene/assets/Sponza/vase_hanging.png
similarity index 100%
rename from projects/bloom/resources/Sponza/vase_hanging.png
rename to projects/first_scene/assets/Sponza/vase_hanging.png
diff --git a/projects/bloom/resources/Sponza/vase_plant.png b/projects/first_scene/assets/Sponza/vase_plant.png
similarity index 100%
rename from projects/bloom/resources/Sponza/vase_plant.png
rename to projects/first_scene/assets/Sponza/vase_plant.png
diff --git a/projects/bloom/resources/Sponza/vase_round.png b/projects/first_scene/assets/Sponza/vase_round.png
similarity index 100%
rename from projects/bloom/resources/Sponza/vase_round.png
rename to projects/first_scene/assets/Sponza/vase_round.png
diff --git a/projects/first_scene/assets/shaders/shader.frag b/projects/first_scene/assets/shaders/shader.frag
new file mode 100644
index 0000000000000000000000000000000000000000..cf96b4c20327fa3f1b1545cba7cebfdfaaf1ba7c
--- /dev/null
+++ b/projects/first_scene/assets/shaders/shader.frag
@@ -0,0 +1,31 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 passNormal;
+layout(location = 1) in vec2 passUV;
+
+layout(location = 0) out vec4 outColor;
+
+layout(set=0, binding=0) uniform texture2D  meshTexture;
+layout(set=0, binding=1) uniform sampler    textureSampler;
+
+void main()	{
+	vec3 lightDirection = normalize(vec3(0.1f, -0.9f, 0.1f));
+
+	float ambient = 0.35f;
+	float diffuse = max(0.0f, -dot(passNormal, lightDirection));
+	float specular = pow(diffuse, 6.0f);
+
+	float brightness = sqrt(
+		(ambient + diffuse + specular) /
+		(2.0f + ambient)
+	);
+
+	vec4 color = texture(sampler2D(meshTexture, textureSampler), passUV);
+
+	if (color.a <= 0.0f) {
+		discard;
+	}
+
+	outColor = vec4(color.rgb * brightness, color.a);
+}
\ No newline at end of file
diff --git a/projects/first_scene/resources/shaders/shader.vert b/projects/first_scene/assets/shaders/shader.vert
similarity index 100%
rename from projects/first_scene/resources/shaders/shader.vert
rename to projects/first_scene/assets/shaders/shader.vert
diff --git a/projects/first_scene/resources/Cutlery/Cutlery_chrome_BaseColor.png b/projects/first_scene/resources/Cutlery/Cutlery_chrome_BaseColor.png
deleted file mode 100644
index 8258525f22097f2382ec5c26ad0df7eb50718642..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Cutlery/Cutlery_chrome_BaseColor.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:0ce87f6407ee40ffa60983587aeb52333d59b4b1c01a53e11f4bb227ba1099d9
-size 109
diff --git a/projects/first_scene/resources/Cutlery/Cutlery_chrome_Normal.png b/projects/first_scene/resources/Cutlery/Cutlery_chrome_Normal.png
deleted file mode 100644
index 620fe7621cf35409ae8c04099dc8bc4bbc04343b..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Cutlery/Cutlery_chrome_Normal.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:68a0064d457a6f7994814b07d943deda778754128935689874334300ede6161d
-size 2332064
diff --git a/projects/first_scene/resources/Cutlery/Cutlery_details_BaseColor.png b/projects/first_scene/resources/Cutlery/Cutlery_details_BaseColor.png
deleted file mode 100644
index 5570e88c569036b9d00155ef6113013aa23f2503..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Cutlery/Cutlery_details_BaseColor.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:42c2715635081eb29c4489ce631798b0e9c881460efc0aa63d0e81641a0dcfe9
-size 108
diff --git a/projects/first_scene/resources/Cutlery/Cutlery_details_Normal.png b/projects/first_scene/resources/Cutlery/Cutlery_details_Normal.png
deleted file mode 100644
index d07681f5bfa25c279321c3074e21e4900c5eb0fa..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Cutlery/Cutlery_details_Normal.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:15b0133e140899c47ccf35b0f99a7e337e3110ae089f45d27faf9983f3e0a1f7
-size 770758
diff --git a/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Caps_BaseColor.png b/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Caps_BaseColor.png
deleted file mode 100644
index 1845e8a7d586a0d23300ad9d04a82f1d5048fcf5..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Caps_BaseColor.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:ea7c82c0f9e25afa401470df1fb6903f508fa138d21ad30f57a9153b0395b198
-size 521315
diff --git a/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Caps_Normal.png b/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Caps_Normal.png
deleted file mode 100644
index 1c800c0489693b66d6163bdc2156d453b68ca19b..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Caps_Normal.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:119efbbc020244ff9b7ff16ac9795a6d4b1808d1b90d81d20d2c874d0dc8a924
-size 1693468
diff --git a/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Glass_Wine_BaseColor.png b/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Glass_Wine_BaseColor.png
deleted file mode 100644
index 36f46ebf25158c78bc26d83860e1c08b2c9e96cf..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Glass_Wine_BaseColor.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:99896468c7d47dd5391d585eecf149f420eca3bfec31923c21fa86c45fe02d0f
-size 108
diff --git a/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Glass_Wine_Normal.png b/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Glass_Wine_Normal.png
deleted file mode 100644
index 28c205d4e70867ec58448ca8ba2c2117c611a367..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Cutlery/Paris_LiquorBottle_01_Glass_Wine_Normal.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:b31618aa5adce4ad476bec2c03718c5ae097250e784344f2d298b8a74c3bfd46
-size 90
diff --git a/projects/first_scene/resources/Cutlery/Plates_Ceramic_BaseColor.png b/projects/first_scene/resources/Cutlery/Plates_Ceramic_BaseColor.png
deleted file mode 100644
index e0104189a1190c160152101e9e328e81ed89121b..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Cutlery/Plates_Ceramic_BaseColor.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:6eff6ccd12d8b39d60ae5ee91edd73d4d7838fcb5d9bc6ff0e671bdf009134e9
-size 109
diff --git a/projects/first_scene/resources/Cutlery/Plates_Ceramic_Normal.png b/projects/first_scene/resources/Cutlery/Plates_Ceramic_Normal.png
deleted file mode 100644
index fa13483d25595ee6b3a00e7fe6ce48aed7b6aaca..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Cutlery/Plates_Ceramic_Normal.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:fe92c40ff4032fdaf10eeafd943657a0c6e0bfb3f38770f5654aa943a660f421
-size 59419
diff --git a/projects/first_scene/resources/Cutlery/Plates_Details_BaseColor-Plates_Details_BaseColor.png b/projects/first_scene/resources/Cutlery/Plates_Details_BaseColor-Plates_Details_BaseColor.png
deleted file mode 100644
index b91d0ac62fbeabbef1ed78f1343f919836cbca40..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Cutlery/Plates_Details_BaseColor-Plates_Details_BaseColor.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:4ca7d436a68a2a1237aee6e763b2954f01666b21f1dbd46929a322ea277483d2
-size 779227
diff --git a/projects/first_scene/resources/Cutlery/Plates_Details_Normal.png b/projects/first_scene/resources/Cutlery/Plates_Details_Normal.png
deleted file mode 100644
index 6efd967984ee2e68b89de9e471b93396c13ca69a..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Cutlery/Plates_Details_Normal.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:b01fc6482054c64d7407b283731e57fce0601a8db28b6781c14fae3c6b30b0fe
-size 504362
diff --git a/projects/first_scene/resources/Cutlery/ToffeeJar_Label_BaseColor.png b/projects/first_scene/resources/Cutlery/ToffeeJar_Label_BaseColor.png
deleted file mode 100644
index d0e0c4f4134cb99d3765d6f078f71efab8861bf1..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Cutlery/ToffeeJar_Label_BaseColor.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:df138ee68c1d455652d1b9ae3dd03e93fcd2f6a0d8a1f12e3710f39143088674
-size 1593466
diff --git a/projects/first_scene/resources/Cutlery/ToffeeJar_Label_Normal.png b/projects/first_scene/resources/Cutlery/ToffeeJar_Label_Normal.png
deleted file mode 100644
index 9f310653b5211575da3cab2f6deb47ec6826a936..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Cutlery/ToffeeJar_Label_Normal.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:6af5da97cbb25d79aea2dde8dd71ecbd495334fe34e99497ba17821be93fd7fd
-size 2696676
diff --git a/projects/first_scene/resources/Cutlery/TransparentGlass_BaseColor.png b/projects/first_scene/resources/Cutlery/TransparentGlass_BaseColor.png
deleted file mode 100644
index 4e4f0fcb312b8f8bd0df965bfe6b8ac62ec5df4d..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Cutlery/TransparentGlass_BaseColor.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:2796affdfdcf6bc805176d9f85505680b5ee52eeec625e9eaeea4f0ff3854883
-size 108
diff --git a/projects/first_scene/resources/Cutlery/TransparentGlass_Normal.png b/projects/first_scene/resources/Cutlery/TransparentGlass_Normal.png
deleted file mode 100644
index 28c205d4e70867ec58448ca8ba2c2117c611a367..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Cutlery/TransparentGlass_Normal.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:b31618aa5adce4ad476bec2c03718c5ae097250e784344f2d298b8a74c3bfd46
-size 90
diff --git a/projects/first_scene/resources/Cutlery/cutlerySzene.bin b/projects/first_scene/resources/Cutlery/cutlerySzene.bin
deleted file mode 100644
index ab9a0aa47205e8f3064d2f16a950d4733fb4f472..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Cutlery/cutlerySzene.bin
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:f545b986e0a1ac5bff5d49693a52042aa37878425818f72c69c243da20d1f99d
-size 183324
diff --git a/projects/first_scene/resources/Cutlery/cutlerySzene.glb b/projects/first_scene/resources/Cutlery/cutlerySzene.glb
deleted file mode 100644
index b0c5f345aaaa3f96d7158a0992ee124aae99a69a..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Cutlery/cutlerySzene.glb
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:fb1bad604192ca36222c0ca485ba87b846ecbd11ee8254327e04e3c993b00116
-size 11150396
diff --git a/projects/first_scene/resources/Cutlery/cutlerySzene.gltf b/projects/first_scene/resources/Cutlery/cutlerySzene.gltf
deleted file mode 100644
index 53e339cda4511f3f1a8670b36469e184aac530e2..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Cutlery/cutlerySzene.gltf
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:c77cd60e2327daca1a01044e45f2c38655f7b781bd07985fc0135328a8a96b57
-size 34312
diff --git a/projects/first_scene/resources/Szene/Szene.bin b/projects/first_scene/resources/Szene/Szene.bin
deleted file mode 100644
index c87d27637516b0bbf864251dd162773f5cc53e06..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Szene/Szene.bin
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:ee4742718f720d589a2a03f5d879f8c50ba9057718d191a43b046eaa9071080d
-size 70328
diff --git a/projects/first_scene/resources/Szene/Szene.gltf b/projects/first_scene/resources/Szene/Szene.gltf
deleted file mode 100644
index e5a32b29af5d0a2ac5f497e60b4b92c1873e1df9..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Szene/Szene.gltf
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:75ba834118792ebbacf528a1690c7d04df4b4c8122b9f99a9aa9a9d075d2c86a
-size 7421
diff --git a/projects/first_scene/resources/Szene/boards2_vcyc.jpg b/projects/first_scene/resources/Szene/boards2_vcyc.jpg
deleted file mode 100644
index 2636039e272289c0fba3fa2d88a060b857501248..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Szene/boards2_vcyc.jpg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:cca33a6e58ddd1b37a6e6853a9aa0e7b15ca678937119194752393dd2a0a0564
-size 1192476
diff --git a/projects/first_scene/resources/Szene/boards2_vcyc_jpg.jpg b/projects/first_scene/resources/Szene/boards2_vcyc_jpg.jpg
deleted file mode 100644
index 2636039e272289c0fba3fa2d88a060b857501248..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/Szene/boards2_vcyc_jpg.jpg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:cca33a6e58ddd1b37a6e6853a9aa0e7b15ca678937119194752393dd2a0a0564
-size 1192476
diff --git a/projects/first_scene/resources/shaders/compile.bat b/projects/first_scene/resources/shaders/compile.bat
deleted file mode 100644
index b4521235c40fe5fb163bab874560c2f219b7517f..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/shaders/compile.bat
+++ /dev/null
@@ -1,3 +0,0 @@
-%VULKAN_SDK%\Bin32\glslc.exe shader.vert -o vert.spv
-%VULKAN_SDK%\Bin32\glslc.exe shader.frag -o frag.spv
-pause
\ No newline at end of file
diff --git a/projects/first_scene/resources/shaders/frag.spv b/projects/first_scene/resources/shaders/frag.spv
deleted file mode 100644
index 087e4e22fb2fcec27d99b3ff2aa1a705fe755796..0000000000000000000000000000000000000000
Binary files a/projects/first_scene/resources/shaders/frag.spv and /dev/null differ
diff --git a/projects/first_scene/resources/shaders/shader.frag b/projects/first_scene/resources/shaders/shader.frag
deleted file mode 100644
index b5494bea7d6497e2e3dcd8559606864a71adb74e..0000000000000000000000000000000000000000
--- a/projects/first_scene/resources/shaders/shader.frag
+++ /dev/null
@@ -1,15 +0,0 @@
-#version 450
-#extension GL_ARB_separate_shader_objects : enable
-
-layout(location = 0) in vec3 passNormal;
-layout(location = 1) in vec2 passUV;
-
-layout(location = 0) out vec3 outColor;
-
-layout(set=0, binding=0) uniform texture2D  meshTexture;
-layout(set=0, binding=1) uniform sampler    textureSampler;
-
-void main()	{
-	outColor = texture(sampler2D(meshTexture, textureSampler), passUV).rgb;
-    //outColor = passNormal * 0.5 + 0.5;
-}
\ No newline at end of file
diff --git a/projects/first_scene/resources/shaders/vert.spv b/projects/first_scene/resources/shaders/vert.spv
deleted file mode 100644
index 374c023e14b351eb43cbcda5951cbb8b3d6f96a1..0000000000000000000000000000000000000000
Binary files a/projects/first_scene/resources/shaders/vert.spv and /dev/null differ
diff --git a/projects/first_scene/src/main.cpp b/projects/first_scene/src/main.cpp
index 420400cdd04865ddd48eec7cf6000e36417d8095..964b048edfc02c8e173fca1931dc5ff804ad65bc 100644
--- a/projects/first_scene/src/main.cpp
+++ b/projects/first_scene/src/main.cpp
@@ -1,264 +1,149 @@
 #include <iostream>
 #include <vkcv/Core.hpp>
+#include <vkcv/Pass.hpp>
 #include <GLFW/glfw3.h>
 #include <vkcv/camera/CameraManager.hpp>
-#include <chrono>
+#include <vkcv/gui/GUI.hpp>
 #include <vkcv/asset/asset_loader.hpp>
-#include <vkcv/Logger.hpp>
-
-glm::mat4 arrayTo4x4Matrix(std::array<float,16> array){
-    glm::mat4 matrix;
-    for (int i = 0; i < 4; i++){
-        for (int j = 0; j < 4; j++){
-            matrix[i][j] = array[j * 4 + i];
-        }
-    }
-    return matrix;
-}
+#include <vkcv/shader/GLSLCompiler.hpp>
+#include <vkcv/scene/Scene.hpp>
+#include <vkcv/Features.hpp>
 
 int main(int argc, const char** argv) {
-	const char* applicationName = "First Scene";
+	const std::string applicationName = "First Scene";
 
 	uint32_t windowWidth = 800;
 	uint32_t windowHeight = 600;
-
-	vkcv::Window window = vkcv::Window::create(
-		applicationName,
-		windowWidth,
-		windowHeight,
-		true
-	);
-
-	vkcv::camera::CameraManager cameraManager(window);
-	uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
-	uint32_t camIndex1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
-
-	cameraManager.getCamera(camIndex0).setPosition(glm::vec3(0, 0, -3));
-	cameraManager.getCamera(camIndex0).setNearFar(0.1f, 30.0f);
 	
-	cameraManager.getCamera(camIndex1).setNearFar(0.1f, 30.0f);
+	vkcv::Features features;
+	features.requireExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
 
 	vkcv::Core core = vkcv::Core::create(
-		window,
-		applicationName,
-		VK_MAKE_VERSION(0, 0, 1),
-		{ vk::QueueFlagBits::eGraphics ,vk::QueueFlagBits::eCompute , vk::QueueFlagBits::eTransfer },
-		{},
-		{ "VK_KHR_swapchain" }
+			applicationName,
+			VK_MAKE_VERSION(0, 0, 1),
+			{vk::QueueFlagBits::eTransfer, vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute},
+			features
 	);
+	
+	vkcv::WindowHandle windowHandle = core.createWindow(applicationName, windowWidth, windowHeight, true);
+	vkcv::Window& window = core.getWindow(windowHandle);
+	vkcv::camera::CameraManager cameraManager(window);
 
-	vkcv::asset::Scene scene;
-
-	const char* path = argc > 1 ? argv[1] : "resources/Sponza/Sponza.gltf";
-	int result = vkcv::asset::loadScene(path, scene);
-
-	if (result == 1) {
-		std::cout << "Mesh loading successful!" << std::endl;
-	}
-	else {
-		std::cout << "Mesh loading failed: " << result << std::endl;
-		return 1;
-	}
-
-	assert(!scene.vertexGroups.empty());
-	std::vector<std::vector<uint8_t>> vBuffers;
-	std::vector<std::vector<uint8_t>> iBuffers;
-
-	std::vector<vkcv::VertexBufferBinding> vBufferBindings;
-	std::vector<std::vector<vkcv::VertexBufferBinding>> vertexBufferBindings;
-	std::vector<vkcv::asset::VertexAttribute> vAttributes;
-
-	for (int i = 0; i < scene.vertexGroups.size(); i++) {
-
-		vBuffers.push_back(scene.vertexGroups[i].vertexBuffer.data);
-		iBuffers.push_back(scene.vertexGroups[i].indexBuffer.data);
-
-		auto& attributes = scene.vertexGroups[i].vertexBuffer.attributes;
-
-		std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) {
-			return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type);
-			});
-	}
-
-	std::vector<vkcv::Buffer<uint8_t>> vertexBuffers;
-	for (const vkcv::asset::VertexGroup& group : scene.vertexGroups) {
-		vertexBuffers.push_back(core.createBuffer<uint8_t>(
-			vkcv::BufferType::VERTEX,
-			group.vertexBuffer.data.size()));
-		vertexBuffers.back().fill(group.vertexBuffer.data);
-	}
-
-	std::vector<vkcv::Buffer<uint8_t>> indexBuffers;
-	for (const auto& dataBuffer : iBuffers) {
-		indexBuffers.push_back(core.createBuffer<uint8_t>(
-			vkcv::BufferType::INDEX,
-			dataBuffer.size()));
-		indexBuffers.back().fill(dataBuffer);
-	}
-
-	int vertexBufferIndex = 0;
-	for (const auto& vertexGroup : scene.vertexGroups) {
-		for (const auto& attribute : vertexGroup.vertexBuffer.attributes) {
-			vAttributes.push_back(attribute);
-			vBufferBindings.push_back(vkcv::VertexBufferBinding(attribute.offset, vertexBuffers[vertexBufferIndex].getVulkanHandle()));
-		}
-		vertexBufferBindings.push_back(vBufferBindings);
-		vBufferBindings.clear();
-		vertexBufferIndex++;
-	}
+    vkcv::gui::GUI gui (core, windowHandle);
 
-	const vkcv::AttachmentDescription present_color_attachment(
-		vkcv::AttachmentOperation::STORE,
-		vkcv::AttachmentOperation::CLEAR,
-		core.getSwapchain().getFormat()
+    auto camHandle0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+	auto camHandle1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+	
+	cameraManager.getCamera(camHandle0).setPosition(glm::vec3(-8, 1, -0.5));
+	cameraManager.getCamera(camHandle0).setNearFar(0.1f, 30.0f);
+	
+	cameraManager.getCamera(camHandle1).setPosition(glm::vec3(-8, 1, -0.5));
+	cameraManager.getCamera(camHandle1).setNearFar(0.1f, 30.0f);
+
+	vkcv::scene::Scene scene = vkcv::scene::Scene::load(
+			core,
+			std::filesystem::path(argc > 1 ? argv[1] : "assets/Sponza/Sponza.gltf"),
+			{
+				vkcv::asset::PrimitiveType::POSITION,
+				vkcv::asset::PrimitiveType::NORMAL,
+				vkcv::asset::PrimitiveType::TEXCOORD_0
+			}
 	);
-
-	const vkcv::AttachmentDescription depth_attachment(
-		vkcv::AttachmentOperation::STORE,
-		vkcv::AttachmentOperation::CLEAR,
-		vk::Format::eD32Sfloat
+	
+	vkcv::PassHandle scenePass = vkcv::passSwapchain(
+			core,
+			window.getSwapchain(),
+			{ vk::Format::eUndefined, vk::Format::eD32Sfloat }
 	);
 
-	vkcv::PassConfig scenePassDefinition({ present_color_attachment, depth_attachment });
-	vkcv::PassHandle scenePass = core.createPass(scenePassDefinition);
-
 	if (!scenePass) {
 		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
 		return EXIT_FAILURE;
 	}
 
-	vkcv::ShaderProgram sceneShaderProgram{};
-	sceneShaderProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/vert.spv"));
-	sceneShaderProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/frag.spv"));
+	vkcv::ShaderProgram sceneShaderProgram;
+	vkcv::shader::GLSLCompiler compiler;
+	
+	compiler.compileProgram(sceneShaderProgram, {
+		{ vkcv::ShaderStage::VERTEX, "assets/shaders/shader.vert" },
+		{ vkcv::ShaderStage::FRAGMENT, "assets/shaders/shader.frag" }
+	}, nullptr);
 
 	const std::vector<vkcv::VertexAttachment> vertexAttachments = sceneShaderProgram.getVertexAttachments();
 	std::vector<vkcv::VertexBinding> bindings;
+	
 	for (size_t i = 0; i < vertexAttachments.size(); i++) {
-		bindings.push_back(vkcv::VertexBinding(i, { vertexAttachments[i] }));
+		bindings.push_back(vkcv::createVertexBinding(i, { vertexAttachments[i] }));
 	}
 
-	const vkcv::VertexLayout sceneLayout(bindings);
-
-	uint32_t setID = 0;
-
-	std::vector<vkcv::DescriptorBinding> descriptorBindings = { sceneShaderProgram.getReflectedDescriptors()[setID] };
-
-	vkcv::SamplerHandle sampler = core.createSampler(
-		vkcv::SamplerFilterType::LINEAR,
-		vkcv::SamplerFilterType::LINEAR,
-		vkcv::SamplerMipmapMode::LINEAR,
-		vkcv::SamplerAddressMode::REPEAT
+	const vkcv::VertexLayout sceneLayout { bindings };
+	const auto& material0 = scene.getMaterial(0);
+	
+	vkcv::GraphicsPipelineHandle scenePipeline = core.createGraphicsPipeline(
+			vkcv::GraphicsPipelineConfig(
+					sceneShaderProgram,
+					scenePass,
+					{ sceneLayout },
+					{ material0.getDescriptorSetLayout() }
+			)
 	);
-
-	std::vector<vkcv::Image> sceneImages;
-	std::vector<vkcv::DescriptorSetHandle> descriptorSets;
-	for (const auto& vertexGroup : scene.vertexGroups) {
-		descriptorSets.push_back(core.createDescriptorSet(descriptorBindings));
-
-		const auto& material = scene.materials[vertexGroup.materialIndex];
-
-		int baseColorIndex = material.baseColor;
-		if (baseColorIndex < 0) {
-			vkcv_log(vkcv::LogLevel::WARNING, "Material lacks base color");
-			baseColorIndex = 0;
-		}
-
-		vkcv::asset::Texture& sceneTexture = scene.textures[baseColorIndex];
-
-		sceneImages.push_back(core.createImage(vk::Format::eR8G8B8A8Srgb, sceneTexture.w, sceneTexture.h));
-		sceneImages.back().fill(sceneTexture.data.data());
-
-		vkcv::DescriptorWrites setWrites;
-		setWrites.sampledImageWrites = { vkcv::SampledImageDescriptorWrite(0, sceneImages.back().getHandle()) };
-		setWrites.samplerWrites = { vkcv::SamplerDescriptorWrite(1, sampler) };
-		core.writeDescriptorSet(descriptorSets.back(), setWrites);
-	}
-
-	const vkcv::PipelineConfig scenePipelineDefsinition{
-		sceneShaderProgram,
-		UINT32_MAX,
-		UINT32_MAX,
-		scenePass,
-		{sceneLayout},
-		{ core.getDescriptorSet(descriptorSets[0]).layout },
-		true };
-	vkcv::PipelineHandle scenePipeline = core.createGraphicsPipeline(scenePipelineDefsinition);
 	
 	if (!scenePipeline) {
 		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
 		return EXIT_FAILURE;
 	}
-
-	vkcv::ImageHandle depthBuffer = core.createImage(vk::Format::eD32Sfloat, windowWidth, windowHeight).getHandle();
+	
+	vkcv::ImageHandle depthBuffer;
 
 	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
-
-    std::vector<vkcv::DrawcallInfo> drawcalls;
-	for(int i = 0; i < scene.vertexGroups.size(); i++){
-        vkcv::Mesh renderMesh(vertexBufferBindings[i], indexBuffers[i].getVulkanHandle(), scene.vertexGroups[i].numIndices);
-
-        vkcv::DescriptorSetUsage descriptorUsage(0, core.getDescriptorSet(descriptorSets[i]).vulkanHandle);
-
-	    drawcalls.push_back(vkcv::DrawcallInfo(renderMesh, {descriptorUsage}));
-	}
-
-	std::vector<glm::mat4> modelMatrices;
-	modelMatrices.resize(scene.vertexGroups.size(), glm::mat4(1.f));
-	for (const auto &mesh : scene.meshes) {
-		const glm::mat4 m = arrayTo4x4Matrix(mesh.modelMatrix);
-		for (const auto &vertexGroupIndex : mesh.vertexGroups) {
-			modelMatrices[vertexGroupIndex] = m;
-		}
-	}
-	std::vector<glm::mat4> mvp;
-
-	auto start = std::chrono::system_clock::now();
-	while (window.isWindowOpen()) {
-        vkcv::Window::pollEvents();
-		
-		if(window.getHeight() == 0 || window.getWidth() == 0)
-			continue;
-		
-		uint32_t swapchainWidth, swapchainHeight;
-		if (!core.beginFrame(swapchainWidth, swapchainHeight)) {
-			continue;
-		}
-		
-		if ((swapchainWidth != windowWidth) || ((swapchainHeight != windowHeight))) {
-			depthBuffer = core.createImage(vk::Format::eD32Sfloat, swapchainWidth, swapchainHeight).getHandle();
-			
-			windowWidth = swapchainWidth;
-			windowHeight = swapchainHeight;
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
+		if ((!depthBuffer) ||
+			(swapchainWidth != core.getImageWidth(depthBuffer)) ||
+			(swapchainHeight != core.getImageHeight(depthBuffer))) {
+			depthBuffer = core.createImage(
+					vk::Format::eD32Sfloat,
+					vkcv::ImageConfig(
+							swapchainWidth,
+							swapchainHeight
+					)
+			);
 		}
-  
-		auto end = std::chrono::system_clock::now();
-		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
 		
-		start = end;
-		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
-		glm::mat4 vp = cameraManager.getActiveCamera().getMVP();
-
-		mvp.clear();
-        for (const auto& m : modelMatrices) {
-            mvp.push_back(vp * m);
-        }
-
-		vkcv::PushConstantData pushConstantData((void*)mvp.data(), sizeof(glm::mat4));
+		cameraManager.update(dt);
 
 		const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer };
 		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
 
-		core.recordDrawcallsToCmdStream(
-			cmdStream,
-			scenePass,
-			scenePipeline,
-			pushConstantData,
-			drawcalls,
-			renderTargets);
+		auto recordMesh = [](const glm::mat4& MVP, const glm::mat4& M,
+							 vkcv::PushConstants &pushConstants,
+							 vkcv::Drawcall& drawcall) {
+			pushConstants.appendDrawcall(MVP);
+		};
+		
+		scene.recordDrawcalls(
+				cmdStream,
+				cameraManager.getActiveCamera(),
+				scenePass,
+				scenePipeline,
+				sizeof(glm::mat4),
+				recordMesh,
+				renderTargets,
+				windowHandle
+		);
+		
 		core.prepareSwapchainImageForPresent(cmdStream);
 		core.submitCommandStream(cmdStream);
-		core.endFrame();
-	}
+
+        gui.beginGUI();
+
+        ImGui::Begin("Settings");
+        ImGui::Text("Deltatime %fms, %f", dt * 1000, 1/dt);
+        ImGui::End();
+
+        gui.endGUI();
+	});
 	
 	return 0;
 }
diff --git a/projects/first_triangle/CMakeLists.txt b/projects/first_triangle/CMakeLists.txt
index ba8c83c06fc804082e6a0a14c3c0414899ef3057..660979099b0fe0096478443a86ddf48c14bd61b0 100644
--- a/projects/first_triangle/CMakeLists.txt
+++ b/projects/first_triangle/CMakeLists.txt
@@ -2,24 +2,11 @@ cmake_minimum_required(VERSION 3.16)
 project(first_triangle)
 
 # setting c++ standard for the project
-set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD 20)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
-# this should fix the execution path to load local files from the project
-set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
-
 # adding source files to the project
-add_executable(first_triangle src/main.cpp)
-
-# this should fix the execution path to load local files from the project (for MSVC)
-if(MSVC)
-	set_target_properties(first_triangle PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-	set_target_properties(first_triangle PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-
-	# in addition to setting the output directory, the working directory has to be set
-	# by default visual studio sets the working directory to the build directory, when using the debugger
-	set_target_properties(first_triangle PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-endif()
+add_project(first_triangle src/main.cpp)
 
 # including headers of dependencies and the VkCV framework
 target_include_directories(first_triangle SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_testing_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include} ${vkcv_gui_include})
diff --git a/projects/first_triangle/README.md b/projects/first_triangle/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..da8cde4e5ae9c1a1b3dfcd2c43204c6dfd26920a
--- /dev/null
+++ b/projects/first_triangle/README.md
@@ -0,0 +1,9 @@
+# First triangle
+An example project to show a triangle can be rendered with the VkCV framework
+
+![Screenshot](../../screenshots/first_triangle.png)
+
+## Details
+
+This project was the first step of the development to this framework. It was the proof of concept 
+the designed API can work to reduce the required overhead code for applications using Vulkan.
diff --git a/projects/first_triangle/shaders/comp.spv b/projects/first_triangle/shaders/comp.spv
deleted file mode 100644
index b414e36b2bea66dab00746298e536d029091e0fd..0000000000000000000000000000000000000000
Binary files a/projects/first_triangle/shaders/comp.spv and /dev/null differ
diff --git a/projects/first_triangle/shaders/compile.bat b/projects/first_triangle/shaders/compile.bat
deleted file mode 100644
index 17743a7c49cdfc6e091c43a42a0adb755a731682..0000000000000000000000000000000000000000
--- a/projects/first_triangle/shaders/compile.bat
+++ /dev/null
@@ -1,4 +0,0 @@
-%VULKAN_SDK%\Bin32\glslc.exe shader.vert -o vert.spv
-%VULKAN_SDK%\Bin32\glslc.exe shader.frag -o frag.spv
-%VULKAN_SDK%\Bin32\glslc.exe shader.comp -o comp.spv
-pause
\ No newline at end of file
diff --git a/projects/first_triangle/shaders/frag.spv b/projects/first_triangle/shaders/frag.spv
deleted file mode 100644
index cb13e606fc0041e24ff6a63c0ec7dcca466732aa..0000000000000000000000000000000000000000
Binary files a/projects/first_triangle/shaders/frag.spv and /dev/null differ
diff --git a/projects/first_triangle/shaders/shader.comp b/projects/first_triangle/shaders/shader.comp
deleted file mode 100644
index fad6cd0815f2f09bf92dcc3171e2e3723f5466df..0000000000000000000000000000000000000000
--- a/projects/first_triangle/shaders/shader.comp
+++ /dev/null
@@ -1,25 +0,0 @@
-#version 440
-
-layout(std430, binding = 0) buffer testBuffer
-{ 
-    float test1[10];
-    float test2[10];
-    float test3[10];
-};
-
-layout( push_constant ) uniform constants{
-    float pushConstant;
-};
-
-layout(local_size_x = 5) in;
-
-void main(){
-
-    if(gl_GlobalInvocationID.x >= 10){
-        return;
-    }
-
-    test1[gl_GlobalInvocationID.x] = gl_GlobalInvocationID.x;
-    test2[gl_GlobalInvocationID.x] = 69;  // nice!
-    test3[gl_GlobalInvocationID.x] = pushConstant;    
-}
\ No newline at end of file
diff --git a/projects/first_triangle/shaders/vert.spv b/projects/first_triangle/shaders/vert.spv
deleted file mode 100644
index 03af5758ffff1b5b6505fe98b02044849026832d..0000000000000000000000000000000000000000
Binary files a/projects/first_triangle/shaders/vert.spv and /dev/null differ
diff --git a/projects/first_triangle/src/main.cpp b/projects/first_triangle/src/main.cpp
index 20cfdddf5c1baa9e8727312daa36de94bd56672f..aae046da7f84bcf26017f5fe609732aa2e0e4df7 100644
--- a/projects/first_triangle/src/main.cpp
+++ b/projects/first_triangle/src/main.cpp
@@ -1,233 +1,96 @@
 #include <iostream>
 #include <vkcv/Core.hpp>
+#include <vkcv/Pass.hpp>
 #include <GLFW/glfw3.h>
 #include <vkcv/camera/CameraManager.hpp>
-#include <chrono>
-
 #include <vkcv/shader/GLSLCompiler.hpp>
-#include <vkcv/gui/GUI.hpp>
 
 int main(int argc, const char** argv) {
-	const char* applicationName = "First Triangle";
+	const std::string applicationName = "First Triangle";
 
 	const int windowWidth = 800;
 	const int windowHeight = 600;
-	vkcv::Window window = vkcv::Window::create(
-		applicationName,
-		windowWidth,
-		windowHeight,
-		false
-	);
-
+	
 	vkcv::Core core = vkcv::Core::create(
-		window,
 		applicationName,
 		VK_MAKE_VERSION(0, 0, 1),
-		{ vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },
-		{},
-		{ "VK_KHR_swapchain" }
+		{ vk::QueueFlagBits::eGraphics },
+		{ VK_KHR_SWAPCHAIN_EXTENSION_NAME }
 	);
-	
-	vkcv::gui::GUI gui (core, window);
-
-	const auto& context = core.getContext();
-	const vk::Instance& instance = context.getInstance();
-	const vk::PhysicalDevice& physicalDevice = context.getPhysicalDevice();
-	const vk::Device& device = context.getDevice();
-
-	struct vec3 {
-		float x, y, z;
-	};
-
-	const size_t n = 5027;
-
-	auto testBuffer = core.createBuffer<vec3>(vkcv::BufferType::VERTEX, n, vkcv::BufferMemoryType::DEVICE_LOCAL);
-	vec3 vec_data[n];
-
-	for (size_t i = 0; i < n; i++) {
-		vec_data[i] = { 42, static_cast<float>(i), 7 };
-	}
-
-	testBuffer.fill(vec_data);
 
-	auto triangleIndexBuffer = core.createBuffer<uint16_t>(vkcv::BufferType::INDEX, n, vkcv::BufferMemoryType::DEVICE_LOCAL);
-	uint16_t indices[3] = { 0, 1, 2 };
-	triangleIndexBuffer.fill(&indices[0], sizeof(indices));
-
-	/*vec3* m = buffer.map();
-	m[0] = { 0, 0, 0 };
-	m[1] = { 0, 0, 0 };
-	m[2] = { 0, 0, 0 };
-	buffer.unmap();*/
-
-	vkcv::SamplerHandle sampler = core.createSampler(
-		vkcv::SamplerFilterType::NEAREST,
-		vkcv::SamplerFilterType::NEAREST,
-		vkcv::SamplerMipmapMode::NEAREST,
-		vkcv::SamplerAddressMode::REPEAT
+	vkcv::WindowHandle windowHandle = core.createWindow(applicationName, windowWidth, windowHeight, true);
+	vkcv::Window& window = core.getWindow(windowHandle);
+	
+	vkcv::PassHandle trianglePass = vkcv::passSwapchain(
+			core,
+			window.getSwapchain(),
+			{ vk::Format::eUndefined }
 	);
 
-	std::cout << "Physical device: " << physicalDevice.getProperties().deviceName << std::endl;
-
-	switch (physicalDevice.getProperties().vendorID) {
-	case 0x1002: std::cout << "Running AMD huh? You like underdogs, are you a Linux user?" << std::endl; break;
-	case 0x10DE: std::cout << "An NVidia GPU, how predictable..." << std::endl; break;
-	case 0x8086: std::cout << "Poor child, running on an Intel GPU, probably integrated..."
-		"or perhaps you are from the future with a dedicated one?" << std::endl; break;
-	case 0x13B5: std::cout << "ARM? What the hell are you running on, next thing I know you're trying to run Vulkan on a leg..." << std::endl; break;
-	default: std::cout << "Unknown GPU vendor?! Either you're on an exotic system or your driver is broken..." << std::endl;
-	}
-
-	// an example attachment for passes that output to the window
-	const vkcv::AttachmentDescription present_color_attachment(
-		vkcv::AttachmentOperation::STORE,
-		vkcv::AttachmentOperation::CLEAR,
-		core.getSwapchain().getFormat());
-
-	vkcv::PassConfig trianglePassDefinition({ present_color_attachment });
-	vkcv::PassHandle trianglePass = core.createPass(trianglePassDefinition);
-
-	if (!trianglePass)
-	{
+	if (!trianglePass) {
 		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
 		return EXIT_FAILURE;
 	}
+	
+	core.setDebugLabel(trianglePass, "Triangle Pass");
 
-	vkcv::ShaderProgram triangleShaderProgram{};
+	vkcv::ShaderProgram triangleShaderProgram;
 	vkcv::shader::GLSLCompiler compiler;
 	
-	compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("shaders/shader.vert"),
-					 [&triangleShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
-		 triangleShaderProgram.addShader(shaderStage, path);
-	});
-	
-	compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("shaders/shader.frag"),
-					 [&triangleShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
-		triangleShaderProgram.addShader(shaderStage, path);
-	});
-
-	const vkcv::PipelineConfig trianglePipelineDefinition {
-		triangleShaderProgram,
-		(uint32_t)windowWidth,
-		(uint32_t)windowHeight,
-		trianglePass,
-		{},
-		{},
-		false
-	};
-
-	vkcv::PipelineHandle trianglePipeline = core.createGraphicsPipeline(trianglePipelineDefinition);
+	compiler.compileProgram(triangleShaderProgram, {
+		{vkcv::ShaderStage::VERTEX, "shaders/shader.vert"},
+		{ vkcv::ShaderStage::FRAGMENT, "shaders/shader.frag" }
+	}, nullptr);
+
+	vkcv::GraphicsPipelineHandle trianglePipeline = core.createGraphicsPipeline(
+			vkcv::GraphicsPipelineConfig(
+					triangleShaderProgram,
+					trianglePass,
+					{},
+					{}
+			)
+	);
 
-	if (!trianglePipeline)
-	{
+	if (!trianglePipeline) {
 		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
 		return EXIT_FAILURE;
 	}
+	
+	core.setDebugLabel(trianglePipeline, "Triangle Pipeline");
 
-	// Compute Pipeline
-	vkcv::ShaderProgram computeShaderProgram{};
-	computeShaderProgram.addShader(vkcv::ShaderStage::COMPUTE, std::filesystem::path("shaders/comp.spv"));
-
-	// take care, assuming shader has exactly one descriptor set
-	vkcv::DescriptorSetHandle computeDescriptorSet = core.createDescriptorSet(computeShaderProgram.getReflectedDescriptors()[0]);
-
-	vkcv::PipelineHandle computePipeline = core.createComputePipeline(
-		computeShaderProgram, 
-		{ core.getDescriptorSet(computeDescriptorSet).layout });
-
-	struct ComputeTestBuffer {
-		float test1[10];
-		float test2[10];
-		float test3[10];
-	};
-
-	vkcv::Buffer computeTestBuffer = core.createBuffer<ComputeTestBuffer>(vkcv::BufferType::STORAGE, 1);
-
-	vkcv::DescriptorWrites computeDescriptorWrites;
-	computeDescriptorWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, computeTestBuffer.getHandle()) };
-	core.writeDescriptorSet(computeDescriptorSet, computeDescriptorWrites);
-
-	/*
-	 * BufferHandle triangleVertices = core.createBuffer(vertices);
-	 * BufferHandle triangleIndices = core.createBuffer(indices);
-	 *
-	 * // triangle Model creation goes here
-	 *
-	 *
-	 * // attachment creation goes here
-	 * PassHandle trianglePass = core.CreatePass(presentationPass);
-	 *
-	 * // shader creation goes here
-	 * // material creation goes here
-	 *
-	 * PipelineHandle trianglePipeline = core.CreatePipeline(trianglePipeline);
-	 */
-	auto start = std::chrono::system_clock::now();
-
-	vkcv::ImageHandle swapchainImageHandle = vkcv::ImageHandle::createSwapchainImageHandle();
-
-	const vkcv::Mesh renderMesh({}, triangleIndexBuffer.getVulkanHandle(), 3);
-	vkcv::DrawcallInfo drawcall(renderMesh, {});
+	vkcv::VertexData vertexData;
+	vertexData.setCount(3);
+	
+	vkcv::InstanceDrawcall drawcall (vertexData);
 
 	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
 
     vkcv::camera::CameraManager cameraManager(window);
-    uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
-    uint32_t camIndex1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+    auto camHandle = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
 	
-	cameraManager.getCamera(camIndex0).setPosition(glm::vec3(0, 0, -2));
-    cameraManager.getCamera(camIndex1).setPosition(glm::vec3(0.0f, 0.0f, 0.0f));
-    cameraManager.getCamera(camIndex1).setCenter(glm::vec3(0.0f, 0.0f, -1.0f));
-
-	while (window.isWindowOpen())
-	{
-        window.pollEvents();
+	cameraManager.getCamera(camHandle).setPosition(glm::vec3(0, 0, -2));
 
-		uint32_t swapchainWidth, swapchainHeight; // No resizing = No problem
-		if (!core.beginFrame(swapchainWidth, swapchainHeight)) {
-			continue;
-		}
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+			uint32_t swapchainWidth, uint32_t swapchainHeight) {
+		cameraManager.update(dt);
 		
-        auto end = std::chrono::system_clock::now();
-        auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
-        start = end;
+		glm::mat4 mvp = cameraManager.getActiveCamera().getMVP();
 		
-		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
-        glm::mat4 mvp = cameraManager.getActiveCamera().getMVP();
-
-		vkcv::PushConstantData pushConstantData((void*)&mvp, sizeof(glm::mat4));
 		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
-
+		core.setDebugLabel(cmdStream, "Render Commands");
+		
 		core.recordDrawcallsToCmdStream(
-			cmdStream,
-			trianglePass,
-			trianglePipeline,
-			pushConstantData,
-			{ drawcall },
-			{ swapchainInput });
-
-		const uint32_t dispatchSize[3] = { 2, 1, 1 };
-		const float theMeaningOfLife = 42;
-
-		core.recordComputeDispatchToCmdStream(
-			cmdStream,
-			computePipeline,
-			dispatchSize,
-			{ vkcv::DescriptorSetUsage(0, core.getDescriptorSet(computeDescriptorSet).vulkanHandle) },
-			vkcv::PushConstantData((void*)&theMeaningOfLife, sizeof(theMeaningOfLife)));
-
+				cmdStream,
+				trianglePipeline,
+				vkcv::pushConstants<glm::mat4>(mvp),
+				{ drawcall },
+				{ swapchainInput },
+				windowHandle
+		);
+		
 		core.prepareSwapchainImageForPresent(cmdStream);
 		core.submitCommandStream(cmdStream);
-		
-		gui.beginGUI();
-		
-		ImGui::Begin("Hello world");
-		ImGui::Text("This is a test!");
-		ImGui::End();
-		
-		gui.endGUI();
-	    
-	    core.endFrame();
-	}
+	});
+	
 	return 0;
 }
diff --git a/projects/head_demo/.gitignore b/projects/head_demo/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..151f56c5bb7098a3beb7b8e049c0ade02914cd91
--- /dev/null
+++ b/projects/head_demo/.gitignore
@@ -0,0 +1 @@
+head_demo
\ No newline at end of file
diff --git a/projects/head_demo/CMakeLists.txt b/projects/head_demo/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..82aebb083054b80f3cf9cbb1bddb94f447e81903
--- /dev/null
+++ b/projects/head_demo/CMakeLists.txt
@@ -0,0 +1,33 @@
+cmake_minimum_required(VERSION 3.16)
+project(head_demo)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+# adding source files to the project
+add_project(head_demo src/main.cpp)
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(head_demo SYSTEM BEFORE PRIVATE
+		${vkcv_include}
+		${vkcv_includes}
+		${vkcv_asset_loader_include}
+		${vkcv_camera_include}
+		${vkcv_scene_include}
+		${vkcv_shader_compiler_include}
+		${vkcv_gui_include}
+		${vkcv_effects_include}
+		${vkcv_upscaling_include})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(head_demo
+		vkcv
+		${vkcv_libraries}
+		vkcv_asset_loader
+		${vkcv_asset_loader_libraries}
+		vkcv_camera vkcv_scene
+		vkcv_shader_compiler
+		vkcv_gui
+		vkcv_effects
+		vkcv_upscaling)
diff --git a/projects/head_demo/README.md b/projects/head_demo/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..c9b0bebc7cda14becd29ef1a6cba950e75b9bd79
--- /dev/null
+++ b/projects/head_demo/README.md
@@ -0,0 +1,11 @@
+# Head demo
+An example project to show how the VkCV framework could be used for medical applications
+
+![Screenshot](../../screenshots/head_demo.png)
+
+## Details
+
+This project renders a mesh from a human skull by [HannahNewey](https://sketchfab.com/HannahNewey)
+and splits the rendering on three axes into diffusive shading and drawing the vertices as point 
+cloud. In a similar way you could visualize different layers of a human body to make a specific 
+part the visual focus without blending the layers above completely away.
diff --git a/projects/head_demo/assets/shaders/clip.inc b/projects/head_demo/assets/shaders/clip.inc
new file mode 100644
index 0000000000000000000000000000000000000000..edb5ac3ccfe5d7f6ad1e59dd88bee4446b85118f
--- /dev/null
+++ b/projects/head_demo/assets/shaders/clip.inc
@@ -0,0 +1,19 @@
+
+#define CLIP_SCALE 10000.0f
+
+vec4 clipPosition(vec4 pos) {
+    return vec4(
+        max(clipX, pos.x),
+        max(clipY, pos.y),
+        max(clipZ, pos.z),
+        1.0f / CLIP_SCALE
+    );
+}
+
+vec4 clipByLimit(vec4 pos) {
+    if (pos.x / pos.w < clipLimit) {
+        return vec4(pos.xyz / pos.w, 1.0f);
+    } else {
+        return vec4(clipLimit, pos.y / pos.w, pos.z / pos.w, 1.0f);
+    }
+}
diff --git a/projects/head_demo/assets/shaders/red.frag b/projects/head_demo/assets/shaders/red.frag
new file mode 100644
index 0000000000000000000000000000000000000000..35735be779edd0bf88890638329dc53f6b691f57
--- /dev/null
+++ b/projects/head_demo/assets/shaders/red.frag
@@ -0,0 +1,11 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 passNormal;
+layout(location = 1) in vec3 passEdge;
+
+layout(location = 0) out vec3 outColor;
+
+void main()	{
+    outColor = (0.1f + max(dot(passNormal, vec3(1.0f, -1.0f, 0.5f)), 0.0f) * 0.9f) * (passEdge + 0.5f);
+}
\ No newline at end of file
diff --git a/projects/head_demo/assets/shaders/shader.frag b/projects/head_demo/assets/shaders/shader.frag
new file mode 100644
index 0000000000000000000000000000000000000000..b8756a6b84af4e93f9ac932ec01d28e4f2e9638b
--- /dev/null
+++ b/projects/head_demo/assets/shaders/shader.frag
@@ -0,0 +1,10 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 passNormal;
+
+layout(location = 0) out vec3 outColor;
+
+void main()	{
+	outColor = (vec3(0.3f) + max(dot(passNormal, vec3(1.0f, -1.0f, 0.5f)), 0.0f) * vec3(0.7f));
+}
\ No newline at end of file
diff --git a/projects/head_demo/assets/shaders/shader.geom b/projects/head_demo/assets/shaders/shader.geom
new file mode 100644
index 0000000000000000000000000000000000000000..275b300ee3466e117876aa46a6ea1cde11f6f17d
--- /dev/null
+++ b/projects/head_demo/assets/shaders/shader.geom
@@ -0,0 +1,53 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+layout(triangles) in;
+layout(triangle_strip, max_vertices = 3) out;
+
+layout(location = 0) in vec3 geomNormal[];
+
+layout(location = 0) out vec3 passNormal;
+
+layout(set=1, binding=0) uniform clipBuffer {
+    float clipLimit;
+    float clipX;
+    float clipY;
+    float clipZ;
+};
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+};
+
+#include "clip.inc"
+
+void main()	{
+    vec4 v0 = gl_in[0].gl_Position;
+    vec4 v1 = gl_in[1].gl_Position;
+    vec4 v2 = gl_in[2].gl_Position;
+
+    v0 = clipPosition(v0 / CLIP_SCALE);
+    v1 = clipPosition(v1 / CLIP_SCALE);
+    v2 = clipPosition(v2 / CLIP_SCALE);
+
+    float dx = abs(v0.x - clipX) + abs(v1.x - clipX) + abs(v2.x - clipX);
+    float dy = abs(v0.y - clipY) + abs(v1.y - clipY) + abs(v2.y - clipY);
+    float dz = abs(v0.z - clipZ) + abs(v1.z - clipZ) + abs(v2.z - clipZ);
+
+    if (dx * dy * dz > 0.0f) {
+        gl_Position = mvp * (v0 * CLIP_SCALE);
+        passNormal = geomNormal[0];
+        EmitVertex();
+
+        gl_Position = mvp * (v1 * CLIP_SCALE);
+        passNormal = geomNormal[1];
+        EmitVertex();
+
+        gl_Position = mvp * (v2 * CLIP_SCALE);
+        passNormal = geomNormal[2];
+        EmitVertex();
+
+        EndPrimitive();
+    }
+}
diff --git a/projects/head_demo/assets/shaders/shader.vert b/projects/head_demo/assets/shaders/shader.vert
new file mode 100644
index 0000000000000000000000000000000000000000..26e43e9c89dc2ffb2355d60be8288fa9832f8baa
--- /dev/null
+++ b/projects/head_demo/assets/shaders/shader.vert
@@ -0,0 +1,12 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 inPosition;
+layout(location = 1) in vec3 inNormal;
+
+layout(location = 0) out vec3 geomNormal;
+
+void main()	{
+	gl_Position = vec4(inPosition, 1.0);
+	geomNormal  = inNormal;
+}
\ No newline at end of file
diff --git a/projects/head_demo/assets/shaders/wired.geom b/projects/head_demo/assets/shaders/wired.geom
new file mode 100644
index 0000000000000000000000000000000000000000..689e073dde7fd993d164cfea0b7ca777d79f8508
--- /dev/null
+++ b/projects/head_demo/assets/shaders/wired.geom
@@ -0,0 +1,53 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+layout(triangles) in;
+layout(points, max_vertices = 1) out;
+
+layout(location = 0) in vec3 geomNormal[];
+
+layout(location = 0) out vec3 passNormal;
+layout(location = 1) out vec3 passEdge;
+
+layout(set=1, binding=0) uniform clipBuffer {
+    float clipLimit;
+    float clipX;
+    float clipY;
+    float clipZ;
+};
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+};
+
+#include "clip.inc"
+
+void main()	{
+    vec4 v0 = gl_in[0].gl_Position;
+    vec4 v1 = gl_in[1].gl_Position;
+    vec4 v2 = gl_in[2].gl_Position;
+
+    v0 = clipPosition(v0 / CLIP_SCALE);
+    v1 = clipPosition(v1 / CLIP_SCALE);
+    v2 = clipPosition(v2 / CLIP_SCALE);
+
+    float dx = abs(v0.x - clipX) + abs(v1.x - clipX) + abs(v2.x - clipX);
+    float dy = abs(v0.y - clipY) + abs(v1.y - clipY) + abs(v2.y - clipY);
+    float dz = abs(v0.z - clipZ) + abs(v1.z - clipZ) + abs(v2.z - clipZ);
+
+    if (dx * dy * dz <= 0.0f) {
+        v0 = clipByLimit(mvp * gl_in[0].gl_Position);
+        v1 = clipByLimit(mvp * gl_in[1].gl_Position);
+        v2 = clipByLimit(mvp * gl_in[2].gl_Position);
+
+        if ((v0.x < clipLimit) || (v1.x < clipLimit) || (v2.x < clipLimit)) {
+            gl_Position = (v0 + v1 + v2) / 3;
+            passNormal = (geomNormal[0] + geomNormal[1] + geomNormal[2]) / 3;
+            passEdge = vec3(dx, dy, dz);
+            EmitVertex();
+
+            EndPrimitive();
+        }
+    }
+}
diff --git a/projects/head_demo/assets/skull/license.txt b/projects/head_demo/assets/skull/license.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6816d6d659087b54dfc6b8e0c2c0e158daa001f0
--- /dev/null
+++ b/projects/head_demo/assets/skull/license.txt
@@ -0,0 +1,11 @@
+Model Information:
+* title:	The Anatomy of the Human Skull
+* source:	https://sketchfab.com/3d-models/the-anatomy-of-the-human-skull-baf6ac7b781a46218dca2b59dee58817
+* author:	HannahNewey (https://sketchfab.com/HannahNewey)
+
+Model License:
+* license type:	CC-BY-NC-SA-4.0 (http://creativecommons.org/licenses/by-nc-sa/4.0/)
+* requirements:	Author must be credited. No commercial use. Modified versions must have the same license.
+
+If you use this 3D model in your project be sure to copy paste this credit wherever you share it:
+This work is based on "The Anatomy of the Human Skull" (https://sketchfab.com/3d-models/the-anatomy-of-the-human-skull-baf6ac7b781a46218dca2b59dee58817) by HannahNewey (https://sketchfab.com/HannahNewey) licensed under CC-BY-NC-SA-4.0 (http://creativecommons.org/licenses/by-nc-sa/4.0/)
\ No newline at end of file
diff --git a/projects/head_demo/assets/skull/scene.bin b/projects/head_demo/assets/skull/scene.bin
new file mode 100644
index 0000000000000000000000000000000000000000..e995091c9d2f10b15959a65d3b0e0de3c9edb0e4
--- /dev/null
+++ b/projects/head_demo/assets/skull/scene.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:df3cb5e715426a8e7a3c4883eb7c53022e09011ded9ae385cd6bc59824129ed8
+size 78176672
diff --git a/projects/head_demo/assets/skull/scene.gltf b/projects/head_demo/assets/skull/scene.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..6a2b75fa5af1e826bf3851db576dd3051e7b77c3
--- /dev/null
+++ b/projects/head_demo/assets/skull/scene.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8cd4f673e0a555dc138b96e07be60546a799f9ef11837870eae27b16ffd5c1c7
+size 36442
diff --git a/projects/head_demo/assets/skull_scaled/scene.bin b/projects/head_demo/assets/skull_scaled/scene.bin
new file mode 100644
index 0000000000000000000000000000000000000000..3edc8865ed5465c33cb8c9d2f2b0eb83b1a5b599
--- /dev/null
+++ b/projects/head_demo/assets/skull_scaled/scene.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:685e971ce4e99be286464c56a373e528dc507ce18b5bfdff45f90db27e49176b
+size 56596296
diff --git a/projects/head_demo/assets/skull_scaled/scene.gltf b/projects/head_demo/assets/skull_scaled/scene.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..72087f182e4cd1ce1a73f7af9db1d36608df2029
--- /dev/null
+++ b/projects/head_demo/assets/skull_scaled/scene.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f05a4c8f979dab2793540bafe2b392133d64af028a0ff97f0159dbc52fc20230
+size 4843
diff --git a/projects/head_demo/src/main.cpp b/projects/head_demo/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f1e9882852bc56ab9834011e5b27d4b5c9ff081f
--- /dev/null
+++ b/projects/head_demo/src/main.cpp
@@ -0,0 +1,267 @@
+#include <iostream>
+#include <vkcv/Buffer.hpp>
+#include <vkcv/Core.hpp>
+#include <vkcv/Pass.hpp>
+#include <GLFW/glfw3.h>
+#include <vkcv/camera/CameraManager.hpp>
+#include <vkcv/gui/GUI.hpp>
+#include <vkcv/asset/asset_loader.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+#include <vkcv/scene/Scene.hpp>
+#include <vkcv/effects/BloomAndFlaresEffect.hpp>
+#include <vkcv/upscaling/FSRUpscaling.hpp>
+
+int main(int argc, const char** argv) {
+	const std::string applicationName = "Head Demo";
+	
+	uint32_t windowWidth = 800;
+	uint32_t windowHeight = 600;
+	
+	vkcv::Features features;
+	features.requireExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
+	
+	features.requireFeature([](vk::PhysicalDeviceFeatures& features) {
+		features.setGeometryShader(true);
+	});
+	
+	vkcv::Core core = vkcv::Core::create(
+			applicationName,
+			VK_MAKE_VERSION(0, 0, 1),
+			{vk::QueueFlagBits::eTransfer, vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute},
+			features
+	);
+	
+	vkcv::WindowHandle windowHandle = core.createWindow(applicationName, windowWidth, windowHeight, true);
+	vkcv::Window& window = core.getWindow(windowHandle);
+	vkcv::camera::CameraManager cameraManager(window);
+	
+	vkcv::gui::GUI gui (core, windowHandle);
+	
+	auto camHandle0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+	auto camHandle1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+	
+	cameraManager.getCamera(camHandle0).setPosition(glm::vec3(15.5f, 0, 0));
+	cameraManager.getCamera(camHandle0).setNearFar(0.1f, 30.0f);
+	
+	cameraManager.getCamera(camHandle1).setPosition(glm::vec3(15.5f, 0, 0));
+	cameraManager.getCamera(camHandle1).setNearFar(0.1f, 30.0f);
+	
+	vkcv::scene::Scene scene = vkcv::scene::Scene::load(
+			core,
+			std::filesystem::path(argc > 1 ? argv[1] : "assets/skull_scaled/scene.gltf"),
+			{
+					vkcv::asset::PrimitiveType::POSITION,
+					vkcv::asset::PrimitiveType::NORMAL
+			}
+	);
+	
+	vk::Format colorFormat = vk::Format::eR16G16B16A16Sfloat;
+	
+	vkcv::PassHandle linePass = vkcv::passFormats(core, { colorFormat, vk::Format::eD32Sfloat });
+	vkcv::PassHandle scenePass = vkcv::passFormats(core, { colorFormat, vk::Format::eD32Sfloat }, false);
+	
+	if ((!scenePass) || (!linePass)) {
+		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+	
+	vkcv::ShaderProgram sceneShaderProgram;
+	vkcv::ShaderProgram lineShaderProgram;
+	vkcv::shader::GLSLCompiler compiler;
+	
+	compiler.compileProgram(sceneShaderProgram, {
+			{ vkcv::ShaderStage::VERTEX, "assets/shaders/shader.vert" },
+			{ vkcv::ShaderStage::GEOMETRY, "assets/shaders/shader.geom" },
+			{ vkcv::ShaderStage::FRAGMENT, "assets/shaders/shader.frag" }
+	}, nullptr);
+	
+	compiler.compileProgram(lineShaderProgram, {
+			{ vkcv::ShaderStage::VERTEX, "assets/shaders/shader.vert" },
+			{ vkcv::ShaderStage::GEOMETRY, "assets/shaders/wired.geom" },
+			{ vkcv::ShaderStage::FRAGMENT, "assets/shaders/red.frag" }
+	}, nullptr);
+	
+	const std::vector<vkcv::VertexAttachment> vertexAttachments = sceneShaderProgram.getVertexAttachments();
+	std::vector<vkcv::VertexBinding> bindings;
+	
+	for (size_t i = 0; i < vertexAttachments.size(); i++) {
+		bindings.push_back(vkcv::createVertexBinding(i, { vertexAttachments[i] }));
+	}
+	
+	const auto& clipBindings = sceneShaderProgram.getReflectedDescriptors().at(1);
+	
+	auto clipDescriptorSetLayout = core.createDescriptorSetLayout(clipBindings);
+	auto clipDescriptorSet = core.createDescriptorSet(clipDescriptorSetLayout);
+	
+	float clipLimit = 1.0f;
+	float clipX = 0.0f;
+	float clipY = 0.0f;
+	float clipZ = 0.0f;
+	
+	auto clipBuffer = vkcv::buffer<float>(core, vkcv::BufferType::UNIFORM, 4);
+	clipBuffer.fill({ clipLimit, -clipX, -clipY, -clipZ });
+	
+	vkcv::DescriptorWrites clipWrites;
+	clipWrites.writeUniformBuffer(0, clipBuffer.getHandle());
+	
+	core.writeDescriptorSet(clipDescriptorSet, clipWrites);
+	
+	float mouseX = -0.0f;
+	bool dragLimit = false;
+	
+	window.e_mouseMove.add([&](double x, double y) {
+		double cx = (x - window.getWidth() * 0.5);
+		double dx = cx / window.getWidth();
+		
+		mouseX = 2.0f * static_cast<float>(dx);
+		
+		if (dragLimit) {
+			clipLimit = mouseX;
+		}
+	});
+	
+	window.e_mouseButton.add([&](int button, int action, int mods) {
+		if ((std::abs(mouseX - clipLimit) < 0.1f) && (action == GLFW_PRESS)) {
+			dragLimit = true;
+		} else {
+			dragLimit = false;
+		}
+	});
+	
+	const vkcv::VertexLayout sceneLayout { bindings };
+	const auto& material0 = scene.getMaterial(0);
+	
+	vkcv::GraphicsPipelineHandle scenePipeline = core.createGraphicsPipeline(
+			vkcv::GraphicsPipelineConfig(
+				sceneShaderProgram,
+				scenePass,
+				{ sceneLayout },
+				{ material0.getDescriptorSetLayout(), clipDescriptorSetLayout }
+			)
+	);
+	
+	vkcv::GraphicsPipelineHandle linePipeline = core.createGraphicsPipeline(
+			vkcv::GraphicsPipelineConfig(
+					lineShaderProgram,
+					linePass,
+					{ sceneLayout },
+					{ material0.getDescriptorSetLayout(), clipDescriptorSetLayout }
+			)
+	);
+	
+	if ((!scenePipeline) || (!linePipeline)) {
+		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+	
+	auto swapchainExtent = core.getSwapchainExtent(window.getSwapchain());
+	
+	vkcv::ImageHandle depthBuffer = core.createImage(
+			vk::Format::eD32Sfloat,
+			vkcv::ImageConfig(
+					swapchainExtent.width,
+					swapchainExtent.height
+			)
+	);
+	
+	vkcv::ImageConfig colorBufferConfig (
+			swapchainExtent.width,
+			swapchainExtent.height
+	);
+	
+	colorBufferConfig.setSupportingStorage(true);
+	colorBufferConfig.setSupportingColorAttachment(true);
+	
+	vkcv::ImageHandle colorBuffer = core.createImage(
+			colorFormat,
+			colorBufferConfig
+	);
+	
+	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+	
+	vkcv::effects::BloomAndFlaresEffect bloomAndFlares (core);
+	vkcv::upscaling::FSRUpscaling upscaling (core);
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
+		if ((swapchainWidth != swapchainExtent.width) ||
+			((swapchainHeight != swapchainExtent.height))) {
+			depthBuffer = core.createImage(
+					vk::Format::eD32Sfloat,
+					vkcv::ImageConfig(
+							swapchainWidth,
+							swapchainHeight
+					)
+			);
+			
+			colorBufferConfig.setWidth(swapchainWidth);
+			colorBufferConfig.setHeight(swapchainHeight);
+			
+			colorBuffer = core.createImage(
+					colorFormat,
+					colorBufferConfig
+			);
+			
+			swapchainExtent.width = swapchainWidth;
+			swapchainExtent.height = swapchainHeight;
+		}
+
+		cameraManager.update(dt);
+		
+		clipBuffer.fill({ clipLimit, -clipX, -clipY, -clipZ });
+		
+		const std::vector<vkcv::ImageHandle> renderTargets = { colorBuffer, depthBuffer };
+		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+		
+		auto recordMesh = [&](const glm::mat4& MVP, const glm::mat4& M,
+							 vkcv::PushConstants &pushConstants,
+							 vkcv::Drawcall& drawcall) {
+			pushConstants.appendDrawcall(MVP);
+			drawcall.useDescriptorSet(1, clipDescriptorSet);
+		};
+		
+		scene.recordDrawcalls(
+				cmdStream,
+				cameraManager.getActiveCamera(),
+				linePass,
+				linePipeline,
+				sizeof(glm::mat4),
+				recordMesh,
+				renderTargets,
+				windowHandle
+		);
+		
+		bloomAndFlares.recordEffect(cmdStream, colorBuffer, colorBuffer);
+		
+		scene.recordDrawcalls(
+				cmdStream,
+				cameraManager.getActiveCamera(),
+				scenePass,
+				scenePipeline,
+				sizeof(glm::mat4),
+				recordMesh,
+				renderTargets,
+				windowHandle
+		);
+		
+		core.prepareImageForSampling(cmdStream, colorBuffer);
+		core.prepareImageForStorage(cmdStream, swapchainInput);
+		upscaling.recordUpscaling(cmdStream, colorBuffer, swapchainInput);
+		
+		core.prepareSwapchainImageForPresent(cmdStream);
+		core.submitCommandStream(cmdStream);
+		
+		gui.beginGUI();
+		
+		ImGui::Begin("Settings");
+		ImGui::SliderFloat("Clip X", &clipX, -1.0f, 1.0f);
+		ImGui::SliderFloat("Clip Y", &clipY, -1.0f, 1.0f);
+		ImGui::SliderFloat("Clip Z", &clipZ, -1.0f, 1.0f);
+		ImGui::Text("Mesh by HannahNewey (https://sketchfab.com/HannahNewey)");
+		ImGui::End();
+		
+		gui.endGUI();
+	});
+	
+	return 0;
+}
diff --git a/projects/indirect_dispatch/.gitignore b/projects/indirect_dispatch/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..5f18d9c205e538dabeb0282640bede0359edc33d
--- /dev/null
+++ b/projects/indirect_dispatch/.gitignore
@@ -0,0 +1 @@
+indirect_dispatch
diff --git a/projects/indirect_dispatch/CMakeLists.txt b/projects/indirect_dispatch/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ad59d5b24efa1af1b387253b4c007fd25c50500f
--- /dev/null
+++ b/projects/indirect_dispatch/CMakeLists.txt
@@ -0,0 +1,49 @@
+cmake_minimum_required(VERSION 3.16)
+project(indirect_dispatch)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+# adding source files to the project
+add_project(indirect_dispatch src/main.cpp)
+
+target_sources(indirect_dispatch PRIVATE
+    src/App.hpp
+    src/App.cpp
+
+    src/AppConfig.hpp
+    src/MotionBlurConfig.hpp
+    
+    src/AppSetup.hpp
+    src/AppSetup.cpp
+    
+    src/MotionBlur.hpp
+    src/MotionBlur.cpp
+    
+    src/MotionBlurSetup.hpp
+    src/MotionBlurSetup.cpp)
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(indirect_dispatch SYSTEM BEFORE PRIVATE
+		${vkcv_include}
+		${vkcv_includes}
+		${vkcv_testing_include}
+		${vkcv_camera_include}
+		${vkcv_shader_compiler_include}
+		${vkcv_gui_include}
+		${vkcv_upscaling_include}
+)
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(indirect_dispatch
+		vkcv
+		${vkcv_libraries}
+		vkcv_asset_loader
+		${vkcv_asset_loader_libraries}
+		vkcv_testing
+		vkcv_camera
+		vkcv_shader_compiler
+		vkcv_gui
+		vkcv_upscaling
+)
\ No newline at end of file
diff --git a/projects/indirect_dispatch/README.md b/projects/indirect_dispatch/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..675e61d402b4d45c362363da25c1514695cd213d
--- /dev/null
+++ b/projects/indirect_dispatch/README.md
@@ -0,0 +1,10 @@
+# Indirect dispatch
+An example project to show usage of indirect compute shader dispatching
+
+![Screenshot](../../screenshots/indirect_dispatch.png)
+
+## Details
+
+The project shows off indirect dispatching of compute shaders for an implementation of motion 
+blur. Additionally, the calculated motion vectors for the motion blur can be used for temporal 
+upscaling techniques such as FSR2.
diff --git a/projects/indirect_dispatch/assets/models/cube.bin b/projects/indirect_dispatch/assets/models/cube.bin
new file mode 100644
index 0000000000000000000000000000000000000000..728d38cd39cd10c30a93c15eef021cb0cf7dda74
--- /dev/null
+++ b/projects/indirect_dispatch/assets/models/cube.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ccc59e0be3552b4347457bc935d8e548b52f12ca91716a0f0dc37d5bac65f123
+size 840
diff --git a/projects/indirect_dispatch/assets/models/cube.gltf b/projects/indirect_dispatch/assets/models/cube.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..ef975c326c71ec1a2fa650a422989534f1c32191
--- /dev/null
+++ b/projects/indirect_dispatch/assets/models/cube.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0072448af64bdebffe8eec5a7f32f110579b1a256cd97438bf227e4cc4a87328
+size 2571
diff --git a/projects/indirect_dispatch/assets/models/grid.png b/projects/indirect_dispatch/assets/models/grid.png
new file mode 100644
index 0000000000000000000000000000000000000000..5f40eee62f7f9dba3dc156ff6a3653ea2e7f5391
--- /dev/null
+++ b/projects/indirect_dispatch/assets/models/grid.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a11c33e4935d93723ab11f597f2aca1ca1ff84af66f2e2d10a01580eb0b7831a
+size 40135
diff --git a/projects/indirect_dispatch/assets/models/ground.bin b/projects/indirect_dispatch/assets/models/ground.bin
new file mode 100644
index 0000000000000000000000000000000000000000..e29e4f18552def1ac64c167d994be959f82e35c7
--- /dev/null
+++ b/projects/indirect_dispatch/assets/models/ground.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f8e20cd1c62da3111536283517b63a149f258ea82b1dff8ddafdb79020065b7c
+size 140
diff --git a/projects/indirect_dispatch/assets/models/ground.gltf b/projects/indirect_dispatch/assets/models/ground.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..6935d3e21a06da1629087c9b0b7f957c57feaf6e
--- /dev/null
+++ b/projects/indirect_dispatch/assets/models/ground.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0a12b8d7cca8110d4ffa9bc4a2223286d1ccfd9c087739a75294e0a3fbfb65c5
+size 2840
diff --git a/projects/indirect_dispatch/assets/shaders/gammaCorrection.comp b/projects/indirect_dispatch/assets/shaders/gammaCorrection.comp
new file mode 100644
index 0000000000000000000000000000000000000000..7a6e129d7f8658d3ea424d35b809a3384d12bccc
--- /dev/null
+++ b/projects/indirect_dispatch/assets/shaders/gammaCorrection.comp
@@ -0,0 +1,24 @@
+#version 440
+#extension GL_GOOGLE_include_directive : enable
+
+layout(set=0, binding=0)        uniform texture2D   inTexture;
+layout(set=0, binding=1)        uniform sampler     textureSampler;
+layout(set=0, binding=2, rgba8) uniform image2D     outImage;
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+void main(){
+
+    ivec2 outImageRes = imageSize(outImage);
+    ivec2 coord       = ivec2(gl_GlobalInvocationID.xy);
+
+    if(any(greaterThanEqual(coord, outImageRes)))
+        return;
+   
+    vec2 uv             = vec2(coord) / outImageRes;
+    vec3 linearColor    = texture(sampler2D(inTexture, textureSampler), uv).rgb;
+    
+    vec3 gammaCorrected = pow(linearColor, vec3(1 / 2.2));
+
+    imageStore(outImage, coord, vec4(gammaCorrected, 0.f));
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/assets/shaders/mesh.frag b/projects/indirect_dispatch/assets/shaders/mesh.frag
new file mode 100644
index 0000000000000000000000000000000000000000..531c9cbf8b5e097af618d2ca639821a62a30611d
--- /dev/null
+++ b/projects/indirect_dispatch/assets/shaders/mesh.frag
@@ -0,0 +1,17 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 passNormal;
+layout(location = 1) in vec2 passUV;
+
+layout(location = 0) out vec3 outColor;
+
+layout(set=0, binding=0)    uniform texture2D   albedoTexture;
+layout(set=0, binding=1)    uniform sampler     textureSampler;
+
+void main()	{
+    vec3    albedo  = texture(sampler2D(albedoTexture, textureSampler), passUV).rgb;
+    vec3    N       = normalize(passNormal);
+    float   light   = max(N.y * 0.5 + 0.5, 0);
+    outColor        = light * albedo;
+}
\ No newline at end of file
diff --git a/projects/bloom/resources/shaders/shader.vert b/projects/indirect_dispatch/assets/shaders/mesh.vert
similarity index 68%
rename from projects/bloom/resources/shaders/shader.vert
rename to projects/indirect_dispatch/assets/shaders/mesh.vert
index 926f86af2860cb57c44d2d5ee78712b6ae155e5c..734fd63cdee66e5fbf61cc427ca21fae18a31d82 100644
--- a/projects/bloom/resources/shaders/shader.vert
+++ b/projects/indirect_dispatch/assets/shaders/mesh.vert
@@ -7,7 +7,6 @@ layout(location = 2) in vec2 inUV;
 
 layout(location = 0) out vec3 passNormal;
 layout(location = 1) out vec2 passUV;
-layout(location = 2) out vec3 passPos;
 
 layout( push_constant ) uniform constants{
     mat4 mvp;
@@ -16,7 +15,6 @@ layout( push_constant ) uniform constants{
 
 void main()	{
 	gl_Position = mvp * vec4(inPosition, 1.0);
-	passNormal  = mat3(model) * inNormal;    // assuming no weird stuff like shearing or non-uniform scaling
+	passNormal  = (model * vec4(inNormal, 0)).xyz;
     passUV      = inUV;
-    passPos     = (model * vec4(inPosition, 1)).xyz;
 }
\ No newline at end of file
diff --git a/projects/indirect_dispatch/assets/shaders/motionBlur.comp b/projects/indirect_dispatch/assets/shaders/motionBlur.comp
new file mode 100644
index 0000000000000000000000000000000000000000..091c21aa7ddfe9db1780aa64adc77fd5457a3843
--- /dev/null
+++ b/projects/indirect_dispatch/assets/shaders/motionBlur.comp
@@ -0,0 +1,194 @@
+#version 440
+#extension GL_GOOGLE_include_directive : enable
+
+#include "motionBlur.inc"
+#include "motionBlurConfig.inc"
+#include "motionBlurWorkTile.inc"
+
+layout(set=0, binding=0)                    uniform texture2D   inColor;
+layout(set=0, binding=1)                    uniform texture2D   inDepth;
+layout(set=0, binding=2)                    uniform texture2D   inMotionFullRes;
+layout(set=0, binding=3)                    uniform texture2D   inMotionNeighbourhoodMax;  
+layout(set=0, binding=4)                    uniform sampler     nearestSampler;
+layout(set=0, binding=5, r11f_g11f_b10f)    uniform image2D     outImage;
+
+layout(set=0, binding=6) buffer WorkTileBuffer {
+    WorkTiles workTiles;
+};
+
+layout(local_size_x = motionTileSize, local_size_y = motionTileSize, local_size_z = 1) in;
+
+layout( push_constant ) uniform constants{
+    // computed from delta time and shutter speed
+    float motionScaleFactor;
+    // camera planes are needed to linearize depth
+    float cameraNearPlane;
+    float cameraFarPlane;
+    float motionTileOffsetLength;
+};
+
+float linearizeDepth(float depth, float near, float far){
+    return near * far / (far + depth * (near - far));
+}
+
+struct SampleData{
+    vec3    color;
+    float   depthLinear;
+    vec2    coordinate;
+    vec2    motion;
+    float   velocityPixels;
+};
+
+struct PointSpreadCompare{
+    float foreground;
+    float background;
+};
+
+// results in range [0, 1]
+// computes if the sample pixel in the foreground would blur over the main pixel and if the sample pixel in the background would be part of the main pixel background
+// contribution depends on if the distance between pixels is smaller than it's velocity
+// note that compared to the constant falloff used in McGuire's papers this function from Jimenez is constant until the last pixel
+// this is important for the later gradient computation
+PointSpreadCompare samplePointSpreadCompare(SampleData mainPixel, SampleData samplePixel){
+    
+    float sampleOffset = distance(mainPixel.coordinate, samplePixel.coordinate);
+    
+    PointSpreadCompare pointSpread;
+    pointSpread.foreground = clamp(1 - sampleOffset + samplePixel.velocityPixels, 0, 1);
+    pointSpread.background = clamp(1 - sampleOffset +   mainPixel.velocityPixels, 0, 1);
+    
+    return pointSpread;
+}
+
+struct DepthClassification{
+    float foreground;
+    float background;
+};
+
+// classifies depthSample compared to depthMain in regards to being in the fore- or background
+// the range is [0, 1] and sums to 1
+DepthClassification sampleDepthClassification(SampleData mainPixel, SampleData samplePixel){
+    
+    const float softDepthExtent = 0.1;
+    
+    DepthClassification classification;
+    // only the sign is different, so the latter term will cancel out on addition, so only two times 0.5 remains which sums to one
+    classification.foreground = clamp(0.5 + (mainPixel.depthLinear - samplePixel.depthLinear) / softDepthExtent, 0, 1);
+    classification.background = clamp(0.5 - (mainPixel.depthLinear - samplePixel.depthLinear) / softDepthExtent, 0, 1);
+    return classification;
+}
+
+// reconstruction filter and helper functions from "Next Generation Post Processing in Call of Duty Advanced Warfare", Jimenez
+// returns value in range [0, 1]
+float computeSampleWeigth(SampleData mainPixel, SampleData samplePixel){
+    
+    PointSpreadCompare  pointSpread         = samplePointSpreadCompare( mainPixel, samplePixel);
+    DepthClassification depthClassification = sampleDepthClassification(mainPixel, samplePixel);
+    
+    return 
+        depthClassification.foreground * pointSpread.foreground + 
+        depthClassification.background * pointSpread.background;
+}
+
+SampleData loadSampleData(vec2 uv){
+    
+    SampleData data;
+    data.color          = texture(sampler2D(inColor, nearestSampler), uv).rgb;
+    data.coordinate     = ivec2(uv * imageSize(outImage)); 
+    data.motion         = processMotionVector(texture(sampler2D(inMotionFullRes, nearestSampler), uv).rg, motionScaleFactor, imageSize(outImage));
+    data.velocityPixels = length(data.motion * imageSize(outImage));
+    data.depthLinear    = texture(sampler2D(inDepth, nearestSampler), uv).r;
+    data.depthLinear    = linearizeDepth(data.depthLinear, cameraNearPlane, cameraFarPlane);
+    
+    return data;
+}
+
+void main(){
+
+    uint    tileIndex       = gl_WorkGroupID.x;
+    ivec2   tileCoordinates = workTiles.tileXY[tileIndex];
+    ivec2   coord           = ivec2(tileCoordinates * motionTileSize + gl_LocalInvocationID.xy);
+
+    if(any(greaterThanEqual(coord, imageSize(outImage))))
+        return;
+   
+    ivec2   textureRes  = textureSize(sampler2D(inColor, nearestSampler), 0);
+    vec2    uv          = vec2(coord + 0.5) / textureRes;   // + 0.5 to shift uv into pixel center
+    
+    // the motion tile lookup is jittered, so the hard edges in the blur are replaced by noise
+    // dither is shifted, so it does not line up with motion tiles
+    float   motionOffset            = motionTileOffsetLength * (dither(coord + ivec2(ditherSize / 2)) * 2 - 1);
+    vec2    motionNeighbourhoodMax  = processMotionVector(texelFetch(sampler2D(inMotionNeighbourhoodMax, nearestSampler), ivec2(coord + motionOffset) / motionTileSize, 0).rg, motionScaleFactor, imageSize(outImage));
+    
+    SampleData mainPixel = loadSampleData(uv);
+    
+    // early out on movement less than half a pixel
+    if(length(motionNeighbourhoodMax * imageSize(outImage)) <= 0.5){
+        imageStore(outImage, coord, vec4(mainPixel.color, 0.f));
+        return;
+    }
+    
+    vec3    color           = vec3(0);
+    float   weightSum       = 0;      
+    
+    // clamping start and end points avoids artifacts at image borders
+    // the sampler clamps the sample uvs anyways, but without clamping here, many samples can be stuck at the border
+    vec2 uvStart    = clamp(uv - motionNeighbourhoodMax, 0, 1);
+    vec2 uvEnd      = clamp(uv + motionNeighbourhoodMax, 0, 1);
+    
+    // samples are placed evenly, but the entire filter is jittered
+    // dither returns either 0 or 1
+    // the sampleUV code expects an offset in range [-0.5, 0.5], so the dither is rescaled to a binary -0.25/0.25
+    float random = dither(coord) * 0.5 - 0.25;
+    
+    const int sampleCountHalf = 8; 
+    
+    // two samples are processed at a time to allow for mirrored background reconstruction
+    for(int i = 0; i < sampleCountHalf; i++){
+        
+        vec2 sampleUV1 = mix(uv, uvEnd,     (i + random + 1) / float(sampleCountHalf + 1));
+        vec2 sampleUV2 = mix(uv, uvStart,   (i + random + 1) / float(sampleCountHalf + 1));
+        
+        SampleData sample1 = loadSampleData(sampleUV1);
+        SampleData sample2 = loadSampleData(sampleUV2);
+        
+        float weight1 = computeSampleWeigth(mainPixel, sample1);
+        float weight2 = computeSampleWeigth(mainPixel, sample2);
+
+        bool mirroredBackgroundReconstruction = true;
+        if(mirroredBackgroundReconstruction){
+            // see Jimenez paper for details and comparison
+            // problem is that in the foreground the background is reconstructed, which is blurry
+            // in the background the background is obviously known, so it is sharper
+            // at the border between fore- and background this causes a discontinuity
+            // to fix this the weights are mirrored on this border, effectively reconstructing the background, even though it is known
+            
+            // these bools check if sample1 is an affected background pixel (further away and slower moving than sample2)
+            bool inBackground = sample1.depthLinear     > sample2.depthLinear;
+            bool blurredOver  = sample1.velocityPixels  < sample2.velocityPixels;
+            
+            // this mirrors the weights depending on the results:
+            // if both conditions are true,   then weight2 is mirrored to weight1
+            // if both conditions are false,  then weight1 is mirrored to weight2, as sample2 is an affected background pixel
+            // if only one condition is true, then the weights are kept as is
+            weight1 = inBackground && blurredOver ? weight2 : weight1;
+            weight2 = inBackground || blurredOver ? weight2 : weight1;
+        }
+        
+        weightSum   += weight1;
+        weightSum   += weight2;
+        
+        color       += sample1.color * weight1;
+        color       += sample2.color * weight2;
+    }
+    
+    // normalize color and weight
+    weightSum   /= sampleCountHalf * 2;
+    color       /= sampleCountHalf * 2;
+    
+    // the main color is considered the background
+    // the weight sum can be interpreted as the alpha of the combined samples, see Jimenez paper
+    color += (1 - weightSum) * mainPixel.color;
+
+    imageStore(outImage, coord, vec4(color, 0.f));
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/assets/shaders/motionBlur.inc b/projects/indirect_dispatch/assets/shaders/motionBlur.inc
new file mode 100644
index 0000000000000000000000000000000000000000..6fdaf4c5f5e4b07a3111946b0732137f42f295ef
--- /dev/null
+++ b/projects/indirect_dispatch/assets/shaders/motionBlur.inc
@@ -0,0 +1,35 @@
+#ifndef MOTION_BLUR
+#define MOTION_BLUR
+
+#include "motionBlurConfig.inc"
+
+// see "A Reconstruction Filter for Plausible Motion Blur", section 2.2
+vec2 processMotionVector(vec2 motion, float motionScaleFactor, ivec2 imageResolution){
+    // every frame a pixel should blur over the distance it moves
+    // as we blur in two directions (where it was and where it will be) we must half the motion 
+    vec2 motionHalf     = motion * 0.5;
+    vec2 motionScaled   = motionHalf * motionScaleFactor; // scale factor contains shutter speed and delta time
+    
+    // pixels are anisotropic, so the ratio for clamping the velocity is computed in pixels instead of uv coordinates
+    vec2    motionPixel     = motionScaled * imageResolution;
+    float   velocityPixels  = length(motionPixel);
+    
+    float   epsilon         = 0.0001;
+    
+    // this clamps the motion to not exceed the radius given by the motion tile size
+    return motionScaled * max(0.5, min(velocityPixels, motionTileSize)) / (velocityPixels + epsilon);
+}
+
+const int ditherSize = 4;
+
+// simple binary dither pattern
+// could be optimized to avoid modulo and branch
+float dither(ivec2 coord){
+    
+    bool x = coord.x % ditherSize < (ditherSize / 2);
+    bool y = coord.y % ditherSize < (ditherSize / 2);
+    
+    return x ^^ y ? 1 : 0;
+}
+
+#endif // #ifndef MOTION_BLUR
\ No newline at end of file
diff --git a/projects/indirect_dispatch/assets/shaders/motionBlurColorCopy.comp b/projects/indirect_dispatch/assets/shaders/motionBlurColorCopy.comp
new file mode 100644
index 0000000000000000000000000000000000000000..1d8f210c86c2670241fa1d011835b120a39eddc0
--- /dev/null
+++ b/projects/indirect_dispatch/assets/shaders/motionBlurColorCopy.comp
@@ -0,0 +1,29 @@
+#version 440
+#extension GL_GOOGLE_include_directive : enable
+
+#include "motionBlurConfig.inc"
+#include "motionBlurWorkTile.inc"
+
+layout(set=0, binding=0)                    uniform texture2D   inColor;
+layout(set=0, binding=1)                    uniform sampler     nearestSampler;
+layout(set=0, binding=2, r11f_g11f_b10f)    uniform image2D     outImage;
+
+layout(set=0, binding=3) buffer WorkTileBuffer {
+    WorkTiles workTiles;
+};
+
+layout(local_size_x = motionTileSize, local_size_y = motionTileSize, local_size_z = 1) in;
+
+void main(){
+
+    uint    tileIndex       = gl_WorkGroupID.x;
+    ivec2   tileCoordinates = workTiles.tileXY[tileIndex];
+    ivec2   coordinate      = ivec2(tileCoordinates * motionTileSize + gl_LocalInvocationID.xy);
+    
+    if(any(greaterThanEqual(coordinate, imageSize(outImage))))
+        return;
+    
+    vec3 color = texelFetch(sampler2D(inColor, nearestSampler), coordinate, 0).rgb;
+    
+    imageStore(outImage, coordinate, vec4(color, 0.f));
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/assets/shaders/motionBlurConfig.inc b/projects/indirect_dispatch/assets/shaders/motionBlurConfig.inc
new file mode 100644
index 0000000000000000000000000000000000000000..5b8679da119d84242c55d7d89de80ed8b64e5cc9
--- /dev/null
+++ b/projects/indirect_dispatch/assets/shaders/motionBlurConfig.inc
@@ -0,0 +1,8 @@
+#ifndef MOTION_BLUR_CONFIG
+#define MOTION_BLUR_CONFIG
+
+const int motionTileSize        = 16;
+const int maxMotionBlurWidth    = 3840;
+const int maxMotionBlurHeight   = 2160;
+
+#endif // #ifndef MOTION_BLUR_CONFIG
\ No newline at end of file
diff --git a/projects/indirect_dispatch/assets/shaders/motionBlurFastPath.comp b/projects/indirect_dispatch/assets/shaders/motionBlurFastPath.comp
new file mode 100644
index 0000000000000000000000000000000000000000..2e27ebedcc4be1da93ff89a187fe1d3e992e8d22
--- /dev/null
+++ b/projects/indirect_dispatch/assets/shaders/motionBlurFastPath.comp
@@ -0,0 +1,68 @@
+#version 440
+#extension GL_GOOGLE_include_directive : enable
+
+#include "motionBlur.inc"
+#include "motionBlurConfig.inc"
+#include "motionBlurWorkTile.inc"
+
+layout(set=0, binding=0)                    uniform texture2D   inColor;
+layout(set=0, binding=1)                    uniform texture2D   inMotionNeighbourhoodMax;  
+layout(set=0, binding=2)                    uniform sampler     nearestSampler;
+layout(set=0, binding=3, r11f_g11f_b10f)    uniform image2D     outImage;
+
+layout(set=0, binding=4) buffer WorkTileBuffer {
+    WorkTiles workTiles;
+};
+
+layout(local_size_x = motionTileSize, local_size_y = motionTileSize, local_size_z = 1) in;
+
+layout( push_constant ) uniform constants{
+    // computed from delta time and shutter speed
+    float motionScaleFactor;
+};
+
+void main(){
+
+    uint    tileIndex       = gl_WorkGroupID.x;
+    ivec2   tileCoordinates = workTiles.tileXY[tileIndex];
+    ivec2   coord           = ivec2(tileCoordinates * motionTileSize + gl_LocalInvocationID.xy);
+
+    if(any(greaterThanEqual(coord, imageSize(outImage))))
+        return;
+   
+    ivec2   textureRes  = textureSize(sampler2D(inColor, nearestSampler), 0);
+    vec2    uv          = vec2(coord + 0.5) / textureRes;   // + 0.5 to shift uv into pixel center
+    
+    vec2    motionNeighbourhoodMax  = processMotionVector(texelFetch(sampler2D(inMotionNeighbourhoodMax, nearestSampler), coord / motionTileSize, 0).rg, motionScaleFactor, imageSize(outImage));
+    
+    // early out on movement less than half a pixel
+    if(length(motionNeighbourhoodMax * imageSize(outImage)) <= 0.5){
+        vec3 color = texture(sampler2D(inColor, nearestSampler), uv).rgb;
+        imageStore(outImage, coord, vec4(color, 0.f));
+        return;
+    }
+    
+    vec3    color = vec3(0);   
+    
+    // clamping start and end points avoids artifacts at image borders
+    // the sampler clamps the sample uvs anyways, but without clamping here, many samples can be stuck at the border
+    vec2 uvStart    = clamp(uv - motionNeighbourhoodMax, 0, 1);
+    vec2 uvEnd      = clamp(uv + motionNeighbourhoodMax, 0, 1);
+    
+    // samples are placed evenly, but the entire filter is jittered
+    // dither returns either 0 or 1
+    // the sampleUV code expects an offset in range [-0.5, 0.5], so the dither is rescaled to a binary -0.25/0.25
+    float random = dither(coord) * 0.5 - 0.25;
+    
+    const int sampleCount = 16; 
+    
+    for(int i = 0; i < sampleCount; i++){
+        
+        vec2 sampleUV   = mix(uvStart, uvEnd,     (i + random + 1) / float(sampleCount + 1));
+        color           += texture(sampler2D(inColor, nearestSampler), sampleUV).rgb;
+    }
+    
+    color /= sampleCount;
+
+    imageStore(outImage, coord, vec4(color, 0.f));
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/assets/shaders/motionBlurTileClassification.comp b/projects/indirect_dispatch/assets/shaders/motionBlurTileClassification.comp
new file mode 100644
index 0000000000000000000000000000000000000000..3c6f9e3715951ac4fe6770725c3314590cbbff47
--- /dev/null
+++ b/projects/indirect_dispatch/assets/shaders/motionBlurTileClassification.comp
@@ -0,0 +1,58 @@
+#version 440
+#extension GL_GOOGLE_include_directive : enable
+
+#include "motionBlurWorkTile.inc"
+
+layout(set=0, binding=0) uniform texture2D  inMotionMax;
+layout(set=0, binding=1) uniform texture2D  inMotionMin;
+layout(set=0, binding=2) uniform sampler    nearestSampler;
+
+layout(set=0, binding=3) buffer FullPathTileBuffer {
+    WorkTiles fullPathTiles;
+};
+
+layout(set=0, binding=4) buffer CopyPathTileBuffer {
+    WorkTiles copyPathTiles;
+};
+
+layout(set=0, binding=5) buffer FastPathTileBuffer {
+    WorkTiles fastPathTiles;
+};
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+layout( push_constant ) uniform constants{
+    uint    width;
+    uint    height;
+    float   fastPathThreshold;
+};
+
+void main(){
+    
+    ivec2 tileCoord = ivec2(gl_GlobalInvocationID.xy);
+    
+    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, textureSize(sampler2D(inMotionMax, nearestSampler), 0))))
+        return;
+    
+    vec2    motionMax           = texelFetch(sampler2D(inMotionMax, nearestSampler), tileCoord, 0).rg;
+    vec2    motionMin           = texelFetch(sampler2D(inMotionMin, nearestSampler), tileCoord, 0).rg;
+    
+    vec2    motionPixelMax      = motionMax * vec2(width, height);
+    vec2    motionPixelMin      = motionMin * vec2(width, height);
+    
+    float   velocityPixelMax    = length(motionPixelMax);
+    float   minMaxDistance      = distance(motionPixelMin, motionPixelMax);
+    
+    if(velocityPixelMax <= 0.5){
+        uint index                  = atomicAdd(copyPathTiles.tileCount, 1);
+        copyPathTiles.tileXY[index] = tileCoord;
+    }
+    else if(minMaxDistance <= fastPathThreshold){
+        uint index                  = atomicAdd(fastPathTiles.tileCount, 1);
+        fastPathTiles.tileXY[index] = tileCoord;
+    }
+    else{
+        uint index                  = atomicAdd(fullPathTiles.tileCount, 1);
+        fullPathTiles.tileXY[index] = tileCoord;
+    }
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/assets/shaders/motionBlurTileClassificationVis.comp b/projects/indirect_dispatch/assets/shaders/motionBlurTileClassificationVis.comp
new file mode 100644
index 0000000000000000000000000000000000000000..3382ff5ef0b407b9a3a7785eda0d19efe5a8f96e
--- /dev/null
+++ b/projects/indirect_dispatch/assets/shaders/motionBlurTileClassificationVis.comp
@@ -0,0 +1,56 @@
+#version 440
+#extension GL_GOOGLE_include_directive : enable
+
+#include "motionBlurConfig.inc"
+#include "motionBlurWorkTile.inc"
+
+layout(set=0, binding=0)                    uniform texture2D   inColor;
+layout(set=0, binding=1)                    uniform sampler     nearestSampler;
+layout(set=0, binding=2, r11f_g11f_b10f)    uniform image2D     outImage;
+
+layout(set=0, binding=3) buffer FullPathTileBuffer {
+    WorkTiles fullPathTiles;
+};
+
+layout(set=0, binding=4) buffer CopyPathTileBuffer {
+    WorkTiles copyPathTiles;
+};
+
+layout(set=0, binding=5) buffer FastPathTileBuffer {
+    WorkTiles fastPathTiles;
+};
+
+layout(local_size_x = motionTileSize, local_size_y = motionTileSize, local_size_z = 1) in;
+
+void main(){
+    
+    uint tileIndexFullPath = gl_WorkGroupID.x;
+    uint tileIndexCopyPath = gl_WorkGroupID.x - fullPathTiles.tileCount;
+    uint tileIndexFastPath = gl_WorkGroupID.x - fullPathTiles.tileCount - copyPathTiles.tileCount;
+    
+    vec3    debugColor;
+    ivec2   tileCoordinates;
+    
+    if(tileIndexFullPath < fullPathTiles.tileCount){
+        debugColor      = vec3(1, 0, 0);
+        tileCoordinates = fullPathTiles.tileXY[tileIndexFullPath];
+    }
+    else if(tileIndexCopyPath < copyPathTiles.tileCount){
+        debugColor      = vec3(0, 1, 0);
+        tileCoordinates = copyPathTiles.tileXY[tileIndexCopyPath];
+    }
+    else if(tileIndexFastPath < fastPathTiles.tileCount){
+        debugColor      = vec3(0, 0, 1);
+        tileCoordinates = fastPathTiles.tileXY[tileIndexFastPath];
+    }
+    else{
+        return;
+    }
+    
+    ivec2   coordinate  = ivec2(tileCoordinates * motionTileSize + gl_LocalInvocationID.xy);
+    vec3    color       = texelFetch(sampler2D(inColor, nearestSampler), coordinate, 0).rgb;
+    
+    color = mix(color, debugColor, 0.5);
+    
+    imageStore(outImage, coordinate, vec4(color, 0));
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/assets/shaders/motionBlurWorkTile.inc b/projects/indirect_dispatch/assets/shaders/motionBlurWorkTile.inc
new file mode 100644
index 0000000000000000000000000000000000000000..8577f100aac524b93eecac406606a962bc52d222
--- /dev/null
+++ b/projects/indirect_dispatch/assets/shaders/motionBlurWorkTile.inc
@@ -0,0 +1,19 @@
+#ifndef MOTION_BLUR_WORK_TILE
+#define MOTION_BLUR_WORK_TILE
+
+#include "motionBlurConfig.inc"
+
+const int maxTileCount = 
+    (maxMotionBlurWidth  + motionTileSize - 1) / motionTileSize * 
+    (maxMotionBlurHeight + motionTileSize - 1) / motionTileSize;
+
+struct WorkTiles{
+    uint    tileCount;
+    // dispatch Y/Z are here so the buffer can be used directly as an indirect dispatch argument buffer
+    uint    dispatchY;
+    uint    dispatchZ;
+    
+    ivec2   tileXY[maxTileCount];
+};
+
+#endif // #ifndef MOTION_BLUR_WORK_TILE
\ No newline at end of file
diff --git a/projects/indirect_dispatch/assets/shaders/motionBlurWorkTileReset.comp b/projects/indirect_dispatch/assets/shaders/motionBlurWorkTileReset.comp
new file mode 100644
index 0000000000000000000000000000000000000000..d4b55582a0a18c0c6a3fecf1dd6ce69ed49ca2c1
--- /dev/null
+++ b/projects/indirect_dispatch/assets/shaders/motionBlurWorkTileReset.comp
@@ -0,0 +1,32 @@
+#version 440
+#extension GL_GOOGLE_include_directive : enable
+
+#include "motionBlurWorkTile.inc"
+
+layout(set=0, binding=0) buffer FullPathTileBuffer {
+    WorkTiles fullPathTiles;
+};
+
+layout(set=0, binding=1) buffer CopyPathTileBuffer {
+    WorkTiles copyPathTiles;
+};
+
+layout(set=0, binding=2) buffer FastPathTileBuffer {
+    WorkTiles fastPathTiles;
+};
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+
+void main(){
+    fullPathTiles.tileCount = 0;
+    fullPathTiles.dispatchY = 1;
+    fullPathTiles.dispatchZ = 1;
+    
+    copyPathTiles.tileCount = 0;
+    copyPathTiles.dispatchY = 1;
+    copyPathTiles.dispatchZ = 1;
+    
+    fastPathTiles.tileCount = 0;
+    fastPathTiles.dispatchY = 1;
+    fastPathTiles.dispatchZ = 1;
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/assets/shaders/motionVector.inc b/projects/indirect_dispatch/assets/shaders/motionVector.inc
new file mode 100644
index 0000000000000000000000000000000000000000..498478cbc38b9666366eaa3d3e1a715dfc30236b
--- /dev/null
+++ b/projects/indirect_dispatch/assets/shaders/motionVector.inc
@@ -0,0 +1,9 @@
+vec2 computeMotionVector(vec4 NDC, vec4 NDCPrevious){
+    vec2 ndc            = NDC.xy            / NDC.w;
+    vec2 ndcPrevious    = NDCPrevious.xy    / NDCPrevious.w;
+
+    vec2 uv         = ndc           * 0.5 + 0.5;
+    vec2 uvPrevious = ndcPrevious   * 0.5 + 0.5;
+
+	return uvPrevious - uv;
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/assets/shaders/motionVectorMinMax.comp b/projects/indirect_dispatch/assets/shaders/motionVectorMinMax.comp
new file mode 100644
index 0000000000000000000000000000000000000000..06b1b98f37579ae33406691bf19999d42ab7eb83
--- /dev/null
+++ b/projects/indirect_dispatch/assets/shaders/motionVectorMinMax.comp
@@ -0,0 +1,57 @@
+#version 440
+#extension GL_GOOGLE_include_directive : enable
+#include "motionBlurConfig.inc"
+
+layout(set=0, binding=0)        uniform texture2D   inMotion;
+layout(set=0, binding=1)        uniform sampler     textureSampler;
+layout(set=0, binding=2, rg16)  uniform image2D     outMotionMax;
+layout(set=0, binding=3, rg16)  uniform image2D     outMotionMin;
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+void main(){
+    
+    ivec2 outImageRes       = imageSize(outMotionMax);
+    ivec2 inImageRes        = textureSize(sampler2D(inMotion, textureSampler), 0);
+    ivec2 motionTileCoord   = ivec2(gl_GlobalInvocationID.xy);
+    
+    if(any(greaterThanEqual(motionTileCoord, outImageRes)))
+        return;
+    
+    float   velocityMax = 0;
+    vec2    motionMax   = vec2(0);
+    
+    float   velocityMin = 100000;
+    vec2    motionMin   = vec2(0);
+    
+    ivec2 motionBufferBaseCoord = motionTileCoord * motionTileSize;
+    
+    for(int x = 0; x < motionTileSize; x++){
+        for(int y = 0; y < motionTileSize; y++){
+            ivec2   sampleCoord     = motionBufferBaseCoord + ivec2(x, y);
+            
+            bool sampleIsOutsideImage = false;
+            sampleIsOutsideImage = sampleIsOutsideImage || any(greaterThanEqual(sampleCoord, inImageRes));
+            sampleIsOutsideImage = sampleIsOutsideImage || any(lessThan(sampleCoord, ivec2(0)));
+            
+            if(sampleIsOutsideImage)
+                continue;
+            
+            vec2    motionSample    = texelFetch(sampler2D(inMotion, textureSampler), sampleCoord, 0).rg;
+            float   velocitySample  = length(motionSample);
+            
+            if(velocitySample > velocityMax){
+                velocityMax = velocitySample;
+                motionMax   = motionSample;
+            }
+            
+            if(velocitySample < velocityMin){
+                velocityMin = velocitySample;
+                motionMin   = motionSample;
+            }
+        }
+    }
+
+    imageStore(outMotionMax, motionTileCoord, vec4(motionMax, 0, 0));
+    imageStore(outMotionMin, motionTileCoord, vec4(motionMin, 0, 0));
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/assets/shaders/motionVectorMinMaxNeighbourhood.comp b/projects/indirect_dispatch/assets/shaders/motionVectorMinMaxNeighbourhood.comp
new file mode 100644
index 0000000000000000000000000000000000000000..3f836341a97a683efe88f41416d541624be03a0e
--- /dev/null
+++ b/projects/indirect_dispatch/assets/shaders/motionVectorMinMaxNeighbourhood.comp
@@ -0,0 +1,59 @@
+#version 440
+#extension GL_GOOGLE_include_directive : enable
+
+layout(set=0, binding=0)        uniform texture2D   inMotionMax;
+layout(set=0, binding=1)        uniform texture2D   inMotionMin;
+layout(set=0, binding=2)        uniform sampler     textureSampler;
+layout(set=0, binding=3, rg16)  uniform image2D     outMotionMaxNeighbourhood;
+layout(set=0, binding=4, rg16)  uniform image2D     outMotionMinNeighbourhood;
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+void main(){
+    
+    ivec2 outImageRes       = imageSize(outMotionMaxNeighbourhood);
+    ivec2 inImageRes        = textureSize(sampler2D(inMotionMax, textureSampler), 0);
+    ivec2 motionTileCoord   = ivec2(gl_GlobalInvocationID.xy);
+    
+    if(any(greaterThanEqual(motionTileCoord, outImageRes)))
+        return;
+    
+    float   velocityMax = 0;
+    vec2    motionMax   = vec2(0);
+    
+    float   velocityMin = 10000;
+    vec2    motionMin   = vec2(0);
+    
+    for(int x = -1; x <= 1; x++){
+        for(int y = -1; y <= 1; y++){
+            ivec2   sampleCoord         = motionTileCoord + ivec2(x, y);
+            
+            bool sampleIsOutsideImage = false;
+            sampleIsOutsideImage = sampleIsOutsideImage || any(greaterThanEqual(sampleCoord, inImageRes));
+            sampleIsOutsideImage = sampleIsOutsideImage || any(lessThan(sampleCoord, ivec2(0)));
+            
+            if(sampleIsOutsideImage)
+                continue;
+            
+            vec2    motionSampleMax     = texelFetch(sampler2D(inMotionMax, textureSampler), sampleCoord, 0).rg;
+            float   velocitySampleMax   = length(motionSampleMax);
+            
+            if(velocitySampleMax > velocityMax){
+                velocityMax = velocitySampleMax;
+                motionMax   = motionSampleMax;
+            }
+            
+            
+            vec2    motionSampleMin     = texelFetch(sampler2D(inMotionMin, textureSampler), sampleCoord, 0).rg;
+            float   velocitySampleMin   = length(motionSampleMin);
+            
+            if(velocitySampleMin < velocityMin){
+                velocityMin = velocitySampleMin;
+                motionMin   = motionSampleMin;
+            }
+        }
+    }
+
+    imageStore(outMotionMaxNeighbourhood, motionTileCoord, vec4(motionMax, 0, 0));
+    imageStore(outMotionMinNeighbourhood, motionTileCoord, vec4(motionMin, 0, 0));
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/assets/shaders/motionVectorVisualisation.comp b/projects/indirect_dispatch/assets/shaders/motionVectorVisualisation.comp
new file mode 100644
index 0000000000000000000000000000000000000000..fdceb575feaf24e7114bbcf223585a28955f45b8
--- /dev/null
+++ b/projects/indirect_dispatch/assets/shaders/motionVectorVisualisation.comp
@@ -0,0 +1,33 @@
+#version 440
+#extension GL_GOOGLE_include_directive : enable
+
+#include "motionBlurConfig.inc"
+
+layout(set=0, binding=0)                    uniform texture2D   inMotion;
+layout(set=0, binding=1)                    uniform sampler     textureSampler;
+layout(set=0, binding=2, r11f_g11f_b10f)    uniform image2D     outImage;
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+layout( push_constant ) uniform constants{
+    float range;
+};
+
+void main(){
+
+    ivec2 outImageRes = imageSize(outImage);
+    ivec2 coord       = ivec2(gl_GlobalInvocationID.xy);
+
+    if(any(greaterThanEqual(coord, outImageRes)))
+        return;
+
+    vec2    uv              = (coord + 0.5) / vec2(outImageRes);
+    ivec2   inTextureRes    = textureSize(sampler2D(inMotion, textureSampler), 0);
+    
+    vec2 motionVector           = texelFetch(sampler2D(inMotion, textureSampler), ivec2(uv * inTextureRes), 0).rg;
+    vec2 motionVectorNormalized = clamp(motionVector / range, -1, 1);
+    
+    vec2 color  = motionVectorNormalized * 0.5 + 0.5;
+
+    imageStore(outImage, coord, vec4(color, 0.5, 0));
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/assets/shaders/prepass.frag b/projects/indirect_dispatch/assets/shaders/prepass.frag
new file mode 100644
index 0000000000000000000000000000000000000000..ccfc84d982253f7b89551c099a92b5686a811163
--- /dev/null
+++ b/projects/indirect_dispatch/assets/shaders/prepass.frag
@@ -0,0 +1,14 @@
+#version 450
+#extension GL_ARB_separate_shader_objects   : enable
+#extension GL_GOOGLE_include_directive      : enable
+
+#include "motionVector.inc"
+
+layout(location = 0) in vec4 passNDC;
+layout(location = 1) in vec4 passNDCPrevious;
+
+layout(location = 0) out vec2 outMotion;
+
+void main()	{
+	outMotion = computeMotionVector(passNDC, passNDCPrevious);
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/assets/shaders/prepass.vert b/projects/indirect_dispatch/assets/shaders/prepass.vert
new file mode 100644
index 0000000000000000000000000000000000000000..230346208007fae0bb7724b5b6d05f62726c4ded
--- /dev/null
+++ b/projects/indirect_dispatch/assets/shaders/prepass.vert
@@ -0,0 +1,18 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 inPosition;
+
+layout(location = 0) out vec4 passNDC;
+layout(location = 1) out vec4 passNDCPrevious;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+    mat4 mvpPrevious;
+};
+
+void main()	{
+	gl_Position     = mvp * vec4(inPosition, 1.0);
+	passNDC         = gl_Position;
+    passNDCPrevious = mvpPrevious * vec4(inPosition, 1.0);
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/shadow.frag b/projects/indirect_dispatch/assets/shaders/sky.frag
similarity index 53%
rename from projects/voxelization/resources/shaders/shadow.frag
rename to projects/indirect_dispatch/assets/shaders/sky.frag
index 848f853f556660b4900b5db7fb6fc98d57c1cd5b..efc0e03b2d6ee1c71930c866293da66857bd56c7 100644
--- a/projects/voxelization/resources/shaders/shadow.frag
+++ b/projects/indirect_dispatch/assets/shaders/sky.frag
@@ -1,6 +1,8 @@
 #version 450
 #extension GL_ARB_separate_shader_objects : enable
 
-void main()	{
+layout(location = 0) out vec3 outColor;
 
+void main()	{
+	outColor = vec3(0, 0.2, 0.9);
 }
\ No newline at end of file
diff --git a/projects/indirect_dispatch/assets/shaders/sky.vert b/projects/indirect_dispatch/assets/shaders/sky.vert
new file mode 100644
index 0000000000000000000000000000000000000000..44b48cd7f3bfc44e2e43edef0d474581d50608de
--- /dev/null
+++ b/projects/indirect_dispatch/assets/shaders/sky.vert
@@ -0,0 +1,13 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 inPosition;
+
+layout( push_constant ) uniform constants{
+    mat4 viewProjection;
+};
+
+void main()	{
+	gl_Position     = viewProjection * vec4(inPosition, 0.0);
+    gl_Position.w   = gl_Position.z;
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/assets/shaders/skyPrepass.frag b/projects/indirect_dispatch/assets/shaders/skyPrepass.frag
new file mode 100644
index 0000000000000000000000000000000000000000..64ec4f18bbcf89153d70019ace570da53d44a505
--- /dev/null
+++ b/projects/indirect_dispatch/assets/shaders/skyPrepass.frag
@@ -0,0 +1,14 @@
+#version 450
+#extension GL_ARB_separate_shader_objects   : enable
+#extension GL_GOOGLE_include_directive      : enable
+
+#include "motionVector.inc"
+
+layout(location = 0) out vec2 outMotion;
+
+layout(location = 0) in vec4 passNDC;
+layout(location = 1) in vec4 passNDCPrevious;
+
+void main()	{
+	outMotion = computeMotionVector(passNDC, passNDCPrevious);
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/assets/shaders/skyPrepass.vert b/projects/indirect_dispatch/assets/shaders/skyPrepass.vert
new file mode 100644
index 0000000000000000000000000000000000000000..31b9016a592d097825a09e1daa888cb7b72b2cbc
--- /dev/null
+++ b/projects/indirect_dispatch/assets/shaders/skyPrepass.vert
@@ -0,0 +1,22 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 inPosition;
+
+layout( push_constant ) uniform constants{
+    mat4 viewProjection;
+    mat4 viewProjectionPrevious;
+};
+
+layout(location = 0) out vec4 passNDC;
+layout(location = 1) out vec4 passNDCPrevious;
+
+void main()	{
+	gl_Position     = viewProjection * vec4(inPosition, 0.0);
+    gl_Position.w   = gl_Position.z;
+    
+    passNDC         = gl_Position;
+    
+    passNDCPrevious     = viewProjectionPrevious * vec4(inPosition, 0.0);
+    passNDCPrevious.w   = passNDCPrevious.z;
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/src/App.cpp b/projects/indirect_dispatch/src/App.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b441c3dda9f51f3cb3c51c15ea29d8069d4ec6ac
--- /dev/null
+++ b/projects/indirect_dispatch/src/App.cpp
@@ -0,0 +1,562 @@
+#include "App.hpp"
+#include "AppConfig.hpp"
+
+#include <vkcv/Sampler.hpp>
+#include <vkcv/gui/GUI.hpp>
+
+#include <vkcv/upscaling/FSR2Upscaling.hpp>
+#include <vkcv/upscaling/FSRUpscaling.hpp>
+#include <vkcv/upscaling/NISUpscaling.hpp>
+#include <vkcv/upscaling/BilinearUpscaling.hpp>
+
+#include <chrono>
+#include <functional>
+
+const char* MotionVectorVisualisationModeLabels[6] = {
+		"None",
+		"Full resolution",
+		"Max tile",
+		"Tile neighbourhood max",
+		"Min Tile",
+		"Tile neighbourhood min"
+};
+
+const char* MotionBlurModeLabels[3] = {
+		"Default",
+		"Disabled",
+		"Tile visualisation"
+};
+
+static vkcv::Features getAppFeatures() {
+	vkcv::Features features;
+	features.requireExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
+	
+	features.requireFeature([](vk::PhysicalDeviceFeatures& features) {
+		features.setShaderInt16(true);
+	});
+	
+	features.requireExtensionFeature<vk::PhysicalDeviceSubgroupSizeControlFeatures>(
+			VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME,
+			[](vk::PhysicalDeviceSubgroupSizeControlFeatures &features) {
+				features.setSubgroupSizeControl(true);
+			}
+	);
+	
+	features.requireExtensionFeature<vk::PhysicalDeviceShaderFloat16Int8Features>(
+			VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME,
+			[](vk::PhysicalDeviceShaderFloat16Int8Features &features) {
+				features.setShaderFloat16(true);
+			}
+	);
+	
+	features.tryExtensionFeature<vk::PhysicalDeviceCoherentMemoryFeaturesAMD>(
+			VK_AMD_DEVICE_COHERENT_MEMORY_EXTENSION_NAME,
+			[](vk::PhysicalDeviceCoherentMemoryFeaturesAMD &features) {
+				features.setDeviceCoherentMemory(true);
+			}
+	);
+	
+	return features;
+}
+
+App::App() : 
+	m_applicationName("Indirect Dispatch"),
+	m_windowWidth(AppConfig::defaultWindowWidth),
+	m_windowHeight(AppConfig::defaultWindowHeight),
+	m_core(vkcv::Core::create(
+		m_applicationName,
+		VK_MAKE_VERSION(0, 0, 1),
+		{ vk::QueueFlagBits::eGraphics ,vk::QueueFlagBits::eCompute , vk::QueueFlagBits::eTransfer },
+		getAppFeatures())),
+	m_windowHandle(m_core.createWindow(m_applicationName, m_windowWidth, m_windowHeight, true)),
+	m_cameraManager(m_core.getWindow(m_windowHandle)){}
+
+bool App::initialize() {
+	if (!loadMeshPass(m_core, &m_meshPass))
+		return false;
+
+	if (!loadSkyPass(m_core, &m_skyPass))
+		return false;
+
+	if (!loadPrePass(m_core, &m_prePass))
+		return false;
+
+	if (!loadSkyPrePass(m_core, &m_skyPrePass))
+		return false;
+
+	if (!loadComputePass(m_core, "assets/shaders/gammaCorrection.comp", &m_gammaCorrectionPass))
+		return false;
+
+	if (!loadMesh(m_core, "assets/models/cube.gltf", &m_cubeMesh))
+		return false;
+
+	if (!loadMesh(m_core, "assets/models/ground.gltf", &m_groundMesh))
+		return false;
+
+	if(!loadImage(m_core, "assets/models/grid.png", &m_gridTexture))
+		return false;
+
+	if (!m_motionBlur.initialize(&m_core, m_windowWidth, m_windowHeight))
+		return false;
+
+	m_linearSampler = vkcv::samplerLinear(m_core, true);
+	m_renderTargets = createRenderTargets(
+			m_core,
+			m_windowWidth,
+			m_windowHeight,
+			vkcv::upscaling::FSR2QualityMode::NONE
+	);
+
+	auto cameraHandle = m_cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+	m_cameraManager.getCamera(cameraHandle).setPosition(glm::vec3(0, 1, -3));
+	m_cameraManager.getCamera(cameraHandle).setNearFar(0.1f, 30.f);
+	
+	vkcv::DescriptorWrites meshPassDescriptorWrites;
+	meshPassDescriptorWrites.writeSampledImage(0, m_gridTexture);
+	meshPassDescriptorWrites.writeSampler(1, m_linearSampler);
+	m_core.writeDescriptorSet(m_meshPass.descriptorSet, meshPassDescriptorWrites);
+
+	return true;
+}
+
+void App::run() {
+
+	auto                         frameStartTime = std::chrono::system_clock::now();
+	const auto                   appStartTime   = std::chrono::system_clock::now();
+	const vkcv::ImageHandle      swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+	const vkcv::InstanceDrawcall skyDrawcall(m_cubeMesh.mesh);
+
+	vkcv::gui::GUI gui(m_core, m_windowHandle);
+
+	eMotionVectorVisualisationMode  motionVectorVisualisationMode   = eMotionVectorVisualisationMode::None;
+	eMotionBlurMode                 motionBlurMode                  = eMotionBlurMode::Default;
+
+	bool    freezeFrame                     = false;
+	float   motionBlurTileOffsetLength      = 3;
+	float   objectVerticalSpeed             = 5;
+	float   objectAmplitude                 = 0;
+	float   objectMeanHeight                = 1;
+	float   objectRotationSpeedX            = 5;
+	float   objectRotationSpeedY            = 5;
+	int     cameraShutterSpeedInverse       = 24;
+	float   motionVectorVisualisationRange  = 0.008;
+	float   motionBlurFastPathThreshold     = 1;
+
+	glm::mat4 viewProjection            = m_cameraManager.getActiveCamera().getMVP();
+	glm::mat4 viewProjectionPrevious    = m_cameraManager.getActiveCamera().getMVP();
+
+	struct Object {
+		MeshResources meshResources;
+		glm::mat4 modelMatrix   = glm::mat4(1.f);
+		glm::mat4 mvp           = glm::mat4(1.f);
+		glm::mat4 mvpPrevious   = glm::mat4(1.f);
+		std::function<void(float, Object&)> modelMatrixUpdate;
+	};
+	std::vector<Object> sceneObjects;
+
+	Object ground;
+	ground.meshResources = m_groundMesh;
+	sceneObjects.push_back(ground);
+
+	Object sphere;
+	sphere.meshResources = m_cubeMesh;
+	sphere.modelMatrixUpdate = [&](float time, Object& obj) {
+		const float currentHeight   = objectMeanHeight + objectAmplitude * glm::sin(time * objectVerticalSpeed);
+		const glm::mat4 translation = glm::translate(glm::mat4(1), glm::vec3(0, currentHeight, 0));
+		const glm::mat4 rotationX   = glm::rotate(glm::mat4(1), objectRotationSpeedX * time, glm::vec3(1, 0, 0));
+		const glm::mat4 rotationY   = glm::rotate(glm::mat4(1), objectRotationSpeedY * time, glm::vec3(0, 1, 0));
+		obj.modelMatrix             = translation * rotationX * rotationY;
+	};
+	sceneObjects.push_back(sphere);
+
+	bool spaceWasPressed = false;
+
+	m_core.getWindow(m_windowHandle).e_key.add([&](int key, int scancode, int action, int mods) {
+		if (key == GLFW_KEY_SPACE) {
+			if (action == GLFW_PRESS) {
+				if (!spaceWasPressed) {
+					freezeFrame = !freezeFrame;
+				}
+				spaceWasPressed = true;
+			}
+			else if (action == GLFW_RELEASE) {
+				spaceWasPressed = false;
+			}
+		}
+	});
+	
+	vkcv::upscaling::FSR2Upscaling fsr2 (m_core);
+	
+	fsr2.bindDepthBuffer(m_renderTargets.depthBuffer);
+	fsr2.bindVelocityBuffer(m_renderTargets.motionBuffer);
+	
+	vkcv::upscaling::FSR2QualityMode fsrMode = vkcv::upscaling::FSR2QualityMode::NONE;
+	vkcv::upscaling::FSR2QualityMode oldFsrMode = fsrMode;
+	
+	int fsrModeIndex = static_cast<int>(fsrMode);
+	
+	const std::vector<const char*> fsrModeNames = {
+			"None",
+			"Quality",
+			"Balanced",
+			"Performance",
+			"Ultra Performance"
+	};
+	
+	bool fsrMipLoadBiasFlag = true;
+	bool fsrMipLoadBiasFlagBackup = fsrMipLoadBiasFlag;
+	
+	vkcv::upscaling::FSRUpscaling fsr1 (m_core);
+	vkcv::upscaling::BilinearUpscaling bilinear (m_core);
+	vkcv::upscaling::NISUpscaling nis (m_core);
+	
+	const std::vector<const char*> modeNames = {
+			"FSR Upscaling 1.0",
+			"FSR Upscaling 2.1.1",
+			"NIS Upscaling",
+			"Bilinear Upscaling"
+	};
+	
+	int upscalingMode = 3;
+	
+	vkcv::SamplerHandle fsr2Sampler;
+	
+	auto frameEndTime = std::chrono::system_clock::now();
+
+	while (vkcv::Window::hasOpenWindow()) {
+		vkcv::Window::pollEvents();
+
+		if (!freezeFrame) {
+
+			frameStartTime          = frameEndTime;
+			viewProjectionPrevious  = viewProjection;
+
+			for (Object& obj : sceneObjects) {
+				obj.mvpPrevious = obj.mvp;
+			}
+		}
+
+		if (m_core.getWindow(m_windowHandle).getHeight() == 0 || m_core.getWindow(m_windowHandle).getWidth() == 0)
+			continue;
+
+		uint32_t swapchainWidth, swapchainHeight;
+		if (!m_core.beginFrame(swapchainWidth, swapchainHeight,m_windowHandle))
+			continue;
+
+		const bool hasResolutionChanged = (
+				(swapchainWidth != m_windowWidth) ||
+				(swapchainHeight != m_windowHeight) ||
+				(oldFsrMode != fsrMode) ||
+				(fsrMipLoadBiasFlagBackup != fsrMipLoadBiasFlag)
+		);
+		
+		if (hasResolutionChanged) {
+			m_windowWidth  = swapchainWidth;
+			m_windowHeight = swapchainHeight;
+			oldFsrMode = fsrMode;
+			fsrMipLoadBiasFlagBackup = fsrMipLoadBiasFlag;
+			
+			fsr2Sampler = m_core.createSampler(
+					vkcv::SamplerFilterType::LINEAR,
+					vkcv::SamplerFilterType::LINEAR,
+					vkcv::SamplerMipmapMode::LINEAR,
+					vkcv::SamplerAddressMode::REPEAT,
+					fsrMipLoadBiasFlag? vkcv::upscaling::getFSR2LodBias(fsrMode) : 0.0f
+			);
+			
+			vkcv::DescriptorWrites meshPassDescriptorWrites;
+			meshPassDescriptorWrites.writeSampler(1, fsr2Sampler);
+			m_core.writeDescriptorSet(m_meshPass.descriptorSet, meshPassDescriptorWrites);
+
+			m_renderTargets = createRenderTargets(
+					m_core,
+					m_windowWidth,
+					m_windowHeight,
+					fsrMode
+			);
+			
+			m_motionBlur.setResolution(m_windowWidth, m_windowHeight);
+			
+			fsr2.bindDepthBuffer(m_renderTargets.depthBuffer);
+			fsr2.bindVelocityBuffer(m_renderTargets.motionBuffer);
+		}
+
+		if(!freezeFrame)
+			frameEndTime = std::chrono::system_clock::now();
+
+		const float microsecondToSecond = 0.000001;
+		const float fDeltaTimeSeconds = microsecondToSecond * std::chrono::duration_cast<std::chrono::microseconds>(frameEndTime - frameStartTime).count();
+
+		m_cameraManager.update(fDeltaTimeSeconds);
+		fsr2.update(fDeltaTimeSeconds, false);
+		
+		const auto& camera = m_cameraManager.getActiveCamera();
+		float near, far;
+		
+		camera.getNearFar(near, far);
+		fsr2.setCamera(near, far, camera.getFov());
+
+		const auto  time         = frameEndTime - appStartTime;
+		const float fCurrentTime = std::chrono::duration_cast<std::chrono::milliseconds>(time).count() * 0.001f;
+		
+		float jitterX, jitterY;
+		
+		fsr2.calcJitterOffset(
+				m_core.getImageWidth(m_renderTargets.colorBuffer),
+				m_core.getImageHeight(m_renderTargets.colorBuffer),
+				jitterX,
+				jitterY
+		);
+		
+		const glm::mat4 jitterMatrix = glm::translate(
+				glm::identity<glm::mat4>(),
+				glm::vec3(jitterX, jitterY, 0.0f)
+		);
+
+		// update matrices
+		if (!freezeFrame) {
+			viewProjection = camera.getMVP();
+
+			for (Object& obj : sceneObjects) {
+				if (obj.modelMatrixUpdate) {
+					obj.modelMatrixUpdate(fCurrentTime, obj);
+				}
+				
+				obj.mvp = jitterMatrix * viewProjection * obj.modelMatrix;
+			}
+		}
+
+		const vkcv::CommandStreamHandle cmdStream = m_core.createCommandStream(vkcv::QueueType::Graphics);
+
+		// prepass
+		vkcv::PushConstants prepassPushConstants(sizeof(glm::mat4) * 2);
+
+		for (const Object& obj : sceneObjects) {
+			glm::mat4 prepassMatrices[2] = { obj.mvp, obj.mvpPrevious };
+			prepassPushConstants.appendDrawcall(prepassMatrices);
+		}
+
+		const std::vector<vkcv::ImageHandle> prepassRenderTargets = {
+			m_renderTargets.motionBuffer,
+			m_renderTargets.depthBuffer
+		};
+
+		std::vector<vkcv::InstanceDrawcall> prepassSceneDrawcalls;
+		for (const Object& obj : sceneObjects) {
+			prepassSceneDrawcalls.push_back(vkcv::InstanceDrawcall(obj.meshResources.mesh));
+		}
+
+		m_core.recordDrawcallsToCmdStream(
+			cmdStream,
+			m_prePass.pipeline,
+			prepassPushConstants,
+			prepassSceneDrawcalls,
+			prepassRenderTargets,
+			m_windowHandle
+		);
+
+		// sky prepass
+		glm::mat4 skyPrepassMatrices[2] = {
+			viewProjection,
+			viewProjectionPrevious
+		};
+		
+		vkcv::PushConstants skyPrepassPushConstants(sizeof(glm::mat4) * 2);
+		skyPrepassPushConstants.appendDrawcall(skyPrepassMatrices);
+
+		m_core.recordDrawcallsToCmdStream(
+			cmdStream,
+			m_skyPrePass.pipeline,
+			skyPrepassPushConstants,
+			{ skyDrawcall },
+			prepassRenderTargets,
+			m_windowHandle
+		);
+
+		// main pass
+		const std::vector<vkcv::ImageHandle> renderTargets   = { 
+			m_renderTargets.colorBuffer, 
+			m_renderTargets.depthBuffer
+		};
+
+		vkcv::PushConstants meshPushConstants(2 * sizeof(glm::mat4));
+		for (const Object& obj : sceneObjects) {
+			glm::mat4 matrices[2] = { obj.mvp, obj.modelMatrix };
+			meshPushConstants.appendDrawcall(matrices);
+		}
+
+		std::vector<vkcv::InstanceDrawcall> forwardSceneDrawcalls;
+		for (const Object& obj : sceneObjects) {
+			vkcv::InstanceDrawcall drawcall (obj.meshResources.mesh);
+			drawcall.useDescriptorSet(0, m_meshPass.descriptorSet);
+			forwardSceneDrawcalls.push_back(drawcall);
+		}
+
+		m_core.recordDrawcallsToCmdStream(
+			cmdStream,
+			m_meshPass.pipeline,
+			meshPushConstants,
+			forwardSceneDrawcalls,
+			renderTargets,
+			m_windowHandle
+		);
+
+		// sky
+		vkcv::PushConstants skyPushConstants = vkcv::pushConstants<glm::mat4>();
+		skyPushConstants.appendDrawcall(jitterMatrix * viewProjection);
+
+		m_core.recordDrawcallsToCmdStream(
+			cmdStream,
+			m_skyPass.pipeline,
+			skyPushConstants,
+			{ skyDrawcall },
+			renderTargets,
+			m_windowHandle
+		);
+		
+		// upscaling
+		m_core.prepareImageForSampling(cmdStream, m_renderTargets.colorBuffer);
+		m_core.prepareImageForStorage(cmdStream, m_renderTargets.finalBuffer);
+		
+		switch (upscalingMode) {
+			case 0:
+				fsr1.recordUpscaling(
+						cmdStream,
+						m_renderTargets.colorBuffer,
+						m_renderTargets.finalBuffer
+				);
+				break;
+			case 1:
+				m_core.prepareImageForSampling(cmdStream, m_renderTargets.depthBuffer);
+				m_core.prepareImageForSampling(cmdStream, m_renderTargets.motionBuffer);
+				
+				fsr2.recordUpscaling(
+						cmdStream,
+						m_renderTargets.colorBuffer,
+						m_renderTargets.finalBuffer
+				);
+				break;
+			case 2:
+				nis.recordUpscaling(
+						cmdStream,
+						m_renderTargets.colorBuffer,
+						m_renderTargets.finalBuffer
+				);
+				break;
+			case 3:
+				bilinear.recordUpscaling(
+						cmdStream,
+						m_renderTargets.colorBuffer,
+						m_renderTargets.finalBuffer
+				);
+				break;
+			default:
+				break;
+		}
+		
+		m_core.prepareImageForSampling(cmdStream, m_renderTargets.finalBuffer);
+
+		// motion blur
+		vkcv::ImageHandle motionBlurOutput;
+
+		if (motionVectorVisualisationMode == eMotionVectorVisualisationMode::None) {
+			motionBlurOutput = m_motionBlur.render(
+				cmdStream,
+				m_renderTargets.motionBuffer,
+				m_renderTargets.finalBuffer,
+				m_renderTargets.depthBuffer,
+				motionBlurMode,
+				near,
+				far,
+				fDeltaTimeSeconds,
+				static_cast<float>(cameraShutterSpeedInverse),
+				motionBlurTileOffsetLength,
+				motionBlurFastPathThreshold
+			);
+		} else {
+			motionBlurOutput = m_motionBlur.renderMotionVectorVisualisation(
+				cmdStream,
+				m_renderTargets.motionBuffer,
+				motionVectorVisualisationMode,
+				motionVectorVisualisationRange
+			);
+		}
+
+		// gamma correction
+		vkcv::DescriptorWrites gammaCorrectionDescriptorWrites;
+		gammaCorrectionDescriptorWrites.writeSampledImage(0, motionBlurOutput);
+		gammaCorrectionDescriptorWrites.writeSampler(1, m_linearSampler);
+		gammaCorrectionDescriptorWrites.writeStorageImage(2, swapchainInput);
+
+		m_core.writeDescriptorSet(m_gammaCorrectionPass.descriptorSet, gammaCorrectionDescriptorWrites);
+
+		m_core.prepareImageForSampling(cmdStream, motionBlurOutput);
+		m_core.prepareImageForStorage (cmdStream, swapchainInput);
+
+		const auto fullScreenImageDispatch = vkcv::dispatchInvocations(
+				vkcv::DispatchSize(m_windowWidth, m_windowHeight),
+				vkcv::DispatchSize(8, 8)
+		);
+
+		m_core.recordComputeDispatchToCmdStream(
+			cmdStream,
+			m_gammaCorrectionPass.pipeline,
+			fullScreenImageDispatch,
+			{ vkcv::useDescriptorSet(0, m_gammaCorrectionPass.descriptorSet) },
+			vkcv::PushConstants(0)
+		);
+
+		m_core.prepareSwapchainImageForPresent(cmdStream);
+		m_core.submitCommandStream(cmdStream);
+
+		gui.beginGUI();
+		ImGui::Begin("Settings");
+
+		ImGui::Checkbox("Freeze frame", &freezeFrame);
+		ImGui::InputFloat("Motion tile offset length", &motionBlurTileOffsetLength);
+		ImGui::InputFloat("Motion blur fast path threshold", &motionBlurFastPathThreshold);
+
+		ImGui::Combo(
+			"Motion blur mode",
+			reinterpret_cast<int*>(&motionBlurMode),
+			MotionBlurModeLabels,
+			static_cast<int>(eMotionBlurMode::OptionCount));
+
+		ImGui::Combo(
+			"Debug view",
+			reinterpret_cast<int*>(&motionVectorVisualisationMode),
+			MotionVectorVisualisationModeLabels,
+			static_cast<int>(eMotionVectorVisualisationMode::OptionCount));
+
+		if (motionVectorVisualisationMode != eMotionVectorVisualisationMode::None)
+			ImGui::InputFloat("Motion vector visualisation range", &motionVectorVisualisationRange);
+
+		ImGui::InputInt("Camera shutter speed inverse", &cameraShutterSpeedInverse);
+
+		ImGui::InputFloat("Object movement speed",      &objectVerticalSpeed);
+		ImGui::InputFloat("Object movement amplitude",  &objectAmplitude);
+		ImGui::InputFloat("Object mean height",         &objectMeanHeight);
+		ImGui::InputFloat("Object rotation speed X",    &objectRotationSpeedX);
+		ImGui::InputFloat("Object rotation speed Y",    &objectRotationSpeedY);
+		
+		float sharpness = fsr2.getSharpness();
+		
+		ImGui::Combo("FSR Quality Mode", &fsrModeIndex, fsrModeNames.data(), fsrModeNames.size());
+		ImGui::DragFloat("FSR Sharpness", &sharpness, 0.001, 0.0f, 1.0f);
+		ImGui::Checkbox("FSR Mip Lod Bias", &fsrMipLoadBiasFlag);
+		ImGui::Combo("Upscaling Mode", &upscalingMode, modeNames.data(), modeNames.size());
+		
+		if ((fsrModeIndex >= 0) && (fsrModeIndex <= 4)) {
+			fsrMode = static_cast<vkcv::upscaling::FSR2QualityMode>(fsrModeIndex);
+		}
+		
+		fsr1.setSharpness(sharpness);
+		fsr2.setSharpness(sharpness);
+		nis.setSharpness(sharpness);
+
+		ImGui::End();
+		gui.endGUI();
+
+		m_core.endFrame(m_windowHandle);
+	}
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/src/App.hpp b/projects/indirect_dispatch/src/App.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a35c2342c4c90e39b089a7c33a73c3aa7ce8a83f
--- /dev/null
+++ b/projects/indirect_dispatch/src/App.hpp
@@ -0,0 +1,38 @@
+#pragma once
+#include <vkcv/Core.hpp>
+#include <vkcv/camera/CameraManager.hpp>
+#include "AppSetup.hpp"
+#include "MotionBlur.hpp"
+
+class App {
+public:
+	App();
+	bool initialize();
+	void run();
+private:
+	const char* m_applicationName;
+
+	uint32_t m_windowWidth;
+	uint32_t m_windowHeight;
+
+	vkcv::Core                  m_core;
+	vkcv::WindowHandle          m_windowHandle;
+	vkcv::camera::CameraManager m_cameraManager;
+
+	MotionBlur m_motionBlur;
+
+	vkcv::ImageHandle m_gridTexture;
+
+	MeshResources m_cubeMesh;
+	MeshResources m_groundMesh;
+
+	GraphicPassHandles m_meshPass;
+	GraphicPassHandles m_skyPass;
+	GraphicPassHandles m_prePass;
+	GraphicPassHandles m_skyPrePass;
+
+	ComputePassHandles m_gammaCorrectionPass;
+
+	AppRenderTargets    m_renderTargets;
+	vkcv::SamplerHandle m_linearSampler;
+};
\ No newline at end of file
diff --git a/projects/indirect_dispatch/src/AppConfig.hpp b/projects/indirect_dispatch/src/AppConfig.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c89c34ea8e3c0c45708ca998a642faffb31403d3
--- /dev/null
+++ b/projects/indirect_dispatch/src/AppConfig.hpp
@@ -0,0 +1,10 @@
+#pragma once
+#include "vulkan/vulkan.hpp"
+
+namespace AppConfig{
+	const int           defaultWindowWidth  = 1280;
+	const int           defaultWindowHeight = 720;
+	const vk::Format    depthBufferFormat   = vk::Format::eD32Sfloat;
+	const vk::Format    colorBufferFormat   = vk::Format::eB10G11R11UfloatPack32;
+	const vk::Format    motionBufferFormat  = vk::Format::eR16G16Sfloat;
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/src/AppSetup.cpp b/projects/indirect_dispatch/src/AppSetup.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5dc6c81e0e9599a561c3de1e359563c1f25334ea
--- /dev/null
+++ b/projects/indirect_dispatch/src/AppSetup.cpp
@@ -0,0 +1,352 @@
+#include "AppSetup.hpp"
+#include "AppConfig.hpp"
+
+#include <vkcv/Buffer.hpp>
+#include <vkcv/Image.hpp>
+#include <vkcv/asset/asset_loader.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+
+bool loadMesh(vkcv::Core& core, const std::filesystem::path& path, MeshResources* outMesh) {
+	assert(outMesh);
+
+	vkcv::asset::Scene scene;
+	const int meshLoadResult = vkcv::asset::loadScene(path.string(), scene);
+
+	if (meshLoadResult != 1) {
+		vkcv_log(vkcv::LogLevel::ERROR, "Mesh loading failed");
+		return false;
+	}
+
+	if (scene.meshes.size() < 1) {
+		vkcv_log(vkcv::LogLevel::ERROR, "Cube mesh scene does not contain any vertex groups");
+		return false;
+	}
+	assert(!scene.vertexGroups.empty());
+
+	auto& vertexData = scene.vertexGroups[0].vertexBuffer;
+	auto& indexData  = scene.vertexGroups[0].indexBuffer;
+
+	vkcv::Buffer<uint8_t> vertexBuffer = vkcv::buffer<uint8_t>(
+		core,
+		vkcv::BufferType::VERTEX,
+		vertexData.data.size(),
+		vkcv::BufferMemoryType::DEVICE_LOCAL);
+
+	vkcv::Buffer<uint8_t> indexBuffer = vkcv::buffer<uint8_t>(
+		core,
+		vkcv::BufferType::INDEX,
+		indexData.data.size(),
+		vkcv::BufferMemoryType::DEVICE_LOCAL);
+
+	vertexBuffer.fill(vertexData.data);
+	indexBuffer.fill(indexData.data);
+
+	outMesh->vertexBuffer = vertexBuffer.getHandle();
+	outMesh->indexBuffer  = indexBuffer.getHandle();
+	
+	const auto vertexBufferBindings = vkcv::asset::loadVertexBufferBindings(
+			vertexData.attributes,
+			vertexBuffer.getHandle(),
+			{
+					vkcv::asset::PrimitiveType::POSITION,
+					vkcv::asset::PrimitiveType::NORMAL,
+					vkcv::asset::PrimitiveType::TEXCOORD_0
+			}
+	);
+
+	outMesh->mesh = vkcv::VertexData(vertexBufferBindings);
+	outMesh->mesh.setIndexBuffer(indexBuffer.getHandle());
+	outMesh->mesh.setCount(scene.vertexGroups[0].numIndices);
+
+	return true;
+}
+
+bool loadImage(vkcv::Core& core, const std::filesystem::path& path, vkcv::ImageHandle* outImage) {
+
+	assert(outImage);
+
+	const vkcv::asset::Texture textureData = vkcv::asset::loadTexture(path);
+
+	if (textureData.channels != 4) {
+		vkcv_log(vkcv::LogLevel::ERROR, "Expecting image with four components");
+		return false;
+	}
+
+	vkcv::Image image = vkcv::image(
+			core,
+			vk::Format::eR8G8B8A8Srgb,
+			textureData.width,
+			textureData.height,
+			1,
+			true
+	);
+
+	image.fill(textureData.data.data(), textureData.data.size());
+	
+	{
+		auto mipStream = core.createCommandStream(vkcv::QueueType::Graphics);
+		image.recordMipChainGeneration(mipStream, core.getDownsampler());
+		core.submitCommandStream(mipStream, false);
+	}
+
+	*outImage = image.getHandle();
+	return true;
+}
+
+bool loadGraphicPass(
+	vkcv::Core& core,
+	const std::filesystem::path vertexPath,
+	const std::filesystem::path fragmentPath,
+	const vkcv::PassConfig&     passConfig,
+	const vkcv::DepthTest       depthTest,
+	GraphicPassHandles*         outPassHandles) {
+
+	assert(outPassHandles);
+
+	outPassHandles->renderPass = core.createPass(passConfig);
+
+	if (!outPassHandles->renderPass) {
+		vkcv_log(vkcv::LogLevel::ERROR, "Error: Could not create renderpass");
+		return false;
+	}
+
+	vkcv::ShaderProgram         shaderProgram;
+	vkcv::shader::GLSLCompiler  compiler;
+
+	compiler.compile(vkcv::ShaderStage::VERTEX, vertexPath,
+		[&shaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shaderProgram.addShader(shaderStage, path);
+	});
+
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, fragmentPath,
+		[&shaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shaderProgram.addShader(shaderStage, path);
+	});
+
+	const std::vector<vkcv::VertexAttachment> vertexAttachments = shaderProgram.getVertexAttachments();
+	std::vector<vkcv::VertexBinding> bindings;
+	for (size_t i = 0; i < vertexAttachments.size(); i++) {
+		bindings.push_back(vkcv::createVertexBinding(i, { vertexAttachments[i] }));
+	}
+
+	const vkcv::VertexLayout vertexLayout { bindings };
+
+	const auto descriptorBindings = shaderProgram.getReflectedDescriptors();
+	const bool hasDescriptor = descriptorBindings.size() > 0;
+	std::vector<vkcv::DescriptorSetLayoutHandle> descriptorSetLayouts = {};
+	if (hasDescriptor)
+	{
+	    outPassHandles->descriptorSetLayout = core.createDescriptorSetLayout(descriptorBindings.at(0));
+	    outPassHandles->descriptorSet = core.createDescriptorSet(outPassHandles->descriptorSetLayout);
+	    descriptorSetLayouts.push_back(outPassHandles->descriptorSetLayout);
+	}
+	
+	vkcv::GraphicsPipelineConfig pipelineConfig(
+		shaderProgram,
+		outPassHandles->renderPass,
+		{ vertexLayout },
+		descriptorSetLayouts
+	);
+	
+	pipelineConfig.setDepthTest(depthTest);
+	outPassHandles->pipeline = core.createGraphicsPipeline(pipelineConfig);
+
+	if (!outPassHandles->pipeline) {
+		vkcv_log(vkcv::LogLevel::ERROR, "Error: Could not create graphics pipeline");
+		return false;
+	}
+
+	return true;
+}
+
+bool loadMeshPass(vkcv::Core& core, GraphicPassHandles* outHandles) {
+
+	assert(outHandles);
+
+	vkcv::AttachmentDescription colorAttachment(
+			AppConfig::colorBufferFormat,
+			vkcv::AttachmentOperation::DONT_CARE,
+			vkcv::AttachmentOperation::STORE
+	);
+
+	vkcv::AttachmentDescription depthAttachment(
+			AppConfig::depthBufferFormat,
+			vkcv::AttachmentOperation::LOAD,
+			vkcv::AttachmentOperation::STORE
+	);
+
+	return loadGraphicPass(
+		core,
+		"assets/shaders/mesh.vert",
+		"assets/shaders/mesh.frag",
+		vkcv::PassConfig(
+				{ colorAttachment, depthAttachment },
+				vkcv::Multisampling::None
+		),
+		vkcv::DepthTest::Equal,
+		outHandles);
+}
+
+bool loadSkyPass(vkcv::Core& core, GraphicPassHandles* outHandles) {
+
+	assert(outHandles);
+
+	vkcv::AttachmentDescription colorAttachment(
+			AppConfig::colorBufferFormat,
+			vkcv::AttachmentOperation::LOAD,
+			vkcv::AttachmentOperation::STORE
+	);
+
+	vkcv::AttachmentDescription depthAttachment(
+			AppConfig::depthBufferFormat,
+			vkcv::AttachmentOperation::LOAD,
+			vkcv::AttachmentOperation::STORE
+	);
+
+	return loadGraphicPass(
+		core,
+		"assets/shaders/sky.vert",
+		"assets/shaders/sky.frag",
+		vkcv::PassConfig(
+				{ colorAttachment, depthAttachment },
+				vkcv::Multisampling::None
+		),
+		vkcv::DepthTest::Equal,
+		outHandles);
+}
+
+bool loadPrePass(vkcv::Core& core, GraphicPassHandles* outHandles) {
+	assert(outHandles);
+
+	vkcv::AttachmentDescription motionAttachment(
+			AppConfig::motionBufferFormat,
+			vkcv::AttachmentOperation::CLEAR,
+			vkcv::AttachmentOperation::STORE
+	);
+
+	vkcv::AttachmentDescription depthAttachment(
+			AppConfig::depthBufferFormat,
+			vkcv::AttachmentOperation::CLEAR,
+			vkcv::AttachmentOperation::STORE
+	);
+
+	return loadGraphicPass(
+		core,
+		"assets/shaders/prepass.vert",
+		"assets/shaders/prepass.frag",
+		vkcv::PassConfig(
+				{ motionAttachment, depthAttachment },
+				vkcv::Multisampling::None
+		),
+		vkcv::DepthTest::LessEqual,
+		outHandles);
+}
+
+bool loadSkyPrePass(vkcv::Core& core, GraphicPassHandles* outHandles) {
+	assert(outHandles);
+
+	vkcv::AttachmentDescription motionAttachment(
+			AppConfig::motionBufferFormat,
+			vkcv::AttachmentOperation::LOAD,
+			vkcv::AttachmentOperation::STORE
+	);
+
+	vkcv::AttachmentDescription depthAttachment(
+			AppConfig::depthBufferFormat,
+			vkcv::AttachmentOperation::LOAD,
+			vkcv::AttachmentOperation::STORE
+	);
+
+	return loadGraphicPass(
+		core,
+		"assets/shaders/skyPrepass.vert",
+		"assets/shaders/skyPrepass.frag",
+		vkcv::PassConfig(
+				{ motionAttachment, depthAttachment },
+				vkcv::Multisampling::None
+		),
+		vkcv::DepthTest::LessEqual,
+		outHandles);
+}
+
+bool loadComputePass(vkcv::Core& core, const std::filesystem::path& path, ComputePassHandles* outComputePass) {
+
+	assert(outComputePass);
+	vkcv::ShaderProgram shaderProgram;
+	vkcv::shader::GLSLCompiler compiler;
+
+	compiler.compile(vkcv::ShaderStage::COMPUTE, path,
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shaderProgram.addShader(shaderStage, path);
+	});
+
+	if (shaderProgram.getReflectedDescriptors().size() < 1) {
+		vkcv_log(vkcv::LogLevel::ERROR, "Compute shader has no descriptor set");
+		return false;
+	}
+
+	outComputePass->descriptorSetLayout = core.createDescriptorSetLayout(shaderProgram.getReflectedDescriptors().at(0));
+	outComputePass->descriptorSet = core.createDescriptorSet(outComputePass->descriptorSetLayout);
+	outComputePass->pipeline = core.createComputePipeline({
+		shaderProgram,
+		{ outComputePass->descriptorSetLayout }});
+
+	if (!outComputePass->pipeline) {
+		vkcv_log(vkcv::LogLevel::ERROR, "Compute shader pipeline creation failed");
+		return false;
+	}
+
+	return true;
+}
+
+AppRenderTargets createRenderTargets(vkcv::Core& core,
+									 uint32_t width,
+									 uint32_t height,
+									 vkcv::upscaling::FSR2QualityMode mode) {
+	AppRenderTargets targets;
+	uint32_t renderWidth, renderHeight;
+	
+	vkcv::upscaling::getFSR2Resolution(
+			mode,
+			width,
+			height,
+			renderWidth,
+			renderHeight
+	);
+	
+	vkcv::ImageConfig depthBufferConfig (renderWidth, renderHeight);
+
+	targets.depthBuffer = core.createImage(
+		AppConfig::depthBufferFormat,
+		depthBufferConfig
+	);
+	
+	vkcv::ImageConfig bufferConfig (renderWidth, renderHeight);
+	bufferConfig.setSupportingColorAttachment(true);
+
+	targets.colorBuffer = core.createImage(
+		AppConfig::colorBufferFormat,
+		bufferConfig
+	);
+
+	targets.motionBuffer = core.createImage(
+		AppConfig::motionBufferFormat,
+		bufferConfig
+	);
+	
+	vkcv::ImageConfig finalConfig (width, height);
+	finalConfig.setSupportingColorAttachment(true);
+	finalConfig.setSupportingStorage(true);
+	
+	targets.finalBuffer = core.createImage(
+			AppConfig::colorBufferFormat,
+			finalConfig
+	);
+	
+	core.setDebugLabel(targets.depthBuffer, "Depth buffer");
+	core.setDebugLabel(targets.colorBuffer, "Color buffer");
+	core.setDebugLabel(targets.motionBuffer, "Motion buffer");
+	core.setDebugLabel(targets.finalBuffer, "Final buffer");
+
+	return targets;
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/src/AppSetup.hpp b/projects/indirect_dispatch/src/AppSetup.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d06910b5f02325d27ee7f2a0e0fbfe4cf7560dab
--- /dev/null
+++ b/projects/indirect_dispatch/src/AppSetup.hpp
@@ -0,0 +1,54 @@
+#pragma once
+#include <vkcv/Core.hpp>
+#include <vkcv/upscaling/FSR2Upscaling.hpp>
+
+struct AppRenderTargets {
+	vkcv::ImageHandle depthBuffer;
+	vkcv::ImageHandle colorBuffer;
+	vkcv::ImageHandle motionBuffer;
+	vkcv::ImageHandle finalBuffer;
+};
+
+struct GraphicPassHandles {
+	vkcv::GraphicsPipelineHandle        pipeline;
+	vkcv::PassHandle                    renderPass;
+	vkcv::DescriptorSetLayoutHandle     descriptorSetLayout;
+	vkcv::DescriptorSetHandle           descriptorSet;
+};
+
+struct ComputePassHandles {
+	vkcv::ComputePipelineHandle         pipeline;
+	vkcv::DescriptorSetLayoutHandle     descriptorSetLayout;
+	vkcv::DescriptorSetHandle           descriptorSet;
+};
+
+struct MeshResources {
+	vkcv::VertexData    mesh;
+	vkcv::BufferHandle  vertexBuffer;
+	vkcv::BufferHandle  indexBuffer;
+};
+
+// loads position, uv and normal of the first mesh in a scene
+bool loadMesh(vkcv::Core& core, const std::filesystem::path& path, MeshResources* outMesh);
+
+bool loadImage(vkcv::Core& core, const std::filesystem::path& path, vkcv::ImageHandle* outImage);
+
+bool loadGraphicPass(
+	vkcv::Core& core,
+	std::filesystem::path vertexPath,
+	std::filesystem::path fragmentPath,
+	const vkcv::PassConfig&     passConfig,
+	vkcv::DepthTest       		depthTest,
+	GraphicPassHandles*         outPassHandles);
+
+bool loadMeshPass  (vkcv::Core& core, GraphicPassHandles* outHandles);
+bool loadSkyPass   (vkcv::Core& core, GraphicPassHandles* outHandles);
+bool loadPrePass   (vkcv::Core& core, GraphicPassHandles* outHandles);
+bool loadSkyPrePass(vkcv::Core& core, GraphicPassHandles* outHandles);
+
+bool loadComputePass(vkcv::Core& core, const std::filesystem::path& path, ComputePassHandles* outComputePass);
+
+AppRenderTargets createRenderTargets(vkcv::Core& core,
+									 uint32_t width,
+									 uint32_t height,
+									 vkcv::upscaling::FSR2QualityMode mode);
\ No newline at end of file
diff --git a/projects/indirect_dispatch/src/MotionBlur.cpp b/projects/indirect_dispatch/src/MotionBlur.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fe55a5821b042feb709deffee806513b18ed06ba
--- /dev/null
+++ b/projects/indirect_dispatch/src/MotionBlur.cpp
@@ -0,0 +1,433 @@
+#include "MotionBlur.hpp"
+#include "MotionBlurConfig.hpp"
+#include "MotionBlurSetup.hpp"
+
+#include <vkcv/Buffer.hpp>
+#include <vkcv/Sampler.hpp>
+
+#include <array>
+
+bool MotionBlur::initialize(vkcv::Core* corePtr, const uint32_t targetWidth, const uint32_t targetHeight) {
+
+	if (!corePtr) {
+		vkcv_log(vkcv::LogLevel::ERROR, "MotionBlur got invalid corePtr")
+		return false;
+	}
+
+	m_core = corePtr;
+
+	if (!loadComputePass(*m_core, "assets/shaders/motionBlur.comp", &m_motionBlurPass))
+		return false;
+
+	if (!loadComputePass(*m_core, "assets/shaders/motionVectorMinMax.comp", &m_motionVectorMinMaxPass))
+		return false;
+
+	if (!loadComputePass(*m_core, "assets/shaders/motionVectorMinMaxNeighbourhood.comp", &m_motionVectorMinMaxNeighbourhoodPass))
+		return false;
+
+	if (!loadComputePass(*m_core, "assets/shaders/motionVectorVisualisation.comp", &m_motionVectorVisualisationPass))
+		return false;
+
+	if (!loadComputePass(*m_core, "assets/shaders/motionBlurColorCopy.comp", &m_colorCopyPass))
+		return false;
+
+	if (!loadComputePass(*m_core, "assets/shaders/motionBlurTileClassification.comp", &m_tileClassificationPass))
+		return false;
+
+	if (!loadComputePass(*m_core, "assets/shaders/motionBlurWorkTileReset.comp", &m_tileResetPass))
+		return false;
+
+	if (!loadComputePass(*m_core, "assets/shaders/motionBlurTileClassificationVis.comp", &m_tileVisualisationPass))
+		return false;
+
+	if (!loadComputePass(*m_core, "assets/shaders/motionBlurFastPath.comp", &m_motionBlurFastPathPass))
+		return false;
+
+	// work tile buffers and descriptors
+	const uint32_t workTileBufferSize = static_cast<uint32_t>(2 * sizeof(uint32_t)) * (3 +
+		((MotionBlurConfig::maxWidth + MotionBlurConfig::maxMotionTileSize - 1) / MotionBlurConfig::maxMotionTileSize) *
+		((MotionBlurConfig::maxHeight + MotionBlurConfig::maxMotionTileSize - 1) / MotionBlurConfig::maxMotionTileSize));
+
+	m_copyPathWorkTileBuffer = vkcv::buffer<uint32_t>(
+		*m_core,
+		vkcv::BufferType::INDIRECT,
+		workTileBufferSize, 
+		vkcv::BufferMemoryType::DEVICE_LOCAL).getHandle();
+
+	m_fullPathWorkTileBuffer = vkcv::buffer<uint32_t>(
+		*m_core,
+		vkcv::BufferType::INDIRECT,
+		workTileBufferSize, 
+		vkcv::BufferMemoryType::DEVICE_LOCAL).getHandle();
+
+	m_fastPathWorkTileBuffer = vkcv::buffer<uint32_t>(
+		*m_core,
+		vkcv::BufferType::INDIRECT,
+		workTileBufferSize,
+		vkcv::BufferMemoryType::DEVICE_LOCAL).getHandle();
+
+	vkcv::DescriptorWrites tileResetDescriptorWrites;
+	tileResetDescriptorWrites.writeStorageBuffer(
+			0, m_fullPathWorkTileBuffer
+	).writeStorageBuffer(
+			1, m_copyPathWorkTileBuffer
+	).writeStorageBuffer(
+			2, m_fastPathWorkTileBuffer
+	);
+
+	m_core->writeDescriptorSet(m_tileResetPass.descriptorSet, tileResetDescriptorWrites);
+
+	m_renderTargets = MotionBlurSetup::createRenderTargets(targetWidth, targetHeight, *m_core);
+
+	m_nearestSampler = vkcv::samplerNearest(*m_core, true);
+	
+	return true;
+}
+
+void MotionBlur::setResolution(const uint32_t targetWidth, const uint32_t targetHeight) {
+	m_renderTargets = MotionBlurSetup::createRenderTargets(targetWidth, targetHeight, *m_core);
+}
+
+vkcv::ImageHandle MotionBlur::render(
+	const vkcv::CommandStreamHandle cmdStream,
+	const vkcv::ImageHandle         motionBufferFullRes,
+	const vkcv::ImageHandle         colorBuffer,
+	const vkcv::ImageHandle         depthBuffer,
+	const eMotionBlurMode           mode,
+	const float                     cameraNear,
+	const float                     cameraFar,
+	const float                     deltaTimeSeconds,
+	const float                     cameraShutterSpeedInverse,
+	const float                     motionTileOffsetLength,
+	const float                     fastPathThreshold) {
+
+	computeMotionTiles(cmdStream, motionBufferFullRes);
+
+	m_core->recordComputeDispatchToCmdStream(
+		cmdStream,
+		m_tileResetPass.pipeline,
+		1,
+		{ vkcv::useDescriptorSet(0, m_tileResetPass.descriptorSet) },
+		vkcv::PushConstants(0)
+	);
+
+	m_core->recordBufferMemoryBarrier(cmdStream, m_fullPathWorkTileBuffer);
+	m_core->recordBufferMemoryBarrier(cmdStream, m_copyPathWorkTileBuffer);
+	m_core->recordBufferMemoryBarrier(cmdStream, m_fastPathWorkTileBuffer);
+
+	// work tile classification
+	vkcv::DescriptorWrites tileClassificationDescriptorWrites;
+	tileClassificationDescriptorWrites.writeSampledImage(
+			0, m_renderTargets.motionMaxNeighbourhood
+	).writeSampledImage(
+			1, m_renderTargets.motionMinNeighbourhood
+	);
+	
+	tileClassificationDescriptorWrites.writeSampler(2, m_nearestSampler);
+	tileClassificationDescriptorWrites.writeStorageBuffer(
+			3, m_fullPathWorkTileBuffer
+	).writeStorageBuffer(
+			4, m_copyPathWorkTileBuffer
+	).writeStorageBuffer(
+			5, m_fastPathWorkTileBuffer
+	);
+
+	m_core->writeDescriptorSet(m_tileClassificationPass.descriptorSet, tileClassificationDescriptorWrites);
+
+	const auto tileClassificationDispatch = vkcv::dispatchInvocations(
+			vkcv::DispatchSize(
+					m_core->getImageWidth(m_renderTargets.motionMaxNeighbourhood),
+					m_core->getImageHeight(m_renderTargets.motionMaxNeighbourhood)
+			),
+			vkcv::DispatchSize(8, 8)
+	);
+
+	struct ClassificationConstants {
+		uint32_t    width;
+		uint32_t    height;
+		float       fastPathThreshold;
+	};
+	ClassificationConstants classificationConstants;
+	classificationConstants.width               = m_core->getImageWidth(m_renderTargets.outputColor);
+	classificationConstants.height              = m_core->getImageHeight(m_renderTargets.outputColor);
+	classificationConstants.fastPathThreshold   = fastPathThreshold;
+
+	vkcv::PushConstants classificationPushConstants = vkcv::pushConstants<ClassificationConstants>();
+    classificationPushConstants.appendDrawcall(classificationConstants);
+
+	m_core->prepareImageForSampling(cmdStream, m_renderTargets.motionMaxNeighbourhood);
+	m_core->prepareImageForSampling(cmdStream, m_renderTargets.motionMinNeighbourhood);
+
+	m_core->recordComputeDispatchToCmdStream(
+		cmdStream,
+		m_tileClassificationPass.pipeline,
+		tileClassificationDispatch,
+		{ vkcv::useDescriptorSet(0, m_tileClassificationPass.descriptorSet) },
+		classificationPushConstants);
+
+	m_core->recordBufferMemoryBarrier(cmdStream, m_fullPathWorkTileBuffer);
+	m_core->recordBufferMemoryBarrier(cmdStream, m_copyPathWorkTileBuffer);
+	m_core->recordBufferMemoryBarrier(cmdStream, m_fastPathWorkTileBuffer);
+
+	vkcv::DescriptorWrites motionBlurDescriptorWrites;
+	motionBlurDescriptorWrites.writeSampledImage(
+			0, colorBuffer
+	).writeSampledImage(
+			1, depthBuffer
+	).writeSampledImage(
+			2, motionBufferFullRes
+	).writeSampledImage(
+			3, m_renderTargets.motionMaxNeighbourhood
+	);
+	
+	motionBlurDescriptorWrites.writeSampler(4, m_nearestSampler);
+	motionBlurDescriptorWrites.writeStorageImage(5, m_renderTargets.outputColor);
+	motionBlurDescriptorWrites.writeStorageBuffer(6, m_fullPathWorkTileBuffer);
+
+	m_core->writeDescriptorSet(m_motionBlurPass.descriptorSet, motionBlurDescriptorWrites);
+
+	vkcv::DescriptorWrites colorCopyDescriptorWrites;
+	colorCopyDescriptorWrites.writeSampledImage(0, colorBuffer);
+	colorCopyDescriptorWrites.writeSampler(1, m_nearestSampler);
+	colorCopyDescriptorWrites.writeStorageImage(2, m_renderTargets.outputColor);
+	colorCopyDescriptorWrites.writeStorageBuffer(3, m_copyPathWorkTileBuffer);
+
+	m_core->writeDescriptorSet(m_colorCopyPass.descriptorSet, colorCopyDescriptorWrites);
+
+
+	vkcv::DescriptorWrites fastPathDescriptorWrites;
+	fastPathDescriptorWrites.writeSampledImage(
+			0, colorBuffer
+	).writeSampledImage(
+			1, m_renderTargets.motionMaxNeighbourhood
+	);
+	
+	fastPathDescriptorWrites.writeSampler(2, m_nearestSampler);
+	fastPathDescriptorWrites.writeStorageImage(3, m_renderTargets.outputColor);
+	fastPathDescriptorWrites.writeStorageBuffer(4, m_fastPathWorkTileBuffer);
+
+	m_core->writeDescriptorSet(m_motionBlurFastPathPass.descriptorSet, fastPathDescriptorWrites);
+
+	// must match layout in "motionBlur.comp"
+	struct MotionBlurConstantData {
+		float motionFactor;
+		float cameraNearPlane;
+		float cameraFarPlane;
+		float motionTileOffsetLength;
+	};
+	MotionBlurConstantData motionBlurConstantData;
+
+	const float deltaTimeMotionBlur = deltaTimeSeconds;
+
+	motionBlurConstantData.motionFactor             = 1 / (deltaTimeMotionBlur * cameraShutterSpeedInverse);
+	motionBlurConstantData.cameraNearPlane          = cameraNear;
+	motionBlurConstantData.cameraFarPlane           = cameraFar;
+	motionBlurConstantData.motionTileOffsetLength   = motionTileOffsetLength;
+
+	vkcv::PushConstants motionBlurPushConstants = vkcv::pushConstants<MotionBlurConstantData>();
+	motionBlurPushConstants.appendDrawcall(motionBlurConstantData);
+
+	struct FastPathConstants {
+		float motionFactor;
+	};
+	FastPathConstants fastPathConstants;
+	fastPathConstants.motionFactor = motionBlurConstantData.motionFactor;
+
+	vkcv::PushConstants fastPathPushConstants = vkcv::pushConstants<FastPathConstants>();
+	fastPathPushConstants.appendDrawcall(fastPathConstants);
+
+	m_core->prepareImageForStorage(cmdStream, m_renderTargets.outputColor);
+	m_core->prepareImageForSampling(cmdStream, colorBuffer);
+	m_core->prepareImageForSampling(cmdStream, depthBuffer);
+	m_core->prepareImageForSampling(cmdStream, m_renderTargets.motionMaxNeighbourhood);
+
+	if (mode == eMotionBlurMode::Default) {
+		m_core->recordComputeIndirectDispatchToCmdStream(
+			cmdStream,
+			m_motionBlurPass.pipeline,
+			m_fullPathWorkTileBuffer,
+			0,
+			{ vkcv::useDescriptorSet(0, m_motionBlurPass.descriptorSet) },
+			motionBlurPushConstants);
+
+		m_core->recordComputeIndirectDispatchToCmdStream(
+			cmdStream,
+			m_colorCopyPass.pipeline,
+			m_copyPathWorkTileBuffer,
+			0,
+			{ vkcv::useDescriptorSet(0, m_colorCopyPass.descriptorSet) },
+			vkcv::PushConstants(0));
+
+		m_core->recordComputeIndirectDispatchToCmdStream(
+			cmdStream,
+			m_motionBlurFastPathPass.pipeline,
+			m_fastPathWorkTileBuffer,
+			0,
+			{ vkcv::useDescriptorSet(0, m_motionBlurFastPathPass.descriptorSet) },
+			fastPathPushConstants);
+	}
+	else if(mode == eMotionBlurMode::Disabled) {
+		return colorBuffer;
+	}
+	else if (mode == eMotionBlurMode::TileVisualisation) {
+
+		vkcv::DescriptorWrites visualisationDescriptorWrites;
+		visualisationDescriptorWrites.writeSampledImage(0, colorBuffer);
+		visualisationDescriptorWrites.writeSampler(1, m_nearestSampler);
+		visualisationDescriptorWrites.writeStorageImage(2, m_renderTargets.outputColor);
+		
+		visualisationDescriptorWrites.writeStorageBuffer(
+				3, m_fullPathWorkTileBuffer
+		).writeStorageBuffer(
+				4, m_copyPathWorkTileBuffer
+		).writeStorageBuffer(
+				5, m_fastPathWorkTileBuffer
+		);
+
+		m_core->writeDescriptorSet(m_tileVisualisationPass.descriptorSet, visualisationDescriptorWrites);
+
+		const uint32_t tileCount = 
+			(m_core->getImageWidth(m_renderTargets.outputColor)  + MotionBlurConfig::maxMotionTileSize - 1) / MotionBlurConfig::maxMotionTileSize * 
+			(m_core->getImageHeight(m_renderTargets.outputColor) + MotionBlurConfig::maxMotionTileSize - 1) / MotionBlurConfig::maxMotionTileSize;
+
+		m_core->recordComputeDispatchToCmdStream(
+			cmdStream,
+			m_tileVisualisationPass.pipeline,
+			tileCount,
+			{ vkcv::useDescriptorSet(0, m_tileVisualisationPass.descriptorSet) },
+			vkcv::PushConstants(0));
+	}
+	else {
+		vkcv_log(vkcv::LogLevel::ERROR, "Unknown eMotionBlurMode enum option");
+		return colorBuffer;
+	}
+
+	return m_renderTargets.outputColor;
+}
+
+vkcv::ImageHandle MotionBlur::renderMotionVectorVisualisation(
+	const vkcv::CommandStreamHandle         cmdStream,
+	const vkcv::ImageHandle                 motionBuffer,
+	const eMotionVectorVisualisationMode    mode,
+	const float                             velocityRange) {
+
+	computeMotionTiles(cmdStream, motionBuffer);
+
+	vkcv::ImageHandle visualisationInput;
+	if (     mode == eMotionVectorVisualisationMode::FullResolution)
+		visualisationInput = motionBuffer;
+	else if (mode == eMotionVectorVisualisationMode::MaxTile)
+		visualisationInput = m_renderTargets.motionMax;
+	else if (mode == eMotionVectorVisualisationMode::MaxTileNeighbourhood)
+		visualisationInput = m_renderTargets.motionMaxNeighbourhood;
+	else if (mode == eMotionVectorVisualisationMode::MinTile)
+		visualisationInput = m_renderTargets.motionMin;
+	else if (mode == eMotionVectorVisualisationMode::MinTileNeighbourhood)
+		visualisationInput = m_renderTargets.motionMinNeighbourhood;
+	else if (mode == eMotionVectorVisualisationMode::None) {
+		vkcv_log(vkcv::LogLevel::ERROR, "renderMotionVectorVisualisation called with visualisation mode 'None'");
+		return motionBuffer;
+	}
+	else {
+		vkcv_log(vkcv::LogLevel::ERROR, "Unknown eDebugView enum value");
+		return motionBuffer;
+	}
+
+	vkcv::DescriptorWrites motionVectorVisualisationDescriptorWrites;
+	motionVectorVisualisationDescriptorWrites.writeSampledImage(0, visualisationInput);
+	motionVectorVisualisationDescriptorWrites.writeSampler(1, m_nearestSampler);
+	motionVectorVisualisationDescriptorWrites.writeStorageImage(2, m_renderTargets.outputColor);
+
+	m_core->writeDescriptorSet(
+		m_motionVectorVisualisationPass.descriptorSet,
+		motionVectorVisualisationDescriptorWrites);
+
+	m_core->prepareImageForSampling(cmdStream, visualisationInput);
+	m_core->prepareImageForStorage(cmdStream, m_renderTargets.outputColor);
+
+	vkcv::PushConstants motionVectorVisualisationPushConstants = vkcv::pushConstants<float>();
+	motionVectorVisualisationPushConstants.appendDrawcall(velocityRange);
+
+	const auto dispatchSizes = vkcv::dispatchInvocations(
+			vkcv::DispatchSize(
+					m_core->getImageWidth(m_renderTargets.outputColor),
+					m_core->getImageHeight(m_renderTargets.outputColor)
+			),
+			vkcv::DispatchSize(8, 8)
+	);
+
+	m_core->recordComputeDispatchToCmdStream(
+		cmdStream,
+		m_motionVectorVisualisationPass.pipeline,
+		dispatchSizes,
+		{ vkcv::useDescriptorSet(0, m_motionVectorVisualisationPass.descriptorSet) },
+		motionVectorVisualisationPushConstants);
+
+	return m_renderTargets.outputColor;
+}
+
+void MotionBlur::computeMotionTiles(
+	const vkcv::CommandStreamHandle cmdStream,
+	const vkcv::ImageHandle         motionBufferFullRes) {
+
+	// motion vector min max tiles
+	vkcv::DescriptorWrites motionVectorMaxTilesDescriptorWrites;
+	motionVectorMaxTilesDescriptorWrites.writeSampledImage(0, motionBufferFullRes);
+	motionVectorMaxTilesDescriptorWrites.writeSampler(1, m_nearestSampler);
+	motionVectorMaxTilesDescriptorWrites.writeStorageImage(
+			2, m_renderTargets.motionMax
+	).writeStorageImage(
+			3, m_renderTargets.motionMin
+	);
+
+	m_core->writeDescriptorSet(m_motionVectorMinMaxPass.descriptorSet, motionVectorMaxTilesDescriptorWrites);
+
+	m_core->prepareImageForSampling(cmdStream, motionBufferFullRes);
+	m_core->prepareImageForStorage(cmdStream, m_renderTargets.motionMax);
+	m_core->prepareImageForStorage(cmdStream, m_renderTargets.motionMin);
+
+	const auto motionTileDispatchCounts = vkcv::dispatchInvocations(
+			vkcv::DispatchSize(
+					m_core->getImageWidth( m_renderTargets.motionMax),
+					m_core->getImageHeight(m_renderTargets.motionMax)
+			),
+			vkcv::DispatchSize(8, 8)
+	);
+
+	m_core->recordComputeDispatchToCmdStream(
+		cmdStream,
+		m_motionVectorMinMaxPass.pipeline,
+		motionTileDispatchCounts,
+		{ vkcv::useDescriptorSet(0, m_motionVectorMinMaxPass.descriptorSet) },
+		vkcv::PushConstants(0));
+
+	// motion vector min max neighbourhood
+	vkcv::DescriptorWrites motionVectorMaxNeighbourhoodDescriptorWrites;
+	motionVectorMaxNeighbourhoodDescriptorWrites.writeSampledImage(
+			0, m_renderTargets.motionMax
+	).writeSampledImage(
+			1, m_renderTargets.motionMin
+	);
+	
+	motionVectorMaxNeighbourhoodDescriptorWrites.writeSampler(2, m_nearestSampler);
+	motionVectorMaxNeighbourhoodDescriptorWrites.writeStorageImage(
+			3, m_renderTargets.motionMaxNeighbourhood
+	).writeStorageImage(
+			4, m_renderTargets.motionMinNeighbourhood
+	);
+
+	m_core->writeDescriptorSet(m_motionVectorMinMaxNeighbourhoodPass.descriptorSet, motionVectorMaxNeighbourhoodDescriptorWrites);
+
+	m_core->prepareImageForSampling(cmdStream, m_renderTargets.motionMax);
+	m_core->prepareImageForSampling(cmdStream, m_renderTargets.motionMin);
+
+	m_core->prepareImageForStorage(cmdStream, m_renderTargets.motionMaxNeighbourhood);
+	m_core->prepareImageForStorage(cmdStream, m_renderTargets.motionMinNeighbourhood);
+
+	m_core->recordComputeDispatchToCmdStream(
+		cmdStream,
+		m_motionVectorMinMaxNeighbourhoodPass.pipeline,
+		motionTileDispatchCounts,
+		{ vkcv::useDescriptorSet(0, m_motionVectorMinMaxNeighbourhoodPass.descriptorSet) },
+		vkcv::PushConstants(0));
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/src/MotionBlur.hpp b/projects/indirect_dispatch/src/MotionBlur.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4e6003799237f69f4a422dd9c20f99255fe711fa
--- /dev/null
+++ b/projects/indirect_dispatch/src/MotionBlur.hpp
@@ -0,0 +1,71 @@
+#pragma once
+#include <vkcv/Core.hpp>
+#include "AppSetup.hpp"
+#include "MotionBlurSetup.hpp"
+
+// selection for motion blur input and visualisation
+enum class eMotionVectorVisualisationMode : int {
+	None                    = 0,
+	FullResolution          = 1,
+	MaxTile                 = 2,
+	MaxTileNeighbourhood    = 3,
+	MinTile                 = 4,
+	MinTileNeighbourhood    = 5,
+	OptionCount             = 6 };
+
+enum class eMotionBlurMode : int {
+	Default             = 0,
+	Disabled            = 1,
+	TileVisualisation   = 2,
+	OptionCount         = 3 };
+
+class MotionBlur {
+public:
+
+	bool initialize(vkcv::Core* corePtr, const uint32_t targetWidth, const uint32_t targetHeight);
+	void setResolution(const uint32_t targetWidth, const uint32_t targetHeight);
+
+	vkcv::ImageHandle render(
+		const vkcv::CommandStreamHandle cmdStream,
+		const vkcv::ImageHandle         motionBufferFullRes,
+		const vkcv::ImageHandle         colorBuffer,
+		const vkcv::ImageHandle         depthBuffer,
+		const eMotionBlurMode           mode,
+		const float                     cameraNear,
+		const float                     cameraFar,
+		const float                     deltaTimeSeconds,
+		const float                     cameraShutterSpeedInverse,
+		const float                     motionTileOffsetLength,
+		const float                     fastPathThreshold);
+
+	vkcv::ImageHandle renderMotionVectorVisualisation(
+		const vkcv::CommandStreamHandle         cmdStream,
+		const vkcv::ImageHandle                 motionBuffer,
+		const eMotionVectorVisualisationMode    mode,
+		const float                             velocityRange);
+
+private:
+	// computes max per tile and neighbourhood tile max
+	void computeMotionTiles(
+		const vkcv::CommandStreamHandle cmdStream,
+		const vkcv::ImageHandle         motionBufferFullRes);
+
+	vkcv::Core* m_core;
+
+	MotionBlurRenderTargets m_renderTargets;
+	vkcv::SamplerHandle     m_nearestSampler;
+
+	ComputePassHandles m_motionBlurPass;
+	ComputePassHandles m_motionVectorMinMaxPass;
+	ComputePassHandles m_motionVectorMinMaxNeighbourhoodPass;
+	ComputePassHandles m_motionVectorVisualisationPass;
+	ComputePassHandles m_colorCopyPass;
+	ComputePassHandles m_tileClassificationPass;
+	ComputePassHandles m_tileResetPass;
+	ComputePassHandles m_tileVisualisationPass;
+	ComputePassHandles m_motionBlurFastPathPass;
+
+	vkcv::BufferHandle m_fullPathWorkTileBuffer;
+	vkcv::BufferHandle m_copyPathWorkTileBuffer;
+	vkcv::BufferHandle m_fastPathWorkTileBuffer;
+};
\ No newline at end of file
diff --git a/projects/indirect_dispatch/src/MotionBlurConfig.hpp b/projects/indirect_dispatch/src/MotionBlurConfig.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7552abd246ca8d2e7489c5065f43ef8b48af7cd2
--- /dev/null
+++ b/projects/indirect_dispatch/src/MotionBlurConfig.hpp
@@ -0,0 +1,10 @@
+#pragma once
+#include "vulkan/vulkan.hpp"
+
+namespace MotionBlurConfig {
+	const vk::Format    motionVectorTileFormat  = vk::Format::eR16G16Sfloat;
+	const vk::Format    outputColorFormat       = vk::Format::eB10G11R11UfloatPack32;
+	const uint32_t      maxMotionTileSize       = 16;	// must match "motionTileSize" in motionBlurConfig.inc
+	const uint32_t      maxWidth                = 3840;
+	const uint32_t      maxHeight               = 2160;
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/src/MotionBlurSetup.cpp b/projects/indirect_dispatch/src/MotionBlurSetup.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7589f77b80398e0bc1d7f1aec9b9570cc20acf3f
--- /dev/null
+++ b/projects/indirect_dispatch/src/MotionBlurSetup.cpp
@@ -0,0 +1,48 @@
+#include "MotionBlurSetup.hpp"
+#include "MotionBlurConfig.hpp"
+
+namespace MotionBlurSetup {
+
+MotionBlurRenderTargets createRenderTargets(const uint32_t width, const uint32_t height, vkcv::Core& core) {
+
+	MotionBlurRenderTargets targets;
+
+	// divide and ceil to int
+	const uint32_t motionMaxWidth  = (width  + (MotionBlurConfig::maxMotionTileSize - 1)) / MotionBlurConfig::maxMotionTileSize;
+	const uint32_t motionMaxHeight = (height + (MotionBlurConfig::maxMotionTileSize - 1)) / MotionBlurConfig::maxMotionTileSize;
+
+	vkcv::ImageConfig targetConfig (motionMaxWidth, motionMaxHeight);
+	targetConfig.setSupportingStorage(true);
+	
+	targets.motionMax = core.createImage(
+		MotionBlurConfig::motionVectorTileFormat,
+		targetConfig
+	);
+
+	targets.motionMaxNeighbourhood = core.createImage(
+		MotionBlurConfig::motionVectorTileFormat,
+		targetConfig
+	);
+
+	targets.motionMin = core.createImage(
+		MotionBlurConfig::motionVectorTileFormat,
+		targetConfig
+	);
+
+	targets.motionMinNeighbourhood = core.createImage(
+		MotionBlurConfig::motionVectorTileFormat,
+		targetConfig
+	);
+	
+	vkcv::ImageConfig outputConfig (width, height);
+	outputConfig.setSupportingStorage(true);
+
+	targets.outputColor = core.createImage(
+		MotionBlurConfig::outputColorFormat,
+		outputConfig
+	);
+
+	return targets;
+}
+
+}	// namespace MotionBlurSetup
\ No newline at end of file
diff --git a/projects/indirect_dispatch/src/MotionBlurSetup.hpp b/projects/indirect_dispatch/src/MotionBlurSetup.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ca169d7c6b04aa152d42ba36c3d2e02e563bbd91
--- /dev/null
+++ b/projects/indirect_dispatch/src/MotionBlurSetup.hpp
@@ -0,0 +1,14 @@
+#pragma once
+#include <vkcv/Core.hpp>
+
+struct MotionBlurRenderTargets {
+	vkcv::ImageHandle outputColor;
+	vkcv::ImageHandle motionMax;
+	vkcv::ImageHandle motionMaxNeighbourhood;
+	vkcv::ImageHandle motionMin;
+	vkcv::ImageHandle motionMinNeighbourhood;
+};
+
+namespace MotionBlurSetup {
+	MotionBlurRenderTargets createRenderTargets(const uint32_t width, const uint32_t height, vkcv::Core& core);
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/src/main.cpp b/projects/indirect_dispatch/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b27e0bcb8f1991d76b570b79da9cc4734cf52950
--- /dev/null
+++ b/projects/indirect_dispatch/src/main.cpp
@@ -0,0 +1,13 @@
+#include "App.hpp"
+
+int main(int argc, const char** argv) {
+
+	App app;
+	if (!app.initialize()) {
+		std::cerr << "Application initialization failed, exiting" << std::endl;
+		return 1;
+	}
+	app.run();
+
+	return 0;
+}
diff --git a/projects/indirect_draw/.gitignore b/projects/indirect_draw/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..92714f6a3381225d29daff0d99efe51e12b40970
--- /dev/null
+++ b/projects/indirect_draw/.gitignore
@@ -0,0 +1 @@
+indirect_draw
\ No newline at end of file
diff --git a/projects/indirect_draw/CMakeLists.txt b/projects/indirect_draw/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..2802210db6111d833172933452bb25abb90a5c5b
--- /dev/null
+++ b/projects/indirect_draw/CMakeLists.txt
@@ -0,0 +1,15 @@
+cmake_minimum_required(VERSION 3.16)
+project(indirect_draw)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+# adding source files to the project
+add_project(indirect_draw src/main.cpp)
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(indirect_draw SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include} ${vkcv_gui_include})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(indirect_draw vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera vkcv_shader_compiler vkcv_gui)
diff --git a/projects/indirect_draw/README.md b/projects/indirect_draw/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..06c74837d579ea03a6a7061f90ea97dd6c0560c5
--- /dev/null
+++ b/projects/indirect_draw/README.md
@@ -0,0 +1,17 @@
+# Indirect draw
+An example project to show usage of indirect draw calls
+
+![Screenshot](../../screenshots/indirect_draw.png)
+
+## Details
+
+The project utilizes multiple Vulkan extensions to access to make indirect draw calls which can be
+modified on the GPU from within compute shaders. This will be used to cull the rendered scene on
+the GPU instead of (potentially) more expensive frustum culling on the CPU.
+
+## Extensions
+
+Here is a list of the used extensions:
+
+ - [VK_KHR_shader_draw_parameters](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_shader_draw_parameters.html)
+ - [VK_EXT_descriptor_indexing](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_descriptor_indexing.html)
diff --git a/projects/first_scene/resources/Sponza/Sponza.bin b/projects/indirect_draw/resources/Sponza/Sponza.bin
similarity index 100%
rename from projects/first_scene/resources/Sponza/Sponza.bin
rename to projects/indirect_draw/resources/Sponza/Sponza.bin
diff --git a/projects/first_scene/resources/Sponza/Sponza.gltf b/projects/indirect_draw/resources/Sponza/Sponza.gltf
similarity index 100%
rename from projects/first_scene/resources/Sponza/Sponza.gltf
rename to projects/indirect_draw/resources/Sponza/Sponza.gltf
diff --git a/projects/indirect_draw/resources/Sponza/SponzaFloor.bin b/projects/indirect_draw/resources/Sponza/SponzaFloor.bin
new file mode 100644
index 0000000000000000000000000000000000000000..684251288f35070d2e7d244877fd844cc00ca632
--- /dev/null
+++ b/projects/indirect_draw/resources/Sponza/SponzaFloor.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:678455aca641cb1f449aa1a5054a7cae132be81c2b333aac283053967da66df0
+size 512
diff --git a/projects/indirect_draw/resources/Sponza/SponzaFloor.gltf b/projects/indirect_draw/resources/Sponza/SponzaFloor.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..b45f1c55ef85f2aa1d4bff01df3d9625aa38c809
--- /dev/null
+++ b/projects/indirect_draw/resources/Sponza/SponzaFloor.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a6deb75441b1138b50a6b0eec05e60df276fe8fb6d58118fdfce2090b6fbe734
+size 3139
diff --git a/projects/first_scene/resources/Sponza/background.png b/projects/indirect_draw/resources/Sponza/background.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/background.png
rename to projects/indirect_draw/resources/Sponza/background.png
diff --git a/projects/first_scene/resources/Sponza/chain_texture.png b/projects/indirect_draw/resources/Sponza/chain_texture.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/chain_texture.png
rename to projects/indirect_draw/resources/Sponza/chain_texture.png
diff --git a/projects/first_scene/resources/Sponza/lion.png b/projects/indirect_draw/resources/Sponza/lion.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/lion.png
rename to projects/indirect_draw/resources/Sponza/lion.png
diff --git a/projects/first_scene/resources/Sponza/spnza_bricks_a_diff.png b/projects/indirect_draw/resources/Sponza/spnza_bricks_a_diff.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/spnza_bricks_a_diff.png
rename to projects/indirect_draw/resources/Sponza/spnza_bricks_a_diff.png
diff --git a/projects/first_scene/resources/Sponza/sponza_arch_diff.png b/projects/indirect_draw/resources/Sponza/sponza_arch_diff.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/sponza_arch_diff.png
rename to projects/indirect_draw/resources/Sponza/sponza_arch_diff.png
diff --git a/projects/first_scene/resources/Sponza/sponza_ceiling_a_diff.png b/projects/indirect_draw/resources/Sponza/sponza_ceiling_a_diff.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/sponza_ceiling_a_diff.png
rename to projects/indirect_draw/resources/Sponza/sponza_ceiling_a_diff.png
diff --git a/projects/first_scene/resources/Sponza/sponza_column_a_diff.png b/projects/indirect_draw/resources/Sponza/sponza_column_a_diff.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/sponza_column_a_diff.png
rename to projects/indirect_draw/resources/Sponza/sponza_column_a_diff.png
diff --git a/projects/first_scene/resources/Sponza/sponza_column_b_diff.png b/projects/indirect_draw/resources/Sponza/sponza_column_b_diff.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/sponza_column_b_diff.png
rename to projects/indirect_draw/resources/Sponza/sponza_column_b_diff.png
diff --git a/projects/first_scene/resources/Sponza/sponza_column_c_diff.png b/projects/indirect_draw/resources/Sponza/sponza_column_c_diff.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/sponza_column_c_diff.png
rename to projects/indirect_draw/resources/Sponza/sponza_column_c_diff.png
diff --git a/projects/first_scene/resources/Sponza/sponza_curtain_blue_diff.png b/projects/indirect_draw/resources/Sponza/sponza_curtain_blue_diff.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/sponza_curtain_blue_diff.png
rename to projects/indirect_draw/resources/Sponza/sponza_curtain_blue_diff.png
diff --git a/projects/first_scene/resources/Sponza/sponza_curtain_diff.png b/projects/indirect_draw/resources/Sponza/sponza_curtain_diff.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/sponza_curtain_diff.png
rename to projects/indirect_draw/resources/Sponza/sponza_curtain_diff.png
diff --git a/projects/first_scene/resources/Sponza/sponza_curtain_green_diff.png b/projects/indirect_draw/resources/Sponza/sponza_curtain_green_diff.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/sponza_curtain_green_diff.png
rename to projects/indirect_draw/resources/Sponza/sponza_curtain_green_diff.png
diff --git a/projects/first_scene/resources/Sponza/sponza_details_diff.png b/projects/indirect_draw/resources/Sponza/sponza_details_diff.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/sponza_details_diff.png
rename to projects/indirect_draw/resources/Sponza/sponza_details_diff.png
diff --git a/projects/first_scene/resources/Sponza/sponza_fabric_blue_diff.png b/projects/indirect_draw/resources/Sponza/sponza_fabric_blue_diff.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/sponza_fabric_blue_diff.png
rename to projects/indirect_draw/resources/Sponza/sponza_fabric_blue_diff.png
diff --git a/projects/first_scene/resources/Sponza/sponza_fabric_diff.png b/projects/indirect_draw/resources/Sponza/sponza_fabric_diff.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/sponza_fabric_diff.png
rename to projects/indirect_draw/resources/Sponza/sponza_fabric_diff.png
diff --git a/projects/first_scene/resources/Sponza/sponza_fabric_green_diff.png b/projects/indirect_draw/resources/Sponza/sponza_fabric_green_diff.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/sponza_fabric_green_diff.png
rename to projects/indirect_draw/resources/Sponza/sponza_fabric_green_diff.png
diff --git a/projects/first_scene/resources/Sponza/sponza_flagpole_diff.png b/projects/indirect_draw/resources/Sponza/sponza_flagpole_diff.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/sponza_flagpole_diff.png
rename to projects/indirect_draw/resources/Sponza/sponza_flagpole_diff.png
diff --git a/projects/first_scene/resources/Sponza/sponza_floor_a_diff.png b/projects/indirect_draw/resources/Sponza/sponza_floor_a_diff.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/sponza_floor_a_diff.png
rename to projects/indirect_draw/resources/Sponza/sponza_floor_a_diff.png
diff --git a/projects/first_scene/resources/Sponza/sponza_roof_diff.png b/projects/indirect_draw/resources/Sponza/sponza_roof_diff.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/sponza_roof_diff.png
rename to projects/indirect_draw/resources/Sponza/sponza_roof_diff.png
diff --git a/projects/first_scene/resources/Sponza/sponza_thorn_diff.png b/projects/indirect_draw/resources/Sponza/sponza_thorn_diff.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/sponza_thorn_diff.png
rename to projects/indirect_draw/resources/Sponza/sponza_thorn_diff.png
diff --git a/projects/first_scene/resources/Sponza/vase_dif.png b/projects/indirect_draw/resources/Sponza/vase_dif.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/vase_dif.png
rename to projects/indirect_draw/resources/Sponza/vase_dif.png
diff --git a/projects/first_scene/resources/Sponza/vase_hanging.png b/projects/indirect_draw/resources/Sponza/vase_hanging.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/vase_hanging.png
rename to projects/indirect_draw/resources/Sponza/vase_hanging.png
diff --git a/projects/first_scene/resources/Sponza/vase_plant.png b/projects/indirect_draw/resources/Sponza/vase_plant.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/vase_plant.png
rename to projects/indirect_draw/resources/Sponza/vase_plant.png
diff --git a/projects/first_scene/resources/Sponza/vase_round.png b/projects/indirect_draw/resources/Sponza/vase_round.png
similarity index 100%
rename from projects/first_scene/resources/Sponza/vase_round.png
rename to projects/indirect_draw/resources/Sponza/vase_round.png
diff --git a/projects/indirect_draw/resources/shaders/culling.comp b/projects/indirect_draw/resources/shaders/culling.comp
new file mode 100644
index 0000000000000000000000000000000000000000..5d5884b9e494baf56ab32576f094f34584b163b3
--- /dev/null
+++ b/projects/indirect_draw/resources/shaders/culling.comp
@@ -0,0 +1,58 @@
+#version 460
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_EXT_nonuniform_qualifier : enable
+
+layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
+
+struct Plane{
+    vec3    pointOnPlane;
+    float   padding0;
+    vec3    normal;
+    float   padding1;
+};
+
+struct DrawCommand{
+    uint indexCount;
+    uint instanceCount;
+    uint firstIndex;
+    int vertexOffset;
+    uint firstInstance;
+};
+
+layout(set=0, binding=0, std140) readonly uniform cameraPlaneBuffer{
+    Plane cameraPlanes[6];
+};
+
+layout(set=0, binding=1, std430) buffer DrawCommandsBuffer{
+    DrawCommand commands[];
+};
+
+layout(set=0, binding=2, std430) readonly buffer boundingBoxBuffer{
+    // x,y,z for meanPosition;
+    // w for boundingSphereRadius
+    vec4 boundingBoxes[];
+};
+
+bool isSphereInsideFrustum(vec3 spherePos, float sphereRadius){
+    bool isInside = true;
+    for(int i = 0; i < 6; i++){
+        Plane p     = cameraPlanes[i];
+        isInside    = isInside && dot(p.normal, spherePos - p.pointOnPlane) - sphereRadius < 0;
+    }
+    return isInside;
+}
+
+void main(){
+    if(gl_GlobalInvocationID.x > commands.length()){
+        return;
+    }
+
+    vec4 bbox = boundingBoxes[gl_GlobalInvocationID.x];
+    if (isSphereInsideFrustum(bbox.xyz, bbox.w)){
+        commands[gl_GlobalInvocationID.x].instanceCount = 1;
+    }
+    else{
+        commands[gl_GlobalInvocationID.x].instanceCount = 0;
+    }
+}
+
diff --git a/projects/indirect_draw/resources/shaders/shader.frag b/projects/indirect_draw/resources/shaders/shader.frag
new file mode 100644
index 0000000000000000000000000000000000000000..f0a0b49314926ef9195d9a145b805b55d17b8807
--- /dev/null
+++ b/projects/indirect_draw/resources/shaders/shader.frag
@@ -0,0 +1,18 @@
+#version 460
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_EXT_nonuniform_qualifier : enable
+
+layout(location = 0) in vec3 passNormal;
+layout(location = 1) in vec2 passUV;
+layout(location = 2) in flat uint passDrawIndex;
+
+layout(location = 0) out vec3 outColor;
+
+layout(set=0, binding=0) uniform sampler standardSampler;
+layout(set=0, binding=2) uniform texture2D baseColorTex[];
+
+
+void main()
+{
+    outColor = texture(sampler2D(baseColorTex[nonuniformEXT(passDrawIndex)], standardSampler), passUV).rgb;
+}
\ No newline at end of file
diff --git a/projects/indirect_draw/resources/shaders/shader.vert b/projects/indirect_draw/resources/shaders/shader.vert
new file mode 100644
index 0000000000000000000000000000000000000000..10b250761437dce6eea01dd195e1248fc9c0bd90
--- /dev/null
+++ b/projects/indirect_draw/resources/shaders/shader.vert
@@ -0,0 +1,27 @@
+#version 460
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_ARB_shader_draw_parameters : enable
+
+layout(location = 0) in vec3 inPosition;
+layout(location = 1) in vec3 inNormal;
+layout(location = 2) in vec2 inUV;
+
+layout(std430, binding=1) readonly buffer uModel {
+    mat4 modelMatrix[];
+};
+
+layout(location = 0) out vec3 passNormal;
+layout(location = 1) out vec2 passUV;
+layout(location = 2) out flat uint passDrawIndex;
+
+layout( push_constant ) uniform constants{
+    mat4 vp;
+};
+
+void main()
+{
+	gl_Position = vp * modelMatrix[gl_DrawID] * vec4(inPosition, 1.0);
+	passNormal  = inNormal;
+    passUV      = inUV;
+    passDrawIndex = gl_DrawID;
+}
\ No newline at end of file
diff --git a/projects/indirect_draw/src/main.cpp b/projects/indirect_draw/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f90fa0892982f68fa959671b5eb6f207aac72b65
--- /dev/null
+++ b/projects/indirect_draw/src/main.cpp
@@ -0,0 +1,596 @@
+#include <iostream>
+#include <vkcv/Buffer.hpp>
+#include <vkcv/Core.hpp>
+#include <vkcv/Pass.hpp>
+#include <vkcv/Sampler.hpp>
+#include <vkcv/Image.hpp>
+#include <vkcv/camera/CameraManager.hpp>
+#include <vkcv/gui/GUI.hpp>
+#include <vkcv/asset/asset_loader.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+
+struct Plane {
+    glm::vec3   pointOnPlane;
+    float       padding0;
+    glm::vec3   normal;
+    float       padding1;
+};
+
+struct CameraPlanes {
+    Plane planes[6];
+};
+
+CameraPlanes computeCameraPlanes(const vkcv::camera::Camera& camera) {
+    const float     fov     = camera.getFov();
+    const glm::vec3 pos     = camera.getPosition();
+    const float     ratio   = camera.getRatio();
+    const glm::vec3 forward = glm::normalize(camera.getFront());
+    float near;
+    float far;
+    camera.getNearFar(near, far);
+
+    glm::vec3 up    = glm::vec3(0, -1, 0);
+    glm::vec3 right = glm::normalize(glm::cross(forward, up));
+    up              = glm::cross(forward, right);
+
+    const glm::vec3 nearCenter      = pos + forward * near;
+    const glm::vec3 farCenter       = pos + forward * far;
+
+    const float tanFovHalf          = glm::tan(fov / 2);
+
+    const glm::vec3 nearUpCenter    = nearCenter + up    * tanFovHalf * near;
+    const glm::vec3 nearDownCenter  = nearCenter - up    * tanFovHalf * near;
+    const glm::vec3 nearRightCenter = nearCenter + right * tanFovHalf * near * ratio;
+    const glm::vec3 nearLeftCenter  = nearCenter - right * tanFovHalf * near * ratio;
+
+    const glm::vec3 farUpCenter     = farCenter + up    * tanFovHalf * far;
+    const glm::vec3 farDownCenter   = farCenter - up    * tanFovHalf * far;
+    const glm::vec3 farRightCenter  = farCenter + right * tanFovHalf * far * ratio;
+    const glm::vec3 farLeftCenter   = farCenter - right * tanFovHalf * far * ratio;
+
+    CameraPlanes cameraPlanes;
+    // near
+    cameraPlanes.planes[0].pointOnPlane = nearCenter;
+    cameraPlanes.planes[0].normal       = -forward;
+    // far
+    cameraPlanes.planes[1].pointOnPlane = farCenter;
+    cameraPlanes.planes[1].normal       = forward;
+
+    // top
+    cameraPlanes.planes[2].pointOnPlane = nearUpCenter;
+    cameraPlanes.planes[2].normal       = glm::normalize(glm::cross(farUpCenter - nearUpCenter, right));
+    // bot
+    cameraPlanes.planes[3].pointOnPlane = nearDownCenter;
+    cameraPlanes.planes[3].normal       = glm::normalize(glm::cross(right, farDownCenter - nearDownCenter));
+
+    // right
+    cameraPlanes.planes[4].pointOnPlane = nearRightCenter;
+    cameraPlanes.planes[4].normal       = glm::normalize(glm::cross(up, farRightCenter - nearRightCenter));
+    // left
+    cameraPlanes.planes[5].pointOnPlane = nearLeftCenter;
+    cameraPlanes.planes[5].normal       = glm::normalize(glm::cross(farLeftCenter - nearLeftCenter, up));
+
+    return cameraPlanes;
+}
+
+struct Vertex
+{
+	glm::vec3 position;
+	glm::vec3 normal;
+	glm::vec2 uv;
+};
+
+struct CompiledMaterial
+{
+    std::vector<vkcv::Image> baseColor;
+    std::vector<vkcv::Image> metalRough;
+    std::vector<vkcv::Image> normal;
+    std::vector<vkcv::Image> occlusion;
+};
+
+void interleaveScene(vkcv::asset::Scene scene,
+                     std::vector<std::vector<Vertex>> &interleavedVertexBuffers,
+                     std::vector<glm::vec4> &boundingBoxBuffers) {
+	
+    for(const auto &mesh : scene.meshes) {
+        for(auto vertexGroupIndex : mesh.vertexGroups) {
+            const auto &vertexGroup = scene.vertexGroups[vertexGroupIndex];
+
+            const vkcv::asset::VertexAttribute* positionAttribute = nullptr;
+            const vkcv::asset::VertexAttribute* normalAttribute   = nullptr;
+            const vkcv::asset::VertexAttribute* uvAttribute       = nullptr;
+			
+			for (const auto& attribute : vertexGroup.vertexBuffer.attributes) {
+				switch (attribute.type) {
+					case vkcv::asset::PrimitiveType::POSITION:
+						positionAttribute = &attribute;
+						break;
+					case vkcv::asset::PrimitiveType::NORMAL:
+						normalAttribute = &attribute;
+						break;
+					case vkcv::asset::PrimitiveType::TEXCOORD_0:
+						uvAttribute = &attribute;
+						break;
+					default:
+						break;
+				}
+			}
+	
+			assert(positionAttribute && normalAttribute && uvAttribute);
+
+            const uint64_t &verticesCount          = vertexGroup.numVertices;
+            const std::vector<uint8_t> &vertexData = vertexGroup.vertexBuffer.data;
+
+            std::vector<Vertex> vertices;
+            vertices.reserve(verticesCount);
+
+            const size_t positionStride = positionAttribute->stride == 0 ? sizeof(glm::vec3) : positionAttribute->stride;
+            const size_t normalStride   = normalAttribute->stride   == 0 ? sizeof(glm::vec3) : normalAttribute->stride;
+            const size_t uvStride       = uvAttribute->stride       == 0 ? sizeof(glm::vec2) : uvAttribute->stride;
+
+            glm::vec3 max_pos(-std::numeric_limits<float>::max());
+            glm::vec3 min_pos(std::numeric_limits<float>::max());
+
+            for(size_t i = 0; i < verticesCount; i++)
+            {
+                const size_t positionOffset = positionAttribute->offset + positionStride * i;
+                const size_t normalOffset   = normalAttribute->offset   + normalStride * i;
+                const size_t uvOffset       = uvAttribute->offset       + uvStride * i;
+
+                Vertex v;
+
+                v.position = *reinterpret_cast<const glm::vec3*>(&(vertexData[positionOffset]));
+                v.normal   = *reinterpret_cast<const glm::vec3*>(&(vertexData[normalOffset]));
+                v.uv       = *reinterpret_cast<const glm::vec3*>(&(vertexData[uvOffset]));
+
+                glm::vec3 posWorld = glm::make_mat4(mesh.modelMatrix.data()) * glm::vec4(v.position, 1);
+
+                max_pos.x = glm::max(max_pos.x, posWorld.x);
+                max_pos.y = glm::max(max_pos.y, posWorld.y);
+                max_pos.z = glm::max(max_pos.z, posWorld.z);
+
+                min_pos.x = glm::min(min_pos.x, posWorld.x);
+                min_pos.y = glm::min(min_pos.y, posWorld.y);
+                min_pos.z = glm::min(min_pos.z, posWorld.z);
+
+                vertices.push_back(v);
+            }
+
+            const glm::vec3 boundingPosition = (max_pos + min_pos) / 2.0f;
+            const float radius = glm::distance(max_pos, min_pos) / 2.0f;
+
+            boundingBoxBuffers.emplace_back(boundingPosition.x,
+                                            boundingPosition.y,
+                                            boundingPosition.z,
+                                            radius);
+
+            interleavedVertexBuffers.push_back(vertices);
+        }
+    }
+}
+
+// Assumes the meshes use index buffers
+void compileMeshForIndirectDraw(vkcv::Core &core,
+                                const vkcv::asset::Scene &scene,
+                                std::vector<uint8_t> &compiledVertexBuffer,
+                                std::vector<uint8_t> &compiledIndexBuffer,
+                                CompiledMaterial &compiledMat,
+                                std::vector<vk::DrawIndexedIndirectCommand> &indexedIndirectCommands)
+{
+    vkcv::Image pseudoImg = vkcv::image(core, vk::Format::eR8G8B8A8Srgb, 2, 2);
+    std::vector<uint8_t> pseudoData = {0, 0, 0, 0};
+    pseudoImg.fill(pseudoData.data());
+	
+	auto mipStream = core.createCommandStream(vkcv::QueueType::Graphics);
+	pseudoImg.recordMipChainGeneration(mipStream, core.getDownsampler());
+
+	uint32_t vertexOffset = 0;
+    for (const auto &mesh : scene.meshes)
+    {
+        for(auto &vertexGroupIndex : mesh.vertexGroups)
+        {
+            auto &vertexGroup = scene.vertexGroups[vertexGroupIndex];
+
+            auto &material      = scene.materials[vertexGroup.materialIndex];
+
+            if(material.baseColor == -1)
+            {
+                std::cout << "baseColor is -1! Pushing pseudo-texture!" << std::endl;
+                compiledMat.baseColor.push_back(pseudoImg);
+            }
+            else
+            {
+                auto &baseColor     = scene.textures[material.baseColor];
+
+                vkcv::Image baseColorImg = vkcv::image(core, vk::Format::eR8G8B8A8Srgb, baseColor.w, baseColor.h);
+                baseColorImg.fill(baseColor.data.data());
+				baseColorImg.recordMipChainGeneration(mipStream, core.getDownsampler());
+
+                compiledMat.baseColor.push_back(baseColorImg);
+            }
+
+            indexedIndirectCommands.emplace_back(static_cast<uint32_t>(vertexGroup.numIndices),
+                                                 1,
+                                                 static_cast<uint32_t>(compiledIndexBuffer.size() / 4),
+                                                 vertexOffset,
+                                                 static_cast<uint32_t>(indexedIndirectCommands.size()));
+
+            vertexOffset += vertexGroup.numVertices;
+
+            compiledVertexBuffer.insert(compiledVertexBuffer.end(),
+                                        vertexGroup.vertexBuffer.data.begin(),
+                                        vertexGroup.vertexBuffer.data.end());
+
+			if(vertexGroup.indexBuffer.type == vkcv::asset::IndexType::UINT8)
+            {
+				for(auto index : vertexGroup.indexBuffer.data)
+				{
+                    uint32_t index32 = static_cast<uint32_t>(index);
+                    uint8_t firstByte = index32;
+                    uint8_t secondByte = index32 >> 8;
+                    uint8_t thirdByte = index32 >> 16;
+                    uint8_t fourthByte = index32 >> 24;
+
+                    compiledIndexBuffer.push_back(firstByte);
+                    compiledIndexBuffer.push_back(secondByte);
+                    compiledIndexBuffer.push_back(thirdByte);
+                    compiledIndexBuffer.push_back(fourthByte);
+                }
+			}
+            else if(vertexGroup.indexBuffer.type == vkcv::asset::IndexType::UINT16)
+            {
+                for (size_t i = 0; i < vertexGroup.indexBuffer.data.size(); i += 2)
+                {
+                    uint16_t index16 = *reinterpret_cast<const uint16_t*>(&vertexGroup.indexBuffer.data[i]);
+                    uint32_t index32 = static_cast<uint32_t>(index16);
+
+                    uint8_t firstByte = index32;
+                    uint8_t secondByte = index32 >> 8;
+                    uint8_t thirdByte = index32 >> 16;
+                    uint8_t fourthByte = index32 >> 24;
+
+                    compiledIndexBuffer.push_back(firstByte);
+                    compiledIndexBuffer.push_back(secondByte);
+                    compiledIndexBuffer.push_back(thirdByte);
+                    compiledIndexBuffer.push_back(fourthByte);
+                }
+			}
+			else
+			{
+				compiledIndexBuffer.insert(compiledIndexBuffer.end(),
+										   vertexGroup.indexBuffer.data.begin(),
+										   vertexGroup.indexBuffer.data.end());
+			}
+        }
+    }
+	
+	core.submitCommandStream(mipStream, false);
+}
+
+int main(int argc, const char** argv) {
+	const std::string applicationName = "Indirect draw";
+
+	vkcv::Features features;
+	features.requireExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
+    features.requireFeature([](vk::PhysicalDeviceFeatures &features){
+        features.setMultiDrawIndirect(true);
+    });
+
+	features.requireExtension(VK_KHR_SHADER_DRAW_PARAMETERS_EXTENSION_NAME);
+    features.requireExtension(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
+    features.requireExtensionFeature<vk::PhysicalDeviceDescriptorIndexingFeatures>(
+            VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME, [](vk::PhysicalDeviceDescriptorIndexingFeatures &features) {
+                // features.setShaderInputAttachmentArrayDynamicIndexing(true);
+                features.setShaderUniformTexelBufferArrayDynamicIndexing(true);
+                features.setShaderStorageTexelBufferArrayDynamicIndexing(true);
+                features.setShaderUniformBufferArrayNonUniformIndexing(true);
+                features.setShaderSampledImageArrayNonUniformIndexing(true);
+                features.setShaderStorageBufferArrayNonUniformIndexing(true);
+                features.setShaderStorageImageArrayNonUniformIndexing(true);
+                // features.setShaderInputAttachmentArrayNonUniformIndexing(true);
+                features.setShaderUniformTexelBufferArrayNonUniformIndexing(true);
+                features.setShaderStorageTexelBufferArrayNonUniformIndexing(true);
+
+                features.setDescriptorBindingUniformBufferUpdateAfterBind(true);
+                features.setDescriptorBindingSampledImageUpdateAfterBind(true);
+                features.setDescriptorBindingStorageImageUpdateAfterBind(true);
+                features.setDescriptorBindingStorageBufferUpdateAfterBind(true);
+                features.setDescriptorBindingUniformTexelBufferUpdateAfterBind(true);
+                features.setDescriptorBindingStorageTexelBufferUpdateAfterBind(true);
+
+                features.setDescriptorBindingUpdateUnusedWhilePending(true);
+                features.setDescriptorBindingPartiallyBound(true);
+                features.setDescriptorBindingVariableDescriptorCount(true);
+                features.setRuntimeDescriptorArray(true);
+            }
+    );
+
+
+	vkcv::Core core = vkcv::Core::create(
+		applicationName,
+		VK_MAKE_VERSION(0, 0, 1),
+		{ vk::QueueFlagBits::eGraphics ,vk::QueueFlagBits::eCompute , vk::QueueFlagBits::eTransfer },
+		features
+	);
+
+	vkcv::WindowHandle windowHandle = core.createWindow(applicationName,800,600,true);
+	vkcv::Window& window = core.getWindow(windowHandle);
+
+    vkcv::gui::GUI gui (core, windowHandle);
+
+    vkcv::asset::Scene asset_scene;
+	const char* path = argc > 1 ? argv[1] : "resources/Sponza/Sponza.gltf";
+	int result = vkcv::asset::loadScene(path, asset_scene);
+
+	if (result == 1) {
+		std::cout << "Loading Sponza successful!" << std::endl;
+	} else {
+		std::cerr << "Loading Sponza failed: " << result << std::endl;
+		return 1;
+	}
+	assert(!asset_scene.vertexGroups.empty());
+    if (asset_scene.textures.empty())
+    {
+        std::cerr << "Error. No textures found. Exiting." << std::endl;
+        return EXIT_FAILURE;
+    }
+
+    vkcv::PassHandle passHandle = vkcv::passSwapchain(
+			core,
+			window.getSwapchain(),
+			{ vk::Format::eUndefined, vk::Format::eD32Sfloat }
+	);
+	
+	if (!passHandle) {
+		std::cerr << "Error. Could not create renderpass. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+
+	vkcv::ShaderProgram sponzaProgram;
+	vkcv::shader::GLSLCompiler compiler;
+	compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/shader.vert"),
+					 [&sponzaProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+        sponzaProgram.addShader(shaderStage, path);
+	});
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/shader.frag"),
+					 [&sponzaProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+        sponzaProgram.addShader(shaderStage, path);
+	});
+
+    vkcv::ShaderProgram cullingProgram;
+    compiler.compile(vkcv::ShaderStage::COMPUTE, std::filesystem::path("resources/shaders/culling.comp"),
+                     [&cullingProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+        cullingProgram.addShader(shaderStage, path);
+    });
+
+    // vertex layout for the pipeline. (assumed to be) used by all sponza meshes.
+    const std::vector<vkcv::VertexAttachment> vertexAttachments = sponzaProgram.getVertexAttachments();
+	const vkcv::VertexLayout sponzaVertexLayout {
+		{ vkcv::createVertexBinding(0, { vertexAttachments }) }
+	};
+
+    std::vector<uint8_t> compiledVertexBuffer; // IGNORED, since the vertex buffer is not interleaved!
+
+    std::vector<uint8_t> compiledIndexBuffer;
+    CompiledMaterial compiledMaterial;
+    std::vector<vk::DrawIndexedIndirectCommand> indexedIndirectCommands;
+
+    compileMeshForIndirectDraw(core,
+                               asset_scene,
+                               compiledVertexBuffer,
+                               compiledIndexBuffer,
+                               compiledMaterial,
+                               indexedIndirectCommands);
+
+	std::vector<std::vector<Vertex>> interleavedVertices;
+    std::vector<glm::vec4> compiledBoundingBoxBuffer;
+    interleaveScene(asset_scene,
+                    interleavedVertices,
+                    compiledBoundingBoxBuffer);
+
+	std::vector<Vertex> compiledInterleavedVertexBuffer;
+	for(auto& vertexGroup : interleavedVertices )
+	{
+        compiledInterleavedVertexBuffer.insert(compiledInterleavedVertexBuffer.end(),vertexGroup.begin(),vertexGroup.end());
+	}
+
+	auto vkCompiledVertexBuffer = vkcv::buffer<Vertex>(
+			core,
+			vkcv::BufferType::VERTEX,
+            compiledInterleavedVertexBuffer.size(),
+			vkcv::BufferMemoryType::DEVICE_LOCAL);
+    vkCompiledVertexBuffer.fill(compiledInterleavedVertexBuffer.data());
+
+    auto vkCompiledIndexBuffer = vkcv::buffer<uint8_t>(
+			core,
+            vkcv::BufferType::INDEX,
+            compiledIndexBuffer.size(),
+            vkcv::BufferMemoryType::DEVICE_LOCAL);
+    vkCompiledIndexBuffer.fill(compiledIndexBuffer.data());
+
+    vkcv::Buffer<vk::DrawIndexedIndirectCommand> indirectBuffer = vkcv::buffer<vk::DrawIndexedIndirectCommand>(
+			core,
+            vkcv::BufferType::INDIRECT,
+            indexedIndirectCommands.size(),
+            vkcv::BufferMemoryType::DEVICE_LOCAL);
+    indirectBuffer.fill(indexedIndirectCommands);
+
+    auto boundingBoxBuffer = vkcv::buffer<glm::vec4>(
+			core,
+            vkcv::BufferType::STORAGE,
+            compiledBoundingBoxBuffer.size());
+    boundingBoxBuffer.fill(compiledBoundingBoxBuffer);
+
+    std::vector<glm::mat4> modelMatrix;
+	for( auto& mesh : asset_scene.meshes)
+	{
+		modelMatrix.push_back(glm::make_mat4(mesh.modelMatrix.data()));
+	}
+	vkcv::Buffer<glm::mat4> modelBuffer = vkcv::buffer<glm::mat4>(
+			core,
+			vkcv::BufferType::STORAGE,
+			modelMatrix.size(),
+			vkcv::BufferMemoryType::DEVICE_LOCAL
+			);
+	modelBuffer.fill(modelMatrix);
+
+	const std::vector<vkcv::VertexBufferBinding> vertexBufferBindings = {
+			vkcv::vertexBufferBinding(vkCompiledVertexBuffer.getHandle())
+	};
+	
+	vkcv::VertexData vertexData (vertexBufferBindings);
+	vertexData.setIndexBuffer(vkCompiledIndexBuffer.getHandle(), vkcv::IndexBitCount::Bit32);
+
+    //assert(compiledMaterial.baseColor.size() == compiledMaterial.metalRough.size());
+
+	vkcv::DescriptorBindings descriptorBindings = sponzaProgram.getReflectedDescriptors().at(0);
+    descriptorBindings[2].descriptorCount = compiledMaterial.baseColor.size();
+
+	vkcv::DescriptorSetLayoutHandle descriptorSetLayout = core.createDescriptorSetLayout(descriptorBindings);
+	vkcv::DescriptorSetHandle descriptorSet = core.createDescriptorSet(descriptorSetLayout);
+
+    vkcv::SamplerHandle standardSampler = vkcv::samplerLinear(core);
+	
+	vkcv::DescriptorWrites setWrites;
+	
+    std::vector<vkcv::SampledImageDescriptorWrite> textureArrayWrites;
+    for(uint32_t i = 0; i < compiledMaterial.baseColor.size(); i++)
+    {
+		setWrites.writeSampledImage(2, compiledMaterial.baseColor[i].getHandle(), 0, false, i);
+    }
+    
+    setWrites.writeSampler(0, standardSampler);
+	setWrites.writeStorageBuffer(1, modelBuffer.getHandle());
+    core.writeDescriptorSet(descriptorSet, setWrites);
+
+	vkcv::GraphicsPipelineHandle sponzaPipelineHandle = core.createGraphicsPipeline(
+			vkcv::GraphicsPipelineConfig(
+					sponzaProgram,
+					passHandle,
+					{ sponzaVertexLayout },
+					{ descriptorSetLayout }
+			)
+	);
+	
+	if (!sponzaPipelineHandle) {
+		std::cerr << "Error. Could not create graphics pipeline. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+    vkcv::DescriptorBindings cullingBindings = cullingProgram.getReflectedDescriptors().at(0);
+    vkcv::DescriptorSetLayoutHandle cullingSetLayout = core.createDescriptorSetLayout(cullingBindings);
+    vkcv::DescriptorSetHandle cullingDescSet = core.createDescriptorSet(cullingSetLayout);
+
+    vkcv::Buffer<CameraPlanes> cameraPlaneBuffer = vkcv::buffer<CameraPlanes>(
+			core,
+            vkcv::BufferType::UNIFORM,
+            1);
+
+    vkcv::DescriptorWrites cullingWrites;
+	cullingWrites.writeStorageBuffer(1, indirectBuffer.getHandle());
+	cullingWrites.writeStorageBuffer(2, boundingBoxBuffer.getHandle());
+    cullingWrites.writeUniformBuffer(0, cameraPlaneBuffer.getHandle());
+    core.writeDescriptorSet(cullingDescSet, cullingWrites);
+
+
+    const vkcv::ComputePipelineConfig computeCullingConfig {
+        cullingProgram,
+        {cullingSetLayout}
+    };
+    vkcv::ComputePipelineHandle cullingPipelineHandle = core.createComputePipeline(computeCullingConfig);
+    if (!cullingPipelineHandle) {
+        std::cerr << "Error. Could not create culling pipeline. Exiting." << std::endl;
+        return EXIT_FAILURE;
+    }
+
+    vkcv::camera::CameraManager cameraManager (window);
+    auto camHandle = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+	
+	cameraManager.getCamera(camHandle).setPosition(glm::vec3(0, 0, -3));
+	cameraManager.getCamera(camHandle).setNearFar(0.1f, 20.f);
+
+    vkcv::ImageHandle depthBuffer;
+	
+    const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+
+    float ceiledDispatchCount = static_cast<float>(indexedIndirectCommands.size()) / 64.0f;
+    ceiledDispatchCount = std::ceil(ceiledDispatchCount);
+    const vkcv::DispatchSize dispatchCount = static_cast<uint32_t>(ceiledDispatchCount);
+
+    vkcv::DescriptorSetUsage cullingUsage = vkcv::useDescriptorSet(0, cullingDescSet);
+    vkcv::PushConstants emptyPushConstant(0);
+
+    bool updateFrustumPlanes    = true;
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
+		if ((!depthBuffer) ||
+			(swapchainWidth != core.getImageWidth(depthBuffer)) ||
+			(swapchainHeight != core.getImageHeight(depthBuffer))) {
+			depthBuffer = core.createImage(
+					vk::Format::eD32Sfloat,
+					vkcv::ImageConfig(
+							swapchainWidth,
+							swapchainHeight
+					)
+			);
+		}
+  
+		cameraManager.update(dt);
+		
+        vkcv::camera::Camera cam = cameraManager.getActiveCamera();
+		vkcv::PushConstants pushConstants = vkcv::pushConstants<glm::mat4>();
+		pushConstants.appendDrawcall(cam.getProjection() * cam.getView());
+
+        if (updateFrustumPlanes) {
+            const CameraPlanes cameraPlanes = computeCameraPlanes(cam);
+            cameraPlaneBuffer.fill({ cameraPlanes });
+        }
+
+		const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer };
+		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+
+        core.recordComputeDispatchToCmdStream(
+				cmdStream,
+				cullingPipelineHandle,
+				dispatchCount,
+				{cullingUsage},
+				emptyPushConstant
+		);
+
+        core.recordBufferMemoryBarrier(cmdStream, indirectBuffer.getHandle());
+
+		vkcv::IndirectDrawcall drawcall (
+				indirectBuffer.getHandle(),
+				vertexData,
+				indexedIndirectCommands.size()
+		);
+		
+		drawcall.useDescriptorSet(0, descriptorSet);
+		
+		core.recordIndirectDrawcallsToCmdStream(
+			cmdStream,
+            sponzaPipelineHandle,
+            pushConstants,
+			{ drawcall },
+			renderTargets,
+			windowHandle
+		);
+
+		core.prepareSwapchainImageForPresent(cmdStream);
+		core.submitCommandStream(cmdStream);
+		
+        gui.beginGUI();
+
+        ImGui::Begin("Settings");
+        ImGui::Checkbox("Update frustum culling", &updateFrustumPlanes);
+		ImGui::Text("Deltatime %fms, %f", dt * 1000, 1/dt);
+
+        ImGui::End();
+
+        gui.endGUI();
+	});
+	
+	return 0;
+}
diff --git a/projects/mesh_shader/.gitignore b/projects/mesh_shader/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..fd009a6281f4b2b6716e193d23829907f4bb5f33
--- /dev/null
+++ b/projects/mesh_shader/.gitignore
@@ -0,0 +1 @@
+mesh_shader
\ No newline at end of file
diff --git a/projects/mesh_shader/CMakeLists.txt b/projects/mesh_shader/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..04b632effa28101a105100c768da712676378c99
--- /dev/null
+++ b/projects/mesh_shader/CMakeLists.txt
@@ -0,0 +1,15 @@
+cmake_minimum_required(VERSION 3.16)
+project(mesh_shader)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+# adding source files to the project
+add_project(mesh_shader src/main.cpp)
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(mesh_shader SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_testing_include} ${vkcv_camera_include} ${vkcv_meshlet_include} ${vkcv_shader_compiler_include} ${vkcv_gui_include})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(mesh_shader vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_testing vkcv_camera vkcv_meshlet vkcv_shader_compiler vkcv_gui)
\ No newline at end of file
diff --git a/projects/mesh_shader/README.md b/projects/mesh_shader/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..5d789a631b5316515e07ed29b59243a97191ccf0
--- /dev/null
+++ b/projects/mesh_shader/README.md
@@ -0,0 +1,24 @@
+# Mesh shader
+An example project to show usage of mesh shaders with the VkCV framework
+
+![Screenshot](../../screenshots/mesh_shader.png)
+
+## Details
+
+The project utilizes a Vulkan extension to use hardware accelerated mesh shaders. Those mesh 
+shaders can replace the usual vertex-, tessellation- and geometry-shader stages in the graphics 
+pipeline. They also act more similar to compute shaders.
+
+The application uses those mesh shaders to cull a rendered mesh into individual groups of triangles 
+which are called meshlets in this context.
+
+The project currently uses the Nvidia GPU exclusive extension for mesh shading but it could be 
+adjusted to use the cross compatible extension 
+"[VK_EXT_mesh_shader](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_mesh_shader.html)" 
+instead.
+
+## Extensions
+
+Here is a list of the used extensions:
+
+- [VK_NV_mesh_shader](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_NV_mesh_shader.html)
diff --git a/projects/mesh_shader/assets/Bunny/Bunny.glb b/projects/mesh_shader/assets/Bunny/Bunny.glb
new file mode 100644
index 0000000000000000000000000000000000000000..181f1f92f1906e1e1ba900768580203efe19e9be
--- /dev/null
+++ b/projects/mesh_shader/assets/Bunny/Bunny.glb
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b8bc6fab11929ca11bdf4e892ffb03b621b10307f705cdea17d82d3dee3b9aae
+size 4045836
diff --git a/projects/mesh_shader/assets/monke.glb b/projects/mesh_shader/assets/monke.glb
new file mode 100644
index 0000000000000000000000000000000000000000..47d0b9131f15a8f0697318d0a47302c71cad1db8
--- /dev/null
+++ b/projects/mesh_shader/assets/monke.glb
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:597584db90a3f51088beea6652d8320e82cb025f9d3d036b89e54ad72c732a06
+size 98612
diff --git a/projects/mesh_shader/assets/shaders/common.inc b/projects/mesh_shader/assets/shaders/common.inc
new file mode 100644
index 0000000000000000000000000000000000000000..280ffee215a8b8342b78d1f5558d63a05e16859b
--- /dev/null
+++ b/projects/mesh_shader/assets/shaders/common.inc
@@ -0,0 +1,4 @@
+struct ObjectMatrices{
+    mat4 model;
+    mat4 mvp;
+};
\ No newline at end of file
diff --git a/projects/mesh_shader/assets/shaders/meshlet.inc b/projects/mesh_shader/assets/shaders/meshlet.inc
new file mode 100644
index 0000000000000000000000000000000000000000..0594f62ceead8ffca09b585305075eb6046f3c46
--- /dev/null
+++ b/projects/mesh_shader/assets/shaders/meshlet.inc
@@ -0,0 +1,8 @@
+struct Meshlet{
+    uint    vertexOffset;
+    uint    vertexCount;
+    uint    indexOffset;
+    uint    indexCount;
+    vec3    meanPosition;
+    float   boundingSphereRadius;
+};
\ No newline at end of file
diff --git a/projects/mesh_shader/assets/shaders/shader.frag b/projects/mesh_shader/assets/shaders/shader.frag
new file mode 100644
index 0000000000000000000000000000000000000000..f4f6982f2089e6c8e102027f3b8763bb38f8e59c
--- /dev/null
+++ b/projects/mesh_shader/assets/shaders/shader.frag
@@ -0,0 +1,32 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in  vec3 passNormal;
+layout(location = 1) in  flat uint passTaskIndex;
+layout(location = 0) out vec3 outColor;
+
+uint lowbias32(uint x)
+{
+    x ^= x >> 16;
+    x *= 0x7feb352dU;
+    x ^= x >> 15;
+    x *= 0x846ca68bU;
+    x ^= x >> 16;
+    return x;
+}
+
+float hashToFloat(uint hash){
+    return (hash % 255) / 255.f;
+}
+
+vec3 colorFromIndex(uint i){
+    return vec3(
+        hashToFloat(lowbias32(i+0)),
+        hashToFloat(lowbias32(i+1)),
+        hashToFloat(lowbias32(i+2)));
+}
+
+void main() {
+	outColor = normalize(passNormal) * 0.5 + 0.5;
+    outColor = colorFromIndex(passTaskIndex);
+}
\ No newline at end of file
diff --git a/projects/mesh_shader/assets/shaders/shader.mesh b/projects/mesh_shader/assets/shaders/shader.mesh
new file mode 100644
index 0000000000000000000000000000000000000000..30c98610f4776204ff526c57c1f793e371194629
--- /dev/null
+++ b/projects/mesh_shader/assets/shaders/shader.mesh
@@ -0,0 +1,78 @@
+#version 460
+#extension GL_ARB_separate_shader_objects   : enable
+#extension GL_GOOGLE_include_directive      : enable
+#extension GL_NV_mesh_shader                : require
+
+#include "meshlet.inc"
+
+layout(local_size_x=32) in;
+
+layout(triangles) out;
+layout(max_vertices=64, max_primitives=126) out;
+
+layout(location = 0) out vec3 passNormal[];
+layout(location = 1) out uint passTaskIndex[];
+
+struct Vertex
+{
+    vec3 position;  float padding0;
+    vec3 normal;    float padding1;
+};
+
+layout(std430, binding = 0) readonly buffer vertexBuffer
+{
+    Vertex vertices[];
+};
+
+layout(std430, binding = 1) readonly buffer indexBuffer
+{
+    uint localIndices[]; // breaks for 16 bit indices
+};
+
+layout(std430, binding = 2) readonly buffer meshletBuffer
+{
+    Meshlet meshlets[];
+};
+
+taskNV in Task {
+  uint meshletIndices[32];
+  mat4 mvp;
+} IN;
+
+void main()	{
+    
+    uint meshletIndex = IN.meshletIndices[gl_WorkGroupID.x];
+    Meshlet meshlet = meshlets[meshletIndex];
+    
+    // set vertices
+    for(uint i = 0; i < 2; i++){
+    
+        uint workIndex = gl_LocalInvocationID.x + 32 * i;
+        if(workIndex >= meshlet.vertexCount){
+            break;
+        }
+    
+        uint vertexIndex    = meshlet.vertexOffset + workIndex;
+        Vertex vertex       = vertices[vertexIndex];
+    
+        gl_MeshVerticesNV[workIndex].gl_Position    = IN.mvp * vec4(vertex.position, 1);
+        passNormal[workIndex]                       = vertex.normal;
+        passTaskIndex[workIndex]                    = meshletIndex;
+    }
+    
+    // set local indices
+    for(uint i = 0; i < 12; i++){
+    
+        uint workIndex = gl_LocalInvocationID.x + i * 32;
+        if(workIndex >= meshlet.indexCount){
+            break;
+        }    
+        
+        uint indexBufferIndex               = meshlet.indexOffset + workIndex;
+        gl_PrimitiveIndicesNV[workIndex]    = localIndices[indexBufferIndex];
+    }
+    
+    if(gl_LocalInvocationID.x == 0){
+        gl_PrimitiveCountNV = meshlet.indexCount / 3;
+    }
+}
\ No newline at end of file
diff --git a/projects/mesh_shader/assets/shaders/shader.task b/projects/mesh_shader/assets/shaders/shader.task
new file mode 100644
index 0000000000000000000000000000000000000000..7a692e98e6384767191d76cef940e295ca127d62
--- /dev/null
+++ b/projects/mesh_shader/assets/shaders/shader.task
@@ -0,0 +1,78 @@
+#version 460
+#extension GL_ARB_separate_shader_objects   : enable
+#extension GL_NV_mesh_shader                : require
+#extension GL_GOOGLE_include_directive      : enable
+
+#include "meshlet.inc"
+#include "common.inc"
+
+layout(local_size_x=32) in;
+
+taskNV out Task {
+  uint meshletIndices[32];
+  mat4 mvp;
+} OUT;
+
+layout( push_constant ) uniform constants{
+    uint matrixIndex;
+    uint meshletCount;
+};
+
+// TODO: reuse mesh stage binding at location 2 after required fix in framework
+layout(std430, binding = 5) readonly buffer meshletBuffer
+{
+    Meshlet meshlets[];
+};
+
+struct Plane{
+    vec3    pointOnPlane;
+    float   padding0;
+    vec3    normal;
+    float   padding1;
+};
+
+layout(set=0, binding=3, std140) uniform cameraPlaneBuffer{
+    Plane cameraPlanes[6];
+};
+
+layout(std430, binding = 4) readonly buffer matrixBuffer
+{
+    ObjectMatrices objectMatrices[];
+};
+
+shared uint taskCount;
+
+bool isSphereInsideFrustum(vec3 spherePos, float sphereRadius, Plane cameraPlanes[6]){
+    bool isInside = true;
+    for(int i = 0; i < 6; i++){
+        Plane p     = cameraPlanes[i];
+        isInside    = isInside && dot(p.normal, spherePos - p.pointOnPlane) - sphereRadius < 0;
+    }
+    return isInside;
+}
+
+void main() {
+
+    if(gl_LocalInvocationID.x >= meshletCount){
+        return;
+    }
+    
+    uint meshletIndex   = gl_GlobalInvocationID.x;
+    Meshlet meshlet     = meshlets[meshletIndex]; 
+    
+    if(gl_LocalInvocationID.x == 0){
+        taskCount = 0;
+    }
+    
+    // TODO: scaling support
+    vec3 meshletPositionWorld = (vec4(meshlet.meanPosition, 1) * objectMatrices[matrixIndex].model).xyz;
+    if(isSphereInsideFrustum(meshletPositionWorld, meshlet.boundingSphereRadius, cameraPlanes)){
+        uint outIndex = atomicAdd(taskCount, 1);
+        OUT.meshletIndices[outIndex] = gl_GlobalInvocationID.x;
+    }
+
+    if(gl_LocalInvocationID.x == 0){
+        gl_TaskCountNV              = taskCount;
+        OUT.mvp = objectMatrices[matrixIndex].mvp;
+    }
+}
\ No newline at end of file
diff --git a/projects/mesh_shader/assets/shaders/shader.vert b/projects/mesh_shader/assets/shaders/shader.vert
new file mode 100644
index 0000000000000000000000000000000000000000..fca5057976f995183c040195bdbd592c63f1074e
--- /dev/null
+++ b/projects/mesh_shader/assets/shaders/shader.vert
@@ -0,0 +1,29 @@
+#version 450
+#extension GL_ARB_separate_shader_objects   : enable
+#extension GL_GOOGLE_include_directive      : enable
+
+#include "common.inc"
+
+layout(location = 0) in vec3 inPosition;
+layout(location = 1) in vec3 inNormal;
+
+layout(location = 0) out vec3 passNormal;
+layout(location = 1) out uint dummyOutput;
+
+layout(std430, binding = 0) readonly buffer matrixBuffer
+{
+    ObjectMatrices objectMatrices[];
+};
+
+layout( push_constant ) uniform constants{
+    uint matrixIndex;
+    uint padding; // pad to same size as mesh shader constants
+};
+
+
+void main()	{
+	gl_Position = objectMatrices[matrixIndex].mvp * vec4(inPosition, 1.0);
+	passNormal  = inNormal;
+    
+    dummyOutput = padding * 0;  // padding must be used, else compiler shrinks constant size
+}
\ No newline at end of file
diff --git a/projects/mesh_shader/src/main.cpp b/projects/mesh_shader/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ca57d819892949f49553dedbb5cbe6409e64a600
--- /dev/null
+++ b/projects/mesh_shader/src/main.cpp
@@ -0,0 +1,403 @@
+#include <iostream>
+#include <vkcv/Buffer.hpp>
+#include <vkcv/Core.hpp>
+#include <vkcv/Pass.hpp>
+#include <GLFW/glfw3.h>
+#include <vkcv/camera/CameraManager.hpp>
+#include <chrono>
+
+#include <vkcv/shader/GLSLCompiler.hpp>
+#include <vkcv/gui/GUI.hpp>
+#include <vkcv/asset/asset_loader.hpp>
+#include <vkcv/meshlet/Meshlet.hpp>
+#include <vkcv/meshlet/Tipsify.hpp>
+#include <vkcv/meshlet/Forsyth.hpp>
+
+struct Plane {
+	glm::vec3 pointOnPlane;
+	float padding0;
+	glm::vec3 normal;
+	float padding1;
+};
+
+struct CameraPlanes {
+	Plane planes[6];
+};
+
+CameraPlanes computeCameraPlanes(const vkcv::camera::Camera& camera) {
+	const float     fov     = camera.getFov();
+	const glm::vec3 pos     = camera.getPosition();
+	const float     ratio   = camera.getRatio();
+	const glm::vec3 forward = glm::normalize(camera.getFront());
+	float near;
+	float far;
+	camera.getNearFar(near, far);
+
+	glm::vec3 up    = glm::vec3(0, -1, 0);
+	glm::vec3 right = glm::normalize(glm::cross(forward, up));
+	up              = glm::cross(forward, right);
+
+	const glm::vec3 nearCenter      = pos + forward * near;
+	const glm::vec3 farCenter       = pos + forward * far;
+
+	const float tanFovHalf          = glm::tan(fov / 2);
+
+	const glm::vec3 nearUpCenter    = nearCenter + up    * tanFovHalf * near;
+	const glm::vec3 nearDownCenter  = nearCenter - up    * tanFovHalf * near;
+	const glm::vec3 nearRightCenter = nearCenter + right * tanFovHalf * near * ratio;
+	const glm::vec3 nearLeftCenter  = nearCenter - right * tanFovHalf * near * ratio;
+
+	const glm::vec3 farUpCenter     = farCenter + up    * tanFovHalf * far;
+	const glm::vec3 farDownCenter   = farCenter - up    * tanFovHalf * far;
+	const glm::vec3 farRightCenter  = farCenter + right * tanFovHalf * far * ratio;
+	const glm::vec3 farLeftCenter   = farCenter - right * tanFovHalf * far * ratio;
+
+	CameraPlanes cameraPlanes;
+	// near
+	cameraPlanes.planes[0].pointOnPlane = nearCenter;
+	cameraPlanes.planes[0].normal       = -forward;
+	// far
+	cameraPlanes.planes[1].pointOnPlane = farCenter;
+	cameraPlanes.planes[1].normal       = forward;
+
+	// top
+	cameraPlanes.planes[2].pointOnPlane = nearUpCenter;
+	cameraPlanes.planes[2].normal       = glm::normalize(glm::cross(farUpCenter - nearUpCenter, right));
+	// bot
+	cameraPlanes.planes[3].pointOnPlane = nearDownCenter;
+	cameraPlanes.planes[3].normal       = glm::normalize(glm::cross(right, farDownCenter - nearDownCenter));
+
+	// right
+	cameraPlanes.planes[4].pointOnPlane = nearRightCenter;
+	cameraPlanes.planes[4].normal       = glm::normalize(glm::cross(up, farRightCenter - nearRightCenter));
+	// left
+	cameraPlanes.planes[5].pointOnPlane = nearLeftCenter;
+	cameraPlanes.planes[5].normal       = glm::normalize(glm::cross(farLeftCenter - nearLeftCenter, up));
+
+	return cameraPlanes;
+}
+
+int main(int argc, const char** argv) {
+	const std::string applicationName = "Mesh shader";
+	
+	vkcv::Features features;
+	features.requireExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
+	features.requireExtensionFeature<vk::PhysicalDeviceMeshShaderFeaturesNV>(
+			VK_NV_MESH_SHADER_EXTENSION_NAME, [](vk::PhysicalDeviceMeshShaderFeaturesNV& features) {
+		features.setTaskShader(true);
+		features.setMeshShader(true);
+	});
+
+	vkcv::Core core = vkcv::Core::create(
+		applicationName,
+		VK_MAKE_VERSION(0, 0, 1),
+		{ vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },
+		features
+	);
+	vkcv::WindowHandle windowHandle = core.createWindow(applicationName, 1280, 720, true);
+	vkcv::Window &window = core.getWindow(windowHandle);
+
+    vkcv::gui::GUI gui (core, windowHandle);
+
+    vkcv::asset::Scene mesh;
+    const char* path = argc > 1 ? argv[1] : "assets/Bunny/Bunny.glb";
+    vkcv::asset::loadScene(path, mesh);
+
+    assert(!mesh.vertexGroups.empty());
+
+    auto vertexBuffer = vkcv::buffer<uint8_t>(
+			core,
+            vkcv::BufferType::VERTEX,
+            mesh.vertexGroups[0].vertexBuffer.data.size(),
+            vkcv::BufferMemoryType::DEVICE_LOCAL
+    );
+    vertexBuffer.fill(mesh.vertexGroups[0].vertexBuffer.data);
+
+    auto indexBuffer = vkcv::buffer<uint8_t>(
+			core,
+            vkcv::BufferType::INDEX,
+            mesh.vertexGroups[0].indexBuffer.data.size(),
+            vkcv::BufferMemoryType::DEVICE_LOCAL
+    );
+    indexBuffer.fill(mesh.vertexGroups[0].indexBuffer.data);
+	
+	const auto vertexBufferBindings = vkcv::asset::loadVertexBufferBindings(
+			mesh.vertexGroups[0].vertexBuffer.attributes,
+			vertexBuffer.getHandle(),
+			{
+					vkcv::asset::PrimitiveType::POSITION,
+					vkcv::asset::PrimitiveType::NORMAL
+			}
+	);
+	
+	const vkcv::asset::VertexAttribute* positionAttribute = nullptr;
+	const vkcv::asset::VertexAttribute* normalAttribute   = nullptr;
+	
+	for (const auto& attribute : mesh.vertexGroups[0].vertexBuffer.attributes) {
+		switch (attribute.type) {
+			case vkcv::asset::PrimitiveType::POSITION:
+				positionAttribute = &attribute;
+				break;
+			case vkcv::asset::PrimitiveType::NORMAL:
+				normalAttribute = &attribute;
+				break;
+			default:
+				break;
+		}
+	}
+	
+	assert(positionAttribute && normalAttribute);
+
+	const auto& bunny = mesh.vertexGroups[0];
+	std::vector<vkcv::meshlet::Vertex> interleavedVertices = vkcv::meshlet::convertToVertices(
+			bunny.vertexBuffer.data,
+			bunny.numVertices,
+			*positionAttribute,
+			*normalAttribute
+	);
+	
+	// mesh shader buffers
+	const auto& assetLoaderIndexBuffer                    = mesh.vertexGroups[0].indexBuffer;
+	std::vector<uint32_t> indexBuffer32Bit                = vkcv::meshlet::assetLoaderIndicesTo32BitIndices(assetLoaderIndexBuffer.data, assetLoaderIndexBuffer.type);
+    vkcv::meshlet::VertexCacheReorderResult tipsifyResult = vkcv::meshlet::tipsifyMesh(indexBuffer32Bit, interleavedVertices.size());
+    vkcv::meshlet::VertexCacheReorderResult forsythResult = vkcv::meshlet::forsythReorder(indexBuffer32Bit, interleavedVertices.size());
+
+    const auto meshShaderModelData = createMeshShaderModelData(interleavedVertices, forsythResult.indexBuffer, forsythResult.skippedIndices);
+
+	auto meshShaderVertexBuffer = vkcv::buffer<vkcv::meshlet::Vertex>(
+		core,
+		vkcv::BufferType::STORAGE,
+		meshShaderModelData.vertices.size());
+	meshShaderVertexBuffer.fill(meshShaderModelData.vertices);
+
+	auto meshShaderIndexBuffer = vkcv::buffer<uint32_t>(
+		core,
+		vkcv::BufferType::STORAGE,
+		meshShaderModelData.localIndices.size());
+	meshShaderIndexBuffer.fill(meshShaderModelData.localIndices);
+
+	auto meshletBuffer = vkcv::buffer<vkcv::meshlet::Meshlet>(
+		core,
+		vkcv::BufferType::STORAGE,
+		meshShaderModelData.meshlets.size(),
+		vkcv::BufferMemoryType::DEVICE_LOCAL
+		);
+	meshletBuffer.fill(meshShaderModelData.meshlets);
+	
+	vkcv::PassHandle renderPass = vkcv::passSwapchain(
+			core,
+			window.getSwapchain(),
+			{ vk::Format::eUndefined, vk::Format::eD32Sfloat }
+	);
+
+	if (!renderPass)
+	{
+		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+	vkcv::ShaderProgram bunnyShaderProgram{};
+	vkcv::shader::GLSLCompiler compiler;
+	
+	compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("assets/shaders/shader.vert"),
+					 [&bunnyShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		 bunnyShaderProgram.addShader(shaderStage, path);
+	});
+	
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("assets/shaders/shader.frag"),
+					 [&bunnyShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		bunnyShaderProgram.addShader(shaderStage, path);
+	});
+
+    const std::vector<vkcv::VertexAttachment> vertexAttachments = bunnyShaderProgram.getVertexAttachments();
+    std::vector<vkcv::VertexBinding> bindings;
+    for (size_t i = 0; i < vertexAttachments.size(); i++) {
+        bindings.push_back(vkcv::createVertexBinding(i, { vertexAttachments[i] }));
+    }
+    const vkcv::VertexLayout bunnyLayout { bindings };
+
+    vkcv::DescriptorSetLayoutHandle vertexShaderDescriptorSetLayout = core.createDescriptorSetLayout(bunnyShaderProgram.getReflectedDescriptors().at(0));
+    vkcv::DescriptorSetHandle vertexShaderDescriptorSet = core.createDescriptorSet(vertexShaderDescriptorSetLayout);
+
+	struct ObjectMatrices {
+		glm::mat4 model;
+		glm::mat4 mvp;
+	};
+	const size_t objectCount = 1;
+	vkcv::Buffer<ObjectMatrices> matrixBuffer = vkcv::buffer<ObjectMatrices>(core, vkcv::BufferType::STORAGE, objectCount);
+
+	vkcv::DescriptorWrites vertexShaderDescriptorWrites;
+	vertexShaderDescriptorWrites.writeStorageBuffer(0, matrixBuffer.getHandle());
+	core.writeDescriptorSet(vertexShaderDescriptorSet, vertexShaderDescriptorWrites);
+
+	vkcv::GraphicsPipelineHandle bunnyPipeline = core.createGraphicsPipeline(
+			vkcv::GraphicsPipelineConfig(
+					bunnyShaderProgram,
+					renderPass,
+					{ bunnyLayout },
+					{ vertexShaderDescriptorSetLayout }
+			)
+	);
+
+	if (!bunnyPipeline)
+	{
+		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+	// mesh shader
+	vkcv::ShaderProgram meshShaderProgram;
+	compiler.compile(vkcv::ShaderStage::TASK, std::filesystem::path("assets/shaders/shader.task"),
+		[&meshShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		meshShaderProgram.addShader(shaderStage, path);
+	});
+
+	compiler.compile(vkcv::ShaderStage::MESH, std::filesystem::path("assets/shaders/shader.mesh"),
+		[&meshShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		meshShaderProgram.addShader(shaderStage, path);
+	});
+
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("assets/shaders/shader.frag"),
+		[&meshShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		meshShaderProgram.addShader(shaderStage, path);
+	});
+
+	vkcv::DescriptorSetLayoutHandle meshShaderDescriptorSetLayout = core.createDescriptorSetLayout(meshShaderProgram.getReflectedDescriptors().at(0));
+	vkcv::DescriptorSetHandle meshShaderDescriptorSet = core.createDescriptorSet(meshShaderDescriptorSetLayout);
+	const vkcv::VertexLayout meshShaderLayout = vkcv::createVertexLayout(bindings);
+
+	vkcv::GraphicsPipelineHandle meshShaderPipeline = core.createGraphicsPipeline(
+			vkcv::GraphicsPipelineConfig(
+					meshShaderProgram,
+					renderPass,
+					{ meshShaderLayout },
+					{ meshShaderDescriptorSetLayout }
+			)
+	);
+
+	if (!meshShaderPipeline)
+	{
+		std::cout << "Error. Could not create mesh shader pipeline. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+	vkcv::Buffer<CameraPlanes> cameraPlaneBuffer = vkcv::buffer<CameraPlanes>(core, vkcv::BufferType::UNIFORM, 1);
+
+	vkcv::DescriptorWrites meshShaderWrites;
+	meshShaderWrites.writeStorageBuffer(
+			0, meshShaderVertexBuffer.getHandle()
+	).writeStorageBuffer(
+			1, meshShaderIndexBuffer.getHandle()
+	).writeStorageBuffer(
+			2, meshletBuffer.getHandle()
+	).writeStorageBuffer(
+			4, matrixBuffer.getHandle()
+	).writeStorageBuffer(
+			5, meshletBuffer.getHandle()
+	);
+	
+	meshShaderWrites.writeUniformBuffer(3, cameraPlaneBuffer.getHandle());
+
+    core.writeDescriptorSet( meshShaderDescriptorSet, meshShaderWrites);
+
+    vkcv::ImageHandle depthBuffer;
+	vkcv::ImageHandle swapchainImageHandle = vkcv::ImageHandle::createSwapchainImageHandle();
+
+	vkcv::VertexData vertexData (vertexBufferBindings);
+	vertexData.setIndexBuffer(indexBuffer.getHandle(), vkcv::IndexBitCount::Bit32);
+	vertexData.setCount(mesh.vertexGroups[0].numIndices);
+
+	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+
+	vkcv::camera::CameraManager cameraManager(window);
+	auto camHandle = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+	
+	cameraManager.getCamera(camHandle).setPosition(glm::vec3(0, 0, -2));
+
+	bool useMeshShader          = true;
+	bool updateFrustumPlanes    = true;
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
+		if ((!depthBuffer) ||
+			(swapchainWidth != core.getImageWidth(depthBuffer)) ||
+			(swapchainHeight != core.getImageHeight(depthBuffer))) {
+			depthBuffer = core.createImage(
+					vk::Format::eD32Sfloat,
+					vkcv::ImageConfig(
+							swapchainWidth,
+							swapchainHeight
+					)
+			);
+		}
+		
+		cameraManager.update(dt);
+
+		const vkcv::camera::Camera& camera = cameraManager.getActiveCamera();
+
+		ObjectMatrices objectMatrices;
+		objectMatrices.model    = *reinterpret_cast<glm::mat4*>(&mesh.meshes.front().modelMatrix);
+		objectMatrices.mvp      = camera.getMVP() * objectMatrices.model;
+
+		matrixBuffer.fill({ objectMatrices });
+
+		struct PushConstants {
+			uint32_t matrixIndex;
+			uint32_t meshletCount;
+		};
+		PushConstants pushConstants{ 0, static_cast<uint32_t>(meshShaderModelData.meshlets.size()) };
+
+		if (updateFrustumPlanes) {
+			const CameraPlanes cameraPlanes = computeCameraPlanes(camera);
+			cameraPlaneBuffer.fill({ cameraPlanes });
+		}
+
+		const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer };
+		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+
+		vkcv::PushConstants pushConstantData = vkcv::pushConstants<PushConstants>();
+		pushConstantData.appendDrawcall(pushConstants);
+
+		if (useMeshShader) {
+			const uint32_t taskCount = (meshShaderModelData.meshlets.size() + 31) / 32;
+			
+			vkcv::TaskDrawcall drawcall (taskCount);
+			drawcall.useDescriptorSet(0, meshShaderDescriptorSet);
+
+			core.recordMeshShaderDrawcalls(
+				cmdStream,
+				meshShaderPipeline,
+				pushConstantData,
+				{ drawcall },
+				{ renderTargets },
+				windowHandle
+			);
+		} else {
+			vkcv::InstanceDrawcall drawcall (vertexData);
+			drawcall.useDescriptorSet(0, vertexShaderDescriptorSet);
+
+			core.recordDrawcallsToCmdStream(
+				cmdStream,
+				bunnyPipeline,
+				pushConstantData,
+				{ drawcall },
+				{ renderTargets },
+				windowHandle
+			);
+		}
+
+		core.prepareSwapchainImageForPresent(cmdStream);
+		core.submitCommandStream(cmdStream);
+		
+		gui.beginGUI();
+		
+		ImGui::Begin("Settings");
+		ImGui::Checkbox("Use mesh shader", &useMeshShader);
+		ImGui::Checkbox("Update frustum culling", &updateFrustumPlanes);
+
+		ImGui::End();
+		gui.endGUI();
+	});
+	
+	return 0;
+}
diff --git a/projects/mpm/.gitignore b/projects/mpm/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..68b4554cbc85d602c1192730d43d1ebfa28c36a6
--- /dev/null
+++ b/projects/mpm/.gitignore
@@ -0,0 +1 @@
+mpm
diff --git a/projects/mpm/CMakeLists.txt b/projects/mpm/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..859a0ac1504e8922300b174fafef66d4d0553a20
--- /dev/null
+++ b/projects/mpm/CMakeLists.txt
@@ -0,0 +1,15 @@
+cmake_minimum_required(VERSION 3.16)
+project(mpm)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+# adding source files to the project
+add_project(mpm src/main.cpp)
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(mpm SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_camera_include} ${vkcv_gui_include} ${vkcv_shader_compiler_include})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(mpm vkcv vkcv_camera vkcv_gui vkcv_shader_compiler)
diff --git a/projects/mpm/README.md b/projects/mpm/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..2b3a8f0ce0be51d2d54b16ab039932d83e31d763
--- /dev/null
+++ b/projects/mpm/README.md
@@ -0,0 +1,11 @@
+# MPM
+An example project to show the implementation of an MPM soft-body simulation with the VkCV framework
+
+![Screenshot](../../screenshots/mpm.png)
+
+## Details
+
+The project shows off an example implementation of an MPM simulation which are widely used
+in computer graphics to simulate the behavior of solids, liquids or gases. It utilizes the discrete
+representation of objects as combined particles as well as a discrete grid of data. Together both 
+representations help to stabilize the result while providing as much detail as possible.
diff --git a/projects/mpm/shaders/.gitignore b/projects/mpm/shaders/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/projects/mpm/shaders/grid.frag b/projects/mpm/shaders/grid.frag
new file mode 100644
index 0000000000000000000000000000000000000000..8eb2fbc2173bbddb5bdc44c52ce81d2e5d52d389
--- /dev/null
+++ b/projects/mpm/shaders/grid.frag
@@ -0,0 +1,30 @@
+#version 450
+
+layout(location = 0) in vec2 passPos;
+layout(location = 1) in vec3 passVelocity;
+layout(location = 2) in float passMass;
+
+layout(location = 0) out vec3 outColor;
+
+void main()	{
+    if (passMass <= 0.0f) {
+        discard;
+    }
+
+    const float value = length(passPos);
+
+    float z = sqrt(0.25 - value * value);
+
+    if (value < 0.5f) {
+        vec3 surface = vec3(passPos.x + 0.5f, passPos.y + 0.5f, z * 2.0f);
+        vec3 velocity = vec3(0.5f);
+
+        if (length(passVelocity) > 0.0f) {
+            velocity = vec3(0.5f) + 0.5f * normalize(passVelocity.xyz);
+        }
+
+        outColor = velocity;
+    } else {
+        discard;
+    }
+}
\ No newline at end of file
diff --git a/projects/mpm/shaders/grid.vert b/projects/mpm/shaders/grid.vert
new file mode 100644
index 0000000000000000000000000000000000000000..54de3f3e1da43e3ea3d018e5c54003b83e055c4c
--- /dev/null
+++ b/projects/mpm/shaders/grid.vert
@@ -0,0 +1,58 @@
+#version 450
+#extension GL_GOOGLE_include_directive : enable
+
+#include "particle.inc"
+
+layout(set=0, binding=0) uniform texture3D gridImage;
+layout(set=0, binding=1) uniform sampler gridSampler;
+
+layout(set=0, binding=2) uniform simulationBlock {
+    Simulation simulation;
+};
+
+layout(location = 0) in vec2 vertexPos;
+
+layout(location = 0) out vec2 passPos;
+layout(location = 1) out vec3 passVelocity;
+layout(location = 2) out float passMass;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+};
+
+ivec3 actual_mod(ivec3 x, ivec3 y) {
+    return x - y * (x/y);
+}
+
+void main()	{
+    ivec3 gridResolution = textureSize(sampler3D(gridImage, gridSampler), 0);
+
+    ivec3 gridID = ivec3(
+        gl_InstanceIndex,
+        gl_InstanceIndex / gridResolution.x,
+        gl_InstanceIndex / gridResolution.x / gridResolution.y
+    );
+
+    gridID = actual_mod(gridID, gridResolution);
+
+    vec3 position = (vec3(gridID) + vec3(0.5f)) / gridResolution;
+
+    vec3 size = vec3(1.0f) / vec3(gridResolution);
+    float volume = size.x * size.y * size.z;
+    float radius = cube_radius(volume);
+
+    vec4 gridData = texture(sampler3D(gridImage, gridSampler), position);
+
+    float mass = gridData.w;
+    float density = mass / volume;
+
+    float alpha = clamp(density / simulation.density, 0.0f, 1.0f);
+
+    passPos = vertexPos;
+    passVelocity = gridData.xyz;
+    passMass = mass;
+
+    // align voxel to face camera
+    gl_Position = mvp * vec4(position, 1);      // transform position into projected view space
+    gl_Position.xy += vertexPos * (radius * 2.0f) * alpha;  // move position directly in view space
+}
\ No newline at end of file
diff --git a/projects/mpm/shaders/init_particle_weights.comp b/projects/mpm/shaders/init_particle_weights.comp
new file mode 100644
index 0000000000000000000000000000000000000000..9b821e88fc7cc3fcea87b7eb6de0f8f6d629d911
--- /dev/null
+++ b/projects/mpm/shaders/init_particle_weights.comp
@@ -0,0 +1,43 @@
+#version 450
+#extension GL_GOOGLE_include_directive : enable
+
+layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
+
+#include "particle.inc"
+
+layout(set=0, binding=0, std430) restrict buffer particleBuffer {
+    Particle particles [];
+};
+
+layout(set=1, binding=0) uniform texture3D gridImage;
+layout(set=1, binding=1) uniform sampler gridSampler;
+
+void main()	{
+    if (gl_GlobalInvocationID.x < particles.length()) {
+        ParticleMinimal minimal = particles[gl_GlobalInvocationID.x].minimal;
+
+        minimal.weight_sum = 1.0f;
+
+        ivec3 gridResolution = textureSize(sampler3D(gridImage, gridSampler), 0);
+        ivec3 gridWindow = ivec3(minimal.size * 2.0f * gridResolution);
+
+        float weight_sum = 0.0f;
+
+        int i, j, k;
+
+        for (i = -gridWindow.x; i <= gridWindow.x; i++) {
+            for (j = -gridWindow.y; j <= gridWindow.y; j++) {
+                for (k = -gridWindow.z; k <= gridWindow.z; k++) {
+                    vec3 offset = vec3(i, j, k) / gridResolution;
+                    vec3 voxel = minimal.position + offset;
+
+                    weight_sum += voxel_particle_weight(voxel, minimal);
+                }
+            }
+        }
+
+        if (weight_sum > 0.0f) {
+            particles[gl_GlobalInvocationID.x].minimal.weight_sum = weight_sum;
+        }
+    }
+}
\ No newline at end of file
diff --git a/projects/mpm/shaders/lines.frag b/projects/mpm/shaders/lines.frag
new file mode 100644
index 0000000000000000000000000000000000000000..37d79d30d2ce751a59b1b467764b2fe464aa5c17
--- /dev/null
+++ b/projects/mpm/shaders/lines.frag
@@ -0,0 +1,7 @@
+#version 450
+
+layout(location = 0) out vec3 outColor;
+
+void main() {
+    outColor = vec3(1.0f);
+}
\ No newline at end of file
diff --git a/projects/mpm/shaders/lines.vert b/projects/mpm/shaders/lines.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b8e3b01c67986156ad980899697ffea05409b752
--- /dev/null
+++ b/projects/mpm/shaders/lines.vert
@@ -0,0 +1,11 @@
+#version 450
+
+layout(location = 0) in vec3 vertexPos;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+};
+
+void main() {
+    gl_Position = mvp * vec4(vertexPos, 1);
+}
\ No newline at end of file
diff --git a/projects/mpm/shaders/particle.frag b/projects/mpm/shaders/particle.frag
new file mode 100644
index 0000000000000000000000000000000000000000..81c0a3594359e816a28b5e3f0301b47ce74a3cd7
--- /dev/null
+++ b/projects/mpm/shaders/particle.frag
@@ -0,0 +1,18 @@
+#version 450
+
+layout(location = 0) in vec2 passPos;
+layout(location = 1) in float passMass;
+
+layout(location = 0) out vec3 outColor;
+
+void main()	{
+    const float value = length(passPos);
+
+    float z = sqrt(0.25 - value * value);
+
+    if (value < 0.5f) {
+        outColor = vec3(passPos.x + 0.5f, passPos.y + 0.5f, z * 2.0f);
+    } else {
+        discard;
+    }
+}
\ No newline at end of file
diff --git a/projects/mpm/shaders/particle.inc b/projects/mpm/shaders/particle.inc
new file mode 100644
index 0000000000000000000000000000000000000000..a622485eab5bbcafdc51c030281e53b1cc1c5f11
--- /dev/null
+++ b/projects/mpm/shaders/particle.inc
@@ -0,0 +1,118 @@
+#ifndef PARTICLE_INC
+#define PARTICLE_INC
+
+#define EPSILON 0.00000001f
+
+struct ParticleMinimal {
+    vec3 position;
+    float size;
+    vec3 velocity;
+    float mass;
+	
+	vec3 pad;
+	float weight_sum;
+};
+
+struct Particle {
+    ParticleMinimal minimal;
+    mat4 deformation;
+	mat4 mls;
+};
+
+#define SIM_FORM_SPHERE 0
+#define SIM_FORM_CUBE 1
+
+#define SIM_TYPE_HYPERELASTIC 0
+#define SIM_TYPE_FLUID 1
+
+#define SIM_MODE_RANDOM 0
+#define SIM_MODE_ORDERED 1
+
+struct Simulation {
+	float density;
+	float size;
+	float lame1;
+	float lame2;
+	
+	int form;
+	int type;
+	float K;
+	float E;
+	
+	float gamma;
+	int mode;
+	float gravity;
+	int count;
+};
+
+const float PI = 3.1415926535897932384626433832795;
+
+float sphere_volume(float radius) {
+	return 4.0f * (radius * radius * radius) * PI / 3.0f;
+}
+
+float sphere_radius(float volume) {
+	return pow(volume * 3.0f / 4.0f / PI, 1.0f / 3.0f);
+}
+
+float cube_volume(float radius) {
+	return 8.0f * (radius * radius * radius);
+}
+
+float cube_radius(float volume) {
+	return pow(volume / 8.0f, 1.0f / 3.0f);
+}
+
+float weight_A(float x) {
+	return max(1.0f - x, 0.0f);
+}
+
+float weight_B(float x) {
+	if (x < 0.5f) {
+		return 0.75f - x * x;
+	} else
+	if (x < 1.5f) {
+		float y = (1.5f - x);
+		return 0.5f * y * y;
+	} else {
+		return 0.0f;
+	}
+}
+
+float weight_C(float x) {
+	if (x < 1.0f) {
+		return (0.5f * x - 1.0f) * x*x + 2.0f / 3.0f;
+	} else
+	if (x < 2.0f) {
+		float y = (2.0f - x);
+		return 0.5f / 3.0f * y * y * y;
+	} else {
+		return 0.0f;
+	}
+}
+
+float voxel_particle_weight(vec3 voxel, ParticleMinimal particle) {
+	vec3 delta = abs(particle.position - voxel) / particle.size;
+	
+	if (any(isnan(delta)) || any(isinf(delta))) {
+		return 0.0f;
+	}
+	
+	vec3 weight = vec3(
+		weight_C(delta.x),
+		weight_C(delta.y),
+		weight_C(delta.z)
+	);
+	
+	float result = (
+		weight.x * weight.y * weight.z
+	) / particle.weight_sum;
+	
+	if ((isnan(result)) || (isinf(result))) {
+		return 0.0f;
+	} else {
+		return result;
+	}
+}
+
+#endif // PARTICLE_INC
\ No newline at end of file
diff --git a/projects/mpm/shaders/particle.vert b/projects/mpm/shaders/particle.vert
new file mode 100644
index 0000000000000000000000000000000000000000..a8f697e79eacba361d80b5858405add675e761bb
--- /dev/null
+++ b/projects/mpm/shaders/particle.vert
@@ -0,0 +1,31 @@
+#version 450
+#extension GL_GOOGLE_include_directive : enable
+
+#include "particle.inc"
+
+layout(set=0, binding=0, std430) readonly buffer particleBuffer {
+    Particle particles [];
+};
+
+layout(location = 0) in vec2 vertexPos;
+
+layout(location = 0) out vec2 passPos;
+layout(location = 1) out float passMass;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+};
+
+void main()	{
+    vec3 position = particles[gl_InstanceIndex].minimal.position;
+    float size = particles[gl_InstanceIndex].minimal.size;
+
+    float mass = particles[gl_InstanceIndex].minimal.mass;
+
+    passPos = vertexPos;
+    passMass = mass;
+
+    // align particle to face camera
+    gl_Position = mvp * vec4(position, 1);      // transform position into projected view space
+    gl_Position.xy += vertexPos * size * 2.0f;  // move position directly in view space
+}
\ No newline at end of file
diff --git a/projects/mpm/shaders/transform_particles_to_grid.comp b/projects/mpm/shaders/transform_particles_to_grid.comp
new file mode 100644
index 0000000000000000000000000000000000000000..1be18c41303ab2208c8cb4a8c33f41b350315d63
--- /dev/null
+++ b/projects/mpm/shaders/transform_particles_to_grid.comp
@@ -0,0 +1,101 @@
+#version 450
+#extension GL_GOOGLE_include_directive : enable
+
+layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
+
+#include "particle.inc"
+
+layout(set=0, binding=0, std430) readonly buffer particleBuffer {
+    Particle particles [];
+};
+
+layout(set=1, binding=0) uniform simulationBlock {
+    Simulation simulation;
+};
+
+layout(set=2, binding=0, rgba16f) restrict writeonly uniform image3D gridImage;
+
+layout( push_constant ) uniform constants {
+    float t;
+    float dt;
+    float speedfactor;
+};
+
+#define SHARED_PARTICLES_BATCH_SIZE 64
+
+shared ParticleMinimal shared_particles [SHARED_PARTICLES_BATCH_SIZE];
+
+void main()	{
+    const vec3 position = (vec3(gl_GlobalInvocationID) + vec3(0.5f)) / imageSize(gridImage);
+
+    float dts = dt * speedfactor;
+
+    vec4 gridValue = vec4(0.0f);
+
+    uint offset = 0;
+
+    for (offset = 0; offset < particles.length(); offset += SHARED_PARTICLES_BATCH_SIZE) {
+        uint localOffset = offset + gl_LocalInvocationIndex;
+
+        if (localOffset < particles.length()) {
+            shared_particles[gl_LocalInvocationIndex] = particles[localOffset].minimal;
+
+            shared_particles[gl_LocalInvocationIndex].pad = (
+                mat3(particles[localOffset].mls) *
+                (position - shared_particles[gl_LocalInvocationIndex].position)
+            ) + (
+                shared_particles[gl_LocalInvocationIndex].velocity *
+                shared_particles[gl_LocalInvocationIndex].mass
+            );
+        } else {
+            shared_particles[gl_LocalInvocationIndex].position = vec3(0.0f);
+            shared_particles[gl_LocalInvocationIndex].size = 0.0f;
+            shared_particles[gl_LocalInvocationIndex].velocity = vec3(0.0f);
+            shared_particles[gl_LocalInvocationIndex].mass = 0.0f;
+
+            shared_particles[gl_LocalInvocationIndex].pad = vec3(0.0f);
+            shared_particles[gl_LocalInvocationIndex].weight_sum = 1.0f;
+        }
+
+        barrier();
+        memoryBarrierShared();
+
+        for (uint i = 0; i < SHARED_PARTICLES_BATCH_SIZE; i++) {
+            float weight = voxel_particle_weight(position, shared_particles[i]);
+
+            gridValue += vec4(
+                shared_particles[i].pad * weight,
+                shared_particles[i].mass * weight
+            );
+        }
+
+        barrier();
+        memoryBarrierShared();
+    }
+
+    if (any(isnan(gridValue.xyz)) || any(isinf(gridValue.xyz))) {
+        gridValue.xyz = vec3(0.0f);
+    }
+
+    gridValue.xyz += vec3(0.0f, -simulation.gravity * dts * gridValue.w, 0.0f);
+
+    bvec3 lowerID = lessThanEqual(gl_GlobalInvocationID, ivec3(0));
+    bvec3 negativeVelocity = lessThan(gridValue.xyz, vec3(0.0f));
+
+    bvec3 greaterID = greaterThanEqual(gl_GlobalInvocationID + ivec3(1), imageSize(gridImage));
+    bvec3 positiveVelocity = greaterThan(gridValue.xyz, vec3(0.0f));
+
+    bvec3 collision = bvec3(
+        (lowerID.x && negativeVelocity.x) || (greaterID.x && positiveVelocity.x),
+        (lowerID.y && negativeVelocity.y) || (greaterID.y && positiveVelocity.y),
+        (lowerID.z && negativeVelocity.z) || (greaterID.z && positiveVelocity.z)
+    );
+
+    gridValue.xyz = mix(gridValue.xyz, -gridValue.xyz, collision);
+
+    imageStore(
+        gridImage,
+        ivec3(gl_GlobalInvocationID),
+        gridValue
+    );
+}
\ No newline at end of file
diff --git a/projects/mpm/shaders/update_particle_velocities.comp b/projects/mpm/shaders/update_particle_velocities.comp
new file mode 100644
index 0000000000000000000000000000000000000000..4420bc9575bcde4d04d7d4f5531d0091362285c2
--- /dev/null
+++ b/projects/mpm/shaders/update_particle_velocities.comp
@@ -0,0 +1,161 @@
+#version 450
+#extension GL_GOOGLE_include_directive : enable
+#extension GL_EXT_control_flow_attributes : enable
+
+layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
+
+#include "particle.inc"
+
+layout(set=0, binding=0, std430) restrict buffer particleBuffer {
+    Particle particles [];
+};
+
+layout(set=1, binding=0) uniform simulationBlock {
+    Simulation simulation;
+};
+
+layout(set=2, binding=0) uniform texture3D gridImage;
+layout(set=2, binding=1) uniform sampler gridSampler;
+
+layout( push_constant ) uniform constants {
+    float t;
+    float dt;
+    float speedfactor;
+};
+
+void main()	{
+    float dts = dt * speedfactor;
+
+    if (gl_GlobalInvocationID.x < particles.length()) {
+        Particle particle = particles[gl_GlobalInvocationID.x];
+
+        vec3 position = particle.minimal.position;
+        float size = particle.minimal.size;
+        float mass = particle.minimal.mass;
+
+        ivec3 gridResolution = textureSize(sampler3D(gridImage, gridSampler), 0);
+        ivec3 gridWindow = ivec3(size * 2.0f * gridResolution);
+
+        mat3 affine_D = mat3(0.0f);
+        mat3 affine_B = mat3(0.0f);
+
+        vec3 velocity_pic = vec3(0.0f);
+        vec3 velocity_flip = vec3(particle.minimal.velocity);
+
+        int i, j, k;
+
+        for (i = -gridWindow.x; i <= gridWindow.x; i++) {
+            for (j = -gridWindow.y; j <= gridWindow.y; j++) {
+                for (k = -gridWindow.z; k <= gridWindow.z; k++) {
+                    vec3 offset = vec3(i, j, k) / gridResolution;
+                    vec3 voxel = position + offset;
+
+                    vec4 gridSample = texture(sampler3D(gridImage, gridSampler), voxel);
+
+                    float weight = voxel_particle_weight(voxel, particle.minimal);
+                    vec3 velocity = gridSample.xyz * weight / gridSample.w;
+
+                    if (any(isnan(velocity)) || any(isinf(velocity))) {
+                        velocity = vec3(0.0f);
+                    }
+
+                    affine_D += outerProduct(weight * offset, offset);
+                    affine_B += outerProduct(velocity, offset);
+
+                    velocity_pic += velocity;
+                }
+            }
+        }
+
+        mat3 mls_Q = mat3(0.0f);
+        mat3 affine_C = mat3(0.0f);
+
+        mat3 F = mat3(particle.deformation);
+
+        mat3 D_inv = inverse(affine_D);
+        float D_det = determinant(D_inv);
+
+        if ((isnan(D_det)) || (isinf(D_det))) {
+            D_inv = mat3(0.0f);
+        } else {
+            D_inv *= min(abs(D_det), 1.0f / EPSILON) / abs(D_det);
+        }
+
+        float J = max(determinant(F), EPSILON);
+        float volume = sphere_volume(size);
+
+        mat3 stress = mat3(0.0f);
+
+        switch (simulation.type) {
+            case SIM_TYPE_HYPERELASTIC:
+                mat3 F_T = transpose(F);
+                mat3 F_T_inv = inverse(F_T);
+
+                mat3 P_term_0 = simulation.lame2 * (F - F_T_inv);
+                mat3 P_term_1 = simulation.lame1 * log(J) * F_T_inv;
+
+                mat3 P = P_term_0 + P_term_1;
+
+                stress = P * F_T;
+                break;
+            case SIM_TYPE_FLUID:
+                float pressure = simulation.K * (1.0f / pow(J, simulation.gamma) - 1.0f);
+
+                stress = mat3(-pressure * J);
+                break;
+            default:
+                break;
+        }
+
+        mls_Q -= dts * volume * stress * D_inv;
+
+        affine_C = affine_B * D_inv;
+        mls_Q += affine_C * mass;
+
+        F = (mat3(1.0f) + dts * affine_C) * F;
+
+        position = position + velocity_pic * dts;
+
+        const float gridRange = (1.0f - 2.0f * size);
+
+        for (uint i = 0; i < 3; i++) {
+            if (position[i] - size < 0.0f) {
+                float a = (size - position[i]) / gridRange;
+                int b = int(floor(a));
+
+                a = (a - b) * gridRange;
+
+                if (b % 2 == 0) {
+                    position[i] = size + a;
+                } else {
+                    position[i] = 1.0f - size - a;
+                }
+
+                if ((velocity_pic[i] < 0.0f) == (b % 2 == 0)) {
+                    velocity_pic[i] *= -1.0f;
+                }
+            } else
+            if (position[i] + size > 1.0f) {
+                float a = (position[i] + size - 1.0f) / gridRange;
+                int b = int(floor(a));
+
+                a = (a - b) * gridRange;
+
+                if (b % 2 == 0) {
+                    position[i] = 1.0f - size - a;
+                } else {
+                    position[i] = size + a;
+                }
+
+                if ((velocity_pic[i] > 0.0f) == (b % 2 == 0)) {
+                    velocity_pic[i] *= -1.0f;
+                }
+            }
+        }
+
+        particles[gl_GlobalInvocationID.x].minimal.position = position;
+        particles[gl_GlobalInvocationID.x].minimal.velocity = velocity_pic;
+        particles[gl_GlobalInvocationID.x].deformation = mat4(F);
+        particles[gl_GlobalInvocationID.x].mls = mat4(mls_Q);
+    }
+}
\ No newline at end of file
diff --git a/projects/mpm/src/main.cpp b/projects/mpm/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..61f27daa9b011b1952ce640653483c8de730eb74
--- /dev/null
+++ b/projects/mpm/src/main.cpp
@@ -0,0 +1,842 @@
+
+#include <vkcv/Buffer.hpp>
+#include <vkcv/Core.hpp>
+#include <vkcv/Pass.hpp>
+#include <vkcv/Image.hpp>
+#include <vkcv/camera/CameraManager.hpp>
+#include <vkcv/gui/GUI.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+
+#include <random>
+
+struct Particle {
+	glm::vec3 position;
+	float size;
+	glm::vec3 velocity;
+	float mass;
+	
+	glm::vec3 pad;
+	float weight_sum;
+	
+	glm::mat4 deformation;
+	glm::mat4 mls;
+};
+
+#define SIM_FORM_SPHERE 0
+#define SIM_FORM_CUBE 1
+
+#define SIM_TYPE_HYPERELASTIC 0
+#define SIM_TYPE_FLUID 1
+
+#define SIM_MODE_RANDOM 0
+#define SIM_MODE_ORDERED 1
+
+struct Simulation {
+	float density;
+	float size;
+	float lame1;
+	float lame2;
+	
+	int form;
+	int type;
+	float K;
+	float E;
+	
+	float gamma;
+	int mode;
+	float gravity;
+	int count;
+};
+
+struct Physics {
+	float t;
+	float dt;
+	float speedfactor;
+};
+
+float sphere_volume(float radius) {
+	return 4.0f * (radius * radius * radius) * M_PI / 3.0f;
+}
+
+float sphere_radius(float volume) {
+	return std::pow(volume * 3.0f / 4.0f / M_PI, 1.0f / 3.0f);
+}
+
+float cube_volume(float radius) {
+	return 8.0f * (radius * radius * radius);
+}
+
+float cube_radius(float volume) {
+	return std::pow(volume / 8.0f, 1.0f / 3.0f);
+}
+
+std::random_device random_dev;
+std::uniform_int_distribution<int> dist(0, RAND_MAX);
+
+float randomFloat(float min, float max) {
+	return min + (max - min) * dist(random_dev) / static_cast<float>(RAND_MAX);
+}
+
+float mod(float x, float y) {
+	return x - std::floor(x / y) * y;
+}
+
+void distributeParticlesCube(Particle *particles, size_t count, const glm::vec3& center, float radius,
+							 float mass, const glm::vec3& velocity, bool random) {
+	const float side = cube_radius(static_cast<float>(count)) * 2.0f;
+	
+	float volume = 0.0f;
+	
+	for (size_t i = 0; i < count; i++) {
+		glm::vec3 offset;
+		
+		if (random) {
+			offset.x = randomFloat(-1.0f, +1.0f);
+			offset.y = randomFloat(-1.0f, +1.0f);
+			offset.z = randomFloat(-1.0f, +1.0f);
+		} else {
+			const float s = static_cast<float>(i) + 0.5f;
+			
+			offset.x = 2.0f * mod(s, side) / side - 1.0f;
+			offset.y = 2.0f * mod(s / side, side) / side - 1.0f;
+			offset.z = 2.0f * mod(s / side / side, side) / side - 1.0f;
+		}
+		
+		offset *= radius;
+		
+		float size = 0.0f;
+		
+		if (random) {
+			const float ax = std::abs(offset.x);
+			const float ay = std::abs(offset.y);
+			const float az = std::abs(offset.z);
+			
+			const float a = std::max(std::max(ax, ay), az);
+			
+			size = (radius - a);
+		} else {
+			size = 2.0f * radius / side;
+		}
+		
+		particles[i].position = center + offset;
+		particles[i].size = size;
+		particles[i].velocity = velocity;
+		
+		volume += cube_volume(size);
+	}
+	
+	for (size_t i = 0; i < count; i++) {
+		particles[i].mass = (mass * cube_volume(particles[i].size) / volume);
+		particles[i].deformation = glm::mat4(1.0f);
+		
+		particles[i].pad = glm::vec3(0.0f);
+		particles[i].weight_sum = 1.0f;
+		
+		particles[i].mls = glm::mat4(0.0f);
+	}
+}
+
+void distributeParticlesSphere(Particle *particles, size_t count, const glm::vec3& center, float radius,
+							   float mass, const glm::vec3& velocity, bool random) {
+	const float side = sphere_radius(static_cast<float>(count)) * 2.0f;
+	
+	float volume = 0.0f;
+	
+	for (size_t i = 0; i < count; i++) {
+		glm::vec3 offset;
+		
+		if (random) {
+			offset.x = randomFloat(-1.0f, +1.0f);
+			offset.y = randomFloat(-1.0f, +1.0f);
+			offset.z = randomFloat(-1.0f, +1.0f);
+			
+			if (glm::length(offset) > 0.0f)
+				offset = glm::normalize(offset);
+			
+			offset *= randomFloat(0.0f, 1.0f);
+		} else {
+			const float range = 0.5f * side;
+			const float s = static_cast<float>(i) + 0.5f;
+			
+			float a = mod(s, range) / range;
+			float b = mod(s / range, M_PI * range);
+			float c = mod(s / range / M_PI / range, M_PI * range * 2.0f);
+			
+			offset.x = a * std::sin(c) * std::sin(b);
+			offset.y = a * std::cos(b);
+			offset.z = a * std::cos(c) * std::sin(b);
+		}
+		
+		offset *= radius;
+		
+		float size = 0.0f;
+		
+		if (random) {
+			size = (radius - glm::length(offset));
+		} else {
+			size = 2.0f * radius / side;
+		}
+		
+		particles[i].position = center + offset;
+		particles[i].size = size;
+		particles[i].velocity = velocity;
+		
+		volume += sphere_volume(size);
+	}
+	
+	// Keep the same densitiy as planned!
+	mass *= (volume / sphere_volume(radius));
+	
+	for (size_t i = 0; i < count; i++) {
+		particles[i].mass = (mass * sphere_volume(particles[i].size) / volume);
+		particles[i].deformation = glm::mat4(1.0f);
+		
+		particles[i].pad = glm::vec3(0.0f);
+		particles[i].weight_sum = 1.0f;
+		
+		particles[i].mls = glm::mat4(0.0f);
+	}
+}
+
+vkcv::ComputePipelineHandle createComputePipeline(vkcv::Core& core, vkcv::shader::GLSLCompiler& compiler,
+												  const std::string& path,
+												  std::vector<vkcv::DescriptorSetHandle>& descriptorSets) {
+	vkcv::ShaderProgram shaderProgram;
+	
+	compiler.compile(
+			vkcv::ShaderStage::COMPUTE,
+			path,
+			[&shaderProgram](vkcv::ShaderStage stage, const std::filesystem::path& path) {
+				shaderProgram.addShader(stage, path);
+			}
+	);
+	
+	const auto& descriptors = shaderProgram.getReflectedDescriptors();
+	
+	size_t count = 0;
+	
+	for (const auto& descriptor : descriptors) {
+		if (descriptor.first >= count) {
+			count = (descriptor.first + 1);
+		}
+	}
+	
+	std::vector<vkcv::DescriptorSetLayoutHandle> descriptorSetLayouts;
+	
+	descriptorSetLayouts.resize(count);
+	descriptorSets.resize(count);
+	
+	for (const auto& descriptor : descriptors) {
+		descriptorSetLayouts[descriptor.first] = core.createDescriptorSetLayout(descriptor.second);
+		descriptorSets[descriptor.first] = core.createDescriptorSet(descriptorSetLayouts[descriptor.first]);
+	}
+	
+	vkcv::ComputePipelineConfig config {
+			shaderProgram,
+			descriptorSetLayouts
+	};
+	
+	return core.createComputePipeline(config);
+}
+
+vkcv::BufferHandle resetParticles(vkcv::Core& core, size_t count, const glm::vec3& velocity,
+					float density, float size, int form, int mode) {
+	vkcv::Buffer<Particle> particles = vkcv::buffer<Particle>(
+			core,
+			vkcv::BufferType::STORAGE,
+			count
+	);
+	
+	std::vector<Particle> particles_vec (particles.getCount());
+	
+	switch (form) {
+		case SIM_FORM_SPHERE:
+			distributeParticlesSphere(
+					particles_vec.data(),
+					particles_vec.size(),
+					glm::vec3(0.5f),
+					size,
+					density * sphere_volume(size),
+					velocity,
+					(mode == 0)
+			);
+			break;
+		case SIM_FORM_CUBE:
+			distributeParticlesCube(
+					particles_vec.data(),
+					particles_vec.size(),
+					glm::vec3(0.5f),
+					size,
+					density * sphere_volume(size),
+					velocity,
+					(mode == 0)
+			);
+			break;
+		default:
+			break;
+	}
+	
+	particles.fill(particles_vec);
+	return particles.getHandle();
+}
+
+int main(int argc, const char **argv) {
+	const std::string applicationName = "MPM";
+	
+	uint32_t windowWidth = 800;
+	uint32_t windowHeight = 600;
+	
+	vkcv::Features features;
+	features.requireExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
+	
+	vkcv::Core core = vkcv::Core::create(
+			applicationName,
+			VK_MAKE_VERSION(0, 0, 1),
+			{vk::QueueFlagBits::eTransfer, vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute},
+			features
+	);
+	vkcv::WindowHandle windowHandle = core.createWindow(applicationName, windowWidth, windowHeight, true);
+	vkcv::Window& window = core.getWindow(windowHandle);
+	vkcv::camera::CameraManager cameraManager(window);
+	
+	vkcv::gui::GUI gui (core, windowHandle);
+	
+	auto trackballHandle = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+	cameraManager.getCamera(trackballHandle).setCenter(glm::vec3(0.5f, 0.5f, 0.5f));   // set camera to look at the center of the particle volume
+	cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+	
+	auto swapchainExtent = core.getSwapchainExtent(window.getSwapchain());
+	
+	vkcv::ImageHandle depthBuffer = core.createImage(
+			vk::Format::eD32Sfloat,
+			vkcv::ImageConfig(
+					swapchainExtent.width,
+					swapchainExtent.height
+			)
+	);
+	
+	vkcv::Image grid = vkcv::image(
+			core,
+			vk::Format::eR16G16B16A16Sfloat,
+			32,
+			32,
+			32,
+			false,
+			true
+	);
+	
+	vkcv::SamplerHandle gridSampler = core.createSampler(
+			vkcv::SamplerFilterType::LINEAR,
+			vkcv::SamplerFilterType::LINEAR,
+			vkcv::SamplerMipmapMode::NEAREST,
+			vkcv::SamplerAddressMode::CLAMP_TO_BORDER,
+			0.0f,
+			vkcv::SamplerBorderColor::FLOAT_ZERO_TRANSPARENT
+	);
+	
+	vkcv::Buffer<Simulation> simulation = vkcv::buffer<Simulation>(
+			core, vkcv::BufferType::UNIFORM, 1, vkcv::BufferMemoryType::HOST_VISIBLE
+	);
+	
+	Simulation* sim = simulation.map();
+	
+	glm::vec3 initialVelocity (0.0f, 0.1f, 0.0f);
+	
+	sim->density = 2500.0f;
+	sim->size = 0.1f;
+	sim->lame1 = 10.0f;
+	sim->lame2 = 20.0f;
+	sim->form = SIM_FORM_SPHERE;
+	sim->type = SIM_TYPE_HYPERELASTIC;
+	sim->K = 2.2f;
+	sim->E = 35.0f;
+	sim->gamma = 1.330f;
+	sim->mode = SIM_MODE_RANDOM;
+	sim->gravity = 9.81f;
+	sim->count = 1024;
+	
+	vkcv::BufferHandle particlesHandle = resetParticles(
+			core,
+			sim->count,
+			initialVelocity,
+			sim->density,
+			sim->size,
+			sim->form,
+			sim->mode
+	);
+	
+	vkcv::shader::GLSLCompiler compiler;
+	
+	std::vector<vkcv::DescriptorSetHandle> initParticleWeightsSets;
+	vkcv::ComputePipelineHandle initParticleWeightsPipeline = createComputePipeline(
+			core, compiler,
+			"shaders/init_particle_weights.comp",
+			initParticleWeightsSets
+	);
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeStorageBuffer(0, particlesHandle);
+		core.writeDescriptorSet(initParticleWeightsSets[0], writes);
+	}
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeSampledImage(0, grid.getHandle());
+		writes.writeSampler(1, gridSampler);
+		core.writeDescriptorSet(initParticleWeightsSets[1], writes);
+	}
+	
+	std::vector<vkcv::DescriptorSetHandle> transformParticlesToGridSets;
+	vkcv::ComputePipelineHandle transformParticlesToGridPipeline = createComputePipeline(
+			core, compiler,
+			"shaders/transform_particles_to_grid.comp",
+			transformParticlesToGridSets
+	);
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeStorageBuffer(0, particlesHandle);
+		core.writeDescriptorSet(transformParticlesToGridSets[0], writes);
+	}
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeUniformBuffer(0, simulation.getHandle());
+		core.writeDescriptorSet(transformParticlesToGridSets[1], writes);
+	}
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeStorageImage(0, grid.getHandle());
+		core.writeDescriptorSet(transformParticlesToGridSets[2], writes);
+	}
+	
+	std::vector<vkcv::DescriptorSetHandle> updateParticleVelocitiesSets;
+	vkcv::ComputePipelineHandle updateParticleVelocitiesPipeline = createComputePipeline(
+			core, compiler,
+			"shaders/update_particle_velocities.comp",
+			updateParticleVelocitiesSets
+	);
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeStorageBuffer(0, particlesHandle);
+		core.writeDescriptorSet(updateParticleVelocitiesSets[0], writes);
+	}
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeUniformBuffer(0, simulation.getHandle());
+		core.writeDescriptorSet(updateParticleVelocitiesSets[1], writes);
+	}
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeSampledImage(0, grid.getHandle());
+		writes.writeSampler(1, gridSampler);
+		core.writeDescriptorSet(updateParticleVelocitiesSets[2], writes);
+	}
+	
+	vkcv::ShaderProgram gfxProgramGrid;
+	
+	compiler.compileProgram(gfxProgramGrid, {
+			{ vkcv::ShaderStage::VERTEX, "shaders/grid.vert" },
+			{ vkcv::ShaderStage::FRAGMENT, "shaders/grid.frag" }
+	}, nullptr);
+	
+	vkcv::ShaderProgram gfxProgramParticles;
+	
+	compiler.compileProgram(gfxProgramParticles, {
+		{ vkcv::ShaderStage::VERTEX, "shaders/particle.vert" },
+		{ vkcv::ShaderStage::FRAGMENT, "shaders/particle.frag" }
+	}, nullptr);
+	
+	vkcv::ShaderProgram gfxProgramLines;
+	
+	compiler.compileProgram(gfxProgramLines, {
+			{ vkcv::ShaderStage::VERTEX, "shaders/lines.vert" },
+			{ vkcv::ShaderStage::FRAGMENT, "shaders/lines.frag" }
+	}, nullptr);
+	
+	vkcv::DescriptorSetLayoutHandle gfxSetLayoutGrid = core.createDescriptorSetLayout(
+			gfxProgramGrid.getReflectedDescriptors().at(0)
+	);
+	
+	vkcv::DescriptorSetHandle gfxSetGrid = core.createDescriptorSet(gfxSetLayoutGrid);
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeSampledImage(0, grid.getHandle());
+		writes.writeSampler(1, gridSampler);
+		writes.writeUniformBuffer(2, simulation.getHandle());
+		core.writeDescriptorSet(gfxSetGrid, writes);
+	}
+	
+	vkcv::DescriptorSetLayoutHandle gfxSetLayoutParticles = core.createDescriptorSetLayout(
+			gfxProgramParticles.getReflectedDescriptors().at(0)
+	);
+	
+	vkcv::DescriptorSetHandle gfxSetParticles = core.createDescriptorSet(gfxSetLayoutParticles);
+	
+	{
+		vkcv::DescriptorWrites writes;
+		writes.writeStorageBuffer(0, particlesHandle);
+		core.writeDescriptorSet(gfxSetParticles, writes);
+	}
+	
+	vkcv::PassHandle gfxPassGrid = vkcv::passSwapchain(
+			core,
+			window.getSwapchain(),
+			{ vk::Format::eUndefined, vk::Format::eD32Sfloat }
+	);
+	
+	vkcv::PassHandle gfxPassParticles = vkcv::passSwapchain(
+			core,
+			window.getSwapchain(),
+			{ vk::Format::eUndefined, vk::Format::eD32Sfloat }
+	);
+	
+	vkcv::PassHandle gfxPassLines = vkcv::passSwapchain(
+			core,
+			window.getSwapchain(),
+			{ vk::Format::eUndefined, vk::Format::eD32Sfloat },
+			false
+	);
+	
+	vkcv::VertexLayout vertexLayoutGrid = vkcv::createVertexLayout({
+		vkcv::createVertexBinding(0, gfxProgramGrid.getVertexAttachments())
+	});
+	
+	vkcv::GraphicsPipelineConfig gfxPipelineConfigGrid (
+			gfxProgramGrid,
+			gfxPassGrid,
+			vertexLayoutGrid,
+			{ gfxSetLayoutGrid }
+	);
+	
+	vkcv::VertexLayout vertexLayoutParticles = vkcv::createVertexLayout({
+		vkcv::createVertexBinding(0, gfxProgramParticles.getVertexAttachments())
+	});
+	
+	vkcv::GraphicsPipelineConfig gfxPipelineConfigParticles (
+			gfxProgramParticles,
+			gfxPassParticles,
+			vertexLayoutParticles,
+			{ gfxSetLayoutParticles }
+	);
+	
+	vkcv::VertexLayout vertexLayoutLines = vkcv::createVertexLayout({
+		vkcv::createVertexBinding(0, gfxProgramLines.getVertexAttachments())
+	});
+	
+	vkcv::GraphicsPipelineConfig gfxPipelineConfigLines (
+			gfxProgramLines,
+			gfxPassLines,
+			vertexLayoutLines,
+			{}
+	);
+	
+	gfxPipelineConfigLines.setPrimitiveTopology(vkcv::PrimitiveTopology::LineList);
+	
+	vkcv::GraphicsPipelineHandle gfxPipelineGrid = core.createGraphicsPipeline(gfxPipelineConfigGrid);
+	vkcv::GraphicsPipelineHandle gfxPipelineParticles = core.createGraphicsPipeline(gfxPipelineConfigParticles);
+	vkcv::GraphicsPipelineHandle gfxPipelineLines = core.createGraphicsPipeline(gfxPipelineConfigLines);
+	
+	vkcv::Buffer<glm::vec2> trianglePositions = vkcv::buffer<glm::vec2>(core, vkcv::BufferType::VERTEX, 3);
+	trianglePositions.fill({
+		glm::vec2(-1.0f, -1.0f),
+		glm::vec2(+0.0f, +1.5f),
+		glm::vec2(+1.0f, -1.0f)
+	});
+	
+	vkcv::VertexData triangleData ({ vkcv::vertexBufferBinding(trianglePositions.getHandle()) });
+	triangleData.setCount(trianglePositions.getCount());
+	
+	vkcv::Buffer<glm::vec3> linesPositions = vkcv::buffer<glm::vec3>(core, vkcv::BufferType::VERTEX, 8);
+	linesPositions.fill({
+		glm::vec3(0.0f, 0.0f, 0.0f),
+		glm::vec3(1.0f, 0.0f, 0.0f),
+		glm::vec3(0.0f, 1.0f, 0.0f),
+		glm::vec3(1.0f, 1.0f, 0.0f),
+		glm::vec3(0.0f, 0.0f, 1.0f),
+		glm::vec3(1.0f, 0.0f, +1.0f),
+		glm::vec3(0.0f, 1.0f, 1.0f),
+		glm::vec3(1.0f, 1.0f, 1.0f)
+	});
+	
+	vkcv::Buffer<uint16_t> linesIndices = vkcv::buffer<uint16_t>(core, vkcv::BufferType::INDEX, 24);
+	linesIndices.fill({
+		0, 1,
+		1, 3,
+		3, 2,
+		2, 0,
+		
+		4, 5,
+		5, 7,
+		7, 6,
+		6, 4,
+		
+		0, 4,
+		1, 5,
+		2, 6,
+		3, 7
+	});
+	
+	vkcv::VertexData linesData ({ vkcv::vertexBufferBinding(linesPositions.getHandle()) });
+	linesData.setIndexBuffer(linesIndices.getHandle());
+	linesData.setCount(linesIndices.getCount());
+	
+	vkcv::InstanceDrawcall drawcallGrid (
+			triangleData,
+			grid.getWidth() * grid.getHeight() * grid.getDepth()
+	);
+	
+	drawcallGrid.useDescriptorSet(0, gfxSetGrid);
+	
+	vkcv::InstanceDrawcall drawcallParticle (triangleData, sim->count);
+	drawcallParticle.useDescriptorSet(0, gfxSetParticles);
+	
+	vkcv::InstanceDrawcall drawcallLines (linesData);
+	
+	bool renderGrid = true;
+	float speed_factor = 1.0f;
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
+		if ((swapchainWidth != swapchainExtent.width) || ((swapchainHeight != swapchainExtent.height))) {
+			depthBuffer = core.createImage(
+					vk::Format::eD32Sfloat,
+					vkcv::ImageConfig(
+							swapchainWidth,
+							swapchainHeight
+					)
+			);
+			
+			swapchainExtent.width = swapchainWidth;
+			swapchainExtent.height = swapchainHeight;
+		}
+		
+		Physics physics;
+		physics.t = static_cast<float>(t);
+		physics.dt = static_cast<float>(dt);
+		physics.speedfactor = speed_factor;
+		
+		vkcv::PushConstants physicsPushConstants = vkcv::pushConstants<Physics>();
+		physicsPushConstants.appendDrawcall(physics);
+		
+		cameraManager.update(physics.dt);
+		
+		glm::mat4 mvp = cameraManager.getActiveCamera().getMVP();
+		vkcv::PushConstants cameraPushConstants = vkcv::pushConstants<glm::mat4>();
+		cameraPushConstants.appendDrawcall(mvp);
+		
+		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+		
+		const auto dispatchSizeGrid = vkcv::dispatchInvocations(
+				vkcv::DispatchSize(grid.getWidth(), grid.getHeight(), grid.getDepth()),
+				vkcv::DispatchSize(4, 4, 4)
+		);
+		
+		const auto dispatchSizeParticles = vkcv::dispatchInvocations(sim->count, 64);
+		
+		for (int step = 0; step < 1; step++) {
+			core.recordBeginDebugLabel(cmdStream, "INIT PARTICLE WEIGHTS", {0.78f, 0.89f, 0.94f, 1.0f});
+			core.recordBufferMemoryBarrier(cmdStream, particlesHandle);
+			core.prepareImageForSampling(cmdStream, grid.getHandle());
+			
+			core.recordComputeDispatchToCmdStream(
+					cmdStream,
+					initParticleWeightsPipeline,
+					dispatchSizeParticles,
+					{
+						vkcv::useDescriptorSet(
+								0, initParticleWeightsSets[0]
+						),
+						vkcv::useDescriptorSet(
+								1, initParticleWeightsSets[1]
+						)
+					},
+					vkcv::PushConstants(0)
+			);
+			
+			core.recordBufferMemoryBarrier(cmdStream, particlesHandle);
+			core.recordEndDebugLabel(cmdStream);
+			
+			core.recordBeginDebugLabel(cmdStream, "TRANSFORM PARTICLES TO GRID", {0.47f, 0.77f, 0.85f, 1.0f});
+			core.recordBufferMemoryBarrier(cmdStream, particlesHandle);
+			core.prepareImageForStorage(cmdStream, grid.getHandle());
+			
+			core.recordComputeDispatchToCmdStream(
+					cmdStream,
+					transformParticlesToGridPipeline,
+					dispatchSizeGrid,
+					{
+						vkcv::useDescriptorSet(
+								0, transformParticlesToGridSets[0]
+						),
+						vkcv::useDescriptorSet(
+								1, transformParticlesToGridSets[1]
+						),
+						vkcv::useDescriptorSet(
+								2, transformParticlesToGridSets[2]
+						)
+					},
+					physicsPushConstants
+			);
+			
+			core.recordImageMemoryBarrier(cmdStream, grid.getHandle());
+			core.recordEndDebugLabel(cmdStream);
+			
+			core.recordBeginDebugLabel(cmdStream, "UPDATE PARTICLE VELOCITIES", {0.78f, 0.89f, 0.94f, 1.0f});
+			core.recordBufferMemoryBarrier(cmdStream, particlesHandle);
+			core.recordBufferMemoryBarrier(cmdStream, simulation.getHandle());
+			core.prepareImageForSampling(cmdStream, grid.getHandle());
+			
+			core.recordComputeDispatchToCmdStream(
+					cmdStream,
+					updateParticleVelocitiesPipeline,
+					dispatchSizeParticles,
+					{
+						vkcv::useDescriptorSet(
+								0, updateParticleVelocitiesSets[0]
+						),
+						vkcv::useDescriptorSet(
+								1, updateParticleVelocitiesSets[1]
+						),
+						vkcv::useDescriptorSet(
+								2, updateParticleVelocitiesSets[2]
+						)
+					},
+					physicsPushConstants
+			);
+			
+			core.recordBufferMemoryBarrier(cmdStream, particlesHandle);
+			core.recordEndDebugLabel(cmdStream);
+		}
+		
+		std::vector<vkcv::ImageHandle> renderTargets {
+				vkcv::ImageHandle::createSwapchainImageHandle(),
+				depthBuffer
+		};
+		
+		if (renderGrid) {
+			core.recordBeginDebugLabel(cmdStream, "RENDER GRID", { 0.13f, 0.20f, 0.22f, 1.0f });
+			core.recordBufferMemoryBarrier(cmdStream, simulation.getHandle());
+			core.prepareImageForSampling(cmdStream, grid.getHandle());
+			
+			core.recordDrawcallsToCmdStream(
+					cmdStream,
+					gfxPipelineGrid,
+					cameraPushConstants,
+					{ drawcallGrid },
+					renderTargets,
+					windowHandle
+			);
+			
+			core.recordEndDebugLabel(cmdStream);
+		} else {
+			core.recordBeginDebugLabel(cmdStream, "RENDER PARTICLES", { 0.13f, 0.20f, 0.22f, 1.0f });
+			core.recordBufferMemoryBarrier(cmdStream, particlesHandle);
+			
+			core.recordDrawcallsToCmdStream(
+					cmdStream,
+					gfxPipelineParticles,
+					cameraPushConstants,
+					{ drawcallParticle },
+					renderTargets,
+					windowHandle
+			);
+			
+			core.recordEndDebugLabel(cmdStream);
+		}
+		
+		core.recordBeginDebugLabel(cmdStream, "RENDER LINES", { 0.13f, 0.20f, 0.22f, 1.0f });
+		
+		core.recordDrawcallsToCmdStream(
+				cmdStream,
+				gfxPipelineLines,
+				cameraPushConstants,
+				{ drawcallLines },
+				renderTargets,
+				windowHandle
+		);
+		
+		core.recordEndDebugLabel(cmdStream);
+		
+		core.prepareSwapchainImageForPresent(cmdStream);
+		core.submitCommandStream(cmdStream);
+		
+		gui.beginGUI();
+		ImGui::Begin("Settings");
+		
+		ImGui::BeginGroup();
+		ImGui::Combo("Mode", &(sim->mode), "Random\0Ordered\0", 2);
+		ImGui::Combo("Form", &(sim->form), "Sphere\0Cube\0", 2);
+		ImGui::Combo("Type", &(sim->type), "Hyperelastic\0Fluid\0", 2);
+		ImGui::EndGroup();
+		
+		ImGui::Spacing();
+		
+		ImGui::SliderInt("Particle Count", &(sim->count), 1, 100000);
+		ImGui::SliderFloat("Density", &(sim->density), std::numeric_limits<float>::epsilon(), 5000.0f);
+		ImGui::SameLine(0.0f, 10.0f);
+		if (ImGui::SmallButton("Reset##density")) {
+			sim->density = 2500.0f;
+		}
+		
+		ImGui::SliderFloat("Radius", &(sim->size), 0.0f, 0.5f);
+		ImGui::SameLine(0.0f, 10.0f);
+		if (ImGui::SmallButton("Reset##radius")) {
+			sim->size = 0.1f;
+		}
+		
+		ImGui::Spacing();
+		
+		ImGui::BeginGroup();
+		ImGui::SliderFloat("Bulk Modulus", &(sim->K), 0.0f, 1000.0f);
+		ImGui::SliderFloat("Young's Modulus", &(sim->E), 0.0f, 1000.0f);
+		ImGui::SliderFloat("Heat Capacity Ratio", &(sim->gamma), 1.0f, 2.0f);
+		ImGui::SliderFloat("Lame1", &(sim->lame1), 0.0f, 1000.0f);
+		ImGui::SliderFloat("Lame2", &(sim->lame2), 0.0f, 1000.0f);
+		ImGui::EndGroup();
+
+		ImGui::Spacing();
+
+		ImGui::SliderFloat("Simulation Speed", &speed_factor, 0.0f, 2.0f);
+		
+		ImGui::Spacing();
+		ImGui::Checkbox("Render Grid", &renderGrid);
+		
+		ImGui::DragFloat3("Initial Velocity", reinterpret_cast<float*>(&initialVelocity), 0.001f);
+		ImGui::SameLine(0.0f, 10.0f);
+		if (ImGui::Button("Reset##particle_velocity")) {
+			particlesHandle = resetParticles(
+					core,
+					sim->count,
+					initialVelocity,
+					sim->density,
+					sim->size,
+					sim->form,
+					sim->mode
+			);
+			
+			vkcv::DescriptorWrites writes;
+			writes.writeStorageBuffer(0, particlesHandle);
+			
+			core.writeDescriptorSet(initParticleWeightsSets[0], writes);
+			core.writeDescriptorSet(transformParticlesToGridSets[0], writes);
+			core.writeDescriptorSet(updateParticleVelocitiesSets[0], writes);
+			
+			core.writeDescriptorSet(gfxSetParticles, writes);
+		}
+		
+		ImGui::SliderFloat("Gravity", &(sim->gravity), 0.0f, 10.0f);
+		
+		ImGui::End();
+		gui.endGUI();
+	});
+	
+	simulation.unmap();
+	return 0;
+}
diff --git a/projects/particle_simulation/.gitignore b/projects/particle_simulation/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..4964f89e973f38358aa57f564f56d3d4b0c328a9
--- /dev/null
+++ b/projects/particle_simulation/.gitignore
@@ -0,0 +1 @@
+particle_simulation
\ No newline at end of file
diff --git a/projects/particle_simulation/CMakeLists.txt b/projects/particle_simulation/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..860bfa15a4b77b71ac79e3433326feb6aa3d7123
--- /dev/null
+++ b/projects/particle_simulation/CMakeLists.txt
@@ -0,0 +1,31 @@
+cmake_minimum_required(VERSION 3.16)
+project(particle_simulation)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+# adding source files to the project
+add_project(particle_simulation
+		src/main.cpp
+		src/ParticleSystem.hpp 
+		src/ParticleSystem.cpp
+		src/Particle.hpp 
+		src/Particle.cpp)
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(particle_simulation SYSTEM BEFORE PRIVATE
+		${vkcv_include}
+		${vkcv_includes}
+		${vkcv_testing_include}
+		${vkcv_camera_include}
+		${vkcv_shader_compiler_include}
+		${vkcv_effects_include})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(particle_simulation
+		vkcv
+		vkcv_testing
+		vkcv_camera
+		vkcv_shader_compiler
+		vkcv_effects)
diff --git a/projects/particle_simulation/README.md b/projects/particle_simulation/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..74163ca06e0f61fe57e5ffbfe57877c2d203724b
--- /dev/null
+++ b/projects/particle_simulation/README.md
@@ -0,0 +1,20 @@
+# Particle simulation
+An example project to show thousands of particles can be simulated with the VkCV framework
+
+![Screenshot space](../../screenshots/particle_simulation_space.png)
+![Screenshot water](../../screenshots/particle_simulation_water.png)
+![Screenshot gravity](../../screenshots/particle_simulation_gravity.png)
+
+## Details
+
+Similar to the projects to show rendering a single triangle or a simple mesh was possible. This
+project shows that many particles can be simulated using compute shaders and rendered using the
+usual graphics pipeline, all with the VkCV framework.
+
+The behavior of the particles can easily be adjusted loading a different compute shader. That is
+the reason the application can be started with three different pipelines using one of those 
+parameters:
+
+ - `--space` to simulate particles flying around in a wild way through space
+ - `--water` to simulate particles dropping down like a waterfall to the ground
+ - `--gravity` to simulate particles being drawn by points of gravity in space flying around those points
diff --git a/projects/particle_simulation/shaders/particleShading.inc b/projects/particle_simulation/shaders/particleShading.inc
new file mode 100644
index 0000000000000000000000000000000000000000..b2d1832b9ccd6ba05a585b59bdfdedd4729e80f8
--- /dev/null
+++ b/projects/particle_simulation/shaders/particleShading.inc
@@ -0,0 +1,6 @@
+float circleFactor(vec2 triangleCoordinates){
+    // percentage of distance from center to circle edge
+    float p = clamp((0.4 - length(triangleCoordinates)) / 0.4, 0, 1);
+    // remapping for nice falloff
+    return sqrt(p);
+}
\ No newline at end of file
diff --git a/projects/particle_simulation/shaders/shader.vert b/projects/particle_simulation/shaders/shader.vert
new file mode 100644
index 0000000000000000000000000000000000000000..0a889b35dbb750dc932de57b611f22acaa1ac3f2
--- /dev/null
+++ b/projects/particle_simulation/shaders/shader.vert
@@ -0,0 +1,45 @@
+#version 460 core
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 particle;
+
+struct Particle
+{
+    vec3 position;
+    float lifeTime;
+    vec3 velocity;
+    float padding_2;
+    vec3 reset_velocity;
+    float padding_3;
+};
+
+layout(std430, binding = 2) coherent buffer buffer_inParticle
+{
+    Particle inParticle[];
+};
+
+layout( push_constant ) uniform constants{
+    mat4 view;
+    mat4 projection;
+};
+
+layout(location = 0) out vec2 passTriangleCoordinates;
+layout(location = 1) out vec3 passVelocity;
+layout(location = 2) out float passlifeTime;
+
+void main()
+{
+    int id = gl_InstanceIndex;
+    passVelocity = inParticle[id].velocity;
+    passlifeTime = inParticle[id].lifeTime;
+    // particle position in view space
+    vec4 positionView = view * vec4(inParticle[id].position, 1);
+    // by adding the triangle position in view space the mesh is always camera facing
+    positionView.xyz += particle;
+    // multiply with projection matrix for final position
+	gl_Position = projection * positionView;
+    
+    // 0.01 corresponds to vertex position size in main
+    float normalizationDivider  = 0.012;
+    passTriangleCoordinates     = particle.xy / normalizationDivider;
+}
\ No newline at end of file
diff --git a/projects/particle_simulation/shaders/shader_gravity.comp b/projects/particle_simulation/shaders/shader_gravity.comp
new file mode 100644
index 0000000000000000000000000000000000000000..77954958c694a3c6c620818dd3b5d999e51b4a42
--- /dev/null
+++ b/projects/particle_simulation/shaders/shader_gravity.comp
@@ -0,0 +1,80 @@
+#version 450 core
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(local_size_x = 256) in;
+
+struct Particle
+{
+    vec3 position;
+    float lifeTime;
+    vec3 velocity;
+    float mass;
+    vec3 reset_velocity;
+    float _padding;
+};
+
+layout(std430, binding = 0) coherent buffer buffer_inParticle
+{
+    Particle inParticle[];
+};
+
+layout( push_constant ) uniform constants{
+    float deltaTime;
+    float rand;
+};
+
+const int n = 4;
+vec4 gravityPoint[n] = vec4[n](
+    vec4(-0.8, -0.5,  0.0, 3),
+    vec4(-0.4,  0.5,  0.8, 2),
+    vec4( 0.8,  0.8, -0.3, 4),
+    vec4( 0.5, -0.7, -0.5, 1)
+);
+
+const float G = 6.6743015e-11;
+const float sim_d_factor = 10e11;
+const float sim_g_factor = 10e30;
+const float sim_t_factor = 5;
+const float c = 299792458;
+
+void main() {
+    uint id = gl_GlobalInvocationID.x;
+    inParticle[id].lifeTime -= deltaTime;
+    vec3 pos = inParticle[id].position;
+    vec3 vel = inParticle[id].velocity;
+    float mass = inParticle[id].mass;
+
+    if(inParticle[id].lifeTime < 0.f)
+    {
+        inParticle[id].lifeTime = 5.f * rand;
+        inParticle[id].mass *= rand;
+
+        pos = vec3(0);
+        vel *= rand;
+    }
+
+    for(int i = 0; i < n; i++)
+    {
+        vec3 d = (gravityPoint[i].xyz - pos) * sim_d_factor;
+        float r = length(d);
+        float g = G * (gravityPoint[i].w * sim_g_factor) / (r * r);
+
+        if (r > 0) {
+            vec3 dvel = (deltaTime * sim_t_factor) * g * (d / r);
+
+            vel = (vel + dvel) / (1.0 + dot(vel, dvel) / (c*c));
+        }
+    }
+
+    pos += vel * (deltaTime * sim_t_factor);
+
+    vec3 a_pos = abs(pos);
+
+    if ((a_pos.x > 2.0) || (a_pos.y > 2.0) || (a_pos.z > 2.0))
+    {
+        inParticle[id].lifeTime *= 0.9;
+    }
+
+    inParticle[id].position = pos;
+    inParticle[id].velocity = vel;
+}
diff --git a/projects/particle_simulation/shaders/shader_space.comp b/projects/particle_simulation/shaders/shader_space.comp
new file mode 100644
index 0000000000000000000000000000000000000000..6e25fff8aec8ceab7c1ffdd9be65d9b8fa8f0974
--- /dev/null
+++ b/projects/particle_simulation/shaders/shader_space.comp
@@ -0,0 +1,73 @@
+#version 450 core
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(local_size_x = 256) in;
+
+struct Particle
+{
+    vec3 position;
+    float lifeTime;
+    vec3 velocity;
+    float padding_2;
+    vec3 reset_velocity;
+    float padding_3;
+};
+
+layout(std430, binding = 0) coherent buffer buffer_inParticle
+{
+    Particle inParticle[];
+};
+
+layout( push_constant ) uniform constants{
+    float deltaTime;
+    float rand;
+};
+
+vec3 attraction(vec3 pos, vec3 attractPos)
+{
+    vec3 delta = attractPos - pos;
+    const float damp = 0.5;
+    float dDampedDot = dot(delta, delta) + damp;
+    float invDist = 1.0f / sqrt(dDampedDot);
+    float invDistCubed = invDist*invDist*invDist;
+    return delta * invDistCubed * 0.0035;
+}
+
+vec3 repulsion(vec3 pos, vec3 attractPos)
+{
+    vec3 delta = attractPos - pos;
+    float targetDistance = sqrt(dot(delta, delta));
+    return delta * (1.0 / (targetDistance * targetDistance * targetDistance)) * -0.000035;
+}
+
+
+const int n = 4;
+vec3 gravity = vec3(0,-9.8,0);
+vec3 gravityPoint[n] = vec3[n](vec3(-0.3, .5, -0.6),vec3(-0.2, 0.6, -0.3),vec3(.4, -0.4, 0.6),vec3(-.4, -0.4, -0.6));
+//vec3 gravityPoint[n] = vec3[n](vec3(-0.5, 0.5, 0));
+void main() {
+    uint id = gl_GlobalInvocationID.x;
+    inParticle[id].lifeTime -= deltaTime;
+    vec3 pos = inParticle[id].position;
+    vec3 vel = inParticle[id].velocity;
+    if(inParticle[id].lifeTime < 0.f)
+    {
+        inParticle[id].lifeTime = 5.f;
+        pos = vec3(0);
+    }
+    //    inParticle[id].position += deltaTime * -normalize(max(2 - distance(inParticle[id].position,respawnPos),0.0) * respawnPos - inParticle[id].position);
+
+    for(int i = 0; i < n; i++)
+    {
+        vel += deltaTime * deltaTime * normalize(max(2 - distance(pos,gravityPoint[i]),0.1) * gravityPoint[i] - pos);
+    }
+
+    if((pos.x <= -2.0) || (pos.x > 2.0) || (pos.y <= -2.0) || (pos.y > 2.0)|| (pos.z <= -2.0) || (pos.z > 2.0)){
+        vel = (-vel * 0.1);
+    }
+
+    pos += normalize(vel) * deltaTime;
+    inParticle[id].position = pos;
+    float rand1 = rand;
+    inParticle[id].velocity = vel;
+}
diff --git a/projects/particle_simulation/shaders/shader_space.frag b/projects/particle_simulation/shaders/shader_space.frag
new file mode 100644
index 0000000000000000000000000000000000000000..7f6d22065caa3c4b3ab2b1f697c9545a66d7bd54
--- /dev/null
+++ b/projects/particle_simulation/shaders/shader_space.frag
@@ -0,0 +1,46 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+#include "particleShading.inc"
+
+layout(location = 0) in vec2 passTriangleCoordinates;
+layout(location = 1) in vec3 passVelocity;
+layout(location = 2) in float passlifeTime;
+
+layout(location = 0) out vec3 outColor;
+
+layout(set=0, binding=0) uniform uColor {
+	vec4 color;
+} Color;
+
+layout(set=0,binding=1) uniform uPosition{
+	vec2 position;
+} Position;
+
+
+void main()
+{
+	vec2 mouse = vec2(Position.position.x, Position.position.y);
+    
+    vec3 c0 = vec3(1, 1, 0.05);
+    vec3 c1 = vec3(1, passlifeTime * 0.5, 0.05);
+    vec3 c2 = vec3(passlifeTime * 0.5,passlifeTime * 0.5,0.05);
+    vec3 c3 = vec3(1, 0.05, 0.05);
+    
+    if(passlifeTime  < 1){
+        outColor = mix(c0, c1, passlifeTime );
+    }
+    else if(passlifeTime  < 2){
+        outColor = mix(c1, c2, passlifeTime  - 1);
+    }
+    else{
+        outColor = mix(c2, c3, clamp((passlifeTime  - 2) * 0.5, 0, 1));
+    }
+   
+   // make the triangle look like a circle
+   outColor *= circleFactor(passTriangleCoordinates);
+   
+   // fade out particle shortly before it dies
+   outColor *= clamp(passlifeTime * 2, 0, 1);
+}
\ No newline at end of file
diff --git a/projects/particle_simulation/shaders/shader_water.comp b/projects/particle_simulation/shaders/shader_water.comp
new file mode 100644
index 0000000000000000000000000000000000000000..d1a0e761038b5fb367a33454c746871f1a6a4553
--- /dev/null
+++ b/projects/particle_simulation/shaders/shader_water.comp
@@ -0,0 +1,84 @@
+#version 450 core
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(local_size_x = 256) in;
+
+struct Particle
+{
+    vec3 position;
+    float lifeTime;
+    vec3 velocity;
+    float padding_2;
+    vec3 reset_velocity;
+    float padding_3;
+};
+
+layout(std430, binding = 0) coherent buffer buffer_inParticle
+{
+    Particle inParticle[];
+};
+
+layout( push_constant ) uniform constants{
+    float deltaTime;
+    float rand;
+};
+
+vec3 attraction(vec3 pos, vec3 attractPos)
+{
+    vec3 delta = attractPos - pos;
+    const float damp = 0.5;
+    float dDampedDot = dot(delta, delta) + damp;
+    float invDist = 1.0f / sqrt(dDampedDot);
+    float invDistCubed = invDist*invDist*invDist;
+    return delta * invDistCubed * 0.0035;
+}
+
+vec3 repulsion(vec3 pos, vec3 attractPos)
+{
+    vec3 delta = attractPos - pos;
+    float targetDistance = sqrt(dot(delta, delta));
+    return delta * (1.0 / (targetDistance * targetDistance * targetDistance)) * -0.000035;
+}
+
+
+const int n = 3;
+vec3 gravity = vec3(0,-9.8,0);
+vec3 gravityPoint[n] = vec3[n](vec3(-0.5, 0.5, 0),vec3(0.5, 0.5, 0),vec3(0, -0.5, 0));
+//vec3 gravityPoint[n] = vec3[n](vec3(-0.5, 0.5, 0));
+void main() {
+    uint id = gl_GlobalInvocationID.x;
+    inParticle[id].lifeTime -= deltaTime;
+    vec3 pos = inParticle[id].position;
+    vec3 vel = inParticle[id].velocity;
+    if(inParticle[id].lifeTime < 0.f)
+    {
+        inParticle[id].lifeTime = 7.f;
+        pos = vec3(0);
+        vel = inParticle[id].reset_velocity;
+        inParticle[id].velocity = inParticle[id].reset_velocity;
+    }
+    //    inParticle[id].position += deltaTime * -normalize(max(2 - distance(inParticle[id].position,respawnPos),0.0) * respawnPos - inParticle[id].position);
+
+    for(int i = 0; i < n; i++)
+    {
+        vel += deltaTime * deltaTime * deltaTime * normalize(max(2 - distance(pos,gravityPoint[i]),0.1) * gravityPoint[i] - pos);
+    }
+
+    //vec3 delta = respawnPos - pos;
+    //float targetDistane = sqrt(dot(delta,delta));
+    //vel += repulsion(pos, respawnPos);
+
+    //if((pos.x <= -1.0) || (pos.x > 1.0) || (pos.y <= -1.0) || (pos.y > 1.0)|| (pos.z <= -1.0) || (pos.z > 1.0))
+    vel = (-vel * 0.01);
+
+    if((pos.y <= -1.0) || (pos.y > 1.0)){
+        vel = reflect(vel, vec3(0,1,0));
+    }
+
+    pos += normalize(vel) * deltaTime;
+    inParticle[id].position = pos;
+
+    float weight = 1.0;
+    float rand1 = rand;
+    inParticle[id].velocity = vel;
+}
diff --git a/projects/particle_simulation/shaders/shader_water.frag b/projects/particle_simulation/shaders/shader_water.frag
new file mode 100644
index 0000000000000000000000000000000000000000..b68f9572a91b05e836c3fead9ae9afd7ce16ba8e
--- /dev/null
+++ b/projects/particle_simulation/shaders/shader_water.frag
@@ -0,0 +1,46 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+#include "particleShading.inc"
+
+layout(location = 0) in vec2 passTriangleCoordinates;
+layout(location = 1) in vec3 passVelocity;
+layout(location = 2) in float passlifeTime;
+
+layout(location = 0) out vec3 outColor;
+
+layout(set=0, binding=0) uniform uColor {
+	vec4 color;
+} Color;
+
+layout(set=0,binding=1) uniform uPosition{
+	vec2 position;
+} Position;
+
+void main()
+{
+	float normlt = 1-normalize(passlifeTime);
+	vec2 mouse = vec2(Position.position.x, Position.position.y);
+    
+    vec3 c0 = vec3(0.2,0.5,1);
+    vec3 c1 = vec3(0.3, 0.7,1);
+    vec3 c2 = vec3(0.5,0.9,1);
+    vec3 c3 = vec3(0.9,1,1);
+    
+    if(passlifeTime  < 1){
+        outColor = mix(c0, c1, passlifeTime );
+    }
+    else if(passlifeTime  < 2){
+        outColor = mix(c1, c2, passlifeTime  - 1);
+    }
+    else{
+        outColor = mix(c2, c3, clamp((passlifeTime  - 2) * 0.5, 0, 1));
+    }
+    
+    // make the triangle look like a circle
+   outColor *= circleFactor(passTriangleCoordinates);
+   
+   // fade out particle shortly before it dies
+   outColor *= clamp(passlifeTime * 2, 0, 1);
+}
diff --git a/projects/voxelization/resources/shaders/tonemapping.comp b/projects/particle_simulation/shaders/tonemapping.comp
similarity index 64%
rename from projects/voxelization/resources/shaders/tonemapping.comp
rename to projects/particle_simulation/shaders/tonemapping.comp
index 2383302fa946e7d92871039daff28232df2eafdd..26f0232d66e3475afdd1266c0cc6288b47ed1c38 100644
--- a/projects/voxelization/resources/shaders/tonemapping.comp
+++ b/projects/particle_simulation/shaders/tonemapping.comp
@@ -1,7 +1,7 @@
 #version 440
 
-layout(set=0, binding=0, r11f_g11f_b10f)    uniform image2D inImage;
-layout(set=0, binding=1, rgba8)             uniform image2D outImage;
+layout(set=0, binding=0, rgba16f)   uniform image2D inImage;
+layout(set=0, binding=1, rgba8)     uniform image2D outImage;
 
 
 layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
@@ -13,7 +13,7 @@ void main(){
     }
     ivec2 uv            = ivec2(gl_GlobalInvocationID.xy);
     vec3 linearColor    = imageLoad(inImage, uv).rgb;
-    vec3 tonemapped     = linearColor / (linearColor + 1); // reinhard tonemapping
+    vec3 tonemapped     = linearColor / (dot(linearColor, vec3(0.21, 0.71, 0.08)) + 1); // reinhard tonemapping
     vec3 gammaCorrected = pow(tonemapped, vec3(1.f / 2.2f));
     imageStore(outImage, uv, vec4(gammaCorrected, 0.f));
 }
\ No newline at end of file
diff --git a/projects/particle_simulation/src/Particle.cpp b/projects/particle_simulation/src/Particle.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b80d063d382c9ae1cb63887388cce065b8289b63
--- /dev/null
+++ b/projects/particle_simulation/src/Particle.cpp
@@ -0,0 +1,42 @@
+
+#include "Particle.hpp"
+
+Particle::Particle(glm::vec3 position, glm::vec3 velocity, float lifeTime)
+: m_position(position),
+  m_lifeTime(lifeTime),
+  m_velocity(velocity),
+  m_mass(1.0f),
+  m_reset_velocity(velocity)
+{}
+
+const glm::vec3& Particle::getPosition()const{
+    return m_position;
+}
+
+bool Particle::isAlive()const{
+    return m_lifeTime > 0.f;
+}
+
+void Particle::setPosition( const glm::vec3 pos ){
+    m_position = pos;
+}
+
+const glm::vec3& Particle::getVelocity()const{
+    return m_velocity;
+}
+
+void Particle::setVelocity( const glm::vec3 vel ){
+    m_velocity = vel;
+}
+
+void Particle::update( const float delta ){
+    m_position += m_velocity * delta;
+}
+
+void Particle::setLifeTime( const float lifeTime ){
+    m_lifeTime = lifeTime;
+}
+
+const float& Particle::getLifeTime()const{
+    return m_lifeTime;
+}
\ No newline at end of file
diff --git a/projects/particle_simulation/src/Particle.hpp b/projects/particle_simulation/src/Particle.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..73e7cbf517709ee03274cfd199081ade3f756545
--- /dev/null
+++ b/projects/particle_simulation/src/Particle.hpp
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <glm/glm.hpp>
+
+class Particle {
+
+public:
+    Particle(glm::vec3 position, glm::vec3 velocity, float lifeTime = 1.f);
+
+    const glm::vec3& getPosition()const;
+
+    void setPosition( const glm::vec3 pos );
+
+    const glm::vec3& getVelocity()const;
+
+    void setVelocity( const glm::vec3 vel );
+
+    void update( const float delta );
+
+    bool isAlive()const;
+
+    void setLifeTime( const float lifeTime );
+
+    const float& getLifeTime()const;
+
+private:
+    // all properties of the Particle
+    glm::vec3 m_position;
+    float m_lifeTime;
+    glm::vec3 m_velocity;
+    float m_mass;
+    glm::vec3 m_reset_velocity;
+    float padding_3;
+};
diff --git a/projects/particle_simulation/src/ParticleSystem.cpp b/projects/particle_simulation/src/ParticleSystem.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b3162d6bea685640d3949577271affc8b2080407
--- /dev/null
+++ b/projects/particle_simulation/src/ParticleSystem.cpp
@@ -0,0 +1,60 @@
+#include "ParticleSystem.hpp"
+
+ParticleSystem::ParticleSystem(uint32_t particleCount ,glm::vec3 minVelocity , glm::vec3 maxVelocity , glm::vec2 lifeTime )
+{
+    m_rdmVel.resize(3);
+    m_rdmVel[0] = std::uniform_real_distribution<float>(minVelocity.x, maxVelocity.x);
+    m_rdmVel[1] = std::uniform_real_distribution<float>(minVelocity.y, maxVelocity.y);
+    m_rdmVel[2] = std::uniform_real_distribution<float>(minVelocity.z, maxVelocity.z);
+    m_rdmLifeTime = std::uniform_real_distribution<float>(lifeTime.x, lifeTime.y);
+
+    for(uint32_t i = 0; i < particleCount ;i++ ){
+        addParticle(Particle(m_respawnPos, getRandomVelocity(), getRandomLifeTime()));
+    }
+}
+
+const std::vector<Particle>& ParticleSystem::getParticles() const{
+    return m_particles;
+}
+
+void ParticleSystem::addParticle( const Particle particle ){
+    m_particles.push_back(particle);
+}
+void ParticleSystem::addParticles( const std::vector<Particle> particles ){
+    m_particles.insert(m_particles.end(), particles.begin(), particles.end());
+}
+
+void ParticleSystem::updateParticles( const float deltaTime ){
+    for(Particle& particle :m_particles){
+        bool alive = particle.isAlive();
+        particle.setPosition( particle.getPosition() * static_cast<float>(alive) + static_cast<float>(!alive) * m_respawnPos );
+        particle.setVelocity( particle.getVelocity() * static_cast<float>(alive) + static_cast<float>(!alive) *  getRandomVelocity());
+        particle.setLifeTime( (particle.getLifeTime() * alive + !alive * getRandomLifeTime() ) - deltaTime );
+        particle.update(deltaTime);
+    }
+}
+
+glm::vec3 ParticleSystem::getRandomVelocity(){
+    return glm::vec3(m_rdmVel[0](m_rdmEngine), m_rdmVel[1](m_rdmEngine),m_rdmVel[2](m_rdmEngine));
+}
+
+float ParticleSystem::getRandomLifeTime(){
+    return m_rdmLifeTime(m_rdmEngine);
+}
+
+void ParticleSystem::setRespawnPos( const glm::vec3 respawnPos){
+    m_respawnPos = respawnPos;
+}
+void ParticleSystem::setRdmLifeTime( const glm::vec2 lifeTime ){
+    m_rdmLifeTime = std::uniform_real_distribution<float> (lifeTime.x,lifeTime.y);
+}
+
+void ParticleSystem::setRdmVelocity( glm::vec3 minVelocity, glm::vec3 maxVelocity ){
+    m_rdmVel[0] = std::uniform_real_distribution<float> (minVelocity.x,maxVelocity.x);
+    m_rdmVel[1] = std::uniform_real_distribution<float> (minVelocity.y,maxVelocity.y);
+    m_rdmVel[2] = std::uniform_real_distribution<float> (minVelocity.z,maxVelocity.z);
+}
+
+const glm::vec3 ParticleSystem::getRespawnPos() const{
+    return m_respawnPos;
+}
diff --git a/projects/particle_simulation/src/ParticleSystem.hpp b/projects/particle_simulation/src/ParticleSystem.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..fe5c99f9b407b9dbdfd414e265e7cd91bbe790b9
--- /dev/null
+++ b/projects/particle_simulation/src/ParticleSystem.hpp
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <vector>
+#include "Particle.hpp"
+#include <random>
+#include "vkcv/Buffer.hpp"
+
+class ParticleSystem {
+
+public:
+    ParticleSystem(uint32_t particleCount , glm::vec3 minVelocity = glm::vec3(0.f,0.f,0.f), glm::vec3 maxVelocity = glm::vec3(1.f,1.f,0.f), glm::vec2 lifeTime = glm::vec2(2.f,3.f));
+    const std::vector<Particle> &getParticles() const;
+    void updateParticles( const float deltaTime );
+    void setRespawnPos( const glm::vec3 respawnPos );
+    void setRdmLifeTime( const glm::vec2 lifeTime );
+    void setRdmVelocity( glm::vec3 minVelocity, glm::vec3 maxVelocity );
+    const glm::vec3 getRespawnPos() const;
+
+private:
+
+    void addParticle( const Particle particle );
+    void addParticles( const std::vector<Particle> particles );
+    glm::vec3 getRandomVelocity();
+    float getRandomLifeTime();
+
+    std::vector<Particle> m_particles;
+    glm::vec3 m_respawnPos = glm::vec3(0.f);
+
+    std::vector<std::uniform_real_distribution<float>> m_rdmVel;
+    std::uniform_real_distribution<float> m_rdmLifeTime;
+    std::default_random_engine m_rdmEngine;
+};
\ No newline at end of file
diff --git a/projects/particle_simulation/src/main.cpp b/projects/particle_simulation/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..72249ee3e993b4f903220f496975ff7ae7328af4
--- /dev/null
+++ b/projects/particle_simulation/src/main.cpp
@@ -0,0 +1,329 @@
+#include <iostream>
+#include <vkcv/Buffer.hpp>
+#include <vkcv/Core.hpp>
+#include <vkcv/Pass.hpp>
+#include <GLFW/glfw3.h>
+#include <vkcv/camera/CameraManager.hpp>
+#include <chrono>
+#include "ParticleSystem.hpp"
+#include <random>
+#include <glm/gtc/matrix_access.hpp>
+#include <ctime>
+#include <vkcv/shader/GLSLCompiler.hpp>
+#include <vkcv/effects/BloomAndFlaresEffect.hpp>
+
+int main(int argc, const char **argv) {
+    const std::string applicationName = "Particlesystem";
+
+    uint32_t windowWidth = 800;
+    uint32_t windowHeight = 600;
+	
+    vkcv::Core core = vkcv::Core::create(
+            applicationName,
+            VK_MAKE_VERSION(0, 0, 1),
+            {vk::QueueFlagBits::eTransfer, vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute},
+			{ VK_KHR_SWAPCHAIN_EXTENSION_NAME }
+    );
+	vkcv::WindowHandle windowHandle = core.createWindow(applicationName, windowWidth, windowHeight, true);
+    vkcv::Window& window = core.getWindow(windowHandle);
+	vkcv::camera::CameraManager cameraManager(window);
+
+    auto particleIndexBuffer = vkcv::buffer<uint16_t>(core, vkcv::BufferType::INDEX, 3,
+													  vkcv::BufferMemoryType::DEVICE_LOCAL);
+    uint16_t indices[3] = {0, 1, 2};
+    particleIndexBuffer.fill(&indices[0], sizeof(indices));
+
+    vk::Format colorFormat = vk::Format::eR16G16B16A16Sfloat;
+    vkcv::PassHandle particlePass = vkcv::passFormat(core, colorFormat);
+
+    if (!particlePass)
+    {
+        std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
+        return EXIT_FAILURE;
+    }
+
+    // use space or use water or gravity
+    std::string shaderPathCompute = "shaders/shader_space.comp";
+	std::string shaderPathFragment = "shaders/shader_space.frag";
+    
+    for (int i = 1; i < argc; i++) {
+    	if (strcmp(argv[i], "--space") == 0) {
+    		shaderPathCompute = "shaders/shader_space.comp";
+			shaderPathFragment = "shaders/shader_space.frag";
+    	} else
+		if (strcmp(argv[i], "--water") == 0) {
+			shaderPathCompute = "shaders/shader_water.comp";
+			shaderPathFragment = "shaders/shader_water.frag";
+		} else
+		if (strcmp(argv[i], "--gravity") == 0) {
+			shaderPathCompute = "shaders/shader_gravity.comp";
+			shaderPathFragment = "shaders/shader_space.frag";
+		}
+    }
+
+    vkcv::shader::GLSLCompiler compiler;
+    vkcv::ShaderProgram computeShaderProgram{};
+    compiler.compile(vkcv::ShaderStage::COMPUTE, shaderPathCompute, [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+        computeShaderProgram.addShader(shaderStage, path);
+    });
+
+    vkcv::DescriptorSetLayoutHandle computeDescriptorSetLayout = core.createDescriptorSetLayout(computeShaderProgram.getReflectedDescriptors().at(0));
+    vkcv::DescriptorSetHandle computeDescriptorSet = core.createDescriptorSet(computeDescriptorSetLayout);
+
+    const std::vector<vkcv::VertexAttachment> computeVertexAttachments = computeShaderProgram.getVertexAttachments();
+
+    std::vector<vkcv::VertexBinding> computeBindings;
+    for (size_t i = 0; i < computeVertexAttachments.size(); i++) {
+        computeBindings.push_back(vkcv::createVertexBinding(i, { computeVertexAttachments[i] }));
+    }
+    const vkcv::VertexLayout computeLayout { computeBindings };
+
+    vkcv::ShaderProgram particleShaderProgram{};
+    compiler.compile(vkcv::ShaderStage::VERTEX, "shaders/shader.vert", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+        particleShaderProgram.addShader(shaderStage, path);
+    });
+    compiler.compile(vkcv::ShaderStage::FRAGMENT, shaderPathFragment, [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+        particleShaderProgram.addShader(shaderStage, path);
+    });
+
+    vkcv::DescriptorSetLayoutHandle descriptorSetLayout = core.createDescriptorSetLayout(
+            particleShaderProgram.getReflectedDescriptors().at(0));
+    vkcv::DescriptorSetHandle descriptorSet = core.createDescriptorSet(descriptorSetLayout);
+
+    vkcv::Buffer<glm::vec3> vertexBuffer = vkcv::buffer<glm::vec3>(
+			core,
+            vkcv::BufferType::VERTEX,
+            3
+    );
+    const std::vector<vkcv::VertexAttachment> vertexAttachments = particleShaderProgram.getVertexAttachments();
+
+    const std::vector<vkcv::VertexBufferBinding> vertexBufferBindings = {
+            vkcv::vertexBufferBinding(vertexBuffer.getHandle())
+	};
+
+    std::vector<vkcv::VertexBinding> bindings;
+    for (size_t i = 0; i < vertexAttachments.size(); i++) {
+        bindings.push_back(vkcv::createVertexBinding(i, {vertexAttachments[i]}));
+    }
+
+    const vkcv::VertexLayout particleLayout { bindings };
+
+    vkcv::GraphicsPipelineConfig particlePipelineDefinition (
+            particleShaderProgram,
+            particlePass,
+            {particleLayout},
+            {descriptorSetLayout}
+	);
+	
+    particlePipelineDefinition.setBlendMode(vkcv::BlendMode::Additive);
+
+    const std::vector<glm::vec3> vertices = {glm::vec3(-0.012, 0.012, 0),
+                                             glm::vec3(0.012, 0.012, 0),
+                                             glm::vec3(0, -0.012, 0)};
+
+    vertexBuffer.fill(vertices);
+
+    vkcv::GraphicsPipelineHandle particlePipeline = core.createGraphicsPipeline(particlePipelineDefinition);
+
+    vkcv::ComputePipelineHandle computePipeline = core.createComputePipeline({
+		computeShaderProgram, {computeDescriptorSetLayout}
+	});
+
+    vkcv::Buffer<glm::vec4> color = vkcv::buffer<glm::vec4>(
+			core,
+            vkcv::BufferType::UNIFORM,
+            1
+    );
+
+    vkcv::Buffer<glm::vec2> position = vkcv::buffer<glm::vec2>(
+			core,
+            vkcv::BufferType::UNIFORM,
+            1
+    );
+
+    glm::vec3 minVelocity = glm::vec3(-0.1f,-0.1f,-0.1f);
+    glm::vec3 maxVelocity = glm::vec3(0.1f,0.1f,0.1f);
+    glm::vec2 lifeTime = glm::vec2(-1.f,8.f);
+    ParticleSystem particleSystem = ParticleSystem( 100000 , minVelocity, maxVelocity, lifeTime);
+
+    vkcv::Buffer<Particle> particleBuffer = vkcv::buffer<Particle>(
+			core,
+            vkcv::BufferType::STORAGE,
+            particleSystem.getParticles().size()
+    );
+
+    particleBuffer.fill(particleSystem.getParticles());
+
+    vkcv::DescriptorWrites setWrites;
+    setWrites.writeUniformBuffer(0, color.getHandle()).writeUniformBuffer(1, position.getHandle());
+    setWrites.writeStorageBuffer(2, particleBuffer.getHandle());
+    core.writeDescriptorSet(descriptorSet, setWrites);
+
+    vkcv::DescriptorWrites computeWrites;
+    computeWrites.writeStorageBuffer(0, particleBuffer.getHandle());
+    core.writeDescriptorSet(computeDescriptorSet, computeWrites);
+
+    if (!particlePipeline || !computePipeline)
+    {
+        std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
+        return EXIT_FAILURE;
+    }
+
+    const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+
+	vkcv::VertexData vertexData (vertexBufferBindings);
+	vertexData.setIndexBuffer(particleIndexBuffer.getHandle());
+	vertexData.setCount(particleIndexBuffer.getCount());
+	
+    auto descriptorUsage = vkcv::useDescriptorSet(0, descriptorSet);
+
+    auto pos = glm::vec2(0.f);
+    auto spawnPosition = glm::vec3(0.f);
+
+	window.e_mouseMove.add([&](double offsetX, double offsetY) {
+        pos = glm::vec2(static_cast<float>(offsetX), static_cast<float>(offsetY));
+        pos.x = (-2 * pos.x + static_cast<float>(window.getWidth())) / static_cast<float>(window.getWidth());
+        pos.y = (-2 * pos.y + static_cast<float>(window.getHeight())) / static_cast<float>(window.getHeight());
+        spawnPosition = glm::vec3(pos.x, pos.y, 0.f);
+        particleSystem.setRespawnPos(glm::vec3(-spawnPosition.x, spawnPosition.y, spawnPosition.z));
+    });
+
+    std::vector<glm::mat4> modelMatrices;
+	
+	vkcv::InstanceDrawcall drawcall (vertexData, particleSystem.getParticles().size());
+	drawcall.useDescriptorSet(0, descriptorSet);
+	
+    glm::vec4 colorData = glm::vec4(1.0f, 1.0f, 0.0f, 1.0f);
+    auto camHandle0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+    auto camHandle1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+
+    cameraManager.getCamera(camHandle0).setNearFar(0.1f, 30.0f);
+    cameraManager.getCamera(camHandle1).setNearFar(0.1f, 30.0f);
+
+    cameraManager.setActiveCamera(camHandle1);
+
+    cameraManager.getCamera(camHandle0).setPosition(glm::vec3(0.0f, 0.0f, -2.0f));
+    cameraManager.getCamera(camHandle1).setPosition(glm::vec3(0.0f, 0.0f, -2.0f));
+
+	const auto swapchainExtent = core.getSwapchainExtent(window.getSwapchain());
+	
+	vkcv::ImageConfig colorBufferConfig (
+			swapchainExtent.width,
+			swapchainExtent.height
+	);
+	
+	colorBufferConfig.setSupportingStorage(true);
+	colorBufferConfig.setSupportingColorAttachment(true);
+	
+    vkcv::ImageHandle colorBuffer = core.createImage(
+			colorFormat,
+			colorBufferConfig
+	);
+	
+	vkcv::effects::BloomAndFlaresEffect bloomAndFlares (core);
+	bloomAndFlares.setUpsamplingLimit(3);
+
+    vkcv::ShaderProgram tonemappingShader;
+    compiler.compile(vkcv::ShaderStage::COMPUTE, "shaders/tonemapping.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+        tonemappingShader.addShader(shaderStage, path);
+    });
+
+    vkcv::DescriptorSetLayoutHandle tonemappingDescriptorLayout = core.createDescriptorSetLayout(tonemappingShader.getReflectedDescriptors().at(0));
+    vkcv::DescriptorSetHandle tonemappingDescriptor = core.createDescriptorSet(tonemappingDescriptorLayout);
+    vkcv::ComputePipelineHandle tonemappingPipe = core.createComputePipeline({
+        tonemappingShader, 
+        { tonemappingDescriptorLayout }
+	});
+
+    std::uniform_real_distribution<float> rdm = std::uniform_real_distribution<float>(0.95f, 1.05f);
+    std::default_random_engine rdmEngine;
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
+		if ((core.getImageWidth(colorBuffer) != swapchainWidth) ||
+			(core.getImageHeight(colorBuffer) != swapchainHeight)) {
+			colorBufferConfig.setWidth(swapchainWidth);
+			colorBufferConfig.setHeight(swapchainHeight);
+			
+			colorBuffer = core.createImage(
+					colorFormat,
+					colorBufferConfig
+			);
+		}
+
+        color.fill(&colorData);
+        position.fill(&pos);
+
+        cameraManager.update(dt);
+
+        // split view and projection to allow for easy billboarding in shader
+        struct {
+			glm::mat4 view;
+			glm::mat4 projection;
+        } renderingMatrices;
+        
+        renderingMatrices.view = cameraManager.getActiveCamera().getView();
+        renderingMatrices.projection = cameraManager.getActiveCamera().getProjection();
+
+        auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+        float random = rdm(rdmEngine);
+        glm::vec2 pushData = glm::vec2(dt, random);
+
+        vkcv::PushConstants pushConstantsCompute = vkcv::pushConstants<glm::vec2>();
+        pushConstantsCompute.appendDrawcall(pushData);
+        
+        core.recordComputeDispatchToCmdStream(
+				cmdStream,
+				computePipeline,
+				vkcv::dispatchInvocations(particleSystem.getParticles().size(), 256),
+				{vkcv::useDescriptorSet(0, computeDescriptorSet)},
+				pushConstantsCompute
+		);
+
+        core.recordBufferMemoryBarrier(cmdStream, particleBuffer.getHandle());
+
+        vkcv::PushConstants pushConstantsDraw (sizeof(renderingMatrices));
+        pushConstantsDraw.appendDrawcall(renderingMatrices);
+        
+        core.recordDrawcallsToCmdStream(
+                cmdStream,
+                particlePipeline,
+				pushConstantsDraw,
+                { drawcall },
+                { colorBuffer },
+                windowHandle
+		);
+	
+		bloomAndFlares.recordEffect(cmdStream, colorBuffer, colorBuffer);
+
+        core.prepareImageForStorage(cmdStream, colorBuffer);
+        core.prepareImageForStorage(cmdStream, swapchainInput);
+
+        vkcv::DescriptorWrites tonemappingDescriptorWrites;
+        tonemappingDescriptorWrites.writeStorageImage(
+				0, colorBuffer
+		).writeStorageImage(
+				1, swapchainInput
+		);
+		
+        core.writeDescriptorSet(tonemappingDescriptor, tonemappingDescriptorWrites);
+
+        const auto tonemappingDispatchCount = vkcv::dispatchInvocations(
+				vkcv::DispatchSize(swapchainWidth, swapchainHeight),
+				vkcv::DispatchSize(8, 8)
+		);
+
+        core.recordComputeDispatchToCmdStream(
+            cmdStream, 
+            tonemappingPipe, 
+            tonemappingDispatchCount, 
+            { vkcv::useDescriptorSet(0, tonemappingDescriptor) },
+            vkcv::PushConstants(0)
+		);
+
+        core.prepareSwapchainImageForPresent(cmdStream);
+        core.submitCommandStream(cmdStream);
+    });
+
+    return 0;
+}
diff --git a/projects/path_tracer/.gitignore b/projects/path_tracer/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..24a57cda822232aa24d513fab3901ff7db36adb1
--- /dev/null
+++ b/projects/path_tracer/.gitignore
@@ -0,0 +1 @@
+path_tracer
\ No newline at end of file
diff --git a/projects/path_tracer/CMakeLists.txt b/projects/path_tracer/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..56f14090ce68b91d0d7163360faaf413fb9e3de9
--- /dev/null
+++ b/projects/path_tracer/CMakeLists.txt
@@ -0,0 +1,15 @@
+cmake_minimum_required(VERSION 3.16)
+project(path_tracer)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+# adding source files to the project
+add_project(path_tracer src/main.cpp)
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(path_tracer SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_testing_include} ${vkcv_asset_loader_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include} ${vkcv_gui_include})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(path_tracer vkcv vkcv_testing vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera vkcv_shader_compiler vkcv_gui)
diff --git a/projects/path_tracer/README.md b/projects/path_tracer/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..0ff2970b686f7b8c8f24d6bb044566dd270b50b9
--- /dev/null
+++ b/projects/path_tracer/README.md
@@ -0,0 +1,10 @@
+# Path tracer
+An example project to show the implementation of a path tracer with the VkCV framework
+
+![Screenshot](../../screenshots/path_tracer.png)
+
+## Details
+
+The project shows off an example implementation of a path tracer using compute shaders to render 
+a physically plausible image given an artificial subset of materials. The path tracer uses a 
+converging technique to reduce the noise of the image iteratively.
diff --git a/projects/path_tracer/shaders/clearImage.comp b/projects/path_tracer/shaders/clearImage.comp
new file mode 100644
index 0000000000000000000000000000000000000000..97998e945112d166be7d00df98ee44ea8322a633
--- /dev/null
+++ b/projects/path_tracer/shaders/clearImage.comp
@@ -0,0 +1,17 @@
+#version 440
+#extension GL_GOOGLE_include_directive : enable
+
+layout(set=0, binding=0, rgba32f) 	uniform image2D outImage;
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+void main(){
+
+    ivec2 outImageRes = imageSize(outImage);
+    ivec2 coord       = ivec2(gl_GlobalInvocationID.xy);
+
+    if(any(greaterThanEqual(coord, outImageRes)))
+        return;
+
+    imageStore(outImage, coord, vec4(0));
+}
\ No newline at end of file
diff --git a/projects/path_tracer/shaders/combineImages.comp b/projects/path_tracer/shaders/combineImages.comp
new file mode 100644
index 0000000000000000000000000000000000000000..d1a4e85caf175dfc3125afd847d7458ddec2fef1
--- /dev/null
+++ b/projects/path_tracer/shaders/combineImages.comp
@@ -0,0 +1,21 @@
+#version 440
+#extension GL_GOOGLE_include_directive : enable
+
+layout(set=0, binding=0, rgba32f) uniform image2D newImage;
+layout(set=0, binding=1, rgba32f) uniform image2D meanImage;
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+void main(){
+
+    ivec2 outImageRes = imageSize(meanImage);
+    ivec2 coord       = ivec2(gl_GlobalInvocationID.xy);
+
+    if(any(greaterThanEqual(coord, outImageRes)))
+        return;
+   
+    vec4 colorNew 	= imageLoad(newImage,  coord);
+	vec4 colorMean 	= imageLoad(meanImage, coord);
+
+    imageStore(meanImage, coord, colorNew + colorMean);
+}
\ No newline at end of file
diff --git a/projects/path_tracer/shaders/path_tracer.comp b/projects/path_tracer/shaders/path_tracer.comp
new file mode 100644
index 0000000000000000000000000000000000000000..f08bdfd123ede964befe5feed4ba9f438dc0a498
--- /dev/null
+++ b/projects/path_tracer/shaders/path_tracer.comp
@@ -0,0 +1,430 @@
+#version 450 core
+#extension GL_ARB_separate_shader_objects : enable
+
+const float pi      	= 3.1415926535897932384626433832795;
+const float hitBias 	= 0.0001;   // used to offset hits to avoid self intersection
+const float denomMin 	= 0.001;
+
+layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;
+
+struct Material {
+    vec3 	emission;
+	float 	ks;		// specular percentage
+    vec3 	albedo;
+    float 	r;		// roughness
+	vec3 	f0;
+	float 	padding;
+};
+
+struct Sphere{
+    vec3 	center;
+    float 	radius;
+    int 	materialIndex;
+	float 	padding[3];
+};
+
+struct Plane{
+	vec3 	center;
+	int 	materialIndex;
+	vec3 	N;
+	float 	padding1;
+	vec2 	extent;
+	vec2 	padding2;
+};
+
+layout(std430, binding = 0) buffer spheres{
+    Sphere inSpheres[];
+};
+
+layout(std430, binding = 1) buffer planes{
+    Plane inPlanes[];
+};
+
+layout(std430, binding = 2) buffer materials{
+    Material inMaterials[];
+};
+
+layout(set=0, binding = 3, rgba32f) uniform image2D outImage;
+
+layout( push_constant ) uniform constants{
+    mat4 	viewToWorld;
+	vec3 	skyColor;
+    int 	sphereCount;
+	int 	planeCount;
+	int 	frameIndex;
+};
+
+// ---- Intersection functions ----
+
+struct Ray{
+	vec3 origin;
+	vec3 direction;
+};
+
+struct Intersection{
+    bool 		hit;
+	float 		distance;
+    vec3 		pos;
+    vec3 		N;
+    Material 	material;
+};
+
+// https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-sphere-intersection
+Intersection raySphereIntersect(Ray ray, Sphere sphere){
+
+	Intersection intersection;
+	intersection.hit = false;
+	
+    vec3 	L 	= sphere.center - ray.origin;
+    float 	tca = dot(L, ray.direction);
+    float 	d2 	= dot(L, L) - tca * tca;
+	
+    if (d2 > sphere.radius * sphere.radius){
+        return intersection;
+    }
+    float thc 	= float(sqrt(sphere.radius * sphere.radius - d2));
+    float t0 	= tca - thc;
+    float t1 	= tca + thc;
+	
+    if (t0 < 0)
+        t0 = t1;
+    
+    if (t0 < 0)
+        return intersection;
+	
+	intersection.hit 		= true;
+	intersection.distance 	= t0;
+	intersection.pos        = ray.origin + ray.direction * intersection.distance;
+	intersection.N          = normalize(intersection.pos - sphere.center);
+	intersection.material 	= inMaterials[sphere.materialIndex];
+	
+    return intersection;
+}
+
+struct Basis{
+	vec3 right;
+	vec3 up;
+	vec3 forward;
+};
+
+Basis buildBasisAroundNormal(vec3 N){
+	Basis 	basis;
+	basis.up 		= N;
+	basis.right 	= abs(basis.up.x) < 0.99 ?  vec3(1, 0, 0) : vec3(0, 0, 1);
+	basis.forward 	= normalize(cross(basis.up, basis.right));
+	basis.right 	= cross(basis.up, basis.forward);
+	return basis;
+}
+
+// see: https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-plane-and-ray-disk-intersection
+Intersection rayPlaneIntersect(Ray ray, Plane plane){
+
+	Intersection intersection;
+	intersection.hit = false;
+
+	vec3 	toPlane = plane.center - ray.origin;
+	float 	denom 	= dot(ray.direction, plane.N);
+	if(abs(denom) < 0.001)
+		return intersection;
+		
+	intersection.distance = dot(toPlane, plane.N) / denom;
+	
+	if(intersection.distance < 0)
+		return intersection;
+	
+	intersection.pos 				= ray.origin + ray.direction * intersection.distance;
+	
+	vec3 	centerToIntersection	= intersection.pos - plane.center;
+	Basis 	planeBasis 				= buildBasisAroundNormal(plane.N);
+	float 	projectedRight			= dot(centerToIntersection, planeBasis.right);
+	float 	projectedUp				= dot(centerToIntersection, planeBasis.forward);
+
+	intersection.hit 		= abs(projectedRight) <= plane.extent.x && abs(projectedUp) <= plane.extent.y;
+	intersection.N 			= plane.N;
+	intersection.material 	= inMaterials[plane.materialIndex];
+	
+	return intersection;
+}
+
+Intersection sceneIntersect(Ray ray) {
+    float minDistance = 100000;  // lets start with something big
+    
+    Intersection intersection;
+    intersection.hit = false;
+    
+    for (int i = 0; i < sphereCount; i++) {
+		Intersection sphereIntersection = raySphereIntersect(ray, inSpheres[i]);
+        if (sphereIntersection.hit && sphereIntersection.distance < minDistance) {            
+            intersection 	= sphereIntersection;
+			minDistance 	= intersection.distance;
+        }
+    }
+	for (int i = 0; i < planeCount; i++){
+		Intersection planeIntersection = rayPlaneIntersect(ray, inPlanes[i]);
+        if (planeIntersection.hit && planeIntersection.distance < minDistance) {
+            intersection 	= planeIntersection;
+			minDistance 	= intersection.distance;
+        }
+	}
+    return intersection;
+}
+
+vec3 biasHitPosition(vec3 hitPos, vec3 rayDirection, vec3 N){
+    // return hitPos + N * hitBias; // works as long as no refraction/transmission is used and camera is outside sphere
+    return hitPos + sign(dot(rayDirection, N)) * N * hitBias;
+}
+
+// ---- noise/hash functions for pseudorandom variables ----
+
+// extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences
+vec2 r2Sequence(uint n){
+	n = n % 42000;
+	const float g = 1.32471795724474602596;
+	return fract(vec2(
+		n /  g,
+		n / (g*g)));
+}
+
+// random() and helpers from: https://www.shadertoy.com/view/XlycWh
+float g_seed = 0;
+
+uint base_hash(uvec2 p) {
+    p = 1103515245U*((p >> 1U)^(p.yx));
+    uint h32 = 1103515245U*((p.x)^(p.y>>3U));
+    return h32^(h32 >> 16);
+}
+
+vec2 hash2(inout float seed) {
+    uint n = base_hash(floatBitsToUint(vec2(seed+=.1,seed+=.1)));
+    uvec2 rz = uvec2(n, n*48271U);
+    return vec2(rz.xy & uvec2(0x7fffffffU))/float(0x7fffffff);
+}
+
+void initRandom(ivec2 coord){
+	g_seed = float(base_hash(coord)/float(0xffffffffU)+frameIndex);
+}
+
+vec2 random(){
+	return hash2(g_seed);
+}
+
+// ---- shading ----
+
+vec3 lambertBRDF(vec3 albedo){
+	return albedo / pi;
+}
+
+vec3 computeDiffuseBRDF(Material material){
+    return lambertBRDF(material.albedo);
+}
+
+float distributionGGX(float r, float NoH){
+	float r2 	= r*r;
+	float denom = pi * pow(NoH*NoH * (r2-1) + 1, 2);
+	return r2 / max(denom, denomMin);
+}
+
+float geometryGGXSmith(float r, float NoL){
+	float r2 	= r*r;
+	float denom = NoL + sqrt(r2 + (1-r2) * NoL*NoL);
+	return 2 * NoL / max(denom, denomMin);
+}
+
+float geometryGGX(float r, float NoV, float NoL){
+	return geometryGGXSmith(r, NoV) * geometryGGXSmith(r, NoL);
+}
+
+vec3 fresnelSchlick(vec3 f0, float NoH){
+	return f0 + (1 - f0) * pow(1 - NoH, 5);
+}
+
+vec3 computeSpecularBRDF(vec3 f0, float r, float NoV, float NoL, float NoH){
+	float 	denom 	= 4 * NoV * NoL;
+	float 	D 		= distributionGGX(r, NoH);
+	float 	G 		= geometryGGX(r, NoV, NoL);
+	vec3	F 		= fresnelSchlick(f0, NoH);
+	return D * F * G / max(denom, denomMin);
+}
+
+// ---- pathtracing and main ----
+
+// distributions: https://link.springer.com/content/pdf/10.1007/978-1-4842-4427-2_16.pdf
+float cosineDistributionPDF(float NoL){
+	return NoL / pi;
+}
+
+vec3 sampleCosineDistribution(vec2 xi){
+	float phi = 2 * pi * xi.y;
+	return vec3(
+		sqrt(xi.x) * cos(phi),
+		sqrt(1 - xi.x),
+		sqrt(xi.x) * sin(phi));
+}
+
+float uniformDistributionPDF(){
+	return 1.f / (2 * pi);
+}
+
+vec3 sampleUniformDistribution(vec2 xi){
+	float phi = 2 * pi * xi.y;
+	return vec3(
+		sqrt(xi.x) * cos(phi),
+		1 - xi.x,
+		sqrt(xi.x) * sin(phi));
+}
+
+float ggxDistributionPDF(float r, float NoH){
+	return distributionGGX(r, NoH) * NoH;
+}
+
+float ggxDistributionPDFReflected(float r, float NoH, float NoV){
+	float jacobian = 0.25 / max(NoV, denomMin);
+	return ggxDistributionPDF(r, NoH) * jacobian;
+}
+
+vec3 sampleGGXDistribution(vec2 xi, float r){
+	float phi 		= 2 * pi * xi.y;
+	float cosTheta 	= sqrt((1 - xi.x) / ((r*r - 1) * xi.x + 1));
+	float sinTheta	= sqrt(1 - cosTheta*cosTheta);
+	return vec3(
+		cos(phi) * sinTheta,
+		cosTheta,
+		sin(phi) * sinTheta);
+}
+
+vec3 sampleTangentToWorldSpace(vec3 tangentSpaceSample, vec3 N){
+	Basis tangentBasis = buildBasisAroundNormal(N);
+	return
+		tangentBasis.right		* tangentSpaceSample.x +
+		tangentBasis.up			* tangentSpaceSample.y +
+		tangentBasis.forward 	* tangentSpaceSample.z;
+}
+
+vec3 castRay(Ray ray) {
+    
+    vec3   	throughput 	= vec3(1);
+    vec3	color   	= vec3(0);
+    
+	const int maxDepth = 10;
+    for(int i = 0; i < maxDepth; i++){
+
+        Intersection intersection = sceneIntersect(ray);
+        
+        vec3 hitLighting 	= vec3(0);
+        vec3 brdf 			= vec3(1);
+		
+		// V is where the ray came from and will lead back to the camera (over multiple bounces)
+		vec3 	V 				= -normalize(ray.direction);
+		vec3 	R 				= reflect(-V, intersection.N);
+		float 	NoV				= max(dot(intersection.N, V), 0);
+		
+		intersection.material.r *= intersection.material.r;	// remapping for perceuptual linearity
+		intersection.material.r = max(intersection.material.r, 0.01);
+		
+		float 	kd 				= 1 - intersection.material.ks;
+		bool 	sampleDiffuse 	= random().x < kd;
+		
+		vec3 	sampleTangentSpace;
+		float 	pdf;
+		if(sampleDiffuse){
+			sampleTangentSpace 	= sampleCosineDistribution(random());
+			ray.direction   	= sampleTangentToWorldSpace(sampleTangentSpace, intersection.N);
+			
+			float NoL 			= max(dot(intersection.N, ray.direction), 0);
+			pdf 				= cosineDistributionPDF(NoL);
+		}
+		else{			
+			#define IMPORTANCE
+			
+			#ifdef IMPORTANCE
+			sampleTangentSpace 	= sampleGGXDistribution(random(), intersection.material.r);
+			ray.direction   	= sampleTangentToWorldSpace(sampleTangentSpace, R);		
+			vec3 	L 			= normalize(ray.direction);
+			pdf 				= ggxDistributionPDFReflected(intersection.material.r, max(sampleTangentSpace.y, 0.01), max(dot(intersection.N, V), 0.01));
+			#else
+			sampleTangentSpace 	= sampleUniformDistribution(random());
+			ray.direction   	= sampleTangentToWorldSpace(sampleTangentSpace, intersection.N);		
+			pdf 				= uniformDistributionPDF();
+			#endif
+		}
+        
+        ray.origin				= biasHitPosition(intersection.pos, ray.direction, intersection.N);
+		
+		// L is where the ray is going, as that is the direction where light will from
+		vec3 	L 			= normalize(ray.direction);
+		vec3 	H			= normalize(L + V);
+		
+        float 	NoL 		= max(dot(intersection.N, L), 0);
+		float	NoH			= max(dot(intersection.N, H), 0);
+		
+        if(intersection.hit){
+            vec3 	diffuseBRDF 	= computeDiffuseBRDF(intersection.material);
+
+			vec3 	specularBRDF 	= computeSpecularBRDF(intersection.material.f0, intersection.material.r, NoV, NoL, NoH);
+			brdf 					= mix(diffuseBRDF, specularBRDF, intersection.material.ks);
+			
+			
+			hitLighting = intersection.material.emission * max(sign(NoV), 0);	// objects only emit in direction of normal
+        }
+        else{
+            hitLighting = skyColor;
+        }
+        
+        color       	+= hitLighting * throughput;
+        throughput  	*= brdf * NoL / max(pdf, denomMin);
+        
+        if(!intersection.hit)
+            break;
+    }
+
+    return color;
+}
+
+// coord must be in pixel coordinates, but already shifted to pixel center
+vec3 computeCameraRay(vec2 coord){
+
+    ivec2 outImageRes   = imageSize(outImage);
+    float fovDegree     = 45;
+    float fov           = fovDegree * pi / 180;
+    
+    vec2 uv     		= coord / vec2(outImageRes);
+    vec2 ndc    		= 2 * uv - 1;
+    
+    float tanFovHalf    = tan(fov / 2.f);
+    float aspectRatio   = outImageRes.x / float(outImageRes.y);
+    float x             =  ndc.x * tanFovHalf * aspectRatio;
+    float y             = -ndc.y * tanFovHalf;
+    
+    // view direction goes through pixel on image plane with z=1
+    vec3 directionViewSpace     = normalize(vec3(x, y, 1));
+    vec3 directionWorldSpace    = mat3(viewToWorld) * directionViewSpace;
+    return directionWorldSpace;
+}
+
+void main(){
+    ivec2 	coord     = ivec2(gl_GlobalInvocationID.xy);
+	vec2 	pixelSize = 1.f / coord;
+	initRandom(coord);
+	
+	Ray cameraRay;
+    cameraRay.origin  	= viewToWorld[3].xyz;
+	vec2 coordCentered 	= coord + 0.5;
+	
+	vec3 color = vec3(0);
+	
+	const int samplesPerPixel = 1;
+	for(int i = 0; i < samplesPerPixel; i++){
+		vec2 jitter 		= r2Sequence(i + frameIndex) - 0.5;
+		cameraRay.direction = computeCameraRay(coordCentered + jitter);
+		color 				+= castRay(cameraRay);
+	}
+	color /= samplesPerPixel;
+		
+	vec4 final = vec4(color, 1);
+	
+	// occasional NaNs in reflection, should be fixed properly
+	if(any(isnan(color)))
+		final = vec4(0);
+	
+    imageStore(outImage, coord, final);
+}
\ No newline at end of file
diff --git a/projects/path_tracer/shaders/presentImage.comp b/projects/path_tracer/shaders/presentImage.comp
new file mode 100644
index 0000000000000000000000000000000000000000..a52159c0c6173779b091e5d4153b15b0a6361780
--- /dev/null
+++ b/projects/path_tracer/shaders/presentImage.comp
@@ -0,0 +1,23 @@
+#version 440
+#extension GL_GOOGLE_include_directive : enable
+
+layout(set=0, binding=0, rgba32f) 	uniform image2D inImage;
+layout(set=0, binding=1, rgba8) 	uniform image2D outImage;
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+void main(){
+
+    ivec2 outImageRes = imageSize(outImage);
+    ivec2 coord       = ivec2(gl_GlobalInvocationID.xy);
+
+    if(any(greaterThanEqual(coord, outImageRes)))
+        return;
+   
+    vec4 colorRaw 				= imageLoad(inImage, coord);
+	vec3 colorNormalized 		= colorRaw.rgb / colorRaw.a;
+	vec3 colorTonemapped		= colorNormalized / (1 + dot(colorNormalized, vec3(0.71, 0.21, 0.08)));	// reinhard tonemapping
+	vec3 colorGammaCorrected 	= pow(colorTonemapped, vec3(1.f / 2.2));
+
+    imageStore(outImage, coord, vec4(colorGammaCorrected, 0));
+}
\ No newline at end of file
diff --git a/projects/path_tracer/src/main.cpp b/projects/path_tracer/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..75ee36b2ac232e20d7baf29db34909dee67cca00
--- /dev/null
+++ b/projects/path_tracer/src/main.cpp
@@ -0,0 +1,443 @@
+#include <vkcv/Buffer.hpp>
+#include <vkcv/Core.hpp>
+#include <vkcv/Pass.hpp>
+#include <vkcv/camera/CameraManager.hpp>
+#include <vkcv/asset/asset_loader.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+#include "vkcv/gui/GUI.hpp"
+#include <chrono>
+#include <vector>
+
+int main(int argc, const char** argv) {
+
+	// structs must match shader version
+	struct Material {
+		Material(const glm::vec3& emission, const glm::vec3& albedo, float ks, float roughness, const glm::vec3& f0)
+			: emission(emission), ks(ks), albedo(albedo), roughness(roughness), f0(f0), padding() {}
+
+		glm::vec3   emission;
+		float       ks;
+		glm::vec3   albedo;
+		float       roughness;
+		glm::vec3   f0;
+		float       padding;
+	};
+
+	struct Sphere {
+		Sphere(const glm::vec3& c, const float& r, const int m)
+			: center(c), radius(r), materialIndex(m), padding() {}
+
+		glm::vec3   center;
+		float       radius;
+		uint32_t    materialIndex;
+		float       padding[3];
+	};
+
+	struct Plane {
+		Plane(const glm::vec3& c, const glm::vec3& n, const glm::vec2 e, int m)
+			: center(c), materialIndex(m), normal(n), padding1(), extent(e), padding3() {}
+
+		glm::vec3   center;
+		uint32_t    materialIndex;
+		glm::vec3   normal;
+		float       padding1;
+		glm::vec2   extent;
+		glm::vec2   padding3;
+	};
+
+	const std::string applicationName = "Path Tracer";
+
+	vkcv::Core core = vkcv::Core::create(
+		applicationName,
+		VK_MAKE_VERSION(0, 0, 1),
+		{ vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },
+		{ "VK_KHR_swapchain" }
+	);
+	
+	const int initialWidth = 1280;
+	const int initialHeight = 720;
+	
+	vkcv::WindowHandle windowHandle = core.createWindow(
+			applicationName,
+			initialWidth,
+			initialHeight,
+			true
+	);
+	
+	vkcv::ImageConfig imageConfig (
+			initialWidth,
+			initialHeight
+	);
+	
+	imageConfig.setSupportingStorage(true);
+
+	// images
+	vkcv::ImageHandle outputImage = core.createImage(
+		vk::Format::eR32G32B32A32Sfloat,
+		imageConfig
+	);
+
+	vkcv::ImageHandle meanImage = core.createImage(
+		vk::Format::eR32G32B32A32Sfloat,
+		imageConfig
+	);
+
+	vkcv::shader::GLSLCompiler compiler;
+
+	// path tracing shader
+	vkcv::ShaderProgram traceShaderProgram{};
+
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "shaders/path_tracer.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		traceShaderProgram.addShader(shaderStage, path);
+	});
+
+	const vkcv::DescriptorBindings& traceDescriptorBindings     = traceShaderProgram.getReflectedDescriptors().at(0);
+	vkcv::DescriptorSetLayoutHandle traceDescriptorSetLayout    = core.createDescriptorSetLayout(traceDescriptorBindings);
+	vkcv::DescriptorSetHandle       traceDescriptorSet          = core.createDescriptorSet(traceDescriptorSetLayout);
+
+	// image combine shader
+	vkcv::ShaderProgram imageCombineShaderProgram{};
+
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "shaders/combineImages.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		imageCombineShaderProgram.addShader(shaderStage, path);
+	});
+
+	const vkcv::DescriptorBindings& imageCombineDescriptorBindings  = imageCombineShaderProgram.getReflectedDescriptors().at(0);
+	vkcv::DescriptorSetLayoutHandle imageCombineDescriptorSetLayout = core.createDescriptorSetLayout(imageCombineDescriptorBindings);
+	vkcv::DescriptorSetHandle       imageCombineDescriptorSet       = core.createDescriptorSet(imageCombineDescriptorSetLayout);
+	vkcv::ComputePipelineHandle     imageCombinePipeline            = core.createComputePipeline({
+		imageCombineShaderProgram, 
+		{ imageCombineDescriptorSetLayout }
+	});
+
+	vkcv::DescriptorWrites imageCombineDescriptorWrites;
+	imageCombineDescriptorWrites.writeStorageImage(0, outputImage).writeStorageImage(1, meanImage);
+	core.writeDescriptorSet(imageCombineDescriptorSet, imageCombineDescriptorWrites);
+
+	// image present shader
+	vkcv::ShaderProgram presentShaderProgram{};
+
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "shaders/presentImage.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		presentShaderProgram.addShader(shaderStage, path);
+	});
+
+	const vkcv::DescriptorBindings& presentDescriptorBindings   = presentShaderProgram.getReflectedDescriptors().at(0);
+	vkcv::DescriptorSetLayoutHandle presentDescriptorSetLayout  = core.createDescriptorSetLayout(presentDescriptorBindings);
+	vkcv::DescriptorSetHandle       presentDescriptorSet        = core.createDescriptorSet(presentDescriptorSetLayout);
+	vkcv::ComputePipelineHandle     presentPipeline             = core.createComputePipeline({
+		presentShaderProgram,
+		{ presentDescriptorSetLayout }
+	});
+
+	// clear shader
+	vkcv::ShaderProgram clearShaderProgram{};
+
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "shaders/clearImage.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		clearShaderProgram.addShader(shaderStage, path);
+	});
+
+	const vkcv::DescriptorBindings& imageClearDescriptorBindings    = clearShaderProgram.getReflectedDescriptors().at(0);
+	vkcv::DescriptorSetLayoutHandle imageClearDescriptorSetLayout   = core.createDescriptorSetLayout(imageClearDescriptorBindings);
+	vkcv::DescriptorSetHandle       imageClearDescriptorSet         = core.createDescriptorSet(imageClearDescriptorSetLayout);
+	vkcv::ComputePipelineHandle     imageClearPipeline              = core.createComputePipeline({
+		clearShaderProgram,
+		{ imageClearDescriptorSetLayout }
+	});
+
+	vkcv::DescriptorWrites imageClearDescriptorWrites;
+	imageClearDescriptorWrites.writeStorageImage(0, meanImage);
+	core.writeDescriptorSet(imageClearDescriptorSet, imageClearDescriptorWrites);
+
+	// buffers
+	typedef std::pair<std::string, Material> MaterialSetting;
+
+	std::vector<MaterialSetting> materialSettings;
+	materialSettings.emplace_back(MaterialSetting("white",  Material(glm::vec3(0),    glm::vec3(0.65),          0, 0.25, glm::vec3(0.04))));
+	materialSettings.emplace_back(MaterialSetting("red",    Material(glm::vec3(0),    glm::vec3(0.5, 0.0, 0.0), 0, 0.25, glm::vec3(0.04))));
+	materialSettings.emplace_back(MaterialSetting("green",  Material(glm::vec3(0),    glm::vec3(0.0, 0.5, 0.0), 0, 0.25, glm::vec3(0.04))));
+	materialSettings.emplace_back(MaterialSetting("light",  Material(glm::vec3(20),   glm::vec3(0),             0, 0.25, glm::vec3(0.04))));
+	materialSettings.emplace_back(MaterialSetting("sphere", Material(glm::vec3(0),    glm::vec3(0.65),          1, 0.25, glm::vec3(0.04))));
+	materialSettings.emplace_back(MaterialSetting("ground", Material(glm::vec3(0),    glm::vec3(0.65),          0, 0.25, glm::vec3(0.04))));
+
+	const uint32_t whiteMaterialIndex   = 0;
+	const uint32_t redMaterialIndex     = 1;
+	const uint32_t greenMaterialIndex   = 2;
+	const uint32_t lightMaterialIndex   = 3;
+	const uint32_t sphereMaterialIndex  = 4;
+	const uint32_t groundMaterialIndex  = 5;
+
+	std::vector<Sphere> spheres;
+	spheres.emplace_back(Sphere(glm::vec3(0, -1.5, 0), 0.5, sphereMaterialIndex));
+
+	std::vector<Plane> planes;
+	planes.emplace_back(Plane(glm::vec3( 0, -2,     0), glm::vec3( 0,  1,  0), glm::vec2(2), groundMaterialIndex));
+	planes.emplace_back(Plane(glm::vec3( 0,  2,     0), glm::vec3( 0, -1,  0), glm::vec2(2), whiteMaterialIndex));
+	planes.emplace_back(Plane(glm::vec3( 2,  0,     0), glm::vec3(-1,  0,  0), glm::vec2(2), redMaterialIndex));
+	planes.emplace_back(Plane(glm::vec3(-2,  0,     0), glm::vec3( 1,  0,  0), glm::vec2(2), greenMaterialIndex));
+	planes.emplace_back(Plane(glm::vec3( 0,  0,     2), glm::vec3( 0,  0, -1), glm::vec2(2), whiteMaterialIndex));
+	planes.emplace_back(Plane(glm::vec3( 0,  1.9,   0), glm::vec3( 0, -1,  0), glm::vec2(1), lightMaterialIndex));
+
+	vkcv::Buffer<Sphere> sphereBuffer = vkcv::buffer<Sphere>(
+		core,
+		vkcv::BufferType::STORAGE,
+		spheres.size());
+	sphereBuffer.fill(spheres);
+
+	vkcv::Buffer<Plane> planeBuffer = vkcv::buffer<Plane>(
+		core,
+		vkcv::BufferType::STORAGE,
+		planes.size());
+	planeBuffer.fill(planes);
+
+	vkcv::Buffer<Material> materialBuffer = vkcv::buffer<Material>(
+		core,
+		vkcv::BufferType::STORAGE,
+		materialSettings.size());
+
+	vkcv::DescriptorWrites traceDescriptorWrites;
+	traceDescriptorWrites.writeStorageBuffer(
+			0, sphereBuffer.getHandle()
+	).writeStorageBuffer(
+			1, planeBuffer.getHandle()
+	).writeStorageBuffer(
+			2, materialBuffer.getHandle()
+	);
+	
+	traceDescriptorWrites.writeStorageImage(3, outputImage);
+	core.writeDescriptorSet(traceDescriptorSet, traceDescriptorWrites);
+
+	vkcv::ComputePipelineHandle tracePipeline = core.createComputePipeline({
+		traceShaderProgram,
+		{ traceDescriptorSetLayout }
+	});
+
+	if (!tracePipeline)
+	{
+		vkcv_log(vkcv::LogLevel::ERROR, "Could not create graphics pipeline. Exiting.");
+		return EXIT_FAILURE;
+	}
+
+	vkcv::camera::CameraManager cameraManager(core.getWindow(windowHandle));
+	auto camHandle = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+
+	cameraManager.getCamera(camHandle).setPosition(glm::vec3(0, 0, -2));
+	
+	int     frameIndex      = 0;
+	bool    clearMeanImage  = true;
+	bool    updateMaterials = true;
+
+	float cameraPitchPrevious   = 0;
+	float cameraYawPrevious     = 0;
+	glm::vec3 cameraPositionPrevious = glm::vec3(0);
+
+	uint32_t widthPrevious  = initialWidth;
+	uint32_t heightPrevious = initialHeight;
+
+	vkcv::gui::GUI gui(core, windowHandle);
+
+	bool renderUI = true;
+	core.getWindow(windowHandle).e_key.add([&renderUI](int key, int scancode, int action, int mods) {
+		if (key == GLFW_KEY_I && action == GLFW_PRESS) {
+			renderUI = !renderUI;
+		}
+	});
+
+	glm::vec3   skyColor            = glm::vec3(0.2, 0.7, 0.8);
+	float       skyColorMultiplier  = 1;
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
+		if ((swapchainWidth != widthPrevious) || (swapchainHeight != heightPrevious)) {
+			imageConfig.setWidth(swapchainWidth);
+			imageConfig.setHeight(swapchainHeight);
+
+			// resize images
+			outputImage = core.createImage(
+				vk::Format::eR32G32B32A32Sfloat,
+				imageConfig
+			);
+
+			meanImage = core.createImage(
+				vk::Format::eR32G32B32A32Sfloat,
+				imageConfig
+			);
+
+			// update descriptor sets
+			traceDescriptorWrites.writeStorageImage(3, outputImage);
+			core.writeDescriptorSet(traceDescriptorSet, traceDescriptorWrites);
+
+			vkcv::DescriptorWrites imageCombineDescriptorWrites;
+			imageCombineDescriptorWrites.writeStorageImage(
+					0, outputImage
+			).writeStorageImage(
+					1, meanImage
+			);
+			
+			core.writeDescriptorSet(imageCombineDescriptorSet, imageCombineDescriptorWrites);
+
+			vkcv::DescriptorWrites imageClearDescriptorWrites;
+			imageClearDescriptorWrites.writeStorageImage(0, meanImage);
+			core.writeDescriptorSet(imageClearDescriptorSet, imageClearDescriptorWrites);
+
+			widthPrevious  = swapchainWidth;
+			heightPrevious = swapchainHeight;
+
+			clearMeanImage = true;
+		}
+
+		cameraManager.update(dt);
+
+		const vkcv::CommandStreamHandle cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+
+		const auto fullscreenDispatchCount = vkcv::dispatchInvocations(
+				vkcv::DispatchSize(swapchainWidth, swapchainHeight),
+				vkcv::DispatchSize(8, 8)
+		);
+
+		if (updateMaterials) {
+			std::vector<Material> materials;
+			for (const auto& settings : materialSettings) {
+				materials.push_back(settings.second);
+			}
+			materialBuffer.fill(materials);
+			updateMaterials = false;
+			clearMeanImage  = true;
+		}
+
+		float cameraPitch;
+		float cameraYaw;
+		cameraManager.getActiveCamera().getAngles(cameraPitch, cameraYaw);
+
+		if (glm::abs(cameraPitch - cameraPitchPrevious) > 0.01 || glm::abs(cameraYaw - cameraYawPrevious) > 0.01)
+			clearMeanImage = true;	// camera rotated
+
+		cameraPitchPrevious = cameraPitch;
+		cameraYawPrevious   = cameraYaw;
+
+		glm::vec3 cameraPosition = cameraManager.getActiveCamera().getPosition();
+
+		if(glm::distance(cameraPosition, cameraPositionPrevious) > 0.0001)
+			clearMeanImage = true;	// camera moved
+
+		cameraPositionPrevious = cameraPosition;
+
+		if (clearMeanImage) {
+			core.prepareImageForStorage(cmdStream, meanImage);
+
+			core.recordComputeDispatchToCmdStream(cmdStream,
+				imageClearPipeline,
+				fullscreenDispatchCount,
+				{ vkcv::useDescriptorSet(0, imageClearDescriptorSet) },
+				vkcv::PushConstants(0));
+
+			clearMeanImage = false;
+		}
+
+		// path tracing
+		struct RaytracingPushConstantData {
+			glm::mat4   viewToWorld;
+			glm::vec3   skyColor;
+			int32_t     sphereCount;
+			int32_t     planeCount;
+			int32_t     frameIndex;
+		};
+
+		RaytracingPushConstantData raytracingPushData;
+		raytracingPushData.viewToWorld  = glm::inverse(cameraManager.getActiveCamera().getView());
+		raytracingPushData.skyColor     = skyColor * skyColorMultiplier;
+		raytracingPushData.sphereCount  = spheres.size();
+		raytracingPushData.planeCount   = planes.size();
+		raytracingPushData.frameIndex   = frameIndex;
+
+		vkcv::PushConstants pushConstantsCompute = vkcv::pushConstants<RaytracingPushConstantData>();
+		pushConstantsCompute.appendDrawcall(raytracingPushData);
+		
+		const auto traceDispatchCount = vkcv::dispatchInvocations(
+				vkcv::DispatchSize(swapchainWidth, swapchainHeight),
+				vkcv::DispatchSize(16, 16)
+		);
+
+		core.prepareImageForStorage(cmdStream, outputImage);
+
+		core.recordComputeDispatchToCmdStream(cmdStream,
+			tracePipeline,
+			traceDispatchCount,
+			{ vkcv::useDescriptorSet(0, traceDescriptorSet) },
+			pushConstantsCompute);
+
+		core.prepareImageForStorage(cmdStream, meanImage);
+		core.recordImageMemoryBarrier(cmdStream, outputImage);
+
+		// combine images
+		core.recordComputeDispatchToCmdStream(cmdStream,
+			imageCombinePipeline,
+			fullscreenDispatchCount,
+			{ vkcv::useDescriptorSet(0, imageCombineDescriptorSet) },
+			vkcv::PushConstants(0));
+
+		core.recordImageMemoryBarrier(cmdStream, meanImage);
+
+		// present image
+		const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+
+		vkcv::DescriptorWrites presentDescriptorWrites;
+		presentDescriptorWrites.writeStorageImage(
+				0, meanImage
+		).writeStorageImage(
+				1, swapchainInput
+		);
+		
+		core.writeDescriptorSet(presentDescriptorSet, presentDescriptorWrites);
+
+		core.prepareImageForStorage(cmdStream, swapchainInput);
+
+		core.recordComputeDispatchToCmdStream(cmdStream,
+			presentPipeline,
+			fullscreenDispatchCount,
+			{ vkcv::useDescriptorSet(0, presentDescriptorSet) },
+			vkcv::PushConstants(0));
+
+		core.prepareSwapchainImageForPresent(cmdStream);
+		core.submitCommandStream(cmdStream);
+
+		if (renderUI) {
+			gui.beginGUI();
+
+			ImGui::Begin("Settings");
+
+			clearMeanImage |= ImGui::ColorEdit3("Sky color", &skyColor.x);
+			clearMeanImage |= ImGui::InputFloat("Sky color multiplier", &skyColorMultiplier);
+
+			if (ImGui::CollapsingHeader("Materials")) {
+
+				for (auto& setting : materialSettings) {
+					if (ImGui::CollapsingHeader(setting.first.c_str())) {
+
+						const glm::vec3 emission            = setting.second.emission;
+						float           emissionStrength    = glm::max(glm::max(glm::max(emission.x, emission.y), emission.z), 1.f);
+						glm::vec3       emissionColor       = emission / emissionStrength;
+
+						updateMaterials |= ImGui::ColorEdit3((std::string("Emission color ")    + setting.first).c_str(), &emissionColor.x);
+						updateMaterials |= ImGui::InputFloat((std::string("Emission strength ") + setting.first).c_str(), &emissionStrength);
+
+						setting.second.emission = emissionStrength * emissionColor;
+
+						updateMaterials |= ImGui::ColorEdit3((std::string("Albedo color ")  + setting.first).c_str(), &setting.second.albedo.x);
+						updateMaterials |= ImGui::ColorEdit3((std::string("F0 ")            + setting.first).c_str(), &setting.second.f0.x);
+						updateMaterials |= ImGui::DragFloat(( std::string("ks ")            + setting.first).c_str(), &setting.second.ks, 0.01, 0, 1);
+						updateMaterials |= ImGui::DragFloat(( std::string("roughness ")     + setting.first).c_str(), &setting.second.roughness, 0.01, 0, 1);
+
+					}
+				}
+			}
+
+			ImGui::End();
+
+			gui.endGUI();
+		}
+
+		frameIndex++;
+	});
+
+	return 0;
+}
diff --git a/projects/ray_tracer/.gitignore b/projects/ray_tracer/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..b9f0cf28bbc12617183c19ebe240a753d741158c
--- /dev/null
+++ b/projects/ray_tracer/.gitignore
@@ -0,0 +1 @@
+ray_tracer
\ No newline at end of file
diff --git a/projects/ray_tracer/CMakeLists.txt b/projects/ray_tracer/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8c8d80b45e2c0f1ec160d509b194b8339da1205f
--- /dev/null
+++ b/projects/ray_tracer/CMakeLists.txt
@@ -0,0 +1,15 @@
+cmake_minimum_required(VERSION 3.16)
+project(ray_tracer)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+# adding source files to the project
+add_project(ray_tracer src/main.cpp "src/scene.hpp")
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(ray_tracer SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_testing_include} ${vkcv_asset_loader_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(ray_tracer vkcv vkcv_testing vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera vkcv_shader_compiler)
diff --git a/projects/ray_tracer/README.md b/projects/ray_tracer/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..69094cb98fd8a2c7d95f5b20fdac4966fb8e94a5
--- /dev/null
+++ b/projects/ray_tracer/README.md
@@ -0,0 +1,11 @@
+# Ray tracer
+An example project to show the implementation of a ray tracer with the VkCV framework
+
+![Screenshot](../../screenshots/ray_tracer.png)
+
+## Details
+
+The project shows off an example implementation of a ray tracer using compute shaders to render
+an image containing abstract geometry and materials with different properties. The ray tracer 
+limits the maximal depth to allow highly detailed reflections without getting too slow for 
+realtime use.
diff --git a/projects/ray_tracer/shaders/raytracing.comp b/projects/ray_tracer/shaders/raytracing.comp
new file mode 100644
index 0000000000000000000000000000000000000000..a7c6b92a646e5c2f753946f74fe7ab78aea44fe6
--- /dev/null
+++ b/projects/ray_tracer/shaders/raytracing.comp
@@ -0,0 +1,292 @@
+#version 450 core
+#extension GL_ARB_separate_shader_objects : enable
+
+// defines constants
+const float pi      = 3.1415926535897932384626433832795;
+const float hitBias = 0.01;   // used to offset hits to avoid self intersection
+
+layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;
+
+//structs of materials, lights, spheres and intersection for use in compute shader
+struct Material {
+    vec3 albedo;
+    vec3 diffuseColor;
+    float specularExponent;
+    float refractiveIndex;
+};
+
+struct Light{
+    vec3 position;
+    float intensity;
+};
+
+struct Sphere{
+    vec3 center;
+    float radius;
+    Material material;
+};
+
+struct Intersection{
+    bool hit;
+    vec3 pos;
+    vec3 N;
+    Material material;
+};
+
+
+//incoming light data
+layout(std430, binding = 0) coherent buffer lights{
+    Light inLights[];
+};
+
+// incoming sphere data
+layout(std430, binding = 1) coherent buffer spheres{
+    Sphere inSpheres[];
+};
+
+// output store image as swapchain input
+layout(set=0, binding = 2, rgba8) uniform image2D outImage;
+
+// incoming constants, because size of dynamic arrays cannot be computed on gpu
+layout( push_constant ) uniform constants{
+    mat4 viewToWorld;
+    int lightCount;
+    int sphereCount;
+};
+
+/*
+* safrReflect computes the new reflected or refracted ray depending on the material
+* @param vec3: raydirection vector
+* @param vec3: normalvector on which should be reflected or refracted
+* @param float: degree of refraction. In case of simple reflection it is 1.0
+* @return vec3: new ray that is the result of the reflection or refraction
+*/
+vec3 safrReflect(vec3 V, vec3 N, float refractIndex){
+    if(refractIndex != 1.0){
+        // Snell's law
+        float cosi = - max(-1.f, min(1.f, dot(V,N)));
+        float etai = 1;
+        float etat = refractIndex;
+        vec3 n = N;
+        float swap;
+        if(cosi < 0){
+            cosi = -cosi;
+            n = -N;
+            swap = etai;
+            etai = etat;
+            etat = swap;
+        }
+        float eta = etai / etat;
+        float k = 1 - eta * eta * (1 - cosi * cosi);
+        if(k < 0){
+            return vec3(0,0,0);
+        } else {
+            return V * eta + n * (eta * cosi - sqrt(k));
+        }
+    }else{
+        return reflect(V, N);
+    }
+}
+
+/*
+* the rayIntersect function checks, if a ray from the raytracer passes through the sphere, hits the sphere or passes by the the sphere
+* @param vec3: origin of ray
+* @param vec3: direction of ray
+* @param float: distance of the ray to the sphere (out because there are no references in shaders)
+* @return bool: if ray interesects sphere or not (out because there are no references in shaders)
+*/
+
+bool rayIntersect(const vec3 origin, const vec3 dir, out float t0, const int id){
+    vec3 L = inSpheres[id].center - origin;
+    float tca = dot(L, dir);
+    float d2 = dot(L, L) - tca * tca;
+    if (d2 > inSpheres[id].radius * inSpheres[id].radius){
+        return false;
+    }
+    float thc = float(sqrt(inSpheres[id].radius * inSpheres[id].radius - d2));
+    t0 = tca - thc;
+    float t1 = tca + thc;
+    if (t0 < 0) {
+        t0 = t1;
+    }
+    if (t0 < 0){
+        return false;
+    }
+    return true;
+}
+
+/*
+* sceneIntersect iterates over whole scene (over every single object) to check for intersections
+* @param vec3: Origin of the ray
+* @param vec3: direction of the ray
+* @return: Intersection struct with hit(bool) position, normal and material of sphere
+*/
+
+Intersection sceneIntersect(const vec3 rayOrigin, const vec3 rayDirection) {
+    //distance if spheres will be rendered
+    float   min_d    = 1.0 / 0.0;  // lets start with something big
+    
+    Intersection intersection;
+    intersection.hit = false;
+    
+    //go over every sphere, check if sphere is hit by ray, save if hit is near enough into intersection struct
+    for (int i = 0; i < sphereCount; i++) {
+        float d;
+        if (rayIntersect(rayOrigin, rayDirection, d, i)) {
+            
+            intersection.hit = true;
+            
+            if(d < min_d){
+                min_d = d;
+                intersection.pos        = rayOrigin + rayDirection * d;
+                intersection.N          = normalize(intersection.pos - inSpheres[i].center);
+                intersection.material   = inSpheres[i].material;
+            }
+        }
+    }
+
+    float checkerboard_dist = min_d;
+    if (abs(rayDirection.y)>1e-3)  {
+        float d = -(rayOrigin.y + 4) / rayDirection.y; // the checkerboard plane has equation y = -4
+        vec3 pt = rayOrigin + rayDirection * d;
+        if (d > 0 && abs(pt.x) < 10 && pt.z<-10 && pt.z>-30 && d < min_d) {
+            checkerboard_dist = d;
+            intersection.hit = true;
+            intersection.pos = pt;
+            intersection.N = vec3(0, 1, 0);
+            intersection.material = inSpheres[0].material;
+        }
+    }
+    return intersection;
+}
+
+/*
+* biasHitPosition computes the new hitposition with respect to the raydirection and a bias
+* @param vec3: Hit Position
+* @param vec3: direction of ray
+* @param vec3: N(ormal)
+* @return vec3: new Hit position depending on hitBias (used to offset hits to avoid self intersection)
+*/
+vec3 biasHitPosition(vec3 hitPos, vec3 rayDirection, vec3 N){
+    return hitPos + sign(dot(rayDirection, N)) * N * hitBias;
+}
+
+/*
+* computeHitLighting iterates over all lights to compute the color for every ray
+* @param Intersection: struct with all the data of the intersection
+* @param vec3: Raydirection
+* @param float: material albedo of the intersection
+* @return colour/shadows of sphere with illumination
+*/
+vec3 computeHitLighting(Intersection intersection, vec3 V, out float outReflectionThroughput){
+    
+    float lightIntensityDiffuse  = 0;
+    float lightIntensitySpecular = 0;
+
+    //iterate over every light source to compute sphere colours/shadows
+    for (int i = 0; i < lightCount; i++) {
+
+        //compute normal + distance between light and intersection
+        vec3   L = normalize(inLights[i].position - intersection.pos);
+        float  d = distance(inLights[i].position, intersection.pos);
+
+        //compute shadows
+        vec3 shadowOrigin = biasHitPosition(intersection.pos, L, intersection.N);
+        Intersection shadowIntersection = sceneIntersect(shadowOrigin, L);
+        bool isShadowed = false;
+        if(shadowIntersection.hit){
+            isShadowed = distance(shadowIntersection.pos, shadowOrigin) < d;
+        }
+        if(isShadowed){
+            continue;
+        }
+
+        lightIntensityDiffuse  += inLights[i].intensity * max(0.f, dot(L, intersection.N));
+        lightIntensitySpecular += pow(max(0.f, dot(safrReflect(V, intersection.N, intersection.material.refractiveIndex), L)), intersection.material.specularExponent) * inLights[i].intensity;
+    }
+
+    outReflectionThroughput = intersection.material.albedo[2];
+    return intersection.material.diffuseColor * lightIntensityDiffuse * intersection.material.albedo[0] + lightIntensitySpecular * intersection.material.albedo[1];
+}
+
+/*
+* castRay throws a ray out of the initial origin with respect to the initial direction checks for intersection and refelction
+* @param vec3: initial origin of ray
+* @param vec3: initial direction of ray
+* @param int: max depth o ray reflection
+* @return s
+*/
+
+vec3 castRay(const vec3 initialOrigin, const vec3 initialDirection, int max_depth) {
+    
+    vec3 skyColor = vec3(0.2, 0.7, 0.8);
+    vec3 rayOrigin    = initialOrigin;
+    vec3 rayDirection = initialDirection;
+    
+    float   reflectionThroughput    = 1;
+    vec3    color                   = vec3(0);
+
+    //iterate to max depth of reflections
+    for(int i = 0; i < max_depth; i++){
+
+        Intersection intersection = sceneIntersect(rayOrigin, rayDirection);
+
+        vec3 hitColor;
+        float hitReflectionThroughput;
+
+        if(intersection.hit){
+            hitColor = computeHitLighting(intersection, rayDirection, hitReflectionThroughput);
+        }else{
+            hitColor = skyColor;
+        }
+
+        color                   += hitColor * reflectionThroughput;
+        reflectionThroughput    *= hitReflectionThroughput;
+
+        //if there is no intersection of a ray with a sphere, break out of the loop
+        if(!intersection.hit){
+            break;
+        }
+
+        //compute new direction and origin of the reflected ray
+        rayDirection    = normalize(safrReflect(rayDirection, intersection.N, intersection.material.refractiveIndex));
+        rayOrigin       = biasHitPosition(intersection.pos, rayDirection, intersection.N);
+    }
+
+    return color;
+}
+
+/*
+* computeDirection transforms the pixel coords to worldspace coords
+* @param ivec2: pixel coordinates
+* @return vec3: world coordinates
+*/
+vec3 computeDirection(ivec2 coord){
+
+    ivec2 outImageRes   = imageSize(outImage);
+    float fovDegree     = 45;
+    float fov           = fovDegree * pi / 180;
+    
+    vec2 uv     = coord / vec2(outImageRes);
+    vec2 ndc    = 2 * uv - 1;
+    
+    float tanFovHalf    = tan(fov / 2.f);
+    float aspectRatio   = outImageRes.x / float(outImageRes.y);
+    float x             =  ndc.x * tanFovHalf * aspectRatio;
+    float y             = -ndc.y * tanFovHalf;
+    
+    vec3 directionViewSpace     = normalize(vec3(x, y, 1));
+    vec3 directionWorldSpace    = mat3(viewToWorld) * directionViewSpace;
+    return directionWorldSpace;
+}
+
+// the main function
+void main(){
+    ivec2 coord     = ivec2(gl_GlobalInvocationID.xy);
+    int max_depth   = 4;
+    vec3 direction  = computeDirection(coord);
+    vec3 cameraPos  = viewToWorld[3].xyz;
+    vec3 color      = castRay(cameraPos, direction, max_depth);
+    
+    imageStore(outImage, coord, vec4(color, 0.f));
+}
\ No newline at end of file
diff --git a/projects/ray_tracer/src/main.cpp b/projects/ray_tracer/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1a906d5d8141148acbbf767163fb173f8053d466
--- /dev/null
+++ b/projects/ray_tracer/src/main.cpp
@@ -0,0 +1,235 @@
+#include <iostream>
+#include <vkcv/Core.hpp>
+#include <vkcv/Buffer.hpp>
+#include <vkcv/Pass.hpp>
+#include <vkcv/Sampler.hpp>
+
+#include <vkcv/camera/CameraManager.hpp>
+#include <vkcv/asset/asset_loader.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+
+#include <cmath>
+#include <vector>
+#include <cstring>
+#include <GLFW/glfw3.h>
+
+#include "scene.hpp"
+
+void createQuadraticLightCluster(std::vector<scene::Light>& lights, int countPerDimension, float dimension, float height, float intensity) {
+    float distance = dimension/countPerDimension;
+
+    for(int x = 0; x <= countPerDimension; x++) {
+        for (int z = 0; z <= countPerDimension; z++) {
+            lights.push_back(scene::Light(glm::vec3(x * distance, height, z * distance),
+                                              float (intensity/countPerDimension) / 10.f) // Divide by 10, because intensity is busting O.o
+                                              );
+        }
+    }
+
+}
+
+int main(int argc, const char** argv) {
+	const std::string applicationName = "Ray tracer";
+
+	//window creation
+	const int windowWidth = 800;
+	const int windowHeight = 600;
+
+	vkcv::Core core = vkcv::Core::create(
+		applicationName,
+		VK_MAKE_VERSION(0, 0, 1),
+		{ vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },
+		{ "VK_KHR_swapchain" }
+	);
+
+	vkcv::WindowHandle windowHandle = core.createWindow(applicationName, windowWidth, windowHeight, true);
+	vkcv::Window& window = core.getWindow(windowHandle);
+
+	std::string shaderPathCompute = "shaders/raytracing.comp";
+
+	//creating the shader programs
+	vkcv::shader::GLSLCompiler compiler;
+	vkcv::ShaderProgram computeShaderProgram;
+
+	compiler.compile(vkcv::ShaderStage::COMPUTE, shaderPathCompute, [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		computeShaderProgram.addShader(shaderStage, path);
+	});
+
+	vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+
+	const vkcv::DescriptorBindings& computeDescriptorBindings = computeShaderProgram.getReflectedDescriptors().at(0);
+	
+	vkcv::DescriptorSetLayoutHandle computeDescriptorSetLayout = core.createDescriptorSetLayout(computeDescriptorBindings);
+	vkcv::DescriptorSetHandle computeDescriptorSet = core.createDescriptorSet(computeDescriptorSetLayout);
+
+	const std::vector<vkcv::VertexAttachment> computeVertexAttachments = computeShaderProgram.getVertexAttachments();
+
+	std::vector<vkcv::VertexBinding> computeBindings;
+	for (size_t i = 0; i < computeVertexAttachments.size(); i++) {
+		computeBindings.push_back(vkcv::createVertexBinding(i, { computeVertexAttachments[i] }));
+	}
+	const vkcv::VertexLayout computeLayout { computeBindings };
+	
+	/*
+	* create the scene
+	*/
+
+	//materials for the spheres
+	std::vector<scene::Material> materials;
+	scene::Material ivory(glm::vec4(0.6, 0.3, 0.1, 0.0), glm::vec3(0.4, 0.4, 0.3), 50., 1.0);
+	scene::Material red_rubber(glm::vec4(0.9, 0.1, 0.0, 0.0), glm::vec3(0.3, 0.1, 0.1), 10., 1.0);
+	scene::Material mirror(glm::vec4(0.0, 10.0, 0.8, 0.0), glm::vec3(1.0, 1.0, 1.0), 1425., 1.0);
+    scene::Material glass(glm::vec4(0.0, 10.0, 0.8, 0.0), glm::vec3(1.0, 1.0, 1.0), 1425., 1.5);
+
+	materials.push_back(ivory);
+	materials.push_back(red_rubber);
+	materials.push_back(mirror);
+
+	//spheres for the scene
+	std::vector<scene::Sphere> spheres;
+	spheres.push_back(scene::Sphere(glm::vec3(-3, 0, -16), 2, ivory));
+	//spheres.push_back(safrScene::Sphere(glm::vec3(-1.0, -1.5, 12), 2, mirror));
+	spheres.push_back(scene::Sphere(glm::vec3(-1.0, -1.5, -12), 2, glass));
+	spheres.push_back(scene::Sphere(glm::vec3(1.5, -0.5, -18), 3, red_rubber));
+	spheres.push_back(scene::Sphere(glm::vec3(7, 5, -18), 4, mirror));
+
+	//lights for the scene
+	std::vector<scene::Light> lights;
+	/*
+	lights.push_back(safrScene::Light(glm::vec3(-20, 20,  20), 1.5));
+	lights.push_back(safrScene::Light(glm::vec3(30,  50, -25), 1.8));
+	lights.push_back(safrScene::Light(glm::vec3(30,  20,  30), 1.7));
+    */
+    createQuadraticLightCluster(lights, 10, 2.5f, 20, 1.5f);
+	
+	vkcv::SamplerHandle sampler = vkcv::samplerLinear(core);
+	
+	//create Buffer for compute shader
+	vkcv::Buffer<scene::Light> lightsBuffer = vkcv::buffer<scene::Light>(
+		core,
+		vkcv::BufferType::STORAGE,
+		lights.size()
+	);
+	lightsBuffer.fill(lights);
+
+	vkcv::Buffer<scene::Sphere> sphereBuffer = vkcv::buffer<scene::Sphere>(
+		core,
+		vkcv::BufferType::STORAGE,
+		spheres.size()
+	);
+	sphereBuffer.fill(spheres);
+
+	vkcv::DescriptorWrites computeWrites;
+	computeWrites.writeStorageBuffer(
+			0, lightsBuffer.getHandle()
+	).writeStorageBuffer(
+			1, sphereBuffer.getHandle()
+	);
+	
+    core.writeDescriptorSet(computeDescriptorSet, computeWrites);
+
+	auto safrIndexBuffer = vkcv::buffer<uint16_t>(core, vkcv::BufferType::INDEX, 3);
+	uint16_t indices[3] = { 0, 1, 2 };
+	safrIndexBuffer.fill(&indices[0], sizeof(indices));
+	
+	vkcv::PassHandle safrPass = vkcv::passSwapchain(
+			core,
+			window.getSwapchain(),
+			{ vk::Format::eUndefined }
+	);
+
+	if (!safrPass) {
+		std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+	vkcv::ComputePipelineHandle computePipeline = core.createComputePipeline(
+			vkcv::ComputePipelineConfig(
+					computeShaderProgram,
+					{ computeDescriptorSetLayout }
+			)
+	);
+
+	if (!computePipeline) {
+		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
+		return EXIT_FAILURE;
+	}
+
+	//create the camera
+	vkcv::camera::CameraManager cameraManager(window);
+	auto camHandle0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+	auto camHandle1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+
+	cameraManager.getCamera(camHandle0).setPosition(glm::vec3(0, 0, 2));
+	
+	cameraManager.getCamera(camHandle1).setPosition(glm::vec3(0, 0, -4));
+	cameraManager.getCamera(camHandle1).setCenter(glm::vec3(0.0f, 0.0f, -1.0f));
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
+		//adjust light position
+		/*
+		639a53157e7d3936caf7c3e40379159cbcf4c89e
+		lights[0].position.x += std::cos(time * 3.0f) * 2.5f;
+		lights[1].position.z += std::cos(time * 2.5f) * 3.0f;
+		lights[2].position.y += std::cos(time * 1.5f) * 4.0f;
+		lightsBuffer.fill(lights);
+		*/
+
+		spheres[0].center.y += std::cos(t * 0.5f * 3.141f) * 0.25f;
+		spheres[1].center.x += std::cos(t * 2.f) * 0.25f;
+		spheres[1].center.z += std::cos(t * 2.f + 0.5f * 3.141f) * 0.25f;
+        sphereBuffer.fill(spheres);
+
+		//update camera
+		cameraManager.update(dt);
+		glm::mat4 mvp = cameraManager.getActiveCamera().getMVP();
+		glm::mat4 proj = cameraManager.getActiveCamera().getProjection();
+
+		//create pushconstants for render
+		vkcv::PushConstants pushConstants(sizeof(glm::mat4) * 2);
+		pushConstants.appendDrawcall(std::array<glm::mat4, 2>{ mvp, proj });
+
+		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+
+		//configure the outImage for compute shader (render into the swapchain image)
+        computeWrites.writeStorageImage(2, swapchainInput);
+        core.writeDescriptorSet(computeDescriptorSet, computeWrites);
+        core.prepareImageForStorage (cmdStream, swapchainInput);
+
+		//fill pushconstants for compute shader
+        struct RaytracingPushConstantData {
+            glm::mat4 viewToWorld;
+            int32_t lightCount;
+            int32_t sphereCount;
+        };
+
+        RaytracingPushConstantData raytracingPushData;
+        raytracingPushData.lightCount   = lights.size();
+        raytracingPushData.sphereCount  = spheres.size();
+        raytracingPushData.viewToWorld  = glm::inverse(cameraManager.getActiveCamera().getView());
+
+        vkcv::PushConstants pushConstantsCompute = vkcv::pushConstants<RaytracingPushConstantData>();
+        pushConstantsCompute.appendDrawcall(raytracingPushData);
+
+		//dispatch compute shader
+		const auto computeDispatchCount = vkcv::dispatchInvocations(
+				vkcv::DispatchSize(swapchainWidth, swapchainHeight),
+				vkcv::DispatchSize(16, 16)
+		);
+		
+		core.recordComputeDispatchToCmdStream(cmdStream,
+			computePipeline,
+			computeDispatchCount,
+			{ vkcv::useDescriptorSet(0, computeDescriptorSet) },
+			pushConstantsCompute
+		);
+
+		core.recordBufferMemoryBarrier(cmdStream, lightsBuffer.getHandle());
+
+		core.prepareSwapchainImageForPresent(cmdStream);
+		core.submitCommandStream(cmdStream);
+	});
+	
+	return 0;
+}
diff --git a/projects/ray_tracer/src/scene.hpp b/projects/ray_tracer/src/scene.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..be1cee22d21fc4009c390f629f6118071502fe47
--- /dev/null
+++ b/projects/ray_tracer/src/scene.hpp
@@ -0,0 +1,51 @@
+#include <iostream>
+#include <vkcv/Core.hpp>
+#include <GLFW/glfw3.h>
+#include <vkcv/camera/CameraManager.hpp>
+#include <vkcv/asset/asset_loader.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+#include <chrono>
+#include <limits>
+#include <cmath>
+#include <vector>
+#include <string.h>	// memcpy(3)
+
+class scene {
+
+public:
+
+	/*
+	* Light struct with a position and intensity of the light source
+	*/
+	struct Light {
+		Light(const glm::vec3& p, const float& i) : position(p), intensity(i) {}
+		glm::vec3 position;
+		float intensity;
+	};
+
+	/*
+	* Material struct with defuse color, albedo and specular component
+	*/
+	struct Material {
+		Material(const glm::vec4& a, const glm::vec3& color, const float& spec, const float& r) : albedo(a), diffuse_color(color), specular_exponent(spec), refractive_index(r) {}
+		Material() : albedo(1, 0, 0, 0), diffuse_color(), specular_exponent(), refractive_index(1) {}
+        glm::vec4 albedo;
+        alignas(16) glm::vec3 diffuse_color;
+        float specular_exponent;
+		float refractive_index;
+	};
+
+	/*
+	* the sphere is defined by it's center, the radius and the material
+	*/
+	struct Sphere {
+		glm::vec3 center;
+		float radius;
+		Material material;
+
+		Sphere(const glm::vec3& c, const float& r, const Material& m) : center(c), radius(r), material(m) {}
+
+	};
+
+	
+};
\ No newline at end of file
diff --git a/projects/rtx_ambient_occlusion/.gitignore b/projects/rtx_ambient_occlusion/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..61b2ff94710af817f199d848e4253062c024a6bf
--- /dev/null
+++ b/projects/rtx_ambient_occlusion/.gitignore
@@ -0,0 +1 @@
+rtx_ambient_occlusion
\ No newline at end of file
diff --git a/projects/rtx_ambient_occlusion/CMakeLists.txt b/projects/rtx_ambient_occlusion/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..2c784a4f173ea5d6e0de1df1a2cfe514a5787153
--- /dev/null
+++ b/projects/rtx_ambient_occlusion/CMakeLists.txt
@@ -0,0 +1,28 @@
+cmake_minimum_required(VERSION 3.16)
+project(rtx_ambient_occlusion)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(rtx_source ${PROJECT_SOURCE_DIR}/src/RTX)
+
+set(rtx_sources
+		${rtx_source}/RTX.hpp
+		${rtx_source}/RTX.cpp
+
+		${rtx_source}/ASManager.hpp
+		${rtx_source}/ASManager.cpp
+
+		${rtx_source}/RTXExtensions.hpp
+		${rtx_source}/RTXExtensions.cpp
+)
+
+# adding source files to the project
+add_project(rtx_ambient_occlusion src/main.cpp src/teapot.hpp ${rtx_sources})
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(rtx_ambient_occlusion SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include} ${vkcv_scene_include} ${vkcv_shader_compiler_include})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(rtx_ambient_occlusion vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera vkcv_scene vkcv_shader_compiler)
diff --git a/projects/rtx_ambient_occlusion/README.md b/projects/rtx_ambient_occlusion/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..d9fd98cf6787038e7e3f9c1b21f61a71541f09e4
--- /dev/null
+++ b/projects/rtx_ambient_occlusion/README.md
@@ -0,0 +1,25 @@
+# RTX ambient occlusion
+An example project to show usage of hardware accelerated ray tracing with the VkCV framework
+
+![Screenshot](../../screenshots/rtx_ambient_occlusion.png)
+
+## Details
+
+The project utilizes multiple Vulkan extensions to make use of hardware accelerated ray tracing. 
+Newer GPU architectures provide special hardware to allow the use of acceleration structures and 
+ray tracing pipelines. This application uses those features to render an implementation of 
+ambient occlusion in realtime.
+
+## Extensions
+
+Here is a list of the used extensions:
+
+- [VK_KHR_get_physical_device_properties2](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_get_physical_device_properties2.html)
+- [VK_KHR_maintenance3](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_maintenance3.html)
+- [VK_KHR_deferred_host_operations](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_deferred_host_operations.html)
+- [VK_KHR_spirv_1_4](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_spirv_1_4.html)
+- [VK_KHR_pipeline_library](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_pipeline_library.html)
+- [VK_EXT_descriptor_indexing](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_descriptor_indexing.html)
+- [VK_KHR_buffer_device_address](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_buffer_device_address.html)
+- [VK_KHR_acceleration_structure](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_acceleration_structure.html)
+- [VK_KHR_ray_tracing_pipeline](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_ray_tracing_pipeline.html)
diff --git a/projects/rtx_ambient_occlusion/resources/shaders/ambientOcclusion.rchit b/projects/rtx_ambient_occlusion/resources/shaders/ambientOcclusion.rchit
new file mode 100644
index 0000000000000000000000000000000000000000..b073916e4eaf992d5e6ae4186013478634263fbd
--- /dev/null
+++ b/projects/rtx_ambient_occlusion/resources/shaders/ambientOcclusion.rchit
@@ -0,0 +1,48 @@
+#version 460
+#extension GL_EXT_ray_tracing : require
+#extension GL_EXT_scalar_block_layout : require
+
+hitAttributeEXT vec2 attributes;
+
+layout(location = 0) rayPayloadInEXT Payload {
+  float hitSky;
+  vec3 worldPosition;
+  vec3 worldNormal;
+} payload;
+
+layout(binding = 2, set = 0) uniform accelerationStructureEXT tlas;     // top level acceleration structure
+
+layout(binding = 3, set = 0, scalar) buffer rtxVertices
+{
+    float vertices[];
+};
+
+layout(binding = 4, set = 0, scalar) buffer rtxIndices
+{
+    uint indices[];
+};
+
+void main() {
+    payload.worldPosition = vec3(1.0, 0.0, 0.5);
+
+    ivec3 indicesVec = ivec3(indices[3 * gl_PrimitiveID + 0], indices[3 * gl_PrimitiveID + 1], indices[3 * gl_PrimitiveID + 2]);
+
+    // current triangle
+    const vec3 v0 = vec3(vertices[3 * indicesVec.x + 0],vertices[3 * indicesVec.x + 1],vertices[3 * indicesVec.x + 2]);
+    const vec3 v1 = vec3(vertices[3 * indicesVec.y + 0],vertices[3 * indicesVec.y + 1],vertices[3 * indicesVec.y + 2]);
+    const vec3 v2 = vec3(vertices[3 * indicesVec.z + 0],vertices[3 * indicesVec.z + 1],vertices[3 * indicesVec.z + 2]);
+
+    // use barycentric coordinates to compute intersection
+    const vec3 barycentrics = vec3(1.0 - attributes.x - attributes.y, attributes.xy);
+    const vec3 objectPosition = v0 * barycentrics.x + v1 * barycentrics.y + v2 * barycentrics.z;
+
+    payload.worldPosition = gl_ObjectToWorldEXT * vec4(objectPosition, 1.0);
+
+    const vec3 objectNormal = cross(v1 - v0, v2 - v0);
+
+    payload.worldNormal = normalize((objectNormal * gl_WorldToObjectEXT).xyz);
+
+    payload.worldNormal = faceforward(payload.worldNormal, gl_WorldRayDirectionEXT, payload.worldNormal);
+
+    payload.hitSky = 0.0f;
+}
diff --git a/projects/rtx_ambient_occlusion/resources/shaders/ambientOcclusion.rgen b/projects/rtx_ambient_occlusion/resources/shaders/ambientOcclusion.rgen
new file mode 100644
index 0000000000000000000000000000000000000000..711070fcf1eec18253f331cbd133330791fa6be6
--- /dev/null
+++ b/projects/rtx_ambient_occlusion/resources/shaders/ambientOcclusion.rgen
@@ -0,0 +1,158 @@
+#version 460
+#extension GL_EXT_ray_tracing : require
+
+#define M_PI 3.1415926535897932384626433832795
+
+// A location for a ray payload (we can have multiple of these)
+layout(location = 0) rayPayloadEXT Payload {
+  float hitSky;
+  vec3 worldPosition;
+  vec3 worldNormal;
+} payload;
+
+layout(binding = 0, set = 0, rgba16) uniform image2D outImg;            // the output image -> maybe use 16 bit values?
+layout(binding = 1, set = 0) uniform accelerationStructureEXT tlas;     // top level acceleration structure
+
+layout( push_constant ) uniform constants {
+    vec4 camera_position;   // as origin for ray generation
+    vec4 camera_right;      // for computing ray direction
+    vec4 camera_up;         // for computing ray direction
+    vec4 camera_forward;    // for computing ray direction
+} camera;
+
+// random() and helpers from: https://www.shadertoy.com/view/XlycWh
+float g_seed = 0;
+
+uint base_hash(uvec2 p) {
+    p = 1103515245U*((p >> 1U)^(p.yx));
+    uint h32 = 1103515245U*((p.x)^(p.y>>3U));
+    return h32^(h32 >> 16);
+}
+
+vec2 hash2(inout float seed) {
+    uint n = base_hash(floatBitsToUint(vec2(seed+=.1,seed+=.1)));
+    uvec2 rz = uvec2(n, n*48271U);
+    return vec2(rz.xy & uvec2(0x7fffffffU))/float(0x7fffffff);
+}
+
+void initRandom(uvec2 coord){
+	g_seed = float(base_hash(coord)/float(0xffffffffU));
+}
+
+vec2 random(){
+	return hash2(g_seed);
+}
+
+/**
+ * Traces the ray from the camera and provides the intersection information.
+ * @param[in,out] hitSky Defines if the ray has hit the sky
+ * @param[in,out] pos The position of intersection
+ * @param[in,out] norm The normal at the position of intersection
+ */
+void TraceCameraRay(out bool hitSky, out vec3 pos, out vec3 norm){
+  // Use a camera model to generate a ray for this pixel.
+  vec2 uv = gl_LaunchIDEXT.xy + vec2(random()); // random breaks up aliasing
+  uv /= vec2(gl_LaunchSizeEXT.xy);
+  uv = (uv * 2.0 - 1.0) // normalize uv coordinates into Vulkan viewport space
+    * vec2(1.0, -1.0);  // flips y-axis
+  const vec3 orig   = camera.camera_position.xyz;
+  const vec3 dir    = normalize(uv.x * camera.camera_right + uv.y * camera.camera_up + camera.camera_forward).xyz;
+
+  // Trace a ray into the scene; get back data in the payload.
+  traceRayEXT(tlas,  // Acceleration structure
+              gl_RayFlagsOpaqueEXT, // Ray flags, here saying "ignore intersection shaders"
+              0xFF,                 // 8-bit instance mask, here saying "trace against all instances"
+              0,                    // SBT record offset
+              0,                    // SBT record stride for offset
+              0,                    // Miss index
+              orig,                 // Ray origin
+              0.0,                  // Minimum t-value
+              dir,                  // Ray direction
+              1000.0,               // Maximum t-value
+              0);                   // Location of payload
+
+  // Read the values from the payload:
+  hitSky    = (payload.hitSky > 0.0);
+  pos       = payload.worldPosition;
+  norm      = payload.worldNormal;
+}
+
+/**
+ * @brief Casts a shadow ray. Returns @p true, if the shadow ray hit the sky.
+ * @param[in] orig The point of origin of the shadow ray.
+ * @param[in] dir The direction of the shadow ray.
+ */
+float CastShadowRay(vec3 orig, vec3 dir){
+  payload.hitSky = 0.0f;  // Assume ray is occluded
+  traceRayEXT(tlas,   // Acceleration structure
+              gl_RayFlagsOpaqueEXT | gl_RayFlagsSkipClosestHitShaderEXT | gl_RayFlagsTerminateOnFirstHitEXT, // Ray flags, here saying "ignore any hit shaders and closest hit shaders, and terminate the ray on the first found intersection"
+              0xFF,    // 8-bit instance mask, here saying "trace against all instances"
+              0,       // SBT record offset
+              0,       // SBT record stride for offset
+              0,       // Miss index
+              orig,    // Ray origin
+              0.0001,  // Minimum t-value - avoid self intersection
+              dir,     // Ray direction
+              1000.0,  // Maximum t-value
+              0);      // Location of payload
+  return payload.hitSky;
+}
+
+vec3 sampleCosineDistribution(vec2 xi){
+	float phi = 2 * M_PI * xi.y;
+	return vec3(
+		sqrt(xi.x) * cos(phi),
+		sqrt(1 - xi.x),
+		sqrt(xi.x) * sin(phi));
+}
+
+struct Basis{
+	vec3 right;
+	vec3 up;
+	vec3 forward;
+};
+
+Basis buildBasisAroundNormal(vec3 N){
+	Basis 	basis;
+	basis.up 		= N;
+	basis.right 	= abs(basis.up.x) < 0.99 ?  vec3(1, 0, 0) : vec3(0, 0, 1);
+	basis.forward 	= normalize(cross(basis.up, basis.right));
+	basis.right 	= cross(basis.up, basis.forward);
+	return basis;
+}
+
+vec3 sampleTangentToWorldSpace(vec3 tangentSpaceSample, vec3 N){
+	Basis tangentBasis = buildBasisAroundNormal(N);
+	return
+		tangentBasis.right		* tangentSpaceSample.x +
+		tangentBasis.up			* tangentSpaceSample.y +
+		tangentBasis.forward 	* tangentSpaceSample.z;
+}
+
+void main(){
+    uint rayCount = 64;    // the amount of rays to be casted
+
+    initRandom(gl_LaunchIDEXT.xy);
+
+    uvec2 pixel = gl_LaunchIDEXT.xy;
+    bool pixelIsSky; // Does the pixel show the sky (not an object)?
+    vec3 pos, norm;  // AO rays from where?
+    TraceCameraRay(pixelIsSky, pos, norm);
+    
+    if(pixelIsSky){
+        // Don't compute ambient occlusion for the sky
+        imageStore(outImg, ivec2(pixel), vec4(0.8,0.8,0.8,1.0));
+        return;
+    }
+
+    // Compute ambient occlusion
+    float aoValue = 0.0;
+    for(uint i = 0; i < rayCount; i++){
+        vec3 sampleTangentSpace = sampleCosineDistribution(random());
+        vec3 sampleWorldSpace   = sampleTangentToWorldSpace(sampleTangentSpace, norm);
+        aoValue                 += CastShadowRay(pos, sampleWorldSpace);
+    }
+    aoValue /= rayCount;
+    
+    imageStore(outImg, ivec2(pixel), vec4(vec3(aoValue), 1));
+}
diff --git a/projects/rtx_ambient_occlusion/resources/shaders/ambientOcclusion.rmiss b/projects/rtx_ambient_occlusion/resources/shaders/ambientOcclusion.rmiss
new file mode 100644
index 0000000000000000000000000000000000000000..c107dbd03e6a8fdfdd84d2b8d280cb6264307c14
--- /dev/null
+++ b/projects/rtx_ambient_occlusion/resources/shaders/ambientOcclusion.rmiss
@@ -0,0 +1,12 @@
+#version 460
+#extension GL_EXT_ray_tracing : require
+
+layout(location = 0) rayPayloadInEXT Payload {
+  float hitSky;
+  vec3 worldPosition;
+  vec3 worldNormal;
+} payload;
+
+void main() {
+    payload.hitSky = 1.0f;
+}
diff --git a/projects/rtx_ambient_occlusion/src/RTX/ASManager.cpp b/projects/rtx_ambient_occlusion/src/RTX/ASManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fefd468067658cde88eefe02834b2e7328672af6
--- /dev/null
+++ b/projects/rtx_ambient_occlusion/src/RTX/ASManager.cpp
@@ -0,0 +1,460 @@
+#include "ASManager.hpp"
+#include <array>
+
+namespace vkcv::rtx {
+    
+
+    ASManager::ASManager(vkcv::Core *core) :
+    m_core(core),
+    m_device(&(core->getContext().getDevice())){
+        // INFO: Using RTX extensions implies that we cannot use the standard dispatcher from Vulkan because using RTX
+        // specific functions via vk::Device will result in validation errors. Instead we need to use a
+        // vk::DispatchLoaderDynamic which is used as dispatcher parameter of the device functions.
+        m_rtxDispatcher = vk::DispatchLoaderDynamic( (PFN_vkGetInstanceProcAddr) m_core->getContext().getInstance().getProcAddr("vkGetInstanceProcAddr") );
+        m_rtxDispatcher.init(m_core->getContext().getInstance());
+
+        // TODO: Recursive call of buildBLAS for bigger scenes. Currently, the RTX module only supports one mesh.
+    }
+
+    ASManager::~ASManager() noexcept {
+        m_rtxDispatcher = vk::DispatchLoaderDynamic( (PFN_vkGetInstanceProcAddr) m_core->getContext().getInstance().getProcAddr("vkGetInstanceProcAddr") );
+        m_rtxDispatcher.init(m_core->getContext().getInstance());
+
+        // destroy every BLAS, its data containers and free used memory blocks
+        for (size_t i=0; i < m_bottomLevelAccelerationStructures.size(); i++) {
+            BottomLevelAccelerationStructure blas = m_bottomLevelAccelerationStructures[i];
+            m_core->getContext().getDevice().destroyAccelerationStructureKHR(blas.vulkanHandle, nullptr, m_rtxDispatcher);
+            m_core->getContext().getDevice().destroy(blas.accelerationBuffer.vulkanHandle);
+            m_core->getContext().getDevice().destroy(blas.indexBuffer.vulkanHandle);
+            m_core->getContext().getDevice().destroy(blas.vertexBuffer.vulkanHandle);
+
+            m_core->getContext().getDevice().freeMemory(blas.accelerationBuffer.deviceMemory);
+            m_core->getContext().getDevice().freeMemory(blas.indexBuffer.deviceMemory);
+            m_core->getContext().getDevice().freeMemory(blas.vertexBuffer.deviceMemory);
+        }
+
+        // destroy the TLAS, its data containers and free used memory blocks
+        TopLevelAccelerationStructure tlas = m_topLevelAccelerationStructure;
+        m_core->getContext().getDevice().destroyAccelerationStructureKHR(tlas.vulkanHandle, nullptr, m_rtxDispatcher);
+        m_core->getContext().getDevice().destroy(tlas.tlasBuffer.vulkanHandle);
+        m_core->getContext().getDevice().destroy(tlas.gpuBufferInstances.vulkanHandle);
+
+        m_core->getContext().getDevice().freeMemory(tlas.tlasBuffer.deviceMemory);
+        m_core->getContext().getDevice().freeMemory(tlas.gpuBufferInstances.deviceMemory);
+    }
+
+
+  
+    vk::CommandPool ASManager::createCommandPool() {
+        vk::CommandPool commandPool;
+        vk::CommandPoolCreateInfo commandPoolCreateInfo;
+        commandPoolCreateInfo.setQueueFamilyIndex(m_core->getContext().getQueueManager().getComputeQueues()[0].familyIndex);
+        vk::Result res = m_device->createCommandPool(&commandPoolCreateInfo, nullptr, &commandPool);
+        if (res != vk::Result::eSuccess) {
+            vkcv_log(LogLevel::ERROR, "ASManager: command pool could not be created! (%s)", vk::to_string(res).c_str());
+        }
+        return commandPool;
+    };
+
+    vk::CommandBuffer ASManager::createAndBeginCommandBuffer(vk::CommandPool commandPool)
+    {
+        vk::CommandBufferAllocateInfo commandBufferAllocateInfo{};
+        commandBufferAllocateInfo.setLevel(vk::CommandBufferLevel::ePrimary);
+        commandBufferAllocateInfo.setCommandPool(commandPool);
+        commandBufferAllocateInfo.setCommandBufferCount(1);
+        vk::CommandBuffer commandBuffer;
+        vk::Result result = m_device->allocateCommandBuffers(&commandBufferAllocateInfo, &commandBuffer);
+        if (result != vk::Result::eSuccess) {
+            vkcv_log(LogLevel::ERROR, "ASManager: command buffer for Acceleration Strucutre Build could not be allocated! (%s)", vk::to_string(result).c_str());
+        }
+		
+		const vk::CommandBufferBeginInfo beginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit);
+		commandBuffer.begin(beginInfo);
+        return commandBuffer;
+    }
+
+    void ASManager::submitCommandBuffer(vk::CommandPool commandPool, vk::CommandBuffer& commandBuffer)
+    {
+        commandBuffer.end();
+
+        vk::SubmitInfo submitInfo;
+        submitInfo.setCommandBufferCount(1);
+        submitInfo.setPCommandBuffers(&commandBuffer);
+
+        vkcv::Queue queue = m_core->getContext().getQueueManager().getComputeQueues()[0];
+
+        queue.handle.submit(submitInfo);
+        queue.handle.waitIdle();
+
+        m_device->freeCommandBuffers(commandPool, 1, &commandBuffer);
+        m_device->destroyCommandPool(commandPool);
+    }
+
+    vk::DeviceAddress ASManager::getBufferDeviceAddress(vk::Buffer buffer)
+    {
+        vk::BufferDeviceAddressInfo bufferDeviceAddressInfo(buffer);
+        return m_device->getBufferAddress(bufferDeviceAddressInfo);
+    }
+
+    void ASManager::createBuffer(RTXBuffer& buffer) {
+
+        vk::BufferCreateInfo bufferCreateInfo;
+        bufferCreateInfo.setFlags(vk::BufferCreateFlags());
+        bufferCreateInfo.setUsage(buffer.bufferUsageFlagBits);
+        bufferCreateInfo.setSize(buffer.deviceSize);
+
+        buffer.vulkanHandle = m_device->createBuffer(bufferCreateInfo);
+        vk::MemoryRequirements2 memoryRequirements2;
+        vk::MemoryDedicatedRequirements dedicatedRequirements;
+        vk::BufferMemoryRequirementsInfo2 bufferRequirements;
+
+        bufferRequirements.setBuffer(buffer.vulkanHandle);
+        memoryRequirements2.pNext = &dedicatedRequirements;
+        m_device->getBufferMemoryRequirements2(&bufferRequirements, &memoryRequirements2); 
+
+        vk::PhysicalDeviceMemoryProperties physicalDeviceMemoryProperties = m_core->getContext().getPhysicalDevice().getMemoryProperties();
+
+        uint32_t memoryTypeIndex = -1;
+        for (size_t i = 0; i < physicalDeviceMemoryProperties.memoryTypeCount; i++) {
+            if ((memoryRequirements2.memoryRequirements.memoryTypeBits & (1 << i))
+                    && (physicalDeviceMemoryProperties.memoryTypes[i].propertyFlags & buffer.memoryPropertyFlagBits) == buffer.memoryPropertyFlagBits) {
+                memoryTypeIndex = i;
+                break;
+            }
+        }
+
+        vk::MemoryAllocateInfo memoryAllocateInfo(
+            memoryRequirements2.memoryRequirements.size,  // size of allocation in bytes
+            memoryTypeIndex // index identifying a memory type from the memoryTypes array of the vk::PhysicalDeviceMemoryProperties structure.
+        );
+        vk::MemoryAllocateFlagsInfo allocateFlagsInfo(
+            vk::MemoryAllocateFlagBits::eDeviceAddress  // vk::MemoryAllocateFlags
+        );
+        memoryAllocateInfo.setPNext(&allocateFlagsInfo);  // extend memory allocate info with allocate flag info
+        buffer.deviceMemory = m_device->allocateMemory(memoryAllocateInfo);
+
+        uint32_t memoryOffset = 0;
+        m_device->bindBufferMemory(buffer.vulkanHandle, buffer.deviceMemory, memoryOffset);
+
+        // only fill data in case of CPU buffer
+        if (buffer.bufferType == RTXBufferType::STAGING) {
+            void* mapped = m_device->mapMemory(buffer.deviceMemory, memoryOffset, buffer.deviceSize);
+            std::memcpy(mapped, buffer.data, buffer.deviceSize);
+            m_device->unmapMemory(buffer.deviceMemory);
+        }
+    }
+
+    void ASManager::copyFromCPUToGPU(RTXBuffer& cpuBuffer, RTXBuffer& gpuBuffer) {
+        vk::CommandPool commandPool= createCommandPool();
+        vk::CommandBuffer commandBuffer= createAndBeginCommandBuffer(commandPool);
+        vk::BufferCopy bufferCopy;
+        bufferCopy.size = cpuBuffer.deviceSize;
+        commandBuffer.copyBuffer(cpuBuffer.vulkanHandle, gpuBuffer.vulkanHandle, 1, &bufferCopy);
+
+        submitCommandBuffer(commandPool,commandBuffer);     
+
+        m_device->destroyBuffer(cpuBuffer.vulkanHandle);
+        m_device->freeMemory(cpuBuffer.deviceMemory);
+    }
+
+    void ASManager::buildBLAS(RTXBuffer vertexBuffer, uint32_t vertexCount, RTXBuffer indexBuffer, uint32_t indexCount) {
+        // TODO: organize hierarchical structure of multiple BLAS
+
+        vk::DeviceAddress vertexBufferAddress = getBufferDeviceAddress(vertexBuffer.vulkanHandle);
+        vk::DeviceAddress indexBufferAddress = getBufferDeviceAddress(indexBuffer.vulkanHandle);
+
+        // triangle mesh data
+        vk::AccelerationStructureGeometryTrianglesDataKHR asTriangles(
+                vk::Format::eR32G32B32Sfloat,   // vertex format
+                vertexBufferAddress, // vertex buffer address (vk::DeviceOrHostAddressConstKHR)
+                3 * sizeof(float), // vertex stride (vk::DeviceSize)
+                uint32_t(vertexCount - 1), // maxVertex (uint32_t)
+                vk::IndexType::eUint32, // indexType (vk::IndexType) --> INFO: UINT16 oder UINT32!
+                indexBufferAddress, // indexData (vk::DeviceOrHostAddressConstKHR)
+                {} // transformData (vk::DeviceOrHostAddressConstKHR)
+        );
+
+        // Geometry data
+        vk::AccelerationStructureGeometryKHR asGeometry(
+                vk::GeometryTypeKHR::eTriangles, // The geometry type, e.g. triangles, AABBs, instances
+                asTriangles, // the geometry data
+                vk::GeometryFlagBitsKHR::eOpaque // This flag disables any-hit shaders to increase ray tracing performance
+        );
+
+        // Ranges for data lists
+        vk::AccelerationStructureBuildRangeInfoKHR asRangeInfo(
+                uint32_t(indexCount / 3), // the primitiveCount (uint32_t)
+                0, // primitiveOffset (uint32_t)
+                0, // firstVertex (uint32_t)
+                0  // transformOffset (uint32_t)
+        );
+
+        // Settings and array of geometries to build into BLAS
+        vk::AccelerationStructureBuildGeometryInfoKHR asBuildInfo(
+                vk::AccelerationStructureTypeKHR::eBottomLevel, // type of the AS: bottom vs. top
+                vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastTrace, // some flags for different purposes, e.g. efficiency
+                vk::BuildAccelerationStructureModeKHR::eBuild, // AS mode: build vs. update
+                {}, // src AS (this seems to be for copying AS)
+                {}, // dst AS (this seems to be for copying AS)
+                1, // the geometryCount. TODO: how many do we need?
+                &asGeometry // the next input entry would be a pointer to a pointer to geometries. Maybe geometryCount depends on the next entry?
+        );
+
+        // Calculate memory needed for AS
+        vk::AccelerationStructureBuildSizesInfoKHR asBuildSizesInfo;
+        m_device->getAccelerationStructureBuildSizesKHR(
+                vk::AccelerationStructureBuildTypeKHR::eDevice, // build on device instead of host
+                &asBuildInfo, // pointer to build info
+                &asRangeInfo.primitiveCount, // array of number of primitives per geometry
+                &asBuildSizesInfo,  // output pointer to store sizes
+                m_rtxDispatcher
+                );
+
+        // create buffer for acceleration structure
+        RTXBuffer blasBuffer;
+        blasBuffer.bufferType = RTXBufferType::ACCELERATION;
+        blasBuffer.deviceSize = asBuildSizesInfo.accelerationStructureSize;
+        blasBuffer.bufferUsageFlagBits = vk::BufferUsageFlagBits::eAccelerationStructureStorageKHR
+                | vk::BufferUsageFlagBits::eShaderDeviceAddress
+                | vk::BufferUsageFlagBits::eStorageBuffer;
+        blasBuffer.memoryPropertyFlagBits = { vk::MemoryPropertyFlagBits::eDeviceLocal };
+
+        createBuffer(blasBuffer); 
+
+        // Create an empty AS object
+        vk::AccelerationStructureCreateInfoKHR asCreateInfo(
+            vk::AccelerationStructureCreateFlagsKHR(), // creation flags
+            blasBuffer.vulkanHandle, // allocated AS buffer.
+            0,
+            asBuildSizesInfo.accelerationStructureSize, // size of the AS
+            asBuildInfo.type // type of the AS
+        );
+
+        // Create the intended AS object
+        vk::AccelerationStructureKHR blasKHR;
+        vk::Result res = m_device->createAccelerationStructureKHR(
+            &asCreateInfo, // AS create info
+            nullptr, // allocator callbacks
+            &blasKHR, // the AS
+            m_rtxDispatcher
+        );
+        if (res != vk::Result::eSuccess) {
+            vkcv_log(vkcv::LogLevel::ERROR, "The Bottom Level Acceleration Structure could not be build! (%s)", vk::to_string(res).c_str());
+        }
+        asBuildInfo.setDstAccelerationStructure(blasKHR);
+
+        // Create temporary scratch buffer used for building the AS
+        RTXBuffer scratchBuffer;
+        scratchBuffer.bufferType = RTXBufferType::SCRATCH;
+        scratchBuffer.deviceSize = asBuildSizesInfo.buildScratchSize;
+        scratchBuffer.bufferUsageFlagBits = vk::BufferUsageFlagBits::eShaderDeviceAddress
+            | vk::BufferUsageFlagBits::eStorageBuffer;
+        scratchBuffer.memoryPropertyFlagBits = { vk::MemoryPropertyFlagBits::eDeviceLocal };
+
+        createBuffer(scratchBuffer);
+
+        asBuildInfo.setScratchData(getBufferDeviceAddress(scratchBuffer.vulkanHandle));
+
+        // Pointer to rangeInfo, used later for build
+        vk::AccelerationStructureBuildRangeInfoKHR* pointerToRangeInfo = &asRangeInfo;
+
+        vk::CommandPool commandPool = createCommandPool();
+        vk::CommandBuffer commandBuffer = createAndBeginCommandBuffer(commandPool);
+
+        commandBuffer.buildAccelerationStructuresKHR(1, &asBuildInfo, &pointerToRangeInfo, m_rtxDispatcher);
+
+        submitCommandBuffer(commandPool, commandBuffer);
+  
+        m_core->getContext().getDevice().destroyBuffer(scratchBuffer.vulkanHandle, nullptr, m_rtxDispatcher);
+        m_core->getContext().getDevice().freeMemory(scratchBuffer.deviceMemory, nullptr, m_rtxDispatcher);
+                
+        BottomLevelAccelerationStructure blas = {
+                vertexBuffer,
+                indexBuffer,
+                blasBuffer,
+                blasKHR
+        };
+        m_bottomLevelAccelerationStructures.push_back(blas);
+    }
+
+    void ASManager::buildTLAS() {
+        // TODO: organize hierarchical structure of multiple BLAS
+
+        // We need an the device address of each BLAS --> TODO: for loop for bigger scenes
+        vk::AccelerationStructureDeviceAddressInfoKHR addressInfo(
+            m_bottomLevelAccelerationStructures[0].vulkanHandle
+        );
+        vk::DeviceAddress blasAddress = m_device->getAccelerationStructureAddressKHR(&addressInfo, m_rtxDispatcher);
+
+        std::array<std::array<float, 4>, 3> transformMatrix = {
+                std::array<float, 4>{1.f, 0.f, 0.f, 0.f},
+                std::array<float, 4>{0.f, 1.f, 0.f, 0.f},
+                std::array<float, 4>{0.f, 0.f, 1.f, 0.f},
+        };
+
+        vk::TransformMatrixKHR transformMatrixKhr(
+            transformMatrix   // std::array<std::array<float,4>,3> const&
+        );
+
+        vk::AccelerationStructureInstanceKHR accelerationStructureInstanceKhr(
+            transformMatrixKhr,    // vk::TransformMatrixKHR transform_ = {},
+            0,   // uint32_t instanceCustomIndex,
+            0xFF,    //uint32_t mask_ = {},
+            0,  // uint32_t instanceShaderBindingTableRecordOffset,
+            vk::GeometryInstanceFlagBitsKHR::eTriangleFacingCullDisable,    // vk::GeometryInstanceFlagsKHR
+            blasAddress // uint64_t accelerationStructureReference (the device address of the BLAS)
+        );
+
+        // create a buffer of instances on the device and upload the array of instances to it
+        RTXBuffer stagingBufferInstances;
+        stagingBufferInstances.bufferType = RTXBufferType::STAGING;
+        stagingBufferInstances.deviceSize = sizeof(accelerationStructureInstanceKhr);
+        stagingBufferInstances.data = &accelerationStructureInstanceKhr;
+        stagingBufferInstances.bufferUsageFlagBits = vk::BufferUsageFlagBits::eShaderDeviceAddress
+            | vk::BufferUsageFlagBits::eTransferSrc;
+        stagingBufferInstances.memoryPropertyFlagBits = vk::MemoryPropertyFlagBits::eHostCoherent
+            | vk::MemoryPropertyFlagBits::eHostVisible;
+
+        createBuffer(stagingBufferInstances);
+
+        RTXBuffer bufferInstances;
+        bufferInstances.bufferType = RTXBufferType::GPU;
+        bufferInstances.deviceSize = sizeof(accelerationStructureInstanceKhr);
+        bufferInstances.bufferUsageFlagBits = vk::BufferUsageFlagBits::eShaderDeviceAddress
+            | vk::BufferUsageFlagBits::eTransferDst
+			| vk::BufferUsageFlagBits::eTransferSrc
+			| vk::BufferUsageFlagBits::eAccelerationStructureBuildInputReadOnlyKHR;
+        bufferInstances.memoryPropertyFlagBits = vk::MemoryPropertyFlagBits::eDeviceLocal;
+
+        createBuffer(bufferInstances);
+        copyFromCPUToGPU(stagingBufferInstances, bufferInstances);   // automatically deletes and frees memory of stagingBufferInstances
+
+        vk::MemoryBarrier barrier;
+        barrier.setSrcAccessMask(vk::AccessFlagBits::eTransferWrite);
+        barrier.setDstAccessMask(vk::AccessFlagBits::eAccelerationStructureWriteKHR);
+        vk::CommandPool commandPool = createCommandPool();
+        vk::CommandBuffer commandBuffer = createAndBeginCommandBuffer(commandPool);
+        commandBuffer.pipelineBarrier(
+            vk::PipelineStageFlagBits::eTransfer,
+            vk::PipelineStageFlagBits::eAccelerationStructureBuildKHR,
+            {},1,&barrier,0, nullptr,0,nullptr);
+        submitCommandBuffer(commandPool, commandBuffer);
+
+
+        // ranging information for TLAS build
+        vk::AccelerationStructureBuildRangeInfoKHR asRangeInfo(
+            1, // primitiveCount -> number of instances
+            0, // primitiveOffset
+            0, // firstVertex
+            0 //transformOffset
+        );
+
+        vk::DeviceAddress bufferInstancesAddress = getBufferDeviceAddress(bufferInstances.vulkanHandle);
+
+        vk::AccelerationStructureGeometryInstancesDataKHR asInstances(
+            false,    // vk::Bool32 arrayOfPointers
+            bufferInstancesAddress    // vk::DeviceOrHostAddressConstKHR data_ = {}
+        );
+
+        // Geometry, in this case instances of BLAS
+        vk::AccelerationStructureGeometryKHR asGeometry(
+            vk::GeometryTypeKHR::eInstances,    // vk::GeometryTypeKHR geometryType_ = vk::GeometryTypeKHR::eTriangles
+            asInstances,    // vk::AccelerationStructureGeometryDataKHR geometry_ = {}
+            {}    // vk::GeometryFlagsKHR flags_ = {}
+        );
+
+        // Finally, create the TLAS
+        vk::AccelerationStructureBuildGeometryInfoKHR asBuildInfo(
+            vk::AccelerationStructureTypeKHR::eTopLevel, // type of the AS: bottom vs. top
+            vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastTrace, // some flags for different purposes, e.g. efficiency
+            vk::BuildAccelerationStructureModeKHR::eBuild, // AS mode: build vs. update
+            {}, // src AS (this seems to be for copying AS)
+            {}, // dst AS (this seems to be for copying AS)
+            1, // the geometryCount.
+            &asGeometry // the next input entry would be a pointer to a pointer to geometries. Maybe geometryCount depends on the next entry?
+        );
+
+        // AS size and scratch space size, given by count of instances (1)
+        vk::AccelerationStructureBuildSizesInfoKHR asSizeInfo;
+        m_core->getContext().getDevice().getAccelerationStructureBuildSizesKHR(
+            vk::AccelerationStructureBuildTypeKHR::eDevice,
+            &asBuildInfo,
+            &asRangeInfo.primitiveCount,
+            &asSizeInfo,
+            m_rtxDispatcher
+        );
+
+        // Create buffer for the TLAS
+        RTXBuffer tlasBuffer;
+        tlasBuffer.bufferType = RTXBufferType::ACCELERATION;
+        tlasBuffer.deviceSize = asSizeInfo.accelerationStructureSize;
+        tlasBuffer.bufferUsageFlagBits = vk::BufferUsageFlagBits::eAccelerationStructureStorageKHR
+            | vk::BufferUsageFlagBits::eShaderDeviceAddress
+            | vk::BufferUsageFlagBits::eStorageBuffer;
+
+        createBuffer(tlasBuffer);
+
+        // Create empty TLAS object
+        vk::AccelerationStructureCreateInfoKHR asCreateInfo(
+            {}, // creation flags
+            tlasBuffer.vulkanHandle, // allocated AS buffer.
+            0,
+            asSizeInfo.accelerationStructureSize, // size of the AS
+            asBuildInfo.type // type of the AS
+        );
+
+        vk::AccelerationStructureKHR tlas;
+        vk::Result res = m_device->createAccelerationStructureKHR(&asCreateInfo, nullptr, &tlas, m_rtxDispatcher);
+        if (res != vk::Result::eSuccess) {
+            vkcv_log(LogLevel::ERROR, "Top Level Acceleration Structure could not be created! (%s)", vk::to_string(res).c_str());
+        }
+        asBuildInfo.setDstAccelerationStructure(tlas);
+
+        // Create temporary scratch buffer used for building the AS
+        RTXBuffer tlasScratchBuffer;  // scratch buffer
+        tlasScratchBuffer.bufferType = RTXBufferType::ACCELERATION;
+        tlasScratchBuffer.deviceSize = asSizeInfo.buildScratchSize;
+        tlasScratchBuffer.bufferUsageFlagBits = vk::BufferUsageFlagBits::eShaderDeviceAddressKHR
+            | vk::BufferUsageFlagBits::eStorageBuffer;
+
+        createBuffer(tlasScratchBuffer);
+
+        vk::BufferDeviceAddressInfo tempBuildDataBufferDeviceAddressInfo(tlasScratchBuffer.vulkanHandle);
+        vk::DeviceAddress tempBuildBufferDataAddress = m_core->getContext().getDevice().getBufferAddressKHR(tempBuildDataBufferDeviceAddressInfo, m_rtxDispatcher);
+        asBuildInfo.setScratchData(tempBuildBufferDataAddress);
+
+        // Pointer to rangeInfo, used later for build
+        vk::AccelerationStructureBuildRangeInfoKHR* pointerToRangeInfo = &asRangeInfo;
+
+        // Build the TLAS.
+
+        vk::CommandPool commandPool2 = createCommandPool();
+        vk::CommandBuffer commandBuffer2 = createAndBeginCommandBuffer(commandPool2);
+        commandBuffer2.buildAccelerationStructuresKHR(1, &asBuildInfo, &pointerToRangeInfo, m_rtxDispatcher);
+        submitCommandBuffer(commandPool2, commandBuffer2);
+        
+        m_device->destroyBuffer(tlasScratchBuffer.vulkanHandle, nullptr, m_rtxDispatcher);
+        m_device->freeMemory(tlasScratchBuffer.deviceMemory, nullptr, m_rtxDispatcher);
+
+        m_topLevelAccelerationStructure = {
+                bufferInstances,
+                tlasBuffer,
+                tlasScratchBuffer,
+                tlas
+        };
+    }
+
+    TopLevelAccelerationStructure ASManager::getTLAS()
+    {
+        return m_topLevelAccelerationStructure;
+    }
+
+    BottomLevelAccelerationStructure ASManager::getBLAS(uint32_t id)
+    {
+        return m_bottomLevelAccelerationStructures[id];
+    }
+
+    const vk::DispatchLoaderDynamic& ASManager::getDispatcher() {
+        return m_rtxDispatcher;
+    }    
+}
\ No newline at end of file
diff --git a/projects/rtx_ambient_occlusion/src/RTX/ASManager.hpp b/projects/rtx_ambient_occlusion/src/RTX/ASManager.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..9a1374356cf4afdb04b0231f3c65e823ebe26dac
--- /dev/null
+++ b/projects/rtx_ambient_occlusion/src/RTX/ASManager.hpp
@@ -0,0 +1,188 @@
+#pragma once
+
+#include <vkcv/Core.hpp>
+
+namespace vkcv::rtx {
+
+    /**
+     * @brief Used for @#RTXBuffer creation depending on the @#RTXBufferType.
+     */
+    enum class RTXBufferType {
+        STAGING,
+        GPU,
+        ACCELERATION,
+        SHADER_BINDING,
+        SCRATCH
+    };
+
+    /**
+     * @brief Used as a container to handle buffer creation and destruction in RTX-specific use cases.
+     */
+    struct RTXBuffer {
+        RTXBufferType bufferType;
+        void* data;
+        vk::DeviceSize deviceSize;
+        vk::DeviceMemory deviceMemory;
+        vk::BufferUsageFlags bufferUsageFlagBits;
+        vk::MemoryPropertyFlags memoryPropertyFlagBits;
+        vk::Buffer vulkanHandle;
+    };
+
+    /**
+     * @brief Used as a container to handle bottom-level acceleration structure (BLAS) construction and destruction.
+     */
+    struct BottomLevelAccelerationStructure {
+        RTXBuffer vertexBuffer;
+        RTXBuffer indexBuffer;
+        RTXBuffer accelerationBuffer;
+        vk::AccelerationStructureKHR vulkanHandle;
+    };
+
+    /**
+     * @brief Used as a container to handle top-level acceleration structure (TLAS) construction and destruction.
+     */
+    struct TopLevelAccelerationStructure {
+        RTXBuffer gpuBufferInstances;
+        RTXBuffer tlasBuffer;
+        RTXBuffer tempBuildDataBuffer;  // scratch buffer
+        vk::AccelerationStructureKHR vulkanHandle;
+    };
+
+    /**
+     * @brief A class for managing acceleration structures (bottom, top).
+     */
+    class ASManager {
+    private:
+        Core* m_core;
+        const vk::Device* m_device;
+        std::vector<BottomLevelAccelerationStructure> m_bottomLevelAccelerationStructures;
+        TopLevelAccelerationStructure m_topLevelAccelerationStructure;
+        vk::DispatchLoaderDynamic m_rtxDispatcher;
+        
+        /**
+         * Creates a command pool.
+         */
+        vk::CommandPool createCommandPool();
+
+        /**
+         * @brief Takes a @p cmdPool, allocates a command buffer and starts recording it.
+         * @param cmdPool The command pool.
+         * @return The allocated command buffer.
+         */
+        vk::CommandBuffer createAndBeginCommandBuffer( vk::CommandPool cmdPool);
+
+        /**
+         * @brief Ends the @p commandBuffer,submits it and waits. Afterwards frees the @p commandBuffer.
+         * @param commandPool The command pool.
+         * @param commandBuffer The command buffer.
+         */
+        void submitCommandBuffer(vk::CommandPool commandPool, vk::CommandBuffer& commandBuffer);
+
+        /**
+         * @brief Gets the device address of a @p buffer.
+         * @param buffer The buffer.
+         * @return The device address of the @p buffer.
+         */
+        vk::DeviceAddress getBufferDeviceAddress(vk::Buffer buffer);
+
+        /**
+         * @brief Copies @p cpuBuffer data into a @p gpuBuffer. Typical use case is a staging buffer (namely,
+         * @p cpuBuffer) used to fill a @p gpuBuffer with @p vk::MemoryPropertyFlagBits::eDeviceLocal flag set.
+         * @p cpuBuffer is destroyed and freed after copying.
+         * @param cpuBuffer
+         * @param gpuBuffer
+         */
+        void copyFromCPUToGPU(RTXBuffer &cpuBuffer, RTXBuffer &gpuBuffer);
+
+    public:
+
+        /**
+         * @brief Constructor of @#ASManager .
+         * @param core
+         */
+        ASManager(vkcv::Core *core);
+
+        /**
+         * @brief Default destructor of @#ASManager.
+         */
+        ~ASManager();
+
+        /**
+         * @brief Returns a @#RTXBuffer object holding data of type @p T.
+         * @param data The input data of type @p T.
+         * @return A @#RTXBuffer object holding @p data of type @p T.
+         */
+        template<class T>
+        RTXBuffer makeBufferFromData(std::vector<T>& data) {
+
+            // first: Staging Buffer creation
+            RTXBuffer stagingBuffer;
+            stagingBuffer.bufferType = RTXBufferType::STAGING;
+            stagingBuffer.deviceSize = sizeof(T) * data.size();
+            stagingBuffer.data = data.data();
+            stagingBuffer.bufferUsageFlagBits = vk::BufferUsageFlagBits::eTransferSrc;
+            stagingBuffer.memoryPropertyFlagBits = vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostVisible;
+
+            createBuffer(stagingBuffer);
+
+            // second: create AS Buffer
+            RTXBuffer targetBuffer;
+            targetBuffer.bufferType = RTXBufferType::GPU;
+            targetBuffer.deviceSize = sizeof(T) * data.size();
+            targetBuffer.bufferUsageFlagBits = vk::BufferUsageFlagBits::eAccelerationStructureBuildInputReadOnlyKHR
+                | vk::BufferUsageFlagBits::eTransferDst
+                | vk::BufferUsageFlagBits::eStorageBuffer
+                | vk::BufferUsageFlagBits::eShaderDeviceAddress;
+            targetBuffer.memoryPropertyFlagBits = vk::MemoryPropertyFlagBits::eDeviceLocal;
+
+            createBuffer(targetBuffer);
+
+            // copy from CPU to GPU
+            copyFromCPUToGPU(stagingBuffer, targetBuffer);
+
+            return targetBuffer;
+        }
+
+        /**
+        * @brief A helper function used by @#ASManager::makeBufferFromData. Creates a fully initialized @#RTXBuffer object
+        * from partially specified @p buffer. All missing data of @p buffer will be completed by this function.
+        * @param buffer The partially specified @#RTXBuffer holding that part of information which is required for
+        * successfully creating a @p vk::Buffer object.
+        */
+        void createBuffer(RTXBuffer& buffer);
+
+        /**
+         * @brief Build a Bottom Level Acceleration Structure (BLAS) object from given @p vertexBuffer and @p indexBuffer.
+         * @param[in] vertexBuffer The vertex data.
+         * @param[in] vertexCount The amount of vertices in @p vertexBuffer.
+         * @param[in] indexBuffer The index data.
+         * @param[in] indexCount The amount of indices in @p indexBuffer.
+         */
+        void buildBLAS(RTXBuffer vertexBuffer, uint32_t vertexCount, RTXBuffer indexBuffer, uint32_t indexCount);
+
+        /**
+         * @brief Build a Top Level Acceleration Structure (TLAS) object from the created
+         * @#ASManager::m_accelerationStructures objects.
+         */
+        void buildTLAS();
+
+        /**
+        * @brief Returns the top-level acceleration structure (TLAS) buffer.
+        * @return A @#TopLevelAccelerationStructure object holding the TLAS.
+        */
+        TopLevelAccelerationStructure getTLAS();
+
+        /**
+         * @brief Returns the bottom-level acceleration structure at @p id.
+         * @param id The ID used for indexing.
+         * @return The specified @#BottomLevelAccelerationStructure object.
+         */
+        BottomLevelAccelerationStructure getBLAS(uint32_t id);
+
+        /**
+         * @brief Returns the dispatcher member variable for access in the @#RTXModule.
+         * @return The dispatcher member variable.
+         */
+        const vk::DispatchLoaderDynamic& getDispatcher();
+    };
+}
\ No newline at end of file
diff --git a/projects/rtx_ambient_occlusion/src/RTX/RTX.cpp b/projects/rtx_ambient_occlusion/src/RTX/RTX.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bd5453abae353eed6739c41dbff084a0c909aaca
--- /dev/null
+++ b/projects/rtx_ambient_occlusion/src/RTX/RTX.cpp
@@ -0,0 +1,330 @@
+#include "RTX.hpp"
+
+namespace vkcv::rtx {
+
+    RTXModule::RTXModule(Core* core, ASManager* asManager, std::vector<float>& vertices,
+        std::vector<uint32_t>& indices, std::vector<vkcv::DescriptorSetHandle>& descriptorSetHandles){
+        m_core = core;
+        m_asManager = asManager;
+        // build acceleration structures BLAS then TLAS --> see ASManager
+        RTXBuffer vertexBuffer = m_asManager->makeBufferFromData(vertices);
+        RTXBuffer indexBuffer  = m_asManager->makeBufferFromData(indices);
+        m_asManager->buildBLAS(vertexBuffer, vertices.size(), indexBuffer,indices.size());
+        m_asManager->buildTLAS();
+        RTXDescriptors(descriptorSetHandles);
+    }
+
+
+    RTXModule::~RTXModule()
+    {
+        m_core->getContext().getDevice().destroy(m_pipeline);
+        m_core->getContext().getDevice().destroy(m_pipelineLayout);
+        m_core->getContext().getDevice().destroy(m_shaderBindingTableBuffer.vulkanHandle);
+        m_core->getContext().getDevice().freeMemory(m_shaderBindingTableBuffer.deviceMemory);
+    }
+    
+    void RTXModule::createShaderBindingTable(uint32_t shaderCount) {
+        vk::PhysicalDeviceRayTracingPipelinePropertiesKHR rayTracingProperties;
+
+        vk::PhysicalDeviceProperties2 physicalProperties;
+        physicalProperties.pNext = &rayTracingProperties;
+
+        m_core->getContext().getPhysicalDevice().getProperties2(&physicalProperties);
+
+        vk::DeviceSize shaderBindingTableSize = rayTracingProperties.shaderGroupHandleSize * shaderCount;
+
+
+        m_shaderBindingTableBuffer.bufferType = RTXBufferType::SHADER_BINDING;
+        m_shaderBindingTableBuffer.bufferUsageFlagBits = vk::BufferUsageFlagBits::eShaderBindingTableKHR | vk::BufferUsageFlagBits::eShaderDeviceAddressKHR;
+        m_shaderBindingTableBuffer.memoryPropertyFlagBits = vk::MemoryPropertyFlagBits::eHostVisible;
+        m_shaderBindingTableBuffer.deviceSize = shaderBindingTableSize;
+
+        m_asManager->createBuffer(m_shaderBindingTableBuffer);
+
+        void* shaderHandleStorage = (void*)malloc(sizeof(uint8_t) * shaderBindingTableSize);
+        
+        if (m_core->getContext().getDevice().getRayTracingShaderGroupHandlesKHR(m_pipeline, 0, shaderCount, shaderBindingTableSize,
+            shaderHandleStorage, m_asManager->getDispatcher()) != vk::Result::eSuccess) {
+            vkcv_log(LogLevel::ERROR, "Could not retrieve shader binding table group handles.");
+        }
+
+        m_shaderGroupBaseAlignment =  rayTracingProperties.shaderGroupBaseAlignment;
+        uint8_t* mapped = (uint8_t*) m_core->getContext().getDevice().mapMemory(m_shaderBindingTableBuffer.deviceMemory, 0, shaderBindingTableSize);
+        for (size_t i = 0; i < shaderCount; i++) {
+            memcpy(mapped, (uint8_t*)shaderHandleStorage + (i * rayTracingProperties.shaderGroupHandleSize), rayTracingProperties.shaderGroupHandleSize);
+            mapped += m_shaderGroupBaseAlignment;
+        }
+
+        m_core->getContext().getDevice().unmapMemory(m_shaderBindingTableBuffer.deviceMemory);
+        free(shaderHandleStorage);        
+    }
+
+    ShaderBindingTableRegions RTXModule::createRegions() {
+        // Define offsets for the RTX shaders. RayGen is the first allocated shader. Each following shader is
+        // shifted by shaderGroupBaseAlignment.
+        // Offset Calculation: offset = count of previous shaders * m_shaderGroupBaseAlignment
+        // Regions are hard coded
+        vk::DeviceSize rayGenOffset = 0; //First Shader group -> offset 0 * m_shaderGroupBaseAlignment =0
+        vk::DeviceSize missOffset = m_shaderGroupBaseAlignment;//Second group, offset = 1 * m_shaderGroupBaseAlignment
+        vk::DeviceSize closestHitOffset = 2 * m_shaderGroupBaseAlignment; //Third group, offset = 2 * m_shaderGroupBaseAlignment
+        vk::DeviceSize shaderBindingTableSize = m_shaderGroupBaseAlignment * 3; // 3 hardcoded to rtx-shader count
+
+        auto m_rtxDispatcher = vk::DispatchLoaderDynamic((PFN_vkGetInstanceProcAddr)m_core->getContext().getInstance().getProcAddr("vkGetInstanceProcAddr"));
+        m_rtxDispatcher.init(m_core->getContext().getInstance());
+        
+
+        // Create regions for the shader binding table buffer which are used for vk::CommandBuffer::traceRaysKHR
+        vk::StridedDeviceAddressRegionKHR rgenRegion;
+        vk::BufferDeviceAddressInfoKHR shaderBindingTableAddressInfo(m_shaderBindingTableBuffer.vulkanHandle);
+        rgenRegion.deviceAddress = m_core->getContext().getDevice().getBufferAddressKHR(shaderBindingTableAddressInfo, m_rtxDispatcher) + rayGenOffset;
+        rgenRegion.setStride(shaderBindingTableSize);
+        rgenRegion.setSize(shaderBindingTableSize);
+        vk::StridedDeviceAddressRegionKHR rmissRegion;
+        rmissRegion.deviceAddress = m_core->getContext().getDevice().getBufferAddressKHR(shaderBindingTableAddressInfo, m_rtxDispatcher) + missOffset;
+        rmissRegion.setStride(shaderBindingTableSize);
+        rmissRegion.setSize(shaderBindingTableSize);
+        vk::StridedDeviceAddressRegionKHR rchitRegion;
+        rchitRegion.deviceAddress = m_core->getContext().getDevice().getBufferAddressKHR(shaderBindingTableAddressInfo, m_rtxDispatcher) + closestHitOffset;
+        rchitRegion.setStride(shaderBindingTableSize);
+        rchitRegion.setSize(shaderBindingTableSize);
+        vk::StridedDeviceAddressRegionKHR rcallRegion = {};
+
+        return ShaderBindingTableRegions{ rgenRegion, rmissRegion, rchitRegion, rcallRegion };
+    }
+
+
+    void RTXModule::RTXDescriptors(std::vector<vkcv::DescriptorSetHandle>& descriptorSetHandles)
+    {
+        //TLAS-Descriptor-Write
+        TopLevelAccelerationStructure tlas = m_asManager->getTLAS();
+        vk::WriteDescriptorSetAccelerationStructureKHR AccelerationDescriptor = {};
+        AccelerationDescriptor.accelerationStructureCount = 1;
+        const TopLevelAccelerationStructure constTLAS = tlas;
+        AccelerationDescriptor.pAccelerationStructures = &constTLAS.vulkanHandle;
+
+        vk::WriteDescriptorSet tlasWrite;
+        tlasWrite.setPNext(&AccelerationDescriptor);
+        tlasWrite.setDstSet(m_core->getVulkanDescriptorSet(descriptorSetHandles[0]));
+        tlasWrite.setDstBinding(1);
+        tlasWrite.setDstArrayElement(0);
+        tlasWrite.setDescriptorCount(1);
+        tlasWrite.setDescriptorType(vk::DescriptorType::eAccelerationStructureKHR);
+        m_core->getContext().getDevice().updateDescriptorSets(tlasWrite, nullptr);
+        tlasWrite.setDstBinding(2);
+        m_core->getContext().getDevice().updateDescriptorSets(tlasWrite, nullptr);
+
+        //INDEX & VERTEX BUFFER
+        BottomLevelAccelerationStructure blas = m_asManager->getBLAS(0);//HARD CODED
+
+        //VERTEX BUFFER
+        vk::DescriptorBufferInfo vertexInfo = {};
+        vertexInfo.setBuffer(blas.vertexBuffer.vulkanHandle);
+        vertexInfo.setOffset(0);
+        vertexInfo.setRange(blas.vertexBuffer.deviceSize); //maybe check if size is correct
+
+        vk::WriteDescriptorSet vertexWrite;
+        vertexWrite.setDstSet(m_core->getVulkanDescriptorSet(descriptorSetHandles[0]));
+        vertexWrite.setDstBinding(3);
+        vertexWrite.setDescriptorCount(1);
+        vertexWrite.setDescriptorType(vk::DescriptorType::eStorageBuffer);
+        vertexWrite.setPBufferInfo(&vertexInfo);
+        m_core->getContext().getDevice().updateDescriptorSets(vertexWrite, nullptr);
+
+        //INDEXBUFFER
+        vk::DescriptorBufferInfo indexInfo = {};
+        indexInfo.setBuffer(blas.indexBuffer.vulkanHandle);
+        indexInfo.setOffset(0);
+        indexInfo.setRange(blas.indexBuffer.deviceSize); //maybe check if size is correct
+
+        vk::WriteDescriptorSet indexWrite;
+        indexWrite.setDstSet(m_core->getVulkanDescriptorSet(descriptorSetHandles[0]));
+        indexWrite.setDstBinding(4);
+        indexWrite.setDescriptorCount(1);
+        indexWrite.setDescriptorType(vk::DescriptorType::eStorageBuffer);
+        indexWrite.setPBufferInfo(&indexInfo);
+        m_core->getContext().getDevice().updateDescriptorSets(indexWrite, nullptr);
+    }
+
+    void RTXModule::createRTXPipelineAndLayout(uint32_t pushConstantSize, std::vector<DescriptorSetLayoutHandle> descriptorSetLayouts, ShaderProgram &rtxShader) {
+        // -- process vkcv::ShaderProgram into vk::ShaderModule
+        std::vector<uint32_t> rayGenShaderCode = rtxShader.getShaderBinary(ShaderStage::RAY_GEN);
+
+        vk::ShaderModuleCreateInfo rayGenShaderModuleInfo(
+            vk::ShaderModuleCreateFlags(), // vk::ShaderModuleCreateFlags flags_,
+            rayGenShaderCode.size() * sizeof(uint32_t), // size_t codeSize
+            rayGenShaderCode.data() // const uint32_t* pCode
+        );
+        vk::ShaderModule rayGenShaderModule = m_core->getContext().getDevice().createShaderModule(rayGenShaderModuleInfo);
+        if (!rayGenShaderModule) {
+            vkcv_log(LogLevel::ERROR, "The Ray Generation Shader Module could not be created!");
+        }
+
+        std::vector<uint32_t> rayMissShaderCode = rtxShader.getShaderBinary(ShaderStage::RAY_MISS);
+        vk::ShaderModuleCreateInfo rayMissShaderModuleInfo(
+            vk::ShaderModuleCreateFlags(), // vk::ShaderModuleCreateFlags flags_,
+            rayMissShaderCode.size() * sizeof(uint32_t), //size_t codeSize
+            rayMissShaderCode.data() // const uint32_t* pCode
+        );
+
+        vk::ShaderModule rayMissShaderModule = m_core->getContext().getDevice().createShaderModule(rayMissShaderModuleInfo);
+        if (!rayMissShaderModule) {
+            vkcv_log(LogLevel::ERROR, "The Ray Miss Shader Module could not be created!");
+        }
+
+        std::vector<uint32_t> rayClosestHitShaderCode = rtxShader.getShaderBinary(ShaderStage::RAY_CLOSEST_HIT);
+        vk::ShaderModuleCreateInfo rayClosestHitShaderModuleInfo(
+            vk::ShaderModuleCreateFlags(), // vk::ShaderModuleCreateFlags flags_,
+            rayClosestHitShaderCode.size() * sizeof(uint32_t), //size_t codeSize
+            rayClosestHitShaderCode.data() // const uint32_t* pCode_
+        );
+        vk::ShaderModule rayClosestHitShaderModule = m_core->getContext().getDevice().createShaderModule(rayClosestHitShaderModuleInfo);
+        if (!rayClosestHitShaderModule) {
+            vkcv_log(LogLevel::ERROR, "The Ray Closest Hit Shader Module could not be created!");
+        }
+
+        // -- PipelineShaderStages
+
+        // ray generation
+        vk::PipelineShaderStageCreateInfo rayGenShaderStageInfo(
+        vk::PipelineShaderStageCreateFlags(), // vk::PipelineShaderStageCreateFlags flags_ = {}
+            vk::ShaderStageFlagBits::eRaygenKHR, // vk::ShaderStageFlagBits stage_ = vk::ShaderStageFlagBits::eVertex,
+            rayGenShaderModule, // vk::ShaderModule module_ = {},
+            "main" // const char* pName_ = {},
+        );
+
+        // ray miss
+        vk::PipelineShaderStageCreateInfo rayMissShaderStageInfo(
+            vk::PipelineShaderStageCreateFlags(), // vk::PipelineShaderStageCreateFlags flags_ = {}
+            vk::ShaderStageFlagBits::eMissKHR, // vk::ShaderStageFlagBits stage_ = vk::ShaderStageFlagBits::eVertex,
+            rayMissShaderModule, // vk::ShaderModule module_ = {},
+            "main" // const char* pName_ = {},
+        );
+      
+        // ray closest hit
+        vk::PipelineShaderStageCreateInfo rayClosestHitShaderStageInfo(
+            vk::PipelineShaderStageCreateFlags(), // vk::PipelineShaderStageCreateFlags flags_ = {}
+            vk::ShaderStageFlagBits::eClosestHitKHR, // vk::ShaderStageFlagBits stage_ = vk::ShaderStageFlagBits::eVertex,
+            rayClosestHitShaderModule, // vk::ShaderModule module_ = {},
+            "main" // const char* pName_ = {},
+        );
+        
+        std::vector<vk::PipelineShaderStageCreateInfo> shaderStages = {   // HARD CODED. TODO: Support more shader stages.
+            rayGenShaderStageInfo, rayMissShaderStageInfo, rayClosestHitShaderStageInfo
+        };
+
+        // -- PipelineLayouts
+
+        std::vector<vk::RayTracingShaderGroupCreateInfoKHR> shaderGroups(shaderStages.size());
+        // Ray Gen
+        shaderGroups[0] = vk::RayTracingShaderGroupCreateInfoKHR(
+            vk::RayTracingShaderGroupTypeKHR::eGeneral, // vk::RayTracingShaderGroupTypeKHR type_ = vk::RayTracingShaderGroupTypeKHR::eGeneral
+            0, // uint32_t generalShader_ = {}
+            VK_SHADER_UNUSED_KHR, // uint32_t closestHitShader_ = {}
+            VK_SHADER_UNUSED_KHR, // uint32_t anyHitShader_ = {}
+            VK_SHADER_UNUSED_KHR, // uint32_t intersectionShader_ = {}
+            nullptr // const void* pShaderGroupCaptureReplayHandle_ = {}
+        );
+        // Ray Miss
+        shaderGroups[1] = vk::RayTracingShaderGroupCreateInfoKHR(
+            vk::RayTracingShaderGroupTypeKHR::eGeneral, // vk::RayTracingShaderGroupTypeKHR type_ = vk::RayTracingShaderGroupTypeKHR::eGeneral
+            1, // uint32_t generalShader_ = {}
+            VK_SHADER_UNUSED_KHR, // uint32_t closestHitShader_ = {}
+            VK_SHADER_UNUSED_KHR, // uint32_t anyHitShader_ = {}
+            VK_SHADER_UNUSED_KHR, // uint32_t intersectionShader_ = {}
+            nullptr // const void* pShaderGroupCaptureReplayHandle_ = {}
+        );
+        // Ray Closest Hit
+        shaderGroups[2] = vk::RayTracingShaderGroupCreateInfoKHR(
+            vk::RayTracingShaderGroupTypeKHR::eTrianglesHitGroup, // vk::RayTracingShaderGroupTypeKHR type_ = vk::RayTracingShaderGroupTypeKHR::eGeneral
+            VK_SHADER_UNUSED_KHR, // uint32_t generalShader_ = {}
+            2, // uint32_t closestHitShader_ = {}
+            VK_SHADER_UNUSED_KHR, // uint32_t anyHitShader_ = {}
+            VK_SHADER_UNUSED_KHR, // uint32_t intersectionShader_ = {}
+            nullptr // const void* pShaderGroupCaptureReplayHandle_ = {}
+        );
+
+        std::vector<vk::DescriptorSetLayout> descriptorSetLayoutsVulkan;
+        for (size_t i=0; i<descriptorSetLayouts.size(); i++) {
+            descriptorSetLayoutsVulkan.push_back(m_core->getVulkanDescriptorSetLayout(descriptorSetLayouts[i]));
+        }
+
+        vk::PushConstantRange pushConstant(
+            vk::ShaderStageFlagBits::eRaygenKHR | vk::ShaderStageFlagBits::eClosestHitKHR | vk::ShaderStageFlagBits::eMissKHR, // vk::ShaderStageFlags stageFlags_ = {},
+            0, // uint32_t offset_ = {},
+            pushConstantSize // uint32_t size_ = {}
+        );
+
+        vk::PipelineLayoutCreateInfo rtxPipelineLayoutCreateInfo(
+            vk::PipelineLayoutCreateFlags(), // vk::PipelineLayoutCreateFlags flags_ = {}
+            (uint32_t) descriptorSetLayoutsVulkan.size(), // uint32_t setLayoutCount_ = {}     HARD CODED (2)
+            descriptorSetLayoutsVulkan.data(), // const vk::DescriptorSetLayout* pSetLayouts_ = {}
+            1, //            0, // uint32_t pushConstantRangeCount_ = {}
+            &pushConstant //            nullptr // const vk::PushConstantRange* pPushConstantRanges_ = {}
+        );
+
+        m_pipelineLayout = m_core->getContext().getDevice().createPipelineLayout(rtxPipelineLayoutCreateInfo);
+        if (!m_pipelineLayout) {
+            vkcv_log(LogLevel::ERROR, "The RTX Pipeline Layout could not be created!");
+        }
+
+        vk::PipelineLibraryCreateInfoKHR rtxPipelineLibraryCreateInfo(
+            0, // uint32_t libraryCount_ = {}
+            nullptr // const vk::Pipeline* pLibraries_ = {}
+        );
+
+        // -- RTX Pipeline
+
+        vk::RayTracingPipelineCreateInfoKHR rtxPipelineInfo(
+            vk::PipelineCreateFlags(), // vk::PipelineCreateFlags flags_ = {}
+            (uint32_t) shaderStages.size(), // uint32_t stageCount_ = {}
+            shaderStages.data(), // const vk::PipelineShaderStageCreateInfo* pStages_ = {}
+            (uint32_t) shaderGroups.size(), // uint32_t groupCount_ = {}
+            shaderGroups.data(), // const vk::RayTracingShaderGroupCreateInfoKHR* pGroups_ = {}
+            16, // uint32_t maxPipelineRayRecursionDepth_ = {}
+            &rtxPipelineLibraryCreateInfo, // const vk::PipelineLibraryCreateInfoKHR* pLibraryInfo_ = {}
+            nullptr, // const vk::RayTracingPipelineInterfaceCreateInfoKHR* pLibraryInterface_ = {}
+            nullptr, // const vk::PipelineDynamicStateCreateInfo* pDynamicState_ = {}
+            m_pipelineLayout // vk::PipelineLayout layout_ = {}
+        );
+
+        auto pipelineResult = m_core->getContext().getDevice().createRayTracingPipelineKHR(
+                vk::DeferredOperationKHR(),
+                vk::PipelineCache(),
+                rtxPipelineInfo,
+                nullptr,
+                m_asManager->getDispatcher()
+        );
+
+        if (pipelineResult.result != vk::Result::eSuccess) {
+            vkcv_log(LogLevel::ERROR, "The RTX Pipeline could not be created!");
+        }
+
+        m_pipeline = pipelineResult.value;
+
+        m_core->getContext().getDevice().destroy(rayGenShaderModule);
+        m_core->getContext().getDevice().destroy(rayMissShaderModule);
+        m_core->getContext().getDevice().destroy(rayClosestHitShaderModule);
+
+        // TODO: add possibility of more than one shader per stage
+        createShaderBindingTable(shaderStages.size());
+    }
+
+    vk::Pipeline RTXModule::getPipeline() {
+        return m_pipeline;
+    }
+
+    vk::Buffer RTXModule::getShaderBindingTableBuffer()
+    {
+        return m_shaderBindingTableBuffer.vulkanHandle;
+    }
+
+    vk::DeviceSize RTXModule::getShaderGroupBaseAlignment()
+    {
+        return m_shaderGroupBaseAlignment;
+    }
+
+    vk::PipelineLayout RTXModule::getPipelineLayout() {
+        return m_pipelineLayout;
+    }
+
+}
\ No newline at end of file
diff --git a/projects/rtx_ambient_occlusion/src/RTX/RTX.hpp b/projects/rtx_ambient_occlusion/src/RTX/RTX.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ece4ac8e6707039ab3fe166750140a8040aed924
--- /dev/null
+++ b/projects/rtx_ambient_occlusion/src/RTX/RTX.hpp
@@ -0,0 +1,100 @@
+#pragma once
+
+#include <vector>
+#include "vulkan/vulkan.hpp"
+#include <vkcv/Core.hpp>
+#include "ASManager.hpp"
+
+namespace vkcv::rtx {
+
+    //struct that holds all shader binding table regions
+    struct ShaderBindingTableRegions {
+        vk::StridedDeviceAddressRegionKHR rgenRegion;
+        vk::StridedDeviceAddressRegionKHR rmissRegion;
+        vk::StridedDeviceAddressRegionKHR rchitRegion;
+        vk::StridedDeviceAddressRegionKHR rcallRegion;
+    };
+
+    class RTXModule {
+    private:
+
+        Core* m_core;
+        ASManager* m_asManager;
+        vk::Pipeline m_pipeline;
+        vk::PipelineLayout m_pipelineLayout;
+        RTXBuffer m_shaderBindingTableBuffer;
+        vk::DeviceSize m_shaderGroupBaseAlignment;
+
+    public:
+
+        /**
+         * @brief Initializes the @#RTXModule with scene data.
+         * @param core The reference to the @#Core.
+         * @param asManager The reference to the @#ASManager.
+         * @param vertices The vertex data of the scene.
+         * @param indices The index data of the scene.
+         * @param descriptorSetHandles The descriptor set handles for RTX.
+         */
+        RTXModule(Core* core, ASManager* asManager, std::vector<float>& vertices,
+            std::vector<uint32_t>& indices, std::vector<vkcv::DescriptorSetHandle>& descriptorSetHandles);
+
+        /**
+         * @brief Default #RTXModule destructor.
+         */
+        ~RTXModule();
+
+        /**
+         * @brief Returns the RTX pipeline.
+         * @return The RTX pipeline.
+         */
+        vk::Pipeline getPipeline();
+
+        /**
+         * @brief Returns the shader binding table buffer.
+         * @return The shader binding table buffer.
+         */
+        vk::Buffer getShaderBindingTableBuffer();
+
+        /**
+         * @brief Returns the shader group base alignment for partitioning the shader binding table buffer.
+         * @return The shader group base alignment.
+         */
+        vk::DeviceSize getShaderGroupBaseAlignment();
+
+        /**
+         * @brief Returns the RTX pipeline layout.
+         * @return The RTX pipeline layout.
+         */
+        vk::PipelineLayout getPipelineLayout();
+
+        /**
+         * @brief Sets the shader group base alignment and creates the shader binding table by allocating a shader
+         * binding table buffer. The allocation depends on @p shaderCount and the shader group base alignment.
+         * @param shaderCount The amount of shaders to be used for RTX.
+         */
+        void createShaderBindingTable(uint32_t shaderCount);
+
+        /**
+         * @brief Divides the shader binding table into regions for each shader type
+         * (ray generation, ray miss, ray closest hit, callable) and returns them as a struct.
+         * @return The struct holding all four regions of type vk::StridedDeviceAddressRegionKHR.
+         */
+        ShaderBindingTableRegions createRegions();
+
+        /**
+         * @brief Creates Descriptor-Writes for RTX
+         * @param descriptorSetHandles The descriptorSetHandles for RTX.
+         */
+        void RTXDescriptors(std::vector<vkcv::DescriptorSetHandle>& descriptorSetHandles);
+
+        /**
+         * @brief Creates the RTX pipeline and the RTX pipeline layout. Currently, only RayGen, RayClosestHit and
+         * RayMiss are supported.
+         * @param pushConstantSize The size of the push constant used in the RTX shaders.
+         * @param descriptorSetLayouts The descriptor set layout handles.
+         * @param rtxShader The RTX shader program.
+         */
+        void createRTXPipelineAndLayout(uint32_t pushConstantSize, std::vector<DescriptorSetLayoutHandle> descriptorSetLayouts, ShaderProgram &rtxShader);
+    };
+
+}
diff --git a/projects/rtx_ambient_occlusion/src/RTX/RTXExtensions.cpp b/projects/rtx_ambient_occlusion/src/RTX/RTXExtensions.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0bc6a4d3dff358bdeab74d9d5da695f81c3675c7
--- /dev/null
+++ b/projects/rtx_ambient_occlusion/src/RTX/RTXExtensions.cpp
@@ -0,0 +1,66 @@
+#include "RTXExtensions.hpp"
+
+namespace vkcv::rtx{
+
+RTXExtensions::RTXExtensions()
+{
+
+    // prepare needed raytracing extensions
+    m_instanceExtensions = {
+            VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME
+    };
+    m_deviceExtensions = {
+            VK_KHR_MAINTENANCE3_EXTENSION_NAME,
+            VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME,
+            VK_KHR_SPIRV_1_4_EXTENSION_NAME,
+            VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME
+    };
+
+    // get all features required by the device extensions
+    for (auto deviceExtension : m_deviceExtensions) {
+        m_features.requireExtension(deviceExtension);
+    }
+	
+	m_features.requireExtensionFeature<vk::PhysicalDeviceDescriptorIndexingFeatures>(
+			VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME,
+			[](vk::PhysicalDeviceDescriptorIndexingFeatures& features) {}
+	);
+	
+	m_features.requireExtensionFeature<vk::PhysicalDeviceBufferDeviceAddressFeatures>(
+			VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME,
+			[](vk::PhysicalDeviceBufferDeviceAddressFeatures& features) {
+				features.setBufferDeviceAddress(true);
+			}
+	);
+	
+    m_features.requireExtensionFeature<vk::PhysicalDeviceAccelerationStructureFeaturesKHR>(
+			VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME,
+			[](vk::PhysicalDeviceAccelerationStructureFeaturesKHR& features) {
+				features.setAccelerationStructure(true);
+			}
+	);
+	
+    m_features.requireExtensionFeature<vk::PhysicalDeviceRayTracingPipelineFeaturesKHR>(
+			VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME,
+			[](vk::PhysicalDeviceRayTracingPipelineFeaturesKHR& features) {
+				features.setRayTracingPipeline(true);
+			}
+	);
+}
+
+std::vector<const char*> RTXExtensions::getInstanceExtensions()
+{
+	return m_instanceExtensions;
+}
+
+std::vector<const char*> RTXExtensions::getDeviceExtensions()
+{
+	return m_deviceExtensions;
+}
+
+vkcv::Features RTXExtensions::getFeatures()
+{
+	return m_features;
+}
+
+}
\ No newline at end of file
diff --git a/projects/rtx_ambient_occlusion/src/RTX/RTXExtensions.hpp b/projects/rtx_ambient_occlusion/src/RTX/RTXExtensions.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..5ad5ae0769b75aa18672648b5eb3cb598b6f8d47
--- /dev/null
+++ b/projects/rtx_ambient_occlusion/src/RTX/RTXExtensions.hpp
@@ -0,0 +1,36 @@
+#pragma once
+#include <vector>
+#include <vkcv/Core.hpp>
+
+namespace vkcv::rtx{
+
+class RTXExtensions {
+private:
+    std::vector<const char*> m_instanceExtensions;  // the instance extensions needed for using RTX
+    std::vector<const char*> m_deviceExtensions;    // the device extensions needed for using RTX
+    vkcv::Features m_features;                      // the features needed to be enabled for using RTX
+public:
+
+    RTXExtensions();
+    ~RTXExtensions()=default;
+    /**
+     * @brief Returns the raytracing instance extensions.
+     * @return The raytracing instance extensions.
+     */
+    std::vector<const char*> getInstanceExtensions();
+
+    /**
+     * @brief Returns the raytracing device extensions.
+     * @return The raytracing device extensions.
+     */
+    std::vector<const char*> getDeviceExtensions();
+
+    /**
+     * @brief Returns the raytracing features.
+     * @return The raytracing features.
+     */
+    vkcv::Features getFeatures();
+                    
+
+};
+}
\ No newline at end of file
diff --git a/projects/rtx_ambient_occlusion/src/main.cpp b/projects/rtx_ambient_occlusion/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2a9d5d204d9c09a5c007494fadd2b3e2285ba372
--- /dev/null
+++ b/projects/rtx_ambient_occlusion/src/main.cpp
@@ -0,0 +1,150 @@
+#include <vkcv/Core.hpp>
+#include <vkcv/camera/CameraManager.hpp>
+#include <chrono>
+#include <vkcv/shader/GLSLCompiler.hpp>
+#include "RTX/RTX.hpp"
+#include "RTX/RTXExtensions.hpp"
+#include "teapot.hpp"
+
+/**
+ * Note: This project is based on the following tutorial https://github.com/Apress/Ray-Tracing-Gems-II/tree/main/Chapter_16.
+ */
+
+int main(int argc, const char** argv) {
+	const std::string applicationName = "RTX Ambient Occlusion";
+
+	// prepare raytracing extensions. IMPORTANT: configure compiler to build in 64 bit mode
+	vkcv::rtx::RTXExtensions rtxExtensions;
+	std::vector<const char*> raytracingInstanceExtensions = rtxExtensions.getInstanceExtensions();
+
+	std::vector<const char*> instanceExtensions = {};   // add some more instance extensions, if needed
+	instanceExtensions.insert(instanceExtensions.end(), raytracingInstanceExtensions.begin(), raytracingInstanceExtensions.end());  // merge together all instance extensions
+
+	vkcv::Features features = rtxExtensions.getFeatures();  // all features required by the RTX device extensions
+	features.requireExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
+	vkcv::Core core = vkcv::Core::create(
+			applicationName,
+			VK_MAKE_VERSION(0, 0, 1),
+			{ vk::QueueFlagBits::eGraphics ,vk::QueueFlagBits::eCompute , vk::QueueFlagBits::eTransfer },
+			features,
+			instanceExtensions
+	);
+
+	vkcv::rtx::ASManager asManager(&core);
+
+	vkcv::WindowHandle windowHandle = core.createWindow(applicationName, 800, 600, true);
+
+	vkcv::camera::CameraManager cameraManager(core.getWindow(windowHandle));
+	auto camHandle = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+	
+	cameraManager.getCamera(camHandle).setPosition(glm::vec3(0, 0, -3));
+	cameraManager.getCamera(camHandle).setNearFar(0.1f, 30.0f);
+	
+    // get Teapot vertices and indices
+    Teapot teapot;
+    std::vector<float> vertices = teapot.getVertices();
+    std::vector<uint32_t> indices = teapot.getIndices();
+
+	vkcv::shader::GLSLCompiler compiler (vkcv::shader::GLSLCompileTarget::RAY_TRACING);
+
+	vkcv::ShaderProgram rtxShaderProgram;
+	compiler.compile(vkcv::ShaderStage::RAY_GEN, std::filesystem::path("resources/shaders/ambientOcclusion.rgen"),
+		[&rtxShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+            rtxShaderProgram.addShader(shaderStage, path);
+		});
+
+	compiler.compile(vkcv::ShaderStage::RAY_CLOSEST_HIT, std::filesystem::path("resources/shaders/ambientOcclusion.rchit"),
+		[&rtxShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+            rtxShaderProgram.addShader(shaderStage, path);
+		});
+
+	compiler.compile(vkcv::ShaderStage::RAY_MISS, std::filesystem::path("resources/shaders/ambientOcclusion.rmiss"),
+		[&rtxShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+            rtxShaderProgram.addShader(shaderStage, path);
+		});
+
+	std::vector<vkcv::DescriptorSetHandle> descriptorSetHandles;
+	std::vector<vkcv::DescriptorSetLayoutHandle> descriptorSetLayoutHandles;
+
+	vkcv::DescriptorSetLayoutHandle rtxShaderDescriptorSetLayout = core.createDescriptorSetLayout(rtxShaderProgram.getReflectedDescriptors().at(0));
+	vkcv::DescriptorSetHandle rtxShaderDescriptorSet = core.createDescriptorSet(rtxShaderDescriptorSetLayout);
+	descriptorSetHandles.push_back(rtxShaderDescriptorSet);
+	descriptorSetLayoutHandles.push_back(rtxShaderDescriptorSetLayout);
+
+	// init RTXModule
+	vkcv::rtx::RTXModule rtxModule(&core, &asManager, vertices, indices,descriptorSetHandles);
+
+	struct RaytracingPushConstantData {
+	    glm::vec4 camera_position;   // as origin for ray generation
+	    glm::vec4 camera_right;      // for computing ray direction
+	    glm::vec4 camera_up;         // for computing ray direction
+	    glm::vec4 camera_forward;    // for computing ray direction
+	};
+
+	uint32_t pushConstantSize = sizeof(RaytracingPushConstantData);
+
+    rtxModule.createRTXPipelineAndLayout(pushConstantSize, descriptorSetLayoutHandles, rtxShaderProgram);
+
+	vk::Pipeline rtxPipeline = rtxModule.getPipeline();
+	vk::PipelineLayout rtxPipelineLayout = rtxModule.getPipelineLayout();
+
+	vkcv::rtx::ShaderBindingTableRegions rtxRegions = rtxModule.createRegions();
+
+	vkcv::ImageHandle depthBuffer;
+
+	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+
+	vkcv::DescriptorWrites rtxWrites;
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
+		if ((!depthBuffer) ||
+			(swapchainWidth != core.getImageWidth(depthBuffer)) ||
+			((swapchainHeight != core.getImageHeight(depthBuffer)))) {
+			depthBuffer = core.createImage(
+					vk::Format::eD32Sfloat,
+					vkcv::ImageConfig(
+							swapchainWidth,
+							swapchainHeight
+					)
+			);
+		}
+		
+		cameraManager.update(dt);
+
+		const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer };
+		
+		RaytracingPushConstantData raytracingPushData;
+		raytracingPushData.camera_position = glm::vec4(cameraManager.getActiveCamera().getPosition(),0);
+		raytracingPushData.camera_right = glm::vec4(glm::cross(cameraManager.getActiveCamera().getUp(), cameraManager.getActiveCamera().getFront()), 0);
+		raytracingPushData.camera_up = glm::vec4(cameraManager.getActiveCamera().getUp(),0);
+		raytracingPushData.camera_forward = glm::vec4(cameraManager.getActiveCamera().getFront(),0);
+
+		vkcv::PushConstants pushConstantsRTX = vkcv::pushConstants<RaytracingPushConstantData>();
+		pushConstantsRTX.appendDrawcall(raytracingPushData);
+
+		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+
+		rtxWrites.writeStorageImage(0, swapchainInput);
+		core.writeDescriptorSet(rtxShaderDescriptorSet, rtxWrites);
+
+		core.prepareImageForStorage(cmdStream, swapchainInput);
+
+		core.recordRayGenerationToCmdStream(
+			cmdStream,
+			rtxPipeline,
+			rtxPipelineLayout,
+			rtxRegions.rgenRegion,
+			rtxRegions.rmissRegion,
+			rtxRegions.rchitRegion,
+			rtxRegions.rcallRegion,
+			{ vkcv::useDescriptorSet(0, rtxShaderDescriptorSet) },
+			pushConstantsRTX,
+			windowHandle);
+
+		core.prepareSwapchainImageForPresent(cmdStream);
+		core.submitCommandStream(cmdStream);
+	});
+
+	return 0;
+}
diff --git a/projects/rtx_ambient_occlusion/src/teapot.hpp b/projects/rtx_ambient_occlusion/src/teapot.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..1f35400a313b230f92098fa2ef59cc3c8f3a30df
--- /dev/null
+++ b/projects/rtx_ambient_occlusion/src/teapot.hpp
@@ -0,0 +1,7114 @@
+#pragma once
+#include <vector>
+
+class Teapot {
+public:
+    /**
+     * @brief The default constructor.
+     */
+    Teapot() = default;
+
+    /**
+     * default destructor
+     */
+    ~Teapot() = default;
+
+    /**
+     * @brief Returns the vertex data of the teapot.
+     * @return The vertex data of the teapot.
+     */
+    std::vector<float> getVertices() {
+        return m_teapotVertices;
+    }
+
+    /**
+     * @brief Returns the index data of the teapot.
+     * @return The index data of the teapot.
+     */
+    std::vector<uint32_t> getIndices() {
+        return m_teapotIndices;
+    }
+
+private:
+
+    std::vector<float> m_teapotVertices =
+            {
+                    0.69999999f, 0.45000005f, 0.00000001f,
+                    0.69098389f, 0.44999996f, 0.11485600f,
+                    0.66483200f, 0.45000011f, 0.22332802f,
+                    0.62288797f, 0.45000002f, 0.32407200f,
+                    0.56649601f, 0.45000008f, 0.41574404f,
+                    0.49699998f, 0.45000005f, 0.49700001f,
+                    0.41574395f, 0.45000005f, 0.56649601f,
+                    0.32407200f, 0.45000008f, 0.62288797f,
+                    0.22332799f, 0.45000005f, 0.66483200f,
+                    0.11485596f, 0.45000005f, 0.69098401f,
+                    0.00000000f, 0.45000005f, 0.69999999f,
+                    0.69296241f, 0.46771872f, 0.00000001f,
+                    0.68403697f, 0.46771869f, 0.11370128f,
+                    0.65814799f, 0.46771878f, 0.22108276f,
+                    0.61662567f, 0.46771872f, 0.32081389f,
+                    0.56080067f, 0.46771872f, 0.41156429f,
+                    0.49200332f, 0.46771872f, 0.49200332f,
+                    0.41156423f, 0.46771872f, 0.56080067f,
+                    0.32081389f, 0.46771872f, 0.61662567f,
+                    0.22108275f, 0.46771872f, 0.65814799f,
+                    0.11370124f, 0.46771872f, 0.68403703f,
+                    0.00000000f, 0.46771872f, 0.69296241f,
+                    0.69020003f, 0.48150003f, 0.00000001f,
+                    0.68131018f, 0.48149997f, 0.11324803f,
+                    0.65552443f, 0.48150006f, 0.22020143f,
+                    0.61416763f, 0.48150000f, 0.31953502f,
+                    0.55856514f, 0.48150009f, 0.40992361f,
+                    0.49004203f, 0.48150003f, 0.49004203f,
+                    0.40992361f, 0.48150006f, 0.55856514f,
+                    0.31953505f, 0.48150003f, 0.61416763f,
+                    0.22020143f, 0.48150003f, 0.65552437f,
+                    0.11324799f, 0.48150003f, 0.68131030f,
+                    0.00000000f, 0.48150003f, 0.69020003f,
+                    0.69111252f, 0.49134374f, 0.00000001f,
+                    0.68221092f, 0.49134374f, 0.11339775f,
+                    0.65639102f, 0.49134377f, 0.22049254f,
+                    0.61497957f, 0.49134377f, 0.31995746f,
+                    0.55930358f, 0.49134377f, 0.41046557f,
+                    0.49068987f, 0.49134374f, 0.49068987f,
+                    0.41046554f, 0.49134374f, 0.55930358f,
+                    0.31995746f, 0.49134374f, 0.61497957f,
+                    0.22049253f, 0.49134377f, 0.65639102f,
+                    0.11339770f, 0.49134374f, 0.68221098f,
+                    0.00000000f, 0.49134374f, 0.69111252f,
+                    0.69510001f, 0.49725008f, 0.00000001f,
+                    0.68614709f, 0.49725005f, 0.11405202f,
+                    0.66017824f, 0.49725014f, 0.22176473f,
+                    0.61852777f, 0.49725008f, 0.32180351f,
+                    0.56253058f, 0.49725008f, 0.41283381f,
+                    0.49352103f, 0.49725008f, 0.49352100f,
+                    0.41283381f, 0.49725008f, 0.56253058f,
+                    0.32180351f, 0.49725008f, 0.61852777f,
+                    0.22176471f, 0.49725008f, 0.66017818f,
+                    0.11405198f, 0.49725008f, 0.68614715f,
+                    0.00000000f, 0.49725008f, 0.69510001f,
+                    0.70156252f, 0.49921876f, 0.00000001f,
+                    0.69252634f, 0.49921870f, 0.11511238f,
+                    0.66631603f, 0.49921876f, 0.22382653f,
+                    0.62427837f, 0.49921873f, 0.32479537f,
+                    0.56776059f, 0.49921879f, 0.41667202f,
+                    0.49810940f, 0.49921876f, 0.49810940f,
+                    0.41667199f, 0.49921879f, 0.56776053f,
+                    0.32479542f, 0.49921876f, 0.62427843f,
+                    0.22382650f, 0.49921876f, 0.66631603f,
+                    0.11511234f, 0.49921876f, 0.69252640f,
+                    0.00000000f, 0.49921876f, 0.70156252f,
+                    0.70989996f, 0.49725002f, 0.00000001f,
+                    0.70075637f, 0.49724996f, 0.11648039f,
+                    0.67423457f, 0.49725008f, 0.22648652f,
+                    0.63169742f, 0.49724999f, 0.32865530f,
+                    0.57450789f, 0.49725002f, 0.42162383f,
+                    0.50402898f, 0.49725002f, 0.50402898f,
+                    0.42162377f, 0.49725002f, 0.57450783f,
+                    0.32865530f, 0.49725002f, 0.63169742f,
+                    0.22648649f, 0.49725005f, 0.67423463f,
+                    0.11648035f, 0.49725002f, 0.70075643f,
+                    0.00000000f, 0.49725002f, 0.70989996f,
+                    0.71951252f, 0.49134377f, 0.00000001f,
+                    0.71024513f, 0.49134374f, 0.11805762f,
+                    0.68336421f, 0.49134380f, 0.22955328f,
+                    0.64025098f, 0.49134377f, 0.33310553f,
+                    0.58228713f, 0.49134383f, 0.42733288f,
+                    0.51085389f, 0.49134377f, 0.51085389f,
+                    0.42733288f, 0.49134377f, 0.58228713f,
+                    0.33310553f, 0.49134377f, 0.64025104f,
+                    0.22955328f, 0.49134380f, 0.68336427f,
+                    0.11805756f, 0.49134377f, 0.71024519f,
+                    0.00000000f, 0.49134377f, 0.71951252f,
+                    0.72979999f, 0.48150003f, 0.00000001f,
+                    0.72040009f, 0.48149997f, 0.11974559f,
+                    0.69313490f, 0.48150006f, 0.23283541f,
+                    0.64940518f, 0.48150000f, 0.33786824f,
+                    0.59061253f, 0.48150009f, 0.43344283f,
+                    0.51815796f, 0.48150003f, 0.51815796f,
+                    0.43344280f, 0.48150006f, 0.59061259f,
+                    0.33786821f, 0.48150003f, 0.64940524f,
+                    0.23283540f, 0.48150003f, 0.69313484f,
+                    0.11974555f, 0.48150003f, 0.72040015f,
+                    0.00000000f, 0.48150003f, 0.72979999f,
+                    0.74016249f, 0.46771878f, 0.00000001f,
+                    0.73062909f, 0.46771872f, 0.12144586f,
+                    0.70297670f, 0.46771881f, 0.23614147f,
+                    0.65862614f, 0.46771878f, 0.34266564f,
+                    0.59899879f, 0.46771878f, 0.43959734f,
+                    0.52551538f, 0.46771878f, 0.52551538f,
+                    0.43959731f, 0.46771878f, 0.59899873f,
+                    0.34266564f, 0.46771878f, 0.65862620f,
+                    0.23614144f, 0.46771878f, 0.70297670f,
+                    0.12144582f, 0.46771878f, 0.73062921f,
+                    0.00000000f, 0.46771878f, 0.74016249f,
+                    0.75000000f, 0.45000005f, 0.00000001f,
+                    0.74033999f, 0.44999996f, 0.12306000f,
+                    0.71232003f, 0.45000011f, 0.23928000f,
+                    0.66737998f, 0.45000002f, 0.34721997f,
+                    0.60696000f, 0.45000008f, 0.44544002f,
+                    0.53250003f, 0.45000005f, 0.53250003f,
+                    0.44543999f, 0.45000005f, 0.60696000f,
+                    0.34722000f, 0.45000008f, 0.66738003f,
+                    0.23927999f, 0.45000005f, 0.71231997f,
+                    0.12305996f, 0.45000005f, 0.74033999f,
+                    0.00000000f, 0.45000005f, 0.75000000f,
+                    0.00000000f, 0.45000005f, -0.69999999f,
+                    0.11485599f, 0.44999996f, -0.69098389f,
+                    0.22332802f, 0.45000011f, -0.66483200f,
+                    0.32407200f, 0.45000002f, -0.62288797f,
+                    0.41574404f, 0.45000008f, -0.56649601f,
+                    0.49700001f, 0.45000005f, -0.49699998f,
+                    0.56649601f, 0.45000005f, -0.41574395f,
+                    0.62288797f, 0.45000008f, -0.32407200f,
+                    0.66483200f, 0.45000005f, -0.22332799f,
+                    0.69098401f, 0.45000005f, -0.11485595f,
+                    0.69999999f, 0.45000005f, 0.00000001f,
+                    0.00000000f, 0.46771872f, -0.69296241f,
+                    0.11370128f, 0.46771869f, -0.68403697f,
+                    0.22108276f, 0.46771878f, -0.65814799f,
+                    0.32081389f, 0.46771872f, -0.61662567f,
+                    0.41156429f, 0.46771872f, -0.56080067f,
+                    0.49200332f, 0.46771872f, -0.49200332f,
+                    0.56080067f, 0.46771872f, -0.41156423f,
+                    0.61662567f, 0.46771872f, -0.32081389f,
+                    0.65814799f, 0.46771872f, -0.22108275f,
+                    0.68403703f, 0.46771872f, -0.11370123f,
+                    0.69296241f, 0.46771872f, 0.00000001f,
+                    0.00000000f, 0.48150003f, -0.69020003f,
+                    0.11324802f, 0.48149997f, -0.68131018f,
+                    0.22020143f, 0.48150006f, -0.65552443f,
+                    0.31953502f, 0.48150000f, -0.61416763f,
+                    0.40992361f, 0.48150009f, -0.55856514f,
+                    0.49004203f, 0.48150003f, -0.49004203f,
+                    0.55856514f, 0.48150006f, -0.40992361f,
+                    0.61416763f, 0.48150003f, -0.31953505f,
+                    0.65552437f, 0.48150003f, -0.22020143f,
+                    0.68131030f, 0.48150003f, -0.11324798f,
+                    0.69020003f, 0.48150003f, 0.00000001f,
+                    0.00000000f, 0.49134374f, -0.69111252f,
+                    0.11339774f, 0.49134374f, -0.68221092f,
+                    0.22049254f, 0.49134377f, -0.65639102f,
+                    0.31995746f, 0.49134377f, -0.61497957f,
+                    0.41046557f, 0.49134377f, -0.55930358f,
+                    0.49068987f, 0.49134374f, -0.49068987f,
+                    0.55930358f, 0.49134374f, -0.41046554f,
+                    0.61497957f, 0.49134374f, -0.31995746f,
+                    0.65639102f, 0.49134377f, -0.22049253f,
+                    0.68221098f, 0.49134374f, -0.11339770f,
+                    0.69111252f, 0.49134374f, 0.00000001f,
+                    0.00000000f, 0.49725008f, -0.69510001f,
+                    0.11405201f, 0.49725005f, -0.68614709f,
+                    0.22176473f, 0.49725014f, -0.66017824f,
+                    0.32180351f, 0.49725008f, -0.61852777f,
+                    0.41283381f, 0.49725008f, -0.56253058f,
+                    0.49352100f, 0.49725008f, -0.49352103f,
+                    0.56253058f, 0.49725008f, -0.41283381f,
+                    0.61852777f, 0.49725008f, -0.32180351f,
+                    0.66017818f, 0.49725008f, -0.22176471f,
+                    0.68614715f, 0.49725008f, -0.11405197f,
+                    0.69510001f, 0.49725008f, 0.00000001f,
+                    0.00000000f, 0.49921876f, -0.70156252f,
+                    0.11511238f, 0.49921870f, -0.69252634f,
+                    0.22382653f, 0.49921876f, -0.66631603f,
+                    0.32479537f, 0.49921873f, -0.62427837f,
+                    0.41667202f, 0.49921879f, -0.56776059f,
+                    0.49810940f, 0.49921876f, -0.49810940f,
+                    0.56776053f, 0.49921879f, -0.41667199f,
+                    0.62427843f, 0.49921876f, -0.32479542f,
+                    0.66631603f, 0.49921876f, -0.22382650f,
+                    0.69252640f, 0.49921876f, -0.11511233f,
+                    0.70156252f, 0.49921876f, 0.00000001f,
+                    0.00000000f, 0.49725002f, -0.70989996f,
+                    0.11648039f, 0.49724996f, -0.70075637f,
+                    0.22648652f, 0.49725008f, -0.67423457f,
+                    0.32865530f, 0.49724999f, -0.63169742f,
+                    0.42162383f, 0.49725002f, -0.57450789f,
+                    0.50402898f, 0.49725002f, -0.50402898f,
+                    0.57450783f, 0.49725002f, -0.42162377f,
+                    0.63169742f, 0.49725002f, -0.32865530f,
+                    0.67423463f, 0.49725005f, -0.22648649f,
+                    0.70075643f, 0.49725002f, -0.11648034f,
+                    0.70989996f, 0.49725002f, 0.00000001f,
+                    0.00000000f, 0.49134377f, -0.71951252f,
+                    0.11805761f, 0.49134374f, -0.71024513f,
+                    0.22955328f, 0.49134380f, -0.68336421f,
+                    0.33310553f, 0.49134377f, -0.64025098f,
+                    0.42733288f, 0.49134383f, -0.58228713f,
+                    0.51085389f, 0.49134377f, -0.51085389f,
+                    0.58228713f, 0.49134377f, -0.42733288f,
+                    0.64025104f, 0.49134377f, -0.33310553f,
+                    0.68336427f, 0.49134380f, -0.22955328f,
+                    0.71024519f, 0.49134377f, -0.11805756f,
+                    0.71951252f, 0.49134377f, 0.00000001f,
+                    0.00000000f, 0.48150003f, -0.72979999f,
+                    0.11974558f, 0.48149997f, -0.72040009f,
+                    0.23283541f, 0.48150006f, -0.69313490f,
+                    0.33786824f, 0.48150000f, -0.64940518f,
+                    0.43344283f, 0.48150009f, -0.59061253f,
+                    0.51815796f, 0.48150003f, -0.51815796f,
+                    0.59061259f, 0.48150006f, -0.43344280f,
+                    0.64940524f, 0.48150003f, -0.33786821f,
+                    0.69313484f, 0.48150003f, -0.23283540f,
+                    0.72040015f, 0.48150003f, -0.11974554f,
+                    0.72979999f, 0.48150003f, 0.00000001f,
+                    0.00000000f, 0.46771878f, -0.74016249f,
+                    0.12144586f, 0.46771872f, -0.73062909f,
+                    0.23614147f, 0.46771881f, -0.70297670f,
+                    0.34266564f, 0.46771878f, -0.65862614f,
+                    0.43959734f, 0.46771878f, -0.59899879f,
+                    0.52551538f, 0.46771878f, -0.52551538f,
+                    0.59899873f, 0.46771878f, -0.43959731f,
+                    0.65862620f, 0.46771878f, -0.34266564f,
+                    0.70297670f, 0.46771878f, -0.23614144f,
+                    0.73062921f, 0.46771878f, -0.12144581f,
+                    0.74016249f, 0.46771878f, 0.00000001f,
+                    0.00000000f, 0.45000005f, -0.75000000f,
+                    0.12305999f, 0.44999996f, -0.74033999f,
+                    0.23928000f, 0.45000011f, -0.71232003f,
+                    0.34721997f, 0.45000002f, -0.66737998f,
+                    0.44544002f, 0.45000008f, -0.60696000f,
+                    0.53250003f, 0.45000005f, -0.53250003f,
+                    0.60696000f, 0.45000005f, -0.44543999f,
+                    0.66738003f, 0.45000008f, -0.34722000f,
+                    0.71231997f, 0.45000005f, -0.23927999f,
+                    0.74033999f, 0.45000005f, -0.12305995f,
+                    0.75000000f, 0.45000005f, 0.00000001f,
+                    0.00000000f, 0.45000005f, 0.69999999f,
+                    -0.11485599f, 0.44999996f, 0.69098389f,
+                    -0.22332802f, 0.45000011f, 0.66483200f,
+                    -0.32407200f, 0.45000002f, 0.62288797f,
+                    -0.41574404f, 0.45000008f, 0.56649601f,
+                    -0.49700001f, 0.45000005f, 0.49699998f,
+                    -0.56649601f, 0.45000005f, 0.41574395f,
+                    -0.62288797f, 0.45000008f, 0.32407200f,
+                    -0.66483200f, 0.45000005f, 0.22332799f,
+                    -0.69098401f, 0.45000005f, 0.11485597f,
+                    -0.69999999f, 0.45000005f, 0.00000001f,
+                    0.00000000f, 0.46771872f, 0.69296241f,
+                    -0.11370128f, 0.46771869f, 0.68403697f,
+                    -0.22108276f, 0.46771878f, 0.65814799f,
+                    -0.32081389f, 0.46771872f, 0.61662567f,
+                    -0.41156429f, 0.46771872f, 0.56080067f,
+                    -0.49200332f, 0.46771872f, 0.49200332f,
+                    -0.56080067f, 0.46771872f, 0.41156423f,
+                    -0.61662567f, 0.46771872f, 0.32081389f,
+                    -0.65814799f, 0.46771872f, 0.22108275f,
+                    -0.68403703f, 0.46771872f, 0.11370125f,
+                    -0.69296241f, 0.46771872f, 0.00000001f,
+                    0.00000000f, 0.48150003f, 0.69020003f,
+                    -0.11324802f, 0.48149997f, 0.68131018f,
+                    -0.22020143f, 0.48150006f, 0.65552443f,
+                    -0.31953502f, 0.48150000f, 0.61416763f,
+                    -0.40992361f, 0.48150009f, 0.55856514f,
+                    -0.49004203f, 0.48150003f, 0.49004203f,
+                    -0.55856514f, 0.48150006f, 0.40992361f,
+                    -0.61416763f, 0.48150003f, 0.31953505f,
+                    -0.65552437f, 0.48150003f, 0.22020143f,
+                    -0.68131030f, 0.48150003f, 0.11324800f,
+                    -0.69020003f, 0.48150003f, 0.00000001f,
+                    0.00000000f, 0.49134374f, 0.69111252f,
+                    -0.11339774f, 0.49134374f, 0.68221092f,
+                    -0.22049254f, 0.49134377f, 0.65639102f,
+                    -0.31995746f, 0.49134377f, 0.61497957f,
+                    -0.41046557f, 0.49134377f, 0.55930358f,
+                    -0.49068987f, 0.49134374f, 0.49068987f,
+                    -0.55930358f, 0.49134374f, 0.41046554f,
+                    -0.61497957f, 0.49134374f, 0.31995746f,
+                    -0.65639102f, 0.49134377f, 0.22049253f,
+                    -0.68221098f, 0.49134374f, 0.11339771f,
+                    -0.69111252f, 0.49134374f, 0.00000001f,
+                    0.00000000f, 0.49725008f, 0.69510001f,
+                    -0.11405201f, 0.49725005f, 0.68614709f,
+                    -0.22176473f, 0.49725014f, 0.66017824f,
+                    -0.32180351f, 0.49725008f, 0.61852777f,
+                    -0.41283381f, 0.49725008f, 0.56253058f,
+                    -0.49352100f, 0.49725008f, 0.49352103f,
+                    -0.56253058f, 0.49725008f, 0.41283381f,
+                    -0.61852777f, 0.49725008f, 0.32180351f,
+                    -0.66017818f, 0.49725008f, 0.22176471f,
+                    -0.68614715f, 0.49725008f, 0.11405198f,
+                    -0.69510001f, 0.49725008f, 0.00000001f,
+                    0.00000000f, 0.49921876f, 0.70156252f,
+                    -0.11511238f, 0.49921870f, 0.69252634f,
+                    -0.22382653f, 0.49921876f, 0.66631603f,
+                    -0.32479537f, 0.49921873f, 0.62427837f,
+                    -0.41667202f, 0.49921879f, 0.56776059f,
+                    -0.49810940f, 0.49921876f, 0.49810940f,
+                    -0.56776053f, 0.49921879f, 0.41667199f,
+                    -0.62427843f, 0.49921876f, 0.32479542f,
+                    -0.66631603f, 0.49921876f, 0.22382650f,
+                    -0.69252640f, 0.49921876f, 0.11511235f,
+                    -0.70156252f, 0.49921876f, 0.00000001f,
+                    0.00000000f, 0.49725002f, 0.70989996f,
+                    -0.11648039f, 0.49724996f, 0.70075637f,
+                    -0.22648652f, 0.49725008f, 0.67423457f,
+                    -0.32865530f, 0.49724999f, 0.63169742f,
+                    -0.42162383f, 0.49725002f, 0.57450789f,
+                    -0.50402898f, 0.49725002f, 0.50402898f,
+                    -0.57450783f, 0.49725002f, 0.42162377f,
+                    -0.63169742f, 0.49725002f, 0.32865530f,
+                    -0.67423463f, 0.49725005f, 0.22648649f,
+                    -0.70075643f, 0.49725002f, 0.11648036f,
+                    -0.70989996f, 0.49725002f, 0.00000001f,
+                    0.00000000f, 0.49134377f, 0.71951252f,
+                    -0.11805761f, 0.49134374f, 0.71024513f,
+                    -0.22955328f, 0.49134380f, 0.68336421f,
+                    -0.33310553f, 0.49134377f, 0.64025098f,
+                    -0.42733288f, 0.49134383f, 0.58228713f,
+                    -0.51085389f, 0.49134377f, 0.51085389f,
+                    -0.58228713f, 0.49134377f, 0.42733288f,
+                    -0.64025104f, 0.49134377f, 0.33310553f,
+                    -0.68336427f, 0.49134380f, 0.22955328f,
+                    -0.71024519f, 0.49134377f, 0.11805757f,
+                    -0.71951252f, 0.49134377f, 0.00000001f,
+                    0.00000000f, 0.48150003f, 0.72979999f,
+                    -0.11974558f, 0.48149997f, 0.72040009f,
+                    -0.23283541f, 0.48150006f, 0.69313490f,
+                    -0.33786824f, 0.48150000f, 0.64940518f,
+                    -0.43344283f, 0.48150009f, 0.59061253f,
+                    -0.51815796f, 0.48150003f, 0.51815796f,
+                    -0.59061259f, 0.48150006f, 0.43344280f,
+                    -0.64940524f, 0.48150003f, 0.33786821f,
+                    -0.69313484f, 0.48150003f, 0.23283540f,
+                    -0.72040015f, 0.48150003f, 0.11974555f,
+                    -0.72979999f, 0.48150003f, 0.00000001f,
+                    0.00000000f, 0.46771878f, 0.74016249f,
+                    -0.12144586f, 0.46771872f, 0.73062909f,
+                    -0.23614147f, 0.46771881f, 0.70297670f,
+                    -0.34266564f, 0.46771878f, 0.65862614f,
+                    -0.43959734f, 0.46771878f, 0.59899879f,
+                    -0.52551538f, 0.46771878f, 0.52551538f,
+                    -0.59899873f, 0.46771878f, 0.43959731f,
+                    -0.65862620f, 0.46771878f, 0.34266564f,
+                    -0.70297670f, 0.46771878f, 0.23614144f,
+                    -0.73062921f, 0.46771878f, 0.12144583f,
+                    -0.74016249f, 0.46771878f, 0.00000001f,
+                    0.00000000f, 0.45000005f, 0.75000000f,
+                    -0.12305999f, 0.44999996f, 0.74033999f,
+                    -0.23928000f, 0.45000011f, 0.71232003f,
+                    -0.34721997f, 0.45000002f, 0.66737998f,
+                    -0.44544002f, 0.45000008f, 0.60696000f,
+                    -0.53250003f, 0.45000005f, 0.53250003f,
+                    -0.60696000f, 0.45000005f, 0.44543999f,
+                    -0.66738003f, 0.45000008f, 0.34722000f,
+                    -0.71231997f, 0.45000005f, 0.23927999f,
+                    -0.74033999f, 0.45000005f, 0.12305997f,
+                    -0.75000000f, 0.45000005f, 0.00000001f,
+                    -0.69999999f, 0.45000005f, 0.00000001f,
+                    -0.69098389f, 0.44999996f, -0.11485598f,
+                    -0.66483200f, 0.45000011f, -0.22332802f,
+                    -0.62288797f, 0.45000002f, -0.32407200f,
+                    -0.56649601f, 0.45000008f, -0.41574404f,
+                    -0.49699998f, 0.45000005f, -0.49700001f,
+                    -0.41574395f, 0.45000005f, -0.56649601f,
+                    -0.32407200f, 0.45000008f, -0.62288797f,
+                    -0.22332799f, 0.45000005f, -0.66483200f,
+                    -0.11485596f, 0.45000005f, -0.69098401f,
+                    0.00000000f, 0.45000005f, -0.69999999f,
+                    -0.69296241f, 0.46771872f, 0.00000001f,
+                    -0.68403697f, 0.46771869f, -0.11370127f,
+                    -0.65814799f, 0.46771878f, -0.22108276f,
+                    -0.61662567f, 0.46771872f, -0.32081389f,
+                    -0.56080067f, 0.46771872f, -0.41156429f,
+                    -0.49200332f, 0.46771872f, -0.49200332f,
+                    -0.41156423f, 0.46771872f, -0.56080067f,
+                    -0.32081389f, 0.46771872f, -0.61662567f,
+                    -0.22108275f, 0.46771872f, -0.65814799f,
+                    -0.11370124f, 0.46771872f, -0.68403703f,
+                    0.00000000f, 0.46771872f, -0.69296241f,
+                    -0.69020003f, 0.48150003f, 0.00000001f,
+                    -0.68131018f, 0.48149997f, -0.11324801f,
+                    -0.65552443f, 0.48150006f, -0.22020143f,
+                    -0.61416763f, 0.48150000f, -0.31953502f,
+                    -0.55856514f, 0.48150009f, -0.40992361f,
+                    -0.49004203f, 0.48150003f, -0.49004203f,
+                    -0.40992361f, 0.48150006f, -0.55856514f,
+                    -0.31953505f, 0.48150003f, -0.61416763f,
+                    -0.22020143f, 0.48150003f, -0.65552437f,
+                    -0.11324799f, 0.48150003f, -0.68131030f,
+                    0.00000000f, 0.48150003f, -0.69020003f,
+                    -0.69111252f, 0.49134374f, 0.00000001f,
+                    -0.68221092f, 0.49134374f, -0.11339773f,
+                    -0.65639102f, 0.49134377f, -0.22049254f,
+                    -0.61497957f, 0.49134377f, -0.31995746f,
+                    -0.55930358f, 0.49134377f, -0.41046557f,
+                    -0.49068987f, 0.49134374f, -0.49068987f,
+                    -0.41046554f, 0.49134374f, -0.55930358f,
+                    -0.31995746f, 0.49134374f, -0.61497957f,
+                    -0.22049253f, 0.49134377f, -0.65639102f,
+                    -0.11339770f, 0.49134374f, -0.68221098f,
+                    0.00000000f, 0.49134374f, -0.69111252f,
+                    -0.69510001f, 0.49725008f, 0.00000001f,
+                    -0.68614709f, 0.49725005f, -0.11405201f,
+                    -0.66017824f, 0.49725014f, -0.22176473f,
+                    -0.61852777f, 0.49725008f, -0.32180351f,
+                    -0.56253058f, 0.49725008f, -0.41283381f,
+                    -0.49352103f, 0.49725008f, -0.49352100f,
+                    -0.41283381f, 0.49725008f, -0.56253058f,
+                    -0.32180351f, 0.49725008f, -0.61852777f,
+                    -0.22176471f, 0.49725008f, -0.66017818f,
+                    -0.11405198f, 0.49725008f, -0.68614715f,
+                    0.00000000f, 0.49725008f, -0.69510001f,
+                    -0.70156252f, 0.49921876f, 0.00000001f,
+                    -0.69252634f, 0.49921870f, -0.11511236f,
+                    -0.66631603f, 0.49921876f, -0.22382653f,
+                    -0.62427837f, 0.49921873f, -0.32479537f,
+                    -0.56776059f, 0.49921879f, -0.41667202f,
+                    -0.49810940f, 0.49921876f, -0.49810940f,
+                    -0.41667199f, 0.49921879f, -0.56776053f,
+                    -0.32479542f, 0.49921876f, -0.62427843f,
+                    -0.22382650f, 0.49921876f, -0.66631603f,
+                    -0.11511234f, 0.49921876f, -0.69252640f,
+                    0.00000000f, 0.49921876f, -0.70156252f,
+                    -0.70989996f, 0.49725002f, 0.00000001f,
+                    -0.70075637f, 0.49724996f, -0.11648037f,
+                    -0.67423457f, 0.49725008f, -0.22648652f,
+                    -0.63169742f, 0.49724999f, -0.32865530f,
+                    -0.57450789f, 0.49725002f, -0.42162383f,
+                    -0.50402898f, 0.49725002f, -0.50402898f,
+                    -0.42162377f, 0.49725002f, -0.57450783f,
+                    -0.32865530f, 0.49725002f, -0.63169742f,
+                    -0.22648649f, 0.49725005f, -0.67423463f,
+                    -0.11648035f, 0.49725002f, -0.70075643f,
+                    0.00000000f, 0.49725002f, -0.70989996f,
+                    -0.71951252f, 0.49134377f, 0.00000001f,
+                    -0.71024513f, 0.49134374f, -0.11805760f,
+                    -0.68336421f, 0.49134380f, -0.22955328f,
+                    -0.64025098f, 0.49134377f, -0.33310553f,
+                    -0.58228713f, 0.49134383f, -0.42733288f,
+                    -0.51085389f, 0.49134377f, -0.51085389f,
+                    -0.42733288f, 0.49134377f, -0.58228713f,
+                    -0.33310553f, 0.49134377f, -0.64025104f,
+                    -0.22955328f, 0.49134380f, -0.68336427f,
+                    -0.11805756f, 0.49134377f, -0.71024519f,
+                    0.00000000f, 0.49134377f, -0.71951252f,
+                    -0.72979999f, 0.48150003f, 0.00000001f,
+                    -0.72040009f, 0.48149997f, -0.11974557f,
+                    -0.69313490f, 0.48150006f, -0.23283541f,
+                    -0.64940518f, 0.48150000f, -0.33786824f,
+                    -0.59061253f, 0.48150009f, -0.43344283f,
+                    -0.51815796f, 0.48150003f, -0.51815796f,
+                    -0.43344280f, 0.48150006f, -0.59061259f,
+                    -0.33786821f, 0.48150003f, -0.64940524f,
+                    -0.23283540f, 0.48150003f, -0.69313484f,
+                    -0.11974555f, 0.48150003f, -0.72040015f,
+                    0.00000000f, 0.48150003f, -0.72979999f,
+                    -0.74016249f, 0.46771878f, 0.00000001f,
+                    -0.73062909f, 0.46771872f, -0.12144585f,
+                    -0.70297670f, 0.46771881f, -0.23614147f,
+                    -0.65862614f, 0.46771878f, -0.34266564f,
+                    -0.59899879f, 0.46771878f, -0.43959734f,
+                    -0.52551538f, 0.46771878f, -0.52551538f,
+                    -0.43959731f, 0.46771878f, -0.59899873f,
+                    -0.34266564f, 0.46771878f, -0.65862620f,
+                    -0.23614144f, 0.46771878f, -0.70297670f,
+                    -0.12144582f, 0.46771878f, -0.73062921f,
+                    0.00000000f, 0.46771878f, -0.74016249f,
+                    -0.75000000f, 0.45000005f, 0.00000001f,
+                    -0.74033999f, 0.44999996f, -0.12305998f,
+                    -0.71232003f, 0.45000011f, -0.23928000f,
+                    -0.66737998f, 0.45000002f, -0.34721997f,
+                    -0.60696000f, 0.45000008f, -0.44544002f,
+                    -0.53250003f, 0.45000005f, -0.53250003f,
+                    -0.44543999f, 0.45000005f, -0.60696000f,
+                    -0.34722000f, 0.45000008f, -0.66738003f,
+                    -0.23927999f, 0.45000005f, -0.71231997f,
+                    -0.12305996f, 0.45000005f, -0.74033999f,
+                    0.00000000f, 0.45000005f, -0.75000000f,
+                    0.75000000f, 0.45000005f, 0.00000001f,
+                    0.74033999f, 0.44999996f, 0.12306000f,
+                    0.71232003f, 0.45000011f, 0.23928000f,
+                    0.66737998f, 0.45000002f, 0.34721997f,
+                    0.60696000f, 0.45000008f, 0.44544002f,
+                    0.53250003f, 0.45000005f, 0.53250003f,
+                    0.44543999f, 0.45000005f, 0.60696000f,
+                    0.34722000f, 0.45000008f, 0.66738003f,
+                    0.23927999f, 0.45000005f, 0.71231997f,
+                    0.12305996f, 0.45000005f, 0.74033999f,
+                    0.00000000f, 0.45000005f, 0.75000000f,
+                    0.78737491f, 0.37128749f, 0.00000000f,
+                    0.77723348f, 0.37128747f, 0.12919247f,
+                    0.74781722f, 0.37128752f, 0.25120407f,
+                    0.70063770f, 0.37128749f, 0.36452308f,
+                    0.63720679f, 0.37128752f, 0.46763775f,
+                    0.55903620f, 0.37128749f, 0.55903620f,
+                    0.46763766f, 0.37128749f, 0.63720679f,
+                    0.36452311f, 0.37128749f, 0.70063770f,
+                    0.25120407f, 0.37128752f, 0.74781728f,
+                    0.12919241f, 0.37128749f, 0.77723348f,
+                    0.00000000f, 0.37128749f, 0.78737491f,
+                    0.82400006f, 0.29280004f, 0.00000000f,
+                    0.81338686f, 0.29280004f, 0.13520193f,
+                    0.78260237f, 0.29280004f, 0.26288900f,
+                    0.73322815f, 0.29280004f, 0.38147905f,
+                    0.66684681f, 0.29280004f, 0.48939016f,
+                    0.58504003f, 0.29280001f, 0.58504003f,
+                    0.48939011f, 0.29280004f, 0.66684675f,
+                    0.38147908f, 0.29280007f, 0.73322821f,
+                    0.26288897f, 0.29280004f, 0.78260231f,
+                    0.13520187f, 0.29280004f, 0.81338698f,
+                    0.00000000f, 0.29280004f, 0.82400006f,
+                    0.85912502f, 0.21476251f, 0.00000000f,
+                    0.84805942f, 0.21476249f, 0.14096522f,
+                    0.81596267f, 0.21476252f, 0.27409527f,
+                    0.76448381f, 0.21476249f, 0.39774051f,
+                    0.69527274f, 0.21476252f, 0.51025152f,
+                    0.60997874f, 0.21476251f, 0.60997874f,
+                    0.51025152f, 0.21476251f, 0.69527274f,
+                    0.39774054f, 0.21476251f, 0.76448381f,
+                    0.27409524f, 0.21476251f, 0.81596261f,
+                    0.14096518f, 0.21476251f, 0.84805954f,
+                    0.00000000f, 0.21476251f, 0.85912502f,
+                    0.89200008f, 0.13740003f, 0.00000000f,
+                    0.88051099f, 0.13740002f, 0.14635937f,
+                    0.84718603f, 0.13740005f, 0.28458369f,
+                    0.79373735f, 0.13740002f, 0.41296035f,
+                    0.72187787f, 0.13740005f, 0.52977669f,
+                    0.63332003f, 0.13740002f, 0.63332003f,
+                    0.52977669f, 0.13740002f, 0.72187781f,
+                    0.41296035f, 0.13740002f, 0.79373741f,
+                    0.28458369f, 0.13740000f, 0.84718597f,
+                    0.14635932f, 0.13740000f, 0.88051111f,
+                    0.00000000f, 0.13740000f, 0.89200008f,
+                    0.92187500f, 0.06093751f, 0.00000000f,
+                    0.91000110f, 0.06093750f, 0.15126126f,
+                    0.87556010f, 0.06093751f, 0.29411504f,
+                    0.82032120f, 0.06093750f, 0.42679128f,
+                    0.74605507f, 0.06093750f, 0.54752004f,
+                    0.65453124f, 0.06093751f, 0.65453124f,
+                    0.54751998f, 0.06093750f, 0.74605501f,
+                    0.42679128f, 0.06093750f, 0.82032126f,
+                    0.29411501f, 0.06093750f, 0.87556005f,
+                    0.15126120f, 0.06093750f, 0.91000128f,
+                    0.00000000f, 0.06093750f, 0.92187500f,
+                    0.94800001f, -0.01440001f, -0.00000000f,
+                    0.93578976f, -0.01440001f, 0.15554784f,
+                    0.90037251f, -0.01440001f, 0.30244994f,
+                    0.84356833f, -0.01440001f, 0.43888608f,
+                    0.76719749f, -0.01440001f, 0.56303620f,
+                    0.67308003f, -0.01440001f, 0.67308003f,
+                    0.56303614f, -0.01440002f, 0.76719743f,
+                    0.43888611f, -0.01440002f, 0.84356833f,
+                    0.30244991f, -0.01440002f, 0.90037251f,
+                    0.15554780f, -0.01440002f, 0.93578976f,
+                    0.00000000f, -0.01440002f, 0.94800001f,
+                    0.96962506f, -0.08838750f, -0.00000000f,
+                    0.95713621f, -0.08838749f, 0.15909605f,
+                    0.92091113f, -0.08838750f, 0.30934918f,
+                    0.86281121f, -0.08838750f, 0.44889757f,
+                    0.78469825f, -0.08838750f, 0.57587969f,
+                    0.68843377f, -0.08838750f, 0.68843377f,
+                    0.57587969f, -0.08838750f, 0.78469819f,
+                    0.44889760f, -0.08838750f, 0.86281121f,
+                    0.30934915f, -0.08838750f, 0.92091107f,
+                    0.15909600f, -0.08838750f, 0.95713627f,
+                    0.00000000f, -0.08838750f, 0.96962506f,
+                    0.98600000f, -0.16080001f, -0.00000000f,
+                    0.97330022f, -0.16080000f, 0.16178288f,
+                    0.93646342f, -0.16080002f, 0.31457347f,
+                    0.87738222f, -0.16080001f, 0.45647857f,
+                    0.79795015f, -0.16080002f, 0.58560514f,
+                    0.70006001f, -0.16080001f, 0.70006001f,
+                    0.58560514f, -0.16080001f, 0.79795015f,
+                    0.45647860f, -0.16080001f, 0.87738228f,
+                    0.31457344f, -0.16080001f, 0.93646336f,
+                    0.16178282f, -0.16080001f, 0.97330034f,
+                    0.00000000f, -0.16080001f, 0.98600000f,
+                    0.99637496f, -0.23141253f, -0.00000000f,
+                    0.98354161f, -0.23141250f, 0.16348520f,
+                    0.94631720f, -0.23141254f, 0.31788349f,
+                    0.88661432f, -0.23141253f, 0.46128178f,
+                    0.80634636f, -0.23141254f, 0.59176707f,
+                    0.70742619f, -0.23141253f, 0.70742619f,
+                    0.59176701f, -0.23141253f, 0.80634630f,
+                    0.46128178f, -0.23141253f, 0.88661432f,
+                    0.31788346f, -0.23141253f, 0.94631708f,
+                    0.16348514f, -0.23141253f, 0.98354173f,
+                    0.00000000f, -0.23141253f, 0.99637496f,
+                    1.00000000f, -0.30000001f, -0.00000000f,
+                    0.98711991f, -0.29999998f, 0.16407999f,
+                    0.94976002f, -0.30000004f, 0.31904000f,
+                    0.88984001f, -0.30000004f, 0.46296000f,
+                    0.80928004f, -0.30000001f, 0.59392005f,
+                    0.71000004f, -0.30000001f, 0.71000004f,
+                    0.59391999f, -0.30000001f, 0.80928004f,
+                    0.46296003f, -0.30000001f, 0.88984001f,
+                    0.31904000f, -0.30000001f, 0.94976002f,
+                    0.16407993f, -0.30000001f, 0.98712003f,
+                    0.00000000f, -0.30000001f, 1.00000000f,
+                    0.00000000f, 0.45000005f, -0.75000000f,
+                    0.12305999f, 0.44999996f, -0.74033999f,
+                    0.23928000f, 0.45000011f, -0.71232003f,
+                    0.34721997f, 0.45000002f, -0.66737998f,
+                    0.44544002f, 0.45000008f, -0.60696000f,
+                    0.53250003f, 0.45000005f, -0.53250003f,
+                    0.60696000f, 0.45000005f, -0.44543999f,
+                    0.66738003f, 0.45000008f, -0.34722000f,
+                    0.71231997f, 0.45000005f, -0.23927999f,
+                    0.74033999f, 0.45000005f, -0.12305995f,
+                    0.75000000f, 0.45000005f, 0.00000001f,
+                    0.00000000f, 0.37128749f, -0.78737491f,
+                    0.12919247f, 0.37128747f, -0.77723348f,
+                    0.25120407f, 0.37128752f, -0.74781722f,
+                    0.36452308f, 0.37128749f, -0.70063770f,
+                    0.46763775f, 0.37128752f, -0.63720679f,
+                    0.55903620f, 0.37128749f, -0.55903620f,
+                    0.63720679f, 0.37128749f, -0.46763766f,
+                    0.70063770f, 0.37128749f, -0.36452311f,
+                    0.74781728f, 0.37128752f, -0.25120407f,
+                    0.77723348f, 0.37128749f, -0.12919241f,
+                    0.78737491f, 0.37128749f, 0.00000000f,
+                    0.00000000f, 0.29280004f, -0.82400006f,
+                    0.13520193f, 0.29280004f, -0.81338686f,
+                    0.26288900f, 0.29280004f, -0.78260237f,
+                    0.38147905f, 0.29280004f, -0.73322815f,
+                    0.48939016f, 0.29280004f, -0.66684681f,
+                    0.58504003f, 0.29280001f, -0.58504003f,
+                    0.66684675f, 0.29280004f, -0.48939011f,
+                    0.73322821f, 0.29280007f, -0.38147908f,
+                    0.78260231f, 0.29280004f, -0.26288897f,
+                    0.81338698f, 0.29280004f, -0.13520187f,
+                    0.82400006f, 0.29280004f, 0.00000000f,
+                    0.00000000f, 0.21476251f, -0.85912502f,
+                    0.14096522f, 0.21476249f, -0.84805942f,
+                    0.27409527f, 0.21476252f, -0.81596267f,
+                    0.39774051f, 0.21476249f, -0.76448381f,
+                    0.51025152f, 0.21476252f, -0.69527274f,
+                    0.60997874f, 0.21476251f, -0.60997874f,
+                    0.69527274f, 0.21476251f, -0.51025152f,
+                    0.76448381f, 0.21476251f, -0.39774054f,
+                    0.81596261f, 0.21476251f, -0.27409524f,
+                    0.84805954f, 0.21476251f, -0.14096518f,
+                    0.85912502f, 0.21476251f, 0.00000000f,
+                    0.00000000f, 0.13740005f, -0.89200008f,
+                    0.14635937f, 0.13740005f, -0.88051099f,
+                    0.28458369f, 0.13740005f, -0.84718603f,
+                    0.41296035f, 0.13740005f, -0.79373735f,
+                    0.52977669f, 0.13740005f, -0.72187787f,
+                    0.63332003f, 0.13740003f, -0.63332003f,
+                    0.72187781f, 0.13740003f, -0.52977669f,
+                    0.79373741f, 0.13740005f, -0.41296035f,
+                    0.84718597f, 0.13740003f, -0.28458369f,
+                    0.88051111f, 0.13740003f, -0.14635932f,
+                    0.89200008f, 0.13740003f, 0.00000000f,
+                    0.00000000f, 0.06093752f, -0.92187500f,
+                    0.15126126f, 0.06093751f, -0.91000110f,
+                    0.29411504f, 0.06093752f, -0.87556010f,
+                    0.42679128f, 0.06093752f, -0.82032120f,
+                    0.54752004f, 0.06093752f, -0.74605507f,
+                    0.65453124f, 0.06093752f, -0.65453124f,
+                    0.74605501f, 0.06093752f, -0.54751998f,
+                    0.82032126f, 0.06093751f, -0.42679128f,
+                    0.87556005f, 0.06093752f, -0.29411501f,
+                    0.91000128f, 0.06093751f, -0.15126120f,
+                    0.92187500f, 0.06093751f, 0.00000000f,
+                    0.00000000f, -0.01440000f, -0.94800001f,
+                    0.15554784f, -0.01440000f, -0.93578976f,
+                    0.30244994f, -0.01440000f, -0.90037251f,
+                    0.43888608f, -0.01440000f, -0.84356833f,
+                    0.56303620f, -0.01440000f, -0.76719749f,
+                    0.67308003f, -0.01440000f, -0.67308003f,
+                    0.76719743f, -0.01440001f, -0.56303614f,
+                    0.84356833f, -0.01440001f, -0.43888611f,
+                    0.90037251f, -0.01440001f, -0.30244991f,
+                    0.93578976f, -0.01440001f, -0.15554780f,
+                    0.94800001f, -0.01440001f, -0.00000000f,
+                    0.00000000f, -0.08838749f, -0.96962506f,
+                    0.15909605f, -0.08838748f, -0.95713621f,
+                    0.30934918f, -0.08838750f, -0.92091113f,
+                    0.44889757f, -0.08838750f, -0.86281121f,
+                    0.57587969f, -0.08838750f, -0.78469825f,
+                    0.68843377f, -0.08838749f, -0.68843377f,
+                    0.78469819f, -0.08838750f, -0.57587969f,
+                    0.86281121f, -0.08838750f, -0.44889760f,
+                    0.92091107f, -0.08838750f, -0.30934915f,
+                    0.95713627f, -0.08838749f, -0.15909600f,
+                    0.96962506f, -0.08838750f, -0.00000000f,
+                    0.00000000f, -0.16080000f, -0.98600000f,
+                    0.16178288f, -0.16079998f, -0.97330022f,
+                    0.31457347f, -0.16080001f, -0.93646342f,
+                    0.45647857f, -0.16080000f, -0.87738222f,
+                    0.58560514f, -0.16080001f, -0.79795015f,
+                    0.70006001f, -0.16080000f, -0.70006001f,
+                    0.79795015f, -0.16080000f, -0.58560514f,
+                    0.87738228f, -0.16080001f, -0.45647860f,
+                    0.93646336f, -0.16080001f, -0.31457344f,
+                    0.97330034f, -0.16080001f, -0.16178282f,
+                    0.98600000f, -0.16080001f, -0.00000000f,
+                    0.00000000f, -0.23141253f, -0.99637496f,
+                    0.16348520f, -0.23141250f, -0.98354161f,
+                    0.31788349f, -0.23141254f, -0.94631720f,
+                    0.46128178f, -0.23141253f, -0.88661432f,
+                    0.59176707f, -0.23141254f, -0.80634636f,
+                    0.70742619f, -0.23141253f, -0.70742619f,
+                    0.80634630f, -0.23141253f, -0.59176701f,
+                    0.88661432f, -0.23141253f, -0.46128178f,
+                    0.94631708f, -0.23141253f, -0.31788346f,
+                    0.98354173f, -0.23141253f, -0.16348514f,
+                    0.99637496f, -0.23141253f, -0.00000000f,
+                    0.00000000f, -0.30000001f, -1.00000000f,
+                    0.16407999f, -0.29999998f, -0.98711991f,
+                    0.31904000f, -0.30000004f, -0.94976002f,
+                    0.46296000f, -0.30000004f, -0.88984001f,
+                    0.59392005f, -0.30000001f, -0.80928004f,
+                    0.71000004f, -0.30000001f, -0.71000004f,
+                    0.80928004f, -0.30000001f, -0.59391999f,
+                    0.88984001f, -0.30000001f, -0.46296003f,
+                    0.94976002f, -0.30000001f, -0.31904000f,
+                    0.98712003f, -0.30000001f, -0.16407993f,
+                    1.00000000f, -0.30000001f, -0.00000000f,
+                    0.00000000f, 0.45000005f, 0.75000000f,
+                    -0.12305999f, 0.44999996f, 0.74033999f,
+                    -0.23928000f, 0.45000011f, 0.71232003f,
+                    -0.34721997f, 0.45000002f, 0.66737998f,
+                    -0.44544002f, 0.45000008f, 0.60696000f,
+                    -0.53250003f, 0.45000005f, 0.53250003f,
+                    -0.60696000f, 0.45000005f, 0.44543999f,
+                    -0.66738003f, 0.45000008f, 0.34722000f,
+                    -0.71231997f, 0.45000005f, 0.23927999f,
+                    -0.74033999f, 0.45000005f, 0.12305997f,
+                    -0.75000000f, 0.45000005f, 0.00000001f,
+                    0.00000000f, 0.37128749f, 0.78737491f,
+                    -0.12919247f, 0.37128747f, 0.77723348f,
+                    -0.25120407f, 0.37128752f, 0.74781722f,
+                    -0.36452308f, 0.37128749f, 0.70063770f,
+                    -0.46763775f, 0.37128752f, 0.63720679f,
+                    -0.55903620f, 0.37128749f, 0.55903620f,
+                    -0.63720679f, 0.37128749f, 0.46763766f,
+                    -0.70063770f, 0.37128749f, 0.36452311f,
+                    -0.74781728f, 0.37128752f, 0.25120407f,
+                    -0.77723348f, 0.37128749f, 0.12919241f,
+                    -0.78737491f, 0.37128749f, 0.00000000f,
+                    0.00000000f, 0.29280004f, 0.82400006f,
+                    -0.13520193f, 0.29280004f, 0.81338686f,
+                    -0.26288900f, 0.29280004f, 0.78260237f,
+                    -0.38147905f, 0.29280004f, 0.73322815f,
+                    -0.48939016f, 0.29280004f, 0.66684681f,
+                    -0.58504003f, 0.29280001f, 0.58504003f,
+                    -0.66684675f, 0.29280004f, 0.48939011f,
+                    -0.73322821f, 0.29280007f, 0.38147908f,
+                    -0.78260231f, 0.29280004f, 0.26288897f,
+                    -0.81338698f, 0.29280004f, 0.13520187f,
+                    -0.82400006f, 0.29280004f, 0.00000000f,
+                    0.00000000f, 0.21476251f, 0.85912502f,
+                    -0.14096522f, 0.21476249f, 0.84805942f,
+                    -0.27409527f, 0.21476252f, 0.81596267f,
+                    -0.39774051f, 0.21476249f, 0.76448381f,
+                    -0.51025152f, 0.21476252f, 0.69527274f,
+                    -0.60997874f, 0.21476251f, 0.60997874f,
+                    -0.69527274f, 0.21476251f, 0.51025152f,
+                    -0.76448381f, 0.21476251f, 0.39774054f,
+                    -0.81596261f, 0.21476251f, 0.27409524f,
+                    -0.84805954f, 0.21476251f, 0.14096518f,
+                    -0.85912502f, 0.21476251f, 0.00000000f,
+                    0.00000000f, 0.13740000f, 0.89200008f,
+                    -0.14635937f, 0.13740000f, 0.88051099f,
+                    -0.28458369f, 0.13740002f, 0.84718603f,
+                    -0.41296035f, 0.13740002f, 0.79373735f,
+                    -0.52977669f, 0.13740002f, 0.72187787f,
+                    -0.63332003f, 0.13740002f, 0.63332003f,
+                    -0.72187781f, 0.13740002f, 0.52977669f,
+                    -0.79373741f, 0.13740003f, 0.41296035f,
+                    -0.84718597f, 0.13740003f, 0.28458369f,
+                    -0.88051111f, 0.13740003f, 0.14635932f,
+                    -0.89200008f, 0.13740003f, 0.00000000f,
+                    0.00000000f, 0.06093750f, 0.92187500f,
+                    -0.15126126f, 0.06093750f, 0.91000110f,
+                    -0.29411504f, 0.06093751f, 0.87556010f,
+                    -0.42679128f, 0.06093750f, 0.82032120f,
+                    -0.54752004f, 0.06093750f, 0.74605507f,
+                    -0.65453124f, 0.06093750f, 0.65453124f,
+                    -0.74605501f, 0.06093750f, 0.54751998f,
+                    -0.82032126f, 0.06093751f, 0.42679128f,
+                    -0.87556005f, 0.06093751f, 0.29411501f,
+                    -0.91000128f, 0.06093751f, 0.15126120f,
+                    -0.92187500f, 0.06093751f, 0.00000000f,
+                    0.00000000f, -0.01440002f, 0.94800001f,
+                    -0.15554784f, -0.01440002f, 0.93578976f,
+                    -0.30244994f, -0.01440002f, 0.90037251f,
+                    -0.43888608f, -0.01440002f, 0.84356833f,
+                    -0.56303620f, -0.01440002f, 0.76719749f,
+                    -0.67308003f, -0.01440001f, 0.67308003f,
+                    -0.76719743f, -0.01440001f, 0.56303614f,
+                    -0.84356833f, -0.01440001f, 0.43888611f,
+                    -0.90037251f, -0.01440001f, 0.30244991f,
+                    -0.93578976f, -0.01440001f, 0.15554780f,
+                    -0.94800001f, -0.01440001f, -0.00000000f,
+                    0.00000000f, -0.08838750f, 0.96962506f,
+                    -0.15909605f, -0.08838750f, 0.95713621f,
+                    -0.30934918f, -0.08838750f, 0.92091113f,
+                    -0.44889757f, -0.08838750f, 0.86281121f,
+                    -0.57587969f, -0.08838751f, 0.78469825f,
+                    -0.68843377f, -0.08838750f, 0.68843377f,
+                    -0.78469819f, -0.08838750f, 0.57587969f,
+                    -0.86281121f, -0.08838750f, 0.44889760f,
+                    -0.92091107f, -0.08838750f, 0.30934915f,
+                    -0.95713627f, -0.08838750f, 0.15909600f,
+                    -0.96962506f, -0.08838750f, -0.00000000f,
+                    0.00000000f, -0.16080001f, 0.98600000f,
+                    -0.16178288f, -0.16080000f, 0.97330022f,
+                    -0.31457347f, -0.16080002f, 0.93646342f,
+                    -0.45647857f, -0.16080001f, 0.87738222f,
+                    -0.58560514f, -0.16080002f, 0.79795015f,
+                    -0.70006001f, -0.16080001f, 0.70006001f,
+                    -0.79795015f, -0.16080001f, 0.58560514f,
+                    -0.87738228f, -0.16080001f, 0.45647860f,
+                    -0.93646336f, -0.16080001f, 0.31457344f,
+                    -0.97330034f, -0.16080001f, 0.16178282f,
+                    -0.98600000f, -0.16080001f, -0.00000000f,
+                    0.00000000f, -0.23141253f, 0.99637496f,
+                    -0.16348520f, -0.23141250f, 0.98354161f,
+                    -0.31788349f, -0.23141254f, 0.94631720f,
+                    -0.46128178f, -0.23141253f, 0.88661432f,
+                    -0.59176707f, -0.23141254f, 0.80634636f,
+                    -0.70742619f, -0.23141253f, 0.70742619f,
+                    -0.80634630f, -0.23141253f, 0.59176701f,
+                    -0.88661432f, -0.23141253f, 0.46128178f,
+                    -0.94631708f, -0.23141253f, 0.31788346f,
+                    -0.98354173f, -0.23141253f, 0.16348514f,
+                    -0.99637496f, -0.23141253f, -0.00000000f,
+                    0.00000000f, -0.30000001f, 1.00000000f,
+                    -0.16407999f, -0.29999998f, 0.98711991f,
+                    -0.31904000f, -0.30000004f, 0.94976002f,
+                    -0.46296000f, -0.30000004f, 0.88984001f,
+                    -0.59392005f, -0.30000001f, 0.80928004f,
+                    -0.71000004f, -0.30000001f, 0.71000004f,
+                    -0.80928004f, -0.30000001f, 0.59391999f,
+                    -0.88984001f, -0.30000001f, 0.46296003f,
+                    -0.94976002f, -0.30000001f, 0.31904000f,
+                    -0.98712003f, -0.30000001f, 0.16407993f,
+                    -1.00000000f, -0.30000001f, -0.00000000f,
+                    -0.75000000f, 0.45000005f, 0.00000001f,
+                    -0.74033999f, 0.44999996f, -0.12305998f,
+                    -0.71232003f, 0.45000011f, -0.23928000f,
+                    -0.66737998f, 0.45000002f, -0.34721997f,
+                    -0.60696000f, 0.45000008f, -0.44544002f,
+                    -0.53250003f, 0.45000005f, -0.53250003f,
+                    -0.44543999f, 0.45000005f, -0.60696000f,
+                    -0.34722000f, 0.45000008f, -0.66738003f,
+                    -0.23927999f, 0.45000005f, -0.71231997f,
+                    -0.12305996f, 0.45000005f, -0.74033999f,
+                    0.00000000f, 0.45000005f, -0.75000000f,
+                    -0.78737491f, 0.37128749f, 0.00000000f,
+                    -0.77723348f, 0.37128747f, -0.12919247f,
+                    -0.74781722f, 0.37128752f, -0.25120407f,
+                    -0.70063770f, 0.37128749f, -0.36452308f,
+                    -0.63720679f, 0.37128752f, -0.46763775f,
+                    -0.55903620f, 0.37128749f, -0.55903620f,
+                    -0.46763766f, 0.37128749f, -0.63720679f,
+                    -0.36452311f, 0.37128749f, -0.70063770f,
+                    -0.25120407f, 0.37128752f, -0.74781728f,
+                    -0.12919241f, 0.37128749f, -0.77723348f,
+                    0.00000000f, 0.37128749f, -0.78737491f,
+                    -0.82400006f, 0.29280004f, 0.00000000f,
+                    -0.81338686f, 0.29280004f, -0.13520193f,
+                    -0.78260237f, 0.29280004f, -0.26288900f,
+                    -0.73322815f, 0.29280004f, -0.38147905f,
+                    -0.66684681f, 0.29280004f, -0.48939016f,
+                    -0.58504003f, 0.29280001f, -0.58504003f,
+                    -0.48939011f, 0.29280004f, -0.66684675f,
+                    -0.38147908f, 0.29280007f, -0.73322821f,
+                    -0.26288897f, 0.29280004f, -0.78260231f,
+                    -0.13520187f, 0.29280004f, -0.81338698f,
+                    0.00000000f, 0.29280004f, -0.82400006f,
+                    -0.85912502f, 0.21476251f, 0.00000000f,
+                    -0.84805942f, 0.21476249f, -0.14096522f,
+                    -0.81596267f, 0.21476252f, -0.27409527f,
+                    -0.76448381f, 0.21476249f, -0.39774051f,
+                    -0.69527274f, 0.21476252f, -0.51025152f,
+                    -0.60997874f, 0.21476251f, -0.60997874f,
+                    -0.51025152f, 0.21476251f, -0.69527274f,
+                    -0.39774054f, 0.21476251f, -0.76448381f,
+                    -0.27409524f, 0.21476251f, -0.81596261f,
+                    -0.14096518f, 0.21476251f, -0.84805954f,
+                    0.00000000f, 0.21476251f, -0.85912502f,
+                    -0.89200008f, 0.13740003f, 0.00000000f,
+                    -0.88051099f, 0.13740003f, -0.14635937f,
+                    -0.84718603f, 0.13740005f, -0.28458369f,
+                    -0.79373735f, 0.13740005f, -0.41296035f,
+                    -0.72187787f, 0.13740005f, -0.52977669f,
+                    -0.63332003f, 0.13740005f, -0.63332003f,
+                    -0.52977669f, 0.13740005f, -0.72187781f,
+                    -0.41296035f, 0.13740005f, -0.79373741f,
+                    -0.28458369f, 0.13740005f, -0.84718597f,
+                    -0.14635932f, 0.13740005f, -0.88051111f,
+                    0.00000000f, 0.13740005f, -0.89200008f,
+                    -0.92187500f, 0.06093751f, 0.00000000f,
+                    -0.91000110f, 0.06093751f, -0.15126126f,
+                    -0.87556010f, 0.06093752f, -0.29411504f,
+                    -0.82032120f, 0.06093751f, -0.42679128f,
+                    -0.74605507f, 0.06093752f, -0.54752004f,
+                    -0.65453124f, 0.06093752f, -0.65453124f,
+                    -0.54751998f, 0.06093752f, -0.74605501f,
+                    -0.42679128f, 0.06093752f, -0.82032126f,
+                    -0.29411501f, 0.06093752f, -0.87556005f,
+                    -0.15126120f, 0.06093752f, -0.91000128f,
+                    0.00000000f, 0.06093752f, -0.92187500f,
+                    -0.94800001f, -0.01440001f, -0.00000000f,
+                    -0.93578976f, -0.01440001f, -0.15554784f,
+                    -0.90037251f, -0.01440001f, -0.30244994f,
+                    -0.84356833f, -0.01440001f, -0.43888608f,
+                    -0.76719749f, -0.01440001f, -0.56303620f,
+                    -0.67308003f, -0.01440000f, -0.67308003f,
+                    -0.56303614f, -0.01440000f, -0.76719743f,
+                    -0.43888611f, -0.01440000f, -0.84356833f,
+                    -0.30244991f, -0.01440000f, -0.90037251f,
+                    -0.15554780f, -0.01440000f, -0.93578976f,
+                    0.00000000f, -0.01440000f, -0.94800001f,
+                    -0.96962506f, -0.08838750f, -0.00000000f,
+                    -0.95713621f, -0.08838749f, -0.15909605f,
+                    -0.92091113f, -0.08838750f, -0.30934918f,
+                    -0.86281121f, -0.08838750f, -0.44889757f,
+                    -0.78469825f, -0.08838750f, -0.57587969f,
+                    -0.68843377f, -0.08838749f, -0.68843377f,
+                    -0.57587969f, -0.08838749f, -0.78469819f,
+                    -0.44889760f, -0.08838749f, -0.86281121f,
+                    -0.30934915f, -0.08838749f, -0.92091107f,
+                    -0.15909600f, -0.08838749f, -0.95713627f,
+                    0.00000000f, -0.08838749f, -0.96962506f,
+                    -0.98600000f, -0.16080001f, -0.00000000f,
+                    -0.97330022f, -0.16080000f, -0.16178288f,
+                    -0.93646342f, -0.16080001f, -0.31457347f,
+                    -0.87738222f, -0.16080000f, -0.45647857f,
+                    -0.79795015f, -0.16080001f, -0.58560514f,
+                    -0.70006001f, -0.16080001f, -0.70006001f,
+                    -0.58560514f, -0.16080000f, -0.79795015f,
+                    -0.45647860f, -0.16080001f, -0.87738228f,
+                    -0.31457344f, -0.16080000f, -0.93646336f,
+                    -0.16178282f, -0.16080000f, -0.97330034f,
+                    0.00000000f, -0.16080000f, -0.98600000f,
+                    -0.99637496f, -0.23141253f, -0.00000000f,
+                    -0.98354161f, -0.23141250f, -0.16348520f,
+                    -0.94631720f, -0.23141254f, -0.31788349f,
+                    -0.88661432f, -0.23141253f, -0.46128178f,
+                    -0.80634636f, -0.23141254f, -0.59176707f,
+                    -0.70742619f, -0.23141253f, -0.70742619f,
+                    -0.59176701f, -0.23141253f, -0.80634630f,
+                    -0.46128178f, -0.23141253f, -0.88661432f,
+                    -0.31788346f, -0.23141253f, -0.94631708f,
+                    -0.16348514f, -0.23141253f, -0.98354173f,
+                    0.00000000f, -0.23141253f, -0.99637496f,
+                    -1.00000000f, -0.30000001f, -0.00000000f,
+                    -0.98711991f, -0.29999998f, -0.16407999f,
+                    -0.94976002f, -0.30000004f, -0.31904000f,
+                    -0.88984001f, -0.30000004f, -0.46296000f,
+                    -0.80928004f, -0.30000001f, -0.59392005f,
+                    -0.71000004f, -0.30000001f, -0.71000004f,
+                    -0.59391999f, -0.30000001f, -0.80928004f,
+                    -0.46296003f, -0.30000001f, -0.88984001f,
+                    -0.31904000f, -0.30000001f, -0.94976002f,
+                    -0.16407993f, -0.30000001f, -0.98712003f,
+                    0.00000000f, -0.30000001f, -1.00000000f,
+                    1.00000000f, -0.30000001f, -0.00000000f,
+                    0.98711991f, -0.29999998f, 0.16407999f,
+                    0.94976002f, -0.30000004f, 0.31904000f,
+                    0.88984001f, -0.30000004f, 0.46296000f,
+                    0.80928004f, -0.30000001f, 0.59392005f,
+                    0.71000004f, -0.30000001f, 0.71000004f,
+                    0.59391999f, -0.30000001f, 0.80928004f,
+                    0.46296003f, -0.30000001f, 0.88984001f,
+                    0.31904000f, -0.30000001f, 0.94976002f,
+                    0.16407993f, -0.30000001f, 0.98712003f,
+                    0.00000000f, -0.30000001f, 1.00000000f,
+                    0.99299991f, -0.36416247f, -0.00000000f,
+                    0.98021001f, -0.36416242f, 0.16293143f,
+                    0.94311166f, -0.36416250f, 0.31680670f,
+                    0.88361102f, -0.36416245f, 0.45971927f,
+                    0.80361503f, -0.36416247f, 0.58976257f,
+                    0.70502996f, -0.36416247f, 0.70502996f,
+                    0.58976251f, -0.36416250f, 0.80361497f,
+                    0.45971930f, -0.36416247f, 0.88361108f,
+                    0.31680670f, -0.36416250f, 0.94311166f,
+                    0.16293137f, -0.36416247f, 0.98021007f,
+                    0.00000000f, -0.36416247f, 0.99299991f,
+                    0.97400004f, -0.42180002f, -0.00000001f,
+                    0.96145481f, -0.42179996f, 0.15981390f,
+                    0.92506635f, -0.42180005f, 0.31074497f,
+                    0.86670411f, -0.42180002f, 0.45092306f,
+                    0.78823876f, -0.42180011f, 0.57847816f,
+                    0.69154000f, -0.42180008f, 0.69154000f,
+                    0.57847810f, -0.42180005f, 0.78823876f,
+                    0.45092303f, -0.42180008f, 0.86670417f,
+                    0.31074494f, -0.42180008f, 0.92506629f,
+                    0.15981385f, -0.42180008f, 0.96145493f,
+                    0.00000000f, -0.42180005f, 0.97400004f,
+                    0.94600004f, -0.47313750f, -0.00000001f,
+                    0.93381548f, -0.47313747f, 0.15521967f,
+                    0.89847302f, -0.47313756f, 0.30181187f,
+                    0.84178865f, -0.47313750f, 0.43796018f,
+                    0.76557899f, -0.47313753f, 0.56184840f,
+                    0.67166001f, -0.47313753f, 0.67166001f,
+                    0.56184828f, -0.47313753f, 0.76557899f,
+                    0.43796021f, -0.47313753f, 0.84178865f,
+                    0.30181184f, -0.47313753f, 0.89847302f,
+                    0.15521961f, -0.47313753f, 0.93381560f,
+                    0.00000000f, -0.47313753f, 0.94600004f,
+                    0.91200000f, -0.51840001f, -0.00000001f,
+                    0.90025330f, -0.51839995f, 0.14964095f,
+                    0.86618114f, -0.51840007f, 0.29096451f,
+                    0.81153411f, -0.51840001f, 0.42221951f,
+                    0.73806345f, -0.51840007f, 0.54165506f,
+                    0.64752001f, -0.51840007f, 0.64752001f,
+                    0.54165506f, -0.51840007f, 0.73806340f,
+                    0.42221954f, -0.51840007f, 0.81153411f,
+                    0.29096448f, -0.51840007f, 0.86618114f,
+                    0.14964090f, -0.51840007f, 0.90025342f,
+                    0.00000000f, -0.51840007f, 0.91200000f,
+                    0.87500000f, -0.55781251f, -0.00000001f,
+                    0.86372989f, -0.55781251f, 0.14356998f,
+                    0.83104002f, -0.55781251f, 0.27916002f,
+                    0.77860999f, -0.55781251f, 0.40509003f,
+                    0.70812005f, -0.55781257f, 0.51968002f,
+                    0.62125003f, -0.55781251f, 0.62125003f,
+                    0.51967996f, -0.55781251f, 0.70812005f,
+                    0.40509003f, -0.55781257f, 0.77860999f,
+                    0.27915999f, -0.55781251f, 0.83104002f,
+                    0.14356995f, -0.55781251f, 0.86373001f,
+                    0.00000000f, -0.55781251f, 0.87500000f,
+                    0.83800000f, -0.59160000f, -0.00000001f,
+                    0.82720655f, -0.59159994f, 0.13749902f,
+                    0.79589891f, -0.59160000f, 0.26735553f,
+                    0.74568588f, -0.59160000f, 0.38796049f,
+                    0.67817670f, -0.59160006f, 0.49770495f,
+                    0.59498000f, -0.59160000f, 0.59497994f,
+                    0.49770495f, -0.59160000f, 0.67817664f,
+                    0.38796049f, -0.59160006f, 0.74568594f,
+                    0.26735550f, -0.59160000f, 0.79589891f,
+                    0.13749899f, -0.59160000f, 0.82720655f,
+                    0.00000000f, -0.59160000f, 0.83800000f,
+                    0.80400008f, -0.61998749f, -0.00000001f,
+                    0.79364455f, -0.61998743f, 0.13192031f,
+                    0.76360714f, -0.61998749f, 0.25650817f,
+                    0.71543139f, -0.61998749f, 0.37221986f,
+                    0.65066117f, -0.61998749f, 0.47751173f,
+                    0.57084000f, -0.61998749f, 0.57084000f,
+                    0.47751170f, -0.61998749f, 0.65066117f,
+                    0.37221986f, -0.61998749f, 0.71543145f,
+                    0.25650814f, -0.61998749f, 0.76360714f,
+                    0.13192026f, -0.61998749f, 0.79364455f,
+                    0.00000000f, -0.61998749f, 0.80400008f,
+                    0.77600002f, -0.64320004f, -0.00000001f,
+                    0.76600504f, -0.64319998f, 0.12732607f,
+                    0.73701382f, -0.64320010f, 0.24757506f,
+                    0.69051588f, -0.64320004f, 0.35925698f,
+                    0.62800133f, -0.64320010f, 0.46088195f,
+                    0.55096000f, -0.64320004f, 0.55096000f,
+                    0.46088192f, -0.64320004f, 0.62800133f,
+                    0.35925698f, -0.64320004f, 0.69051588f,
+                    0.24757503f, -0.64320004f, 0.73701382f,
+                    0.12732603f, -0.64320004f, 0.76600516f,
+                    0.00000000f, -0.64320004f, 0.77600002f,
+                    0.75699997f, -0.66146249f, -0.00000001f,
+                    0.74724966f, -0.66146243f, 0.12420854f,
+                    0.71896833f, -0.66146255f, 0.24151328f,
+                    0.67360884f, -0.66146243f, 0.35046071f,
+                    0.61262494f, -0.66146255f, 0.44959745f,
+                    0.53746998f, -0.66146249f, 0.53746998f,
+                    0.44959742f, -0.66146249f, 0.61262494f,
+                    0.35046071f, -0.66146249f, 0.67360890f,
+                    0.24151327f, -0.66146255f, 0.71896827f,
+                    0.12420851f, -0.66146249f, 0.74724984f,
+                    0.00000000f, -0.66146249f, 0.75699997f,
+                    0.75000000f, -0.67500001f, -0.00000001f,
+                    0.74033999f, -0.67499995f, 0.12305998f,
+                    0.71232003f, -0.67500007f, 0.23928000f,
+                    0.66737998f, -0.67500001f, 0.34721997f,
+                    0.60696000f, -0.67500007f, 0.44544002f,
+                    0.53250003f, -0.67500007f, 0.53250003f,
+                    0.44543999f, -0.67500001f, 0.60696000f,
+                    0.34722000f, -0.67500001f, 0.66738003f,
+                    0.23927999f, -0.67500001f, 0.71231997f,
+                    0.12305996f, -0.67500001f, 0.74033999f,
+                    0.00000000f, -0.67500001f, 0.75000000f,
+                    0.00000000f, -0.30000001f, -1.00000000f,
+                    0.16407999f, -0.29999998f, -0.98711991f,
+                    0.31904000f, -0.30000004f, -0.94976002f,
+                    0.46296000f, -0.30000004f, -0.88984001f,
+                    0.59392005f, -0.30000001f, -0.80928004f,
+                    0.71000004f, -0.30000001f, -0.71000004f,
+                    0.80928004f, -0.30000001f, -0.59391999f,
+                    0.88984001f, -0.30000001f, -0.46296003f,
+                    0.94976002f, -0.30000001f, -0.31904000f,
+                    0.98712003f, -0.30000001f, -0.16407993f,
+                    1.00000000f, -0.30000001f, -0.00000000f,
+                    0.00000000f, -0.36416247f, -0.99299991f,
+                    0.16293143f, -0.36416242f, -0.98021001f,
+                    0.31680670f, -0.36416250f, -0.94311166f,
+                    0.45971927f, -0.36416245f, -0.88361102f,
+                    0.58976257f, -0.36416247f, -0.80361503f,
+                    0.70502996f, -0.36416247f, -0.70502996f,
+                    0.80361497f, -0.36416250f, -0.58976251f,
+                    0.88361108f, -0.36416247f, -0.45971930f,
+                    0.94311166f, -0.36416250f, -0.31680670f,
+                    0.98021007f, -0.36416247f, -0.16293137f,
+                    0.99299991f, -0.36416247f, -0.00000000f,
+                    0.00000000f, -0.42180002f, -0.97400004f,
+                    0.15981390f, -0.42179996f, -0.96145481f,
+                    0.31074497f, -0.42180005f, -0.92506635f,
+                    0.45092306f, -0.42180002f, -0.86670411f,
+                    0.57847816f, -0.42180005f, -0.78823876f,
+                    0.69154000f, -0.42180002f, -0.69154000f,
+                    0.78823876f, -0.42180002f, -0.57847810f,
+                    0.86670417f, -0.42180002f, -0.45092303f,
+                    0.92506629f, -0.42180002f, -0.31074494f,
+                    0.96145493f, -0.42180002f, -0.15981385f,
+                    0.97400004f, -0.42180002f, -0.00000001f,
+                    0.00000000f, -0.47313750f, -0.94600004f,
+                    0.15521967f, -0.47313747f, -0.93381548f,
+                    0.30181187f, -0.47313756f, -0.89847302f,
+                    0.43796018f, -0.47313750f, -0.84178865f,
+                    0.56184840f, -0.47313753f, -0.76557899f,
+                    0.67166001f, -0.47313750f, -0.67166001f,
+                    0.76557899f, -0.47313750f, -0.56184828f,
+                    0.84178865f, -0.47313750f, -0.43796021f,
+                    0.89847302f, -0.47313750f, -0.30181184f,
+                    0.93381560f, -0.47313750f, -0.15521961f,
+                    0.94600004f, -0.47313750f, -0.00000001f,
+                    0.00000000f, -0.51840001f, -0.91200000f,
+                    0.14964095f, -0.51839995f, -0.90025330f,
+                    0.29096451f, -0.51840001f, -0.86618114f,
+                    0.42221951f, -0.51840001f, -0.81153411f,
+                    0.54165506f, -0.51840007f, -0.73806345f,
+                    0.64752001f, -0.51840001f, -0.64752001f,
+                    0.73806340f, -0.51840001f, -0.54165506f,
+                    0.81153411f, -0.51840007f, -0.42221954f,
+                    0.86618114f, -0.51840007f, -0.29096448f,
+                    0.90025342f, -0.51840001f, -0.14964090f,
+                    0.91200000f, -0.51840001f, -0.00000001f,
+                    0.00000000f, -0.55781251f, -0.87500000f,
+                    0.14356999f, -0.55781251f, -0.86372989f,
+                    0.27916002f, -0.55781251f, -0.83104002f,
+                    0.40509003f, -0.55781251f, -0.77860999f,
+                    0.51968002f, -0.55781257f, -0.70812005f,
+                    0.62125003f, -0.55781251f, -0.62125003f,
+                    0.70812005f, -0.55781251f, -0.51967996f,
+                    0.77860999f, -0.55781257f, -0.40509003f,
+                    0.83104002f, -0.55781251f, -0.27915999f,
+                    0.86373001f, -0.55781251f, -0.14356995f,
+                    0.87500000f, -0.55781251f, -0.00000001f,
+                    0.00000000f, -0.59160000f, -0.83800000f,
+                    0.13749902f, -0.59159994f, -0.82720655f,
+                    0.26735553f, -0.59160000f, -0.79589891f,
+                    0.38796049f, -0.59160000f, -0.74568588f,
+                    0.49770495f, -0.59160006f, -0.67817670f,
+                    0.59497994f, -0.59160000f, -0.59498000f,
+                    0.67817664f, -0.59160000f, -0.49770495f,
+                    0.74568594f, -0.59160006f, -0.38796049f,
+                    0.79589891f, -0.59160000f, -0.26735550f,
+                    0.82720655f, -0.59160000f, -0.13749899f,
+                    0.83800000f, -0.59160000f, -0.00000001f,
+                    0.00000000f, -0.61998749f, -0.80400008f,
+                    0.13192032f, -0.61998743f, -0.79364455f,
+                    0.25650817f, -0.61998749f, -0.76360714f,
+                    0.37221986f, -0.61998749f, -0.71543139f,
+                    0.47751173f, -0.61998749f, -0.65066117f,
+                    0.57084000f, -0.61998749f, -0.57084000f,
+                    0.65066117f, -0.61998749f, -0.47751170f,
+                    0.71543145f, -0.61998749f, -0.37221986f,
+                    0.76360714f, -0.61998749f, -0.25650814f,
+                    0.79364455f, -0.61998749f, -0.13192026f,
+                    0.80400008f, -0.61998749f, -0.00000001f,
+                    0.00000000f, -0.64320004f, -0.77600002f,
+                    0.12732607f, -0.64319998f, -0.76600504f,
+                    0.24757506f, -0.64320010f, -0.73701382f,
+                    0.35925698f, -0.64320004f, -0.69051588f,
+                    0.46088195f, -0.64320010f, -0.62800133f,
+                    0.55096000f, -0.64320004f, -0.55096000f,
+                    0.62800133f, -0.64320004f, -0.46088192f,
+                    0.69051588f, -0.64320004f, -0.35925698f,
+                    0.73701382f, -0.64320004f, -0.24757503f,
+                    0.76600516f, -0.64320004f, -0.12732603f,
+                    0.77600002f, -0.64320004f, -0.00000001f,
+                    0.00000000f, -0.66146249f, -0.75699997f,
+                    0.12420855f, -0.66146243f, -0.74724966f,
+                    0.24151328f, -0.66146255f, -0.71896833f,
+                    0.35046071f, -0.66146243f, -0.67360884f,
+                    0.44959745f, -0.66146255f, -0.61262494f,
+                    0.53746998f, -0.66146249f, -0.53746998f,
+                    0.61262494f, -0.66146249f, -0.44959742f,
+                    0.67360890f, -0.66146249f, -0.35046071f,
+                    0.71896827f, -0.66146255f, -0.24151327f,
+                    0.74724984f, -0.66146249f, -0.12420852f,
+                    0.75699997f, -0.66146249f, -0.00000001f,
+                    0.00000000f, -0.67500001f, -0.75000000f,
+                    0.12305999f, -0.67499995f, -0.74033999f,
+                    0.23928000f, -0.67500007f, -0.71232003f,
+                    0.34721997f, -0.67500001f, -0.66737998f,
+                    0.44544002f, -0.67500007f, -0.60696000f,
+                    0.53250003f, -0.67500007f, -0.53250003f,
+                    0.60696000f, -0.67500001f, -0.44543999f,
+                    0.66738003f, -0.67500001f, -0.34722000f,
+                    0.71231997f, -0.67500001f, -0.23927999f,
+                    0.74033999f, -0.67500001f, -0.12305997f,
+                    0.75000000f, -0.67500001f, -0.00000001f,
+                    0.00000000f, -0.30000001f, 1.00000000f,
+                    -0.16407999f, -0.29999998f, 0.98711991f,
+                    -0.31904000f, -0.30000004f, 0.94976002f,
+                    -0.46296000f, -0.30000004f, 0.88984001f,
+                    -0.59392005f, -0.30000001f, 0.80928004f,
+                    -0.71000004f, -0.30000001f, 0.71000004f,
+                    -0.80928004f, -0.30000001f, 0.59391999f,
+                    -0.88984001f, -0.30000001f, 0.46296003f,
+                    -0.94976002f, -0.30000001f, 0.31904000f,
+                    -0.98712003f, -0.30000001f, 0.16407993f,
+                    -1.00000000f, -0.30000001f, -0.00000000f,
+                    0.00000000f, -0.36416247f, 0.99299991f,
+                    -0.16293143f, -0.36416242f, 0.98021001f,
+                    -0.31680670f, -0.36416250f, 0.94311166f,
+                    -0.45971927f, -0.36416245f, 0.88361102f,
+                    -0.58976257f, -0.36416247f, 0.80361503f,
+                    -0.70502996f, -0.36416247f, 0.70502996f,
+                    -0.80361497f, -0.36416250f, 0.58976251f,
+                    -0.88361108f, -0.36416247f, 0.45971930f,
+                    -0.94311166f, -0.36416250f, 0.31680670f,
+                    -0.98021007f, -0.36416247f, 0.16293137f,
+                    -0.99299991f, -0.36416247f, -0.00000000f,
+                    0.00000000f, -0.42180005f, 0.97400004f,
+                    -0.15981390f, -0.42179999f, 0.96145481f,
+                    -0.31074497f, -0.42180005f, 0.92506635f,
+                    -0.45092306f, -0.42180002f, 0.86670411f,
+                    -0.57847816f, -0.42180011f, 0.78823876f,
+                    -0.69154000f, -0.42180008f, 0.69154000f,
+                    -0.78823876f, -0.42180002f, 0.57847810f,
+                    -0.86670417f, -0.42180005f, 0.45092303f,
+                    -0.92506629f, -0.42180005f, 0.31074494f,
+                    -0.96145493f, -0.42180002f, 0.15981385f,
+                    -0.97400004f, -0.42180002f, -0.00000001f,
+                    0.00000000f, -0.47313753f, 0.94600004f,
+                    -0.15521967f, -0.47313747f, 0.93381548f,
+                    -0.30181187f, -0.47313756f, 0.89847302f,
+                    -0.43796018f, -0.47313750f, 0.84178865f,
+                    -0.56184840f, -0.47313753f, 0.76557899f,
+                    -0.67166001f, -0.47313750f, 0.67166001f,
+                    -0.76557899f, -0.47313753f, 0.56184828f,
+                    -0.84178865f, -0.47313753f, 0.43796021f,
+                    -0.89847302f, -0.47313750f, 0.30181184f,
+                    -0.93381560f, -0.47313750f, 0.15521961f,
+                    -0.94600004f, -0.47313750f, -0.00000001f,
+                    0.00000000f, -0.51840007f, 0.91200000f,
+                    -0.14964095f, -0.51839995f, 0.90025330f,
+                    -0.29096451f, -0.51840007f, 0.86618114f,
+                    -0.42221951f, -0.51840007f, 0.81153411f,
+                    -0.54165506f, -0.51840007f, 0.73806345f,
+                    -0.64752001f, -0.51840007f, 0.64752001f,
+                    -0.73806340f, -0.51840007f, 0.54165506f,
+                    -0.81153411f, -0.51840007f, 0.42221954f,
+                    -0.86618114f, -0.51840007f, 0.29096448f,
+                    -0.90025342f, -0.51840001f, 0.14964090f,
+                    -0.91200000f, -0.51840001f, -0.00000001f,
+                    0.00000000f, -0.55781251f, 0.87500000f,
+                    -0.14356999f, -0.55781251f, 0.86372989f,
+                    -0.27916002f, -0.55781251f, 0.83104002f,
+                    -0.40509003f, -0.55781251f, 0.77860999f,
+                    -0.51968002f, -0.55781257f, 0.70812005f,
+                    -0.62125003f, -0.55781251f, 0.62125003f,
+                    -0.70812005f, -0.55781251f, 0.51967996f,
+                    -0.77860999f, -0.55781257f, 0.40509003f,
+                    -0.83104002f, -0.55781251f, 0.27915999f,
+                    -0.86373001f, -0.55781251f, 0.14356995f,
+                    -0.87500000f, -0.55781251f, -0.00000001f,
+                    0.00000000f, -0.59160000f, 0.83800000f,
+                    -0.13749902f, -0.59159994f, 0.82720655f,
+                    -0.26735553f, -0.59160000f, 0.79589891f,
+                    -0.38796049f, -0.59160000f, 0.74568588f,
+                    -0.49770495f, -0.59160006f, 0.67817670f,
+                    -0.59497994f, -0.59160000f, 0.59498000f,
+                    -0.67817664f, -0.59160000f, 0.49770495f,
+                    -0.74568594f, -0.59160006f, 0.38796049f,
+                    -0.79589891f, -0.59160000f, 0.26735550f,
+                    -0.82720655f, -0.59160000f, 0.13749899f,
+                    -0.83800000f, -0.59160000f, -0.00000001f,
+                    0.00000000f, -0.61998749f, 0.80400008f,
+                    -0.13192032f, -0.61998743f, 0.79364455f,
+                    -0.25650817f, -0.61998749f, 0.76360714f,
+                    -0.37221986f, -0.61998749f, 0.71543139f,
+                    -0.47751173f, -0.61998749f, 0.65066117f,
+                    -0.57084000f, -0.61998749f, 0.57084000f,
+                    -0.65066117f, -0.61998749f, 0.47751170f,
+                    -0.71543145f, -0.61998749f, 0.37221986f,
+                    -0.76360714f, -0.61998749f, 0.25650814f,
+                    -0.79364455f, -0.61998749f, 0.13192026f,
+                    -0.80400008f, -0.61998749f, -0.00000001f,
+                    0.00000000f, -0.64320004f, 0.77600002f,
+                    -0.12732607f, -0.64319998f, 0.76600504f,
+                    -0.24757506f, -0.64320010f, 0.73701382f,
+                    -0.35925698f, -0.64320004f, 0.69051588f,
+                    -0.46088195f, -0.64320010f, 0.62800133f,
+                    -0.55096000f, -0.64320004f, 0.55096000f,
+                    -0.62800133f, -0.64320004f, 0.46088192f,
+                    -0.69051588f, -0.64320004f, 0.35925698f,
+                    -0.73701382f, -0.64320004f, 0.24757503f,
+                    -0.76600516f, -0.64320004f, 0.12732603f,
+                    -0.77600002f, -0.64320004f, -0.00000001f,
+                    0.00000000f, -0.66146249f, 0.75699997f,
+                    -0.12420855f, -0.66146243f, 0.74724966f,
+                    -0.24151328f, -0.66146255f, 0.71896833f,
+                    -0.35046071f, -0.66146243f, 0.67360884f,
+                    -0.44959745f, -0.66146255f, 0.61262494f,
+                    -0.53746998f, -0.66146249f, 0.53746998f,
+                    -0.61262494f, -0.66146249f, 0.44959742f,
+                    -0.67360890f, -0.66146249f, 0.35046071f,
+                    -0.71896827f, -0.66146255f, 0.24151327f,
+                    -0.74724984f, -0.66146249f, 0.12420850f,
+                    -0.75699997f, -0.66146249f, -0.00000001f,
+                    0.00000000f, -0.67500001f, 0.75000000f,
+                    -0.12305999f, -0.67499995f, 0.74033999f,
+                    -0.23928000f, -0.67500007f, 0.71232003f,
+                    -0.34721997f, -0.67500001f, 0.66737998f,
+                    -0.44544002f, -0.67500007f, 0.60696000f,
+                    -0.53250003f, -0.67500007f, 0.53250003f,
+                    -0.60696000f, -0.67500001f, 0.44543999f,
+                    -0.66738003f, -0.67500001f, 0.34722000f,
+                    -0.71231997f, -0.67500001f, 0.23927999f,
+                    -0.74033999f, -0.67500001f, 0.12305995f,
+                    -0.75000000f, -0.67500001f, -0.00000001f,
+                    -1.00000000f, -0.30000001f, -0.00000000f,
+                    -0.98711991f, -0.29999998f, -0.16407999f,
+                    -0.94976002f, -0.30000004f, -0.31904000f,
+                    -0.88984001f, -0.30000004f, -0.46296000f,
+                    -0.80928004f, -0.30000001f, -0.59392005f,
+                    -0.71000004f, -0.30000001f, -0.71000004f,
+                    -0.59391999f, -0.30000001f, -0.80928004f,
+                    -0.46296003f, -0.30000001f, -0.88984001f,
+                    -0.31904000f, -0.30000001f, -0.94976002f,
+                    -0.16407993f, -0.30000001f, -0.98712003f,
+                    0.00000000f, -0.30000001f, -1.00000000f,
+                    -0.99299991f, -0.36416247f, -0.00000000f,
+                    -0.98021001f, -0.36416242f, -0.16293143f,
+                    -0.94311166f, -0.36416250f, -0.31680670f,
+                    -0.88361102f, -0.36416245f, -0.45971927f,
+                    -0.80361503f, -0.36416247f, -0.58976257f,
+                    -0.70502996f, -0.36416247f, -0.70502996f,
+                    -0.58976251f, -0.36416250f, -0.80361497f,
+                    -0.45971930f, -0.36416247f, -0.88361108f,
+                    -0.31680670f, -0.36416250f, -0.94311166f,
+                    -0.16293137f, -0.36416247f, -0.98021007f,
+                    0.00000000f, -0.36416247f, -0.99299991f,
+                    -0.97400004f, -0.42180002f, -0.00000001f,
+                    -0.96145481f, -0.42179996f, -0.15981390f,
+                    -0.92506635f, -0.42180005f, -0.31074497f,
+                    -0.86670411f, -0.42180002f, -0.45092306f,
+                    -0.78823876f, -0.42180005f, -0.57847816f,
+                    -0.69154000f, -0.42180002f, -0.69154000f,
+                    -0.57847810f, -0.42180002f, -0.78823876f,
+                    -0.45092303f, -0.42180002f, -0.86670417f,
+                    -0.31074494f, -0.42180002f, -0.92506629f,
+                    -0.15981385f, -0.42180002f, -0.96145493f,
+                    0.00000000f, -0.42180002f, -0.97400004f,
+                    -0.94600004f, -0.47313750f, -0.00000001f,
+                    -0.93381548f, -0.47313747f, -0.15521967f,
+                    -0.89847302f, -0.47313756f, -0.30181187f,
+                    -0.84178865f, -0.47313750f, -0.43796018f,
+                    -0.76557899f, -0.47313753f, -0.56184840f,
+                    -0.67166001f, -0.47313750f, -0.67166001f,
+                    -0.56184828f, -0.47313750f, -0.76557899f,
+                    -0.43796021f, -0.47313750f, -0.84178865f,
+                    -0.30181184f, -0.47313750f, -0.89847302f,
+                    -0.15521961f, -0.47313750f, -0.93381560f,
+                    0.00000000f, -0.47313750f, -0.94600004f,
+                    -0.91200000f, -0.51840001f, -0.00000001f,
+                    -0.90025330f, -0.51839995f, -0.14964096f,
+                    -0.86618114f, -0.51840001f, -0.29096451f,
+                    -0.81153411f, -0.51840001f, -0.42221951f,
+                    -0.73806345f, -0.51840007f, -0.54165506f,
+                    -0.64752001f, -0.51840001f, -0.64752001f,
+                    -0.54165506f, -0.51840001f, -0.73806340f,
+                    -0.42221954f, -0.51840007f, -0.81153411f,
+                    -0.29096448f, -0.51840007f, -0.86618114f,
+                    -0.14964090f, -0.51840001f, -0.90025342f,
+                    0.00000000f, -0.51840001f, -0.91200000f,
+                    -0.87500000f, -0.55781251f, -0.00000001f,
+                    -0.86372989f, -0.55781251f, -0.14357001f,
+                    -0.83104002f, -0.55781251f, -0.27916002f,
+                    -0.77860999f, -0.55781251f, -0.40509003f,
+                    -0.70812005f, -0.55781257f, -0.51968002f,
+                    -0.62125003f, -0.55781251f, -0.62125003f,
+                    -0.51967996f, -0.55781251f, -0.70812005f,
+                    -0.40509003f, -0.55781257f, -0.77860999f,
+                    -0.27915999f, -0.55781251f, -0.83104002f,
+                    -0.14356995f, -0.55781251f, -0.86373001f,
+                    0.00000000f, -0.55781251f, -0.87500000f,
+                    -0.83800000f, -0.59160000f, -0.00000001f,
+                    -0.82720655f, -0.59159994f, -0.13749903f,
+                    -0.79589891f, -0.59160000f, -0.26735553f,
+                    -0.74568588f, -0.59160000f, -0.38796049f,
+                    -0.67817670f, -0.59160006f, -0.49770495f,
+                    -0.59498000f, -0.59160000f, -0.59497994f,
+                    -0.49770495f, -0.59160000f, -0.67817664f,
+                    -0.38796049f, -0.59160006f, -0.74568594f,
+                    -0.26735550f, -0.59160000f, -0.79589891f,
+                    -0.13749899f, -0.59160000f, -0.82720655f,
+                    0.00000000f, -0.59160000f, -0.83800000f,
+                    -0.80400008f, -0.61998749f, -0.00000001f,
+                    -0.79364455f, -0.61998743f, -0.13192032f,
+                    -0.76360714f, -0.61998749f, -0.25650817f,
+                    -0.71543139f, -0.61998749f, -0.37221986f,
+                    -0.65066117f, -0.61998749f, -0.47751173f,
+                    -0.57084000f, -0.61998749f, -0.57084000f,
+                    -0.47751170f, -0.61998749f, -0.65066117f,
+                    -0.37221986f, -0.61998749f, -0.71543145f,
+                    -0.25650814f, -0.61998749f, -0.76360714f,
+                    -0.13192026f, -0.61998749f, -0.79364455f,
+                    0.00000000f, -0.61998749f, -0.80400008f,
+                    -0.77600002f, -0.64320004f, -0.00000001f,
+                    -0.76600504f, -0.64319998f, -0.12732607f,
+                    -0.73701382f, -0.64320010f, -0.24757506f,
+                    -0.69051588f, -0.64320004f, -0.35925698f,
+                    -0.62800133f, -0.64320010f, -0.46088195f,
+                    -0.55096000f, -0.64320004f, -0.55096000f,
+                    -0.46088192f, -0.64320004f, -0.62800133f,
+                    -0.35925698f, -0.64320004f, -0.69051588f,
+                    -0.24757503f, -0.64320004f, -0.73701382f,
+                    -0.12732603f, -0.64320004f, -0.76600516f,
+                    0.00000000f, -0.64320004f, -0.77600002f,
+                    -0.75699997f, -0.66146249f, -0.00000001f,
+                    -0.74724966f, -0.66146243f, -0.12420855f,
+                    -0.71896833f, -0.66146255f, -0.24151328f,
+                    -0.67360884f, -0.66146243f, -0.35046071f,
+                    -0.61262494f, -0.66146255f, -0.44959745f,
+                    -0.53746998f, -0.66146249f, -0.53746998f,
+                    -0.44959742f, -0.66146249f, -0.61262494f,
+                    -0.35046071f, -0.66146249f, -0.67360890f,
+                    -0.24151327f, -0.66146255f, -0.71896827f,
+                    -0.12420851f, -0.66146249f, -0.74724984f,
+                    0.00000000f, -0.66146249f, -0.75699997f,
+                    -0.75000000f, -0.67500001f, -0.00000001f,
+                    -0.74033999f, -0.67499995f, -0.12306000f,
+                    -0.71232003f, -0.67500007f, -0.23928000f,
+                    -0.66737998f, -0.67500001f, -0.34721997f,
+                    -0.60696000f, -0.67500007f, -0.44544002f,
+                    -0.53250003f, -0.67500007f, -0.53250003f,
+                    -0.44543999f, -0.67500001f, -0.60696000f,
+                    -0.34722000f, -0.67500001f, -0.66738003f,
+                    -0.23927999f, -0.67500001f, -0.71231997f,
+                    -0.12305996f, -0.67500001f, -0.74033999f,
+                    0.00000000f, -0.67500001f, -0.75000000f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500017f, 0.00000001f,
+                    0.00000000f, 0.82500011f, 0.00000001f,
+                    0.00000000f, 0.82500011f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.09730000f, 0.82072502f, 0.00000001f,
+                    0.09605332f, 0.82072490f, 0.01602404f,
+                    0.09243499f, 0.82072508f, 0.03113591f,
+                    0.08662736f, 0.82072502f, 0.04515318f,
+                    0.07881293f, 0.82072508f, 0.05789340f,
+                    0.06917413f, 0.82072502f, 0.06917413f,
+                    0.05789339f, 0.82072502f, 0.07881294f,
+                    0.04515317f, 0.82072508f, 0.08662736f,
+                    0.03113590f, 0.82072502f, 0.09243499f,
+                    0.01602403f, 0.82072502f, 0.09605335f,
+                    0.00000000f, 0.82072502f, 0.09730001f,
+                    0.15440002f, 0.80880016f, 0.00000001f,
+                    0.15242170f, 0.80880004f, 0.02542728f,
+                    0.14667983f, 0.80880022f, 0.04940725f,
+                    0.13746388f, 0.80880022f, 0.07165039f,
+                    0.12506345f, 0.80880028f, 0.09186716f,
+                    0.10976801f, 0.80880016f, 0.10976802f,
+                    0.09186715f, 0.80880016f, 0.12506345f,
+                    0.07165038f, 0.80880022f, 0.13746390f,
+                    0.04940724f, 0.80880022f, 0.14667983f,
+                    0.02542726f, 0.80880016f, 0.15242171f,
+                    0.00000000f, 0.80880016f, 0.15440002f,
+                    0.17909999f, 0.79057503f, 0.00000001f,
+                    0.17680508f, 0.79057491f, 0.02949390f,
+                    0.17014435f, 0.79057509f, 0.05730941f,
+                    0.15945368f, 0.79057509f, 0.08311062f,
+                    0.14506906f, 0.79057503f, 0.10656159f,
+                    0.12732637f, 0.79057503f, 0.12732637f,
+                    0.10656157f, 0.79057503f, 0.14506905f,
+                    0.08311062f, 0.79057503f, 0.15945369f,
+                    0.05730940f, 0.79057503f, 0.17014435f,
+                    0.02949388f, 0.79057503f, 0.17680509f,
+                    0.00000000f, 0.79057503f, 0.17909999f,
+                    0.17920001f, 0.76740009f, 0.00000001f,
+                    0.17690356f, 0.76740003f, 0.02950812f,
+                    0.17023848f, 0.76740015f, 0.05733787f,
+                    0.15954098f, 0.76740009f, 0.08315296f,
+                    0.14514741f, 0.76740015f, 0.10661709f,
+                    0.12739401f, 0.76740009f, 0.12739401f,
+                    0.10661709f, 0.76740009f, 0.14514741f,
+                    0.08315295f, 0.76740009f, 0.15954098f,
+                    0.05733785f, 0.76740015f, 0.17023847f,
+                    0.02950810f, 0.76740009f, 0.17690358f,
+                    0.00000000f, 0.76740009f, 0.17920001f,
+                    0.16250001f, 0.74062496f, 0.00000001f,
+                    0.16041712f, 0.74062496f, 0.02675413f,
+                    0.15437202f, 0.74062502f, 0.05198801f,
+                    0.14466989f, 0.74062496f, 0.07539638f,
+                    0.13161601f, 0.74062496f, 0.09667401f,
+                    0.11551563f, 0.74062496f, 0.11551563f,
+                    0.09667400f, 0.74062496f, 0.13161600f,
+                    0.07539637f, 0.74062502f, 0.14466989f,
+                    0.05198800f, 0.74062502f, 0.15437201f,
+                    0.02675411f, 0.74062496f, 0.16041712f,
+                    0.00000000f, 0.74062496f, 0.16250001f,
+                    0.13680001f, 0.71160007f, 0.00000001f,
+                    0.13504578f, 0.71160001f, 0.02251614f,
+                    0.12995483f, 0.71160012f, 0.04375527f,
+                    0.12178454f, 0.71160007f, 0.06345994f,
+                    0.11079245f, 0.71160012f, 0.08137268f,
+                    0.09723600f, 0.71160007f, 0.09723601f,
+                    0.08137267f, 0.71160007f, 0.11079245f,
+                    0.06345994f, 0.71160007f, 0.12178455f,
+                    0.04375526f, 0.71160007f, 0.12995481f,
+                    0.02251612f, 0.71160007f, 0.13504580f,
+                    0.00000000f, 0.71160007f, 0.13680001f,
+                    0.10990001f, 0.68167502f, 0.00000001f,
+                    0.10848958f, 0.68167496f, 0.01807833f,
+                    0.10439678f, 0.68167502f, 0.03513508f,
+                    0.09782913f, 0.68167496f, 0.05096266f,
+                    0.08899431f, 0.68167502f, 0.06535347f,
+                    0.07809988f, 0.68167502f, 0.07809989f,
+                    0.06535345f, 0.68167502f, 0.08899432f,
+                    0.05096266f, 0.68167502f, 0.09782915f,
+                    0.03513507f, 0.68167502f, 0.10439678f,
+                    0.01807831f, 0.68167502f, 0.10848960f,
+                    0.00000000f, 0.68167502f, 0.10990001f,
+                    0.08960000f, 0.65219998f, 0.00000001f,
+                    0.08844854f, 0.65219992f, 0.01472490f,
+                    0.08510771f, 0.65220004f, 0.02862286f,
+                    0.07974781f, 0.65219998f, 0.04152356f,
+                    0.07253914f, 0.65219998f, 0.05325671f,
+                    0.06365200f, 0.65219998f, 0.06365201f,
+                    0.05325670f, 0.65219998f, 0.07253915f,
+                    0.04152355f, 0.65219998f, 0.07974782f,
+                    0.02862285f, 0.65219998f, 0.08510773f,
+                    0.01472489f, 0.65219998f, 0.08844855f,
+                    0.00000000f, 0.65219998f, 0.08960001f,
+                    0.08370000f, 0.62452501f, 0.00000001f,
+                    0.08262266f, 0.62452495f, 0.01374006f,
+                    0.07949751f, 0.62452501f, 0.02671403f,
+                    0.07448471f, 0.62452501f, 0.03876166f,
+                    0.06774452f, 0.62452507f, 0.04972278f,
+                    0.05943713f, 0.62452501f, 0.05943713f,
+                    0.04972277f, 0.62452501f, 0.06774452f,
+                    0.03876166f, 0.62452501f, 0.07448472f,
+                    0.02671402f, 0.62452501f, 0.07949752f,
+                    0.01374005f, 0.62452501f, 0.08262268f,
+                    0.00000000f, 0.62452501f, 0.08370001f,
+                    0.10000000f, 0.60000002f, 0.00000001f,
+                    0.09871199f, 0.59999996f, 0.01640801f,
+                    0.09497602f, 0.60000008f, 0.03190401f,
+                    0.08898400f, 0.60000008f, 0.04629601f,
+                    0.08092801f, 0.60000002f, 0.05939201f,
+                    0.07100000f, 0.60000002f, 0.07100001f,
+                    0.05939200f, 0.60000002f, 0.08092801f,
+                    0.04629600f, 0.60000002f, 0.08898401f,
+                    0.03190400f, 0.60000002f, 0.09497601f,
+                    0.01640799f, 0.60000002f, 0.09871200f,
+                    0.00000000f, 0.60000002f, 0.10000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500017f, 0.00000001f,
+                    0.00000000f, 0.82500011f, 0.00000001f,
+                    0.00000000f, 0.82500011f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82072502f, -0.09729999f,
+                    0.01602403f, 0.82072490f, -0.09605332f,
+                    0.03113590f, 0.82072508f, -0.09243497f,
+                    0.04515317f, 0.82072502f, -0.08662734f,
+                    0.05789339f, 0.82072508f, -0.07881292f,
+                    0.06917413f, 0.82072502f, -0.06917411f,
+                    0.07881293f, 0.82072502f, -0.05789338f,
+                    0.08662736f, 0.82072508f, -0.04515316f,
+                    0.09243498f, 0.82072502f, -0.03113589f,
+                    0.09605334f, 0.82072502f, -0.01602402f,
+                    0.09730000f, 0.82072502f, 0.00000001f,
+                    0.00000000f, 0.80880016f, -0.15440002f,
+                    0.02542727f, 0.80880004f, -0.15242170f,
+                    0.04940724f, 0.80880022f, -0.14667983f,
+                    0.07165038f, 0.80880022f, -0.13746388f,
+                    0.09186715f, 0.80880028f, -0.12506345f,
+                    0.10976800f, 0.80880016f, -0.10976801f,
+                    0.12506345f, 0.80880016f, -0.09186714f,
+                    0.13746388f, 0.80880022f, -0.07165038f,
+                    0.14667982f, 0.80880022f, -0.04940723f,
+                    0.15242171f, 0.80880016f, -0.02542725f,
+                    0.15440002f, 0.80880016f, 0.00000001f,
+                    0.00000000f, 0.79057503f, -0.17909999f,
+                    0.02949389f, 0.79057491f, -0.17680508f,
+                    0.05730940f, 0.79057509f, -0.17014435f,
+                    0.08311061f, 0.79057509f, -0.15945368f,
+                    0.10656158f, 0.79057503f, -0.14506905f,
+                    0.12732637f, 0.79057503f, -0.12732637f,
+                    0.14506905f, 0.79057503f, -0.10656157f,
+                    0.15945369f, 0.79057503f, -0.08311061f,
+                    0.17014435f, 0.79057503f, -0.05730939f,
+                    0.17680509f, 0.79057503f, -0.02949387f,
+                    0.17909999f, 0.79057503f, 0.00000001f,
+                    0.00000000f, 0.76740009f, -0.17920001f,
+                    0.02950811f, 0.76740003f, -0.17690356f,
+                    0.05733786f, 0.76740015f, -0.17023848f,
+                    0.08315294f, 0.76740009f, -0.15954097f,
+                    0.10661709f, 0.76740015f, -0.14514740f,
+                    0.12739401f, 0.76740009f, -0.12739399f,
+                    0.14514741f, 0.76740009f, -0.10661709f,
+                    0.15954098f, 0.76740009f, -0.08315295f,
+                    0.17023847f, 0.76740015f, -0.05733785f,
+                    0.17690358f, 0.76740009f, -0.02950809f,
+                    0.17920001f, 0.76740009f, 0.00000001f,
+                    0.00000000f, 0.74062496f, -0.16250001f,
+                    0.02675412f, 0.74062496f, -0.16041712f,
+                    0.05198800f, 0.74062502f, -0.15437202f,
+                    0.07539637f, 0.74062496f, -0.14466989f,
+                    0.09667401f, 0.74062496f, -0.13161601f,
+                    0.11551563f, 0.74062496f, -0.11551563f,
+                    0.13161600f, 0.74062496f, -0.09667400f,
+                    0.14466989f, 0.74062502f, -0.07539637f,
+                    0.15437201f, 0.74062502f, -0.05198799f,
+                    0.16041712f, 0.74062496f, -0.02675411f,
+                    0.16250001f, 0.74062496f, 0.00000001f,
+                    0.00000000f, 0.71160007f, -0.13679999f,
+                    0.02251613f, 0.71160001f, -0.13504578f,
+                    0.04375527f, 0.71160012f, -0.12995481f,
+                    0.06345994f, 0.71160007f, -0.12178454f,
+                    0.08137268f, 0.71160012f, -0.11079244f,
+                    0.09723600f, 0.71160007f, -0.09723599f,
+                    0.11079245f, 0.71160007f, -0.08137266f,
+                    0.12178455f, 0.71160007f, -0.06345993f,
+                    0.12995481f, 0.71160007f, -0.04375526f,
+                    0.13504580f, 0.71160007f, -0.02251611f,
+                    0.13680001f, 0.71160007f, 0.00000001f,
+                    0.00000000f, 0.68167502f, -0.10990000f,
+                    0.01807832f, 0.68167496f, -0.10848958f,
+                    0.03513507f, 0.68167502f, -0.10439678f,
+                    0.05096266f, 0.68167496f, -0.09782913f,
+                    0.06535346f, 0.68167502f, -0.08899431f,
+                    0.07809988f, 0.68167502f, -0.07809987f,
+                    0.08899431f, 0.68167502f, -0.06535345f,
+                    0.09782915f, 0.68167502f, -0.05096265f,
+                    0.10439678f, 0.68167502f, -0.03513506f,
+                    0.10848960f, 0.68167502f, -0.01807830f,
+                    0.10990001f, 0.68167502f, 0.00000001f,
+                    0.00000000f, 0.65219998f, -0.08960000f,
+                    0.01472490f, 0.65219992f, -0.08844854f,
+                    0.02862285f, 0.65220004f, -0.08510771f,
+                    0.04152355f, 0.65219998f, -0.07974780f,
+                    0.05325671f, 0.65219998f, -0.07253914f,
+                    0.06365199f, 0.65219998f, -0.06365199f,
+                    0.07253914f, 0.65219998f, -0.05325670f,
+                    0.07974781f, 0.65219998f, -0.04152355f,
+                    0.08510771f, 0.65219998f, -0.02862284f,
+                    0.08844854f, 0.65219998f, -0.01472488f,
+                    0.08960000f, 0.65219998f, 0.00000001f,
+                    0.00000000f, 0.62452501f, -0.08369999f,
+                    0.01374006f, 0.62452495f, -0.08262266f,
+                    0.02671402f, 0.62452501f, -0.07949750f,
+                    0.03876166f, 0.62452501f, -0.07448471f,
+                    0.04972277f, 0.62452507f, -0.06774451f,
+                    0.05943713f, 0.62452501f, -0.05943712f,
+                    0.06774452f, 0.62452501f, -0.04972276f,
+                    0.07448471f, 0.62452501f, -0.03876166f,
+                    0.07949751f, 0.62452501f, -0.02671401f,
+                    0.08262268f, 0.62452501f, -0.01374004f,
+                    0.08370000f, 0.62452501f, 0.00000001f,
+                    0.00000000f, 0.60000002f, -0.09999999f,
+                    0.01640800f, 0.59999996f, -0.09871199f,
+                    0.03190400f, 0.60000008f, -0.09497599f,
+                    0.04629600f, 0.60000008f, -0.08898398f,
+                    0.05939200f, 0.60000002f, -0.08092800f,
+                    0.07100000f, 0.60000002f, -0.07099999f,
+                    0.08092801f, 0.60000002f, -0.05939199f,
+                    0.08898400f, 0.60000002f, -0.04629600f,
+                    0.09497601f, 0.60000002f, -0.03190399f,
+                    0.09871200f, 0.60000002f, -0.01640799f,
+                    0.10000000f, 0.60000002f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500017f, 0.00000001f,
+                    0.00000000f, 0.82500011f, 0.00000001f,
+                    0.00000000f, 0.82500011f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82072502f, 0.09730001f,
+                    -0.01602403f, 0.82072490f, 0.09605334f,
+                    -0.03113590f, 0.82072508f, 0.09243499f,
+                    -0.04515317f, 0.82072502f, 0.08662736f,
+                    -0.05789339f, 0.82072508f, 0.07881294f,
+                    -0.06917413f, 0.82072502f, 0.06917413f,
+                    -0.07881293f, 0.82072502f, 0.05789340f,
+                    -0.08662736f, 0.82072508f, 0.04515318f,
+                    -0.09243498f, 0.82072502f, 0.03113591f,
+                    -0.09605334f, 0.82072502f, 0.01602404f,
+                    -0.09730000f, 0.82072502f, 0.00000001f,
+                    0.00000000f, 0.80880016f, 0.15440002f,
+                    -0.02542727f, 0.80880004f, 0.15242170f,
+                    -0.04940724f, 0.80880022f, 0.14667983f,
+                    -0.07165038f, 0.80880022f, 0.13746390f,
+                    -0.09186715f, 0.80880028f, 0.12506345f,
+                    -0.10976800f, 0.80880016f, 0.10976802f,
+                    -0.12506345f, 0.80880016f, 0.09186715f,
+                    -0.13746388f, 0.80880022f, 0.07165039f,
+                    -0.14667982f, 0.80880022f, 0.04940725f,
+                    -0.15242171f, 0.80880016f, 0.02542727f,
+                    -0.15440002f, 0.80880016f, 0.00000001f,
+                    0.00000000f, 0.79057503f, 0.17909999f,
+                    -0.02949389f, 0.79057491f, 0.17680508f,
+                    -0.05730940f, 0.79057509f, 0.17014435f,
+                    -0.08311061f, 0.79057509f, 0.15945368f,
+                    -0.10656158f, 0.79057503f, 0.14506906f,
+                    -0.12732637f, 0.79057503f, 0.12732637f,
+                    -0.14506905f, 0.79057503f, 0.10656157f,
+                    -0.15945369f, 0.79057503f, 0.08311062f,
+                    -0.17014435f, 0.79057503f, 0.05730941f,
+                    -0.17680509f, 0.79057503f, 0.02949389f,
+                    -0.17909999f, 0.79057503f, 0.00000001f,
+                    0.00000000f, 0.76740009f, 0.17920001f,
+                    -0.02950811f, 0.76740003f, 0.17690358f,
+                    -0.05733786f, 0.76740015f, 0.17023848f,
+                    -0.08315294f, 0.76740009f, 0.15954098f,
+                    -0.10661709f, 0.76740015f, 0.14514741f,
+                    -0.12739401f, 0.76740009f, 0.12739401f,
+                    -0.14514741f, 0.76740009f, 0.10661709f,
+                    -0.15954098f, 0.76740009f, 0.08315295f,
+                    -0.17023847f, 0.76740015f, 0.05733786f,
+                    -0.17690358f, 0.76740009f, 0.02950811f,
+                    -0.17920001f, 0.76740009f, 0.00000001f,
+                    0.00000000f, 0.74062496f, 0.16250001f,
+                    -0.02675412f, 0.74062496f, 0.16041712f,
+                    -0.05198800f, 0.74062502f, 0.15437202f,
+                    -0.07539637f, 0.74062496f, 0.14466989f,
+                    -0.09667401f, 0.74062496f, 0.13161601f,
+                    -0.11551563f, 0.74062496f, 0.11551563f,
+                    -0.13161600f, 0.74062496f, 0.09667400f,
+                    -0.14466989f, 0.74062502f, 0.07539638f,
+                    -0.15437201f, 0.74062502f, 0.05198800f,
+                    -0.16041712f, 0.74062496f, 0.02675412f,
+                    -0.16250001f, 0.74062496f, 0.00000001f,
+                    0.00000000f, 0.71160007f, 0.13680001f,
+                    -0.02251613f, 0.71160001f, 0.13504578f,
+                    -0.04375527f, 0.71160012f, 0.12995483f,
+                    -0.06345994f, 0.71160007f, 0.12178455f,
+                    -0.08137268f, 0.71160012f, 0.11079246f,
+                    -0.09723600f, 0.71160007f, 0.09723601f,
+                    -0.11079245f, 0.71160007f, 0.08137267f,
+                    -0.12178455f, 0.71160007f, 0.06345994f,
+                    -0.12995481f, 0.71160007f, 0.04375527f,
+                    -0.13504580f, 0.71160007f, 0.02251613f,
+                    -0.13680001f, 0.71160007f, 0.00000001f,
+                    0.00000000f, 0.68167502f, 0.10990001f,
+                    -0.01807832f, 0.68167496f, 0.10848960f,
+                    -0.03513507f, 0.68167502f, 0.10439679f,
+                    -0.05096266f, 0.68167496f, 0.09782915f,
+                    -0.06535346f, 0.68167502f, 0.08899432f,
+                    -0.07809988f, 0.68167502f, 0.07809988f,
+                    -0.08899431f, 0.68167502f, 0.06535346f,
+                    -0.09782915f, 0.68167502f, 0.05096267f,
+                    -0.10439678f, 0.68167502f, 0.03513508f,
+                    -0.10848960f, 0.68167502f, 0.01807832f,
+                    -0.10990001f, 0.68167502f, 0.00000001f,
+                    0.00000000f, 0.65219998f, 0.08960001f,
+                    -0.01472490f, 0.65219992f, 0.08844855f,
+                    -0.02862285f, 0.65220004f, 0.08510773f,
+                    -0.04152355f, 0.65219998f, 0.07974782f,
+                    -0.05325671f, 0.65219998f, 0.07253915f,
+                    -0.06365199f, 0.65219998f, 0.06365201f,
+                    -0.07253914f, 0.65219998f, 0.05325671f,
+                    -0.07974781f, 0.65219998f, 0.04152356f,
+                    -0.08510771f, 0.65219998f, 0.02862286f,
+                    -0.08844854f, 0.65219998f, 0.01472490f,
+                    -0.08960000f, 0.65219998f, 0.00000001f,
+                    0.00000000f, 0.62452501f, 0.08370001f,
+                    -0.01374006f, 0.62452495f, 0.08262267f,
+                    -0.02671402f, 0.62452501f, 0.07949752f,
+                    -0.03876166f, 0.62452501f, 0.07448472f,
+                    -0.04972277f, 0.62452507f, 0.06774452f,
+                    -0.05943713f, 0.62452501f, 0.05943713f,
+                    -0.06774452f, 0.62452501f, 0.04972278f,
+                    -0.07448471f, 0.62452501f, 0.03876167f,
+                    -0.07949751f, 0.62452501f, 0.02671402f,
+                    -0.08262268f, 0.62452501f, 0.01374006f,
+                    -0.08370000f, 0.62452501f, 0.00000001f,
+                    0.00000000f, 0.60000002f, 0.10000001f,
+                    -0.01640800f, 0.59999996f, 0.09871200f,
+                    -0.03190400f, 0.60000008f, 0.09497602f,
+                    -0.04629600f, 0.60000008f, 0.08898401f,
+                    -0.05939200f, 0.60000002f, 0.08092801f,
+                    -0.07100000f, 0.60000002f, 0.07100001f,
+                    -0.08092801f, 0.60000002f, 0.05939201f,
+                    -0.08898400f, 0.60000002f, 0.04629601f,
+                    -0.09497601f, 0.60000002f, 0.03190401f,
+                    -0.09871200f, 0.60000002f, 0.01640800f,
+                    -0.10000000f, 0.60000002f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500017f, 0.00000001f,
+                    0.00000000f, 0.82500011f, 0.00000001f,
+                    0.00000000f, 0.82500011f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    0.00000000f, 0.82500005f, 0.00000001f,
+                    -0.09730000f, 0.82072502f, 0.00000001f,
+                    -0.09605332f, 0.82072490f, -0.01602402f,
+                    -0.09243499f, 0.82072508f, -0.03113589f,
+                    -0.08662736f, 0.82072502f, -0.04515316f,
+                    -0.07881293f, 0.82072508f, -0.05789338f,
+                    -0.06917413f, 0.82072502f, -0.06917411f,
+                    -0.05789339f, 0.82072502f, -0.07881292f,
+                    -0.04515317f, 0.82072508f, -0.08662735f,
+                    -0.03113590f, 0.82072502f, -0.09243497f,
+                    -0.01602403f, 0.82072502f, -0.09605333f,
+                    0.00000000f, 0.82072502f, -0.09729999f,
+                    -0.15440002f, 0.80880016f, 0.00000001f,
+                    -0.15242170f, 0.80880004f, -0.02542726f,
+                    -0.14667983f, 0.80880022f, -0.04940723f,
+                    -0.13746388f, 0.80880022f, -0.07165037f,
+                    -0.12506345f, 0.80880028f, -0.09186714f,
+                    -0.10976801f, 0.80880016f, -0.10976800f,
+                    -0.09186715f, 0.80880016f, -0.12506345f,
+                    -0.07165038f, 0.80880022f, -0.13746388f,
+                    -0.04940724f, 0.80880022f, -0.14667982f,
+                    -0.02542726f, 0.80880016f, -0.15242171f,
+                    0.00000000f, 0.80880016f, -0.15440002f,
+                    -0.17909999f, 0.79057503f, 0.00000001f,
+                    -0.17680508f, 0.79057491f, -0.02949388f,
+                    -0.17014435f, 0.79057509f, -0.05730940f,
+                    -0.15945368f, 0.79057509f, -0.08311061f,
+                    -0.14506906f, 0.79057503f, -0.10656158f,
+                    -0.12732637f, 0.79057503f, -0.12732637f,
+                    -0.10656157f, 0.79057503f, -0.14506905f,
+                    -0.08311062f, 0.79057503f, -0.15945369f,
+                    -0.05730940f, 0.79057503f, -0.17014435f,
+                    -0.02949388f, 0.79057503f, -0.17680509f,
+                    0.00000000f, 0.79057503f, -0.17909999f,
+                    -0.17920001f, 0.76740009f, 0.00000001f,
+                    -0.17690356f, 0.76740003f, -0.02950810f,
+                    -0.17023848f, 0.76740015f, -0.05733785f,
+                    -0.15954098f, 0.76740009f, -0.08315294f,
+                    -0.14514741f, 0.76740015f, -0.10661709f,
+                    -0.12739401f, 0.76740009f, -0.12739401f,
+                    -0.10661709f, 0.76740009f, -0.14514740f,
+                    -0.08315295f, 0.76740009f, -0.15954098f,
+                    -0.05733785f, 0.76740015f, -0.17023847f,
+                    -0.02950810f, 0.76740009f, -0.17690358f,
+                    0.00000000f, 0.76740009f, -0.17920001f,
+                    -0.16250001f, 0.74062496f, 0.00000001f,
+                    -0.16041712f, 0.74062496f, -0.02675412f,
+                    -0.15437202f, 0.74062502f, -0.05198800f,
+                    -0.14466989f, 0.74062496f, -0.07539637f,
+                    -0.13161601f, 0.74062496f, -0.09667400f,
+                    -0.11551563f, 0.74062496f, -0.11551563f,
+                    -0.09667400f, 0.74062496f, -0.13161600f,
+                    -0.07539637f, 0.74062502f, -0.14466989f,
+                    -0.05198800f, 0.74062502f, -0.15437201f,
+                    -0.02675411f, 0.74062496f, -0.16041712f,
+                    0.00000000f, 0.74062496f, -0.16250001f,
+                    -0.13680001f, 0.71160007f, 0.00000001f,
+                    -0.13504578f, 0.71160001f, -0.02251612f,
+                    -0.12995483f, 0.71160012f, -0.04375526f,
+                    -0.12178454f, 0.71160007f, -0.06345993f,
+                    -0.11079245f, 0.71160012f, -0.08137267f,
+                    -0.09723600f, 0.71160007f, -0.09723599f,
+                    -0.08137267f, 0.71160007f, -0.11079245f,
+                    -0.06345994f, 0.71160007f, -0.12178454f,
+                    -0.04375526f, 0.71160007f, -0.12995481f,
+                    -0.02251612f, 0.71160007f, -0.13504578f,
+                    0.00000000f, 0.71160007f, -0.13679999f,
+                    -0.10990001f, 0.68167502f, 0.00000001f,
+                    -0.10848958f, 0.68167496f, -0.01807831f,
+                    -0.10439678f, 0.68167502f, -0.03513507f,
+                    -0.09782913f, 0.68167496f, -0.05096265f,
+                    -0.08899431f, 0.68167502f, -0.06535345f,
+                    -0.07809988f, 0.68167502f, -0.07809987f,
+                    -0.06535345f, 0.68167502f, -0.08899430f,
+                    -0.05096266f, 0.68167502f, -0.09782913f,
+                    -0.03513507f, 0.68167502f, -0.10439677f,
+                    -0.01807831f, 0.68167502f, -0.10848959f,
+                    0.00000000f, 0.68167502f, -0.10990000f,
+                    -0.08960000f, 0.65219998f, 0.00000001f,
+                    -0.08844854f, 0.65219992f, -0.01472489f,
+                    -0.08510771f, 0.65220004f, -0.02862284f,
+                    -0.07974781f, 0.65219998f, -0.04152355f,
+                    -0.07253914f, 0.65219998f, -0.05325670f,
+                    -0.06365200f, 0.65219998f, -0.06365199f,
+                    -0.05325670f, 0.65219998f, -0.07253914f,
+                    -0.04152355f, 0.65219998f, -0.07974781f,
+                    -0.02862285f, 0.65219998f, -0.08510771f,
+                    -0.01472489f, 0.65219998f, -0.08844854f,
+                    0.00000000f, 0.65219998f, -0.08960000f,
+                    -0.08370000f, 0.62452501f, 0.00000001f,
+                    -0.08262266f, 0.62452495f, -0.01374005f,
+                    -0.07949751f, 0.62452501f, -0.02671401f,
+                    -0.07448471f, 0.62452501f, -0.03876165f,
+                    -0.06774452f, 0.62452507f, -0.04972276f,
+                    -0.05943713f, 0.62452501f, -0.05943712f,
+                    -0.04972277f, 0.62452501f, -0.06774451f,
+                    -0.03876166f, 0.62452501f, -0.07448471f,
+                    -0.02671402f, 0.62452501f, -0.07949750f,
+                    -0.01374005f, 0.62452501f, -0.08262267f,
+                    0.00000000f, 0.62452501f, -0.08369999f,
+                    -0.10000000f, 0.60000002f, 0.00000001f,
+                    -0.09871199f, 0.59999996f, -0.01640799f,
+                    -0.09497602f, 0.60000008f, -0.03190399f,
+                    -0.08898400f, 0.60000008f, -0.04629599f,
+                    -0.08092801f, 0.60000002f, -0.05939199f,
+                    -0.07100000f, 0.60000002f, -0.07099999f,
+                    -0.05939200f, 0.60000002f, -0.08092800f,
+                    -0.04629600f, 0.60000002f, -0.08898400f,
+                    -0.03190400f, 0.60000002f, -0.09497599f,
+                    -0.01640799f, 0.60000002f, -0.09871200f,
+                    0.00000000f, 0.60000002f, -0.09999999f,
+                    0.10000000f, 0.60000002f, 0.00000001f,
+                    0.09871199f, 0.59999996f, 0.01640801f,
+                    0.09497602f, 0.60000008f, 0.03190401f,
+                    0.08898400f, 0.60000008f, 0.04629601f,
+                    0.08092801f, 0.60000002f, 0.05939201f,
+                    0.07100000f, 0.60000002f, 0.07100001f,
+                    0.05939200f, 0.60000002f, 0.08092801f,
+                    0.04629600f, 0.60000002f, 0.08898401f,
+                    0.03190400f, 0.60000002f, 0.09497601f,
+                    0.01640799f, 0.60000002f, 0.09871200f,
+                    0.00000000f, 0.60000002f, 0.10000001f,
+                    0.13969998f, 0.57959992f, 0.00000001f,
+                    0.13790064f, 0.57959986f, 0.02292198f,
+                    0.13268146f, 0.57959998f, 0.04456990f,
+                    0.12431064f, 0.57959992f, 0.06467552f,
+                    0.11305641f, 0.57959998f, 0.08297063f,
+                    0.09918699f, 0.57959992f, 0.09918701f,
+                    0.08297062f, 0.57959992f, 0.11305644f,
+                    0.06467551f, 0.57959998f, 0.12431066f,
+                    0.04456988f, 0.57959992f, 0.13268149f,
+                    0.02292197f, 0.57959992f, 0.13790068f,
+                    0.00000000f, 0.57959992f, 0.13970001f,
+                    0.19560002f, 0.56280005f, 0.00000001f,
+                    0.19308068f, 0.56279999f, 0.03209405f,
+                    0.18577307f, 0.56280011f, 0.06240424f,
+                    0.17405272f, 0.56280005f, 0.09055499f,
+                    0.15829518f, 0.56280005f, 0.11617076f,
+                    0.13887602f, 0.56280005f, 0.13887601f,
+                    0.11617076f, 0.56280005f, 0.15829518f,
+                    0.09055499f, 0.56280005f, 0.17405272f,
+                    0.06240423f, 0.56280005f, 0.18577307f,
+                    0.03209404f, 0.56280005f, 0.19308069f,
+                    0.00000000f, 0.56280005f, 0.19560002f,
+                    0.26289999f, 0.54869998f, 0.00000001f,
+                    0.25951380f, 0.54869998f, 0.04313663f,
+                    0.24969190f, 0.54869998f, 0.08387563f,
+                    0.23393893f, 0.54870003f, 0.12171219f,
+                    0.21275970f, 0.54869998f, 0.15614158f,
+                    0.18665901f, 0.54869998f, 0.18665899f,
+                    0.15614155f, 0.54869998f, 0.21275972f,
+                    0.12171219f, 0.54869998f, 0.23393893f,
+                    0.08387562f, 0.54869998f, 0.24969190f,
+                    0.04313662f, 0.54869998f, 0.25951385f,
+                    0.00000000f, 0.54869998f, 0.26289999f,
+                    0.33680001f, 0.53640002f, 0.00000001f,
+                    0.33246198f, 0.53639996f, 0.05526215f,
+                    0.31987920f, 0.53640002f, 0.10745268f,
+                    0.29969811f, 0.53640002f, 0.15592493f,
+                    0.27256551f, 0.53640002f, 0.20003228f,
+                    0.23912801f, 0.53640002f, 0.23912801f,
+                    0.20003226f, 0.53640002f, 0.27256551f,
+                    0.15592495f, 0.53640008f, 0.29969811f,
+                    0.10745268f, 0.53640008f, 0.31987917f,
+                    0.05526213f, 0.53640002f, 0.33246201f,
+                    0.00000000f, 0.53640002f, 0.33680001f,
+                    0.41250002f, 0.52499998f, 0.00000001f,
+                    0.40718701f, 0.52499992f, 0.06768300f,
+                    0.39177606f, 0.52499998f, 0.13160400f,
+                    0.36705902f, 0.52499998f, 0.19097100f,
+                    0.33382803f, 0.52499998f, 0.24499202f,
+                    0.29287499f, 0.52499998f, 0.29287499f,
+                    0.24499199f, 0.52499998f, 0.33382803f,
+                    0.19097102f, 0.52499998f, 0.36705902f,
+                    0.13160400f, 0.52499998f, 0.39177603f,
+                    0.06768297f, 0.52499998f, 0.40718701f,
+                    0.00000000f, 0.52499998f, 0.41250002f,
+                    0.48519999f, 0.51359999f, 0.00000001f,
+                    0.47895056f, 0.51359999f, 0.07961162f,
+                    0.46082360f, 0.51359999f, 0.15479822f,
+                    0.43175036f, 0.51359999f, 0.22462820f,
+                    0.39266267f, 0.51360005f, 0.28817001f,
+                    0.34449199f, 0.51359999f, 0.34449199f,
+                    0.28816998f, 0.51359999f, 0.39266264f,
+                    0.22462821f, 0.51359999f, 0.43175036f,
+                    0.15479821f, 0.51359999f, 0.46082354f,
+                    0.07961158f, 0.51359999f, 0.47895062f,
+                    0.00000000f, 0.51359999f, 0.48519999f,
+                    0.55009997f, 0.50129998f, 0.00000001f,
+                    0.54301465f, 0.50129992f, 0.09026041f,
+                    0.52246296f, 0.50129998f, 0.17550392f,
+                    0.48950094f, 0.50129998f, 0.25467429f,
+                    0.44518495f, 0.50129998f, 0.32671541f,
+                    0.39057100f, 0.50129998f, 0.39057100f,
+                    0.32671538f, 0.50129998f, 0.44518495f,
+                    0.25467432f, 0.50129998f, 0.48950097f,
+                    0.17550389f, 0.50129998f, 0.52246296f,
+                    0.09026037f, 0.50129998f, 0.54301471f,
+                    0.00000000f, 0.50129998f, 0.55009997f,
+                    0.60240000f, 0.48720002f, 0.00000001f,
+                    0.59464109f, 0.48719996f, 0.09884179f,
+                    0.57213551f, 0.48720005f, 0.19218971f,
+                    0.53603959f, 0.48719999f, 0.27888709f,
+                    0.48751029f, 0.48720002f, 0.35777742f,
+                    0.42770401f, 0.48720002f, 0.42770401f,
+                    0.35777742f, 0.48720002f, 0.48751026f,
+                    0.27888709f, 0.48720005f, 0.53603959f,
+                    0.19218969f, 0.48720002f, 0.57213545f,
+                    0.09884176f, 0.48720002f, 0.59464109f,
+                    0.00000000f, 0.48720002f, 0.60240000f,
+                    0.63730001f, 0.47040004f, 0.00000001f,
+                    0.62909156f, 0.47039998f, 0.10456818f,
+                    0.60528207f, 0.47040007f, 0.20332420f,
+                    0.56709504f, 0.47040001f, 0.29504442f,
+                    0.51575416f, 0.47040004f, 0.37850523f,
+                    0.45248300f, 0.47040004f, 0.45248300f,
+                    0.37850523f, 0.47040004f, 0.51575416f,
+                    0.29504442f, 0.47040004f, 0.56709504f,
+                    0.20332420f, 0.47040004f, 0.60528207f,
+                    0.10456815f, 0.47040004f, 0.62909162f,
+                    0.00000000f, 0.47040004f, 0.63730001f,
+                    0.64999998f, 0.45000005f, 0.00000001f,
+                    0.64162791f, 0.44999996f, 0.10665201f,
+                    0.61734402f, 0.45000011f, 0.20737600f,
+                    0.57839590f, 0.45000002f, 0.30092400f,
+                    0.52603197f, 0.45000008f, 0.38604802f,
+                    0.46149999f, 0.45000005f, 0.46149999f,
+                    0.38604796f, 0.45000005f, 0.52603197f,
+                    0.30092400f, 0.45000008f, 0.57839596f,
+                    0.20737599f, 0.45000005f, 0.61734402f,
+                    0.10665196f, 0.45000005f, 0.64162797f,
+                    0.00000000f, 0.45000005f, 0.64999998f,
+                    0.00000000f, 0.60000002f, -0.09999999f,
+                    0.01640800f, 0.59999996f, -0.09871199f,
+                    0.03190400f, 0.60000008f, -0.09497599f,
+                    0.04629600f, 0.60000008f, -0.08898398f,
+                    0.05939200f, 0.60000002f, -0.08092800f,
+                    0.07100000f, 0.60000002f, -0.07099999f,
+                    0.08092801f, 0.60000002f, -0.05939199f,
+                    0.08898400f, 0.60000002f, -0.04629600f,
+                    0.09497601f, 0.60000002f, -0.03190399f,
+                    0.09871200f, 0.60000002f, -0.01640799f,
+                    0.10000000f, 0.60000002f, 0.00000001f,
+                    0.00000000f, 0.57959992f, -0.13969998f,
+                    0.02292197f, 0.57959986f, -0.13790064f,
+                    0.04456988f, 0.57959998f, -0.13268146f,
+                    0.06467551f, 0.57959992f, -0.12431064f,
+                    0.08297062f, 0.57959998f, -0.11305641f,
+                    0.09918699f, 0.57959992f, -0.09918699f,
+                    0.11305641f, 0.57959992f, -0.08297061f,
+                    0.12431064f, 0.57959998f, -0.06467551f,
+                    0.13268146f, 0.57959992f, -0.04456988f,
+                    0.13790065f, 0.57959992f, -0.02292196f,
+                    0.13969998f, 0.57959992f, 0.00000001f,
+                    0.00000000f, 0.56280005f, -0.19560000f,
+                    0.03209405f, 0.56279999f, -0.19308066f,
+                    0.06240423f, 0.56280011f, -0.18577306f,
+                    0.09055499f, 0.56280005f, -0.17405270f,
+                    0.11617076f, 0.56280005f, -0.15829517f,
+                    0.13887601f, 0.56280005f, -0.13887601f,
+                    0.15829518f, 0.56280005f, -0.11617075f,
+                    0.17405272f, 0.56280005f, -0.09055498f,
+                    0.18577307f, 0.56280005f, -0.06240422f,
+                    0.19308069f, 0.56280005f, -0.03209403f,
+                    0.19560002f, 0.56280005f, 0.00000001f,
+                    0.00000000f, 0.54869998f, -0.26289999f,
+                    0.04313663f, 0.54869998f, -0.25951380f,
+                    0.08387563f, 0.54869998f, -0.24969190f,
+                    0.12171219f, 0.54870003f, -0.23393893f,
+                    0.15614158f, 0.54869998f, -0.21275970f,
+                    0.18665899f, 0.54869998f, -0.18665899f,
+                    0.21275972f, 0.54869998f, -0.15614155f,
+                    0.23393893f, 0.54869998f, -0.12171219f,
+                    0.24969190f, 0.54869998f, -0.08387561f,
+                    0.25951385f, 0.54869998f, -0.04313661f,
+                    0.26289999f, 0.54869998f, 0.00000001f,
+                    0.00000000f, 0.53640002f, -0.33680001f,
+                    0.05526214f, 0.53639996f, -0.33246198f,
+                    0.10745268f, 0.53640002f, -0.31987920f,
+                    0.15592493f, 0.53640002f, -0.29969811f,
+                    0.20003228f, 0.53640002f, -0.27256551f,
+                    0.23912801f, 0.53640002f, -0.23912799f,
+                    0.27256551f, 0.53640002f, -0.20003225f,
+                    0.29969811f, 0.53640008f, -0.15592495f,
+                    0.31987917f, 0.53640008f, -0.10745267f,
+                    0.33246201f, 0.53640002f, -0.05526212f,
+                    0.33680001f, 0.53640002f, 0.00000001f,
+                    0.00000000f, 0.52499998f, -0.41250002f,
+                    0.06768300f, 0.52499992f, -0.40718701f,
+                    0.13160400f, 0.52499998f, -0.39177606f,
+                    0.19097100f, 0.52499998f, -0.36705902f,
+                    0.24499202f, 0.52499998f, -0.33382803f,
+                    0.29287499f, 0.52499998f, -0.29287499f,
+                    0.33382803f, 0.52499998f, -0.24499199f,
+                    0.36705902f, 0.52499998f, -0.19097102f,
+                    0.39177603f, 0.52499998f, -0.13160400f,
+                    0.40718701f, 0.52499998f, -0.06768297f,
+                    0.41250002f, 0.52499998f, 0.00000001f,
+                    0.00000000f, 0.51359999f, -0.48519999f,
+                    0.07961161f, 0.51359999f, -0.47895056f,
+                    0.15479822f, 0.51359999f, -0.46082360f,
+                    0.22462820f, 0.51359999f, -0.43175036f,
+                    0.28817001f, 0.51360005f, -0.39266267f,
+                    0.34449199f, 0.51359999f, -0.34449199f,
+                    0.39266264f, 0.51359999f, -0.28816998f,
+                    0.43175036f, 0.51359999f, -0.22462821f,
+                    0.46082354f, 0.51359999f, -0.15479821f,
+                    0.47895062f, 0.51359999f, -0.07961158f,
+                    0.48519999f, 0.51359999f, 0.00000001f,
+                    0.00000000f, 0.50129998f, -0.55009997f,
+                    0.09026040f, 0.50129992f, -0.54301465f,
+                    0.17550392f, 0.50129998f, -0.52246296f,
+                    0.25467429f, 0.50129998f, -0.48950094f,
+                    0.32671541f, 0.50129998f, -0.44518495f,
+                    0.39057100f, 0.50129998f, -0.39057100f,
+                    0.44518495f, 0.50129998f, -0.32671538f,
+                    0.48950097f, 0.50129998f, -0.25467432f,
+                    0.52246296f, 0.50129998f, -0.17550389f,
+                    0.54301471f, 0.50129998f, -0.09026036f,
+                    0.55009997f, 0.50129998f, 0.00000001f,
+                    0.00000000f, 0.48720002f, -0.60240000f,
+                    0.09884179f, 0.48719996f, -0.59464109f,
+                    0.19218971f, 0.48720005f, -0.57213551f,
+                    0.27888709f, 0.48719999f, -0.53603959f,
+                    0.35777742f, 0.48720002f, -0.48751029f,
+                    0.42770401f, 0.48720002f, -0.42770401f,
+                    0.48751026f, 0.48720002f, -0.35777742f,
+                    0.53603959f, 0.48720005f, -0.27888709f,
+                    0.57213545f, 0.48720002f, -0.19218969f,
+                    0.59464109f, 0.48720002f, -0.09884175f,
+                    0.60240000f, 0.48720002f, 0.00000001f,
+                    0.00000000f, 0.47040004f, -0.63730001f,
+                    0.10456818f, 0.47039998f, -0.62909156f,
+                    0.20332420f, 0.47040007f, -0.60528207f,
+                    0.29504442f, 0.47040001f, -0.56709504f,
+                    0.37850523f, 0.47040004f, -0.51575416f,
+                    0.45248300f, 0.47040004f, -0.45248300f,
+                    0.51575416f, 0.47040004f, -0.37850523f,
+                    0.56709504f, 0.47040004f, -0.29504442f,
+                    0.60528207f, 0.47040004f, -0.20332420f,
+                    0.62909162f, 0.47040004f, -0.10456815f,
+                    0.63730001f, 0.47040004f, 0.00000001f,
+                    0.00000000f, 0.45000005f, -0.64999998f,
+                    0.10665200f, 0.44999996f, -0.64162791f,
+                    0.20737600f, 0.45000011f, -0.61734402f,
+                    0.30092400f, 0.45000002f, -0.57839590f,
+                    0.38604802f, 0.45000008f, -0.52603197f,
+                    0.46149999f, 0.45000005f, -0.46149999f,
+                    0.52603197f, 0.45000005f, -0.38604796f,
+                    0.57839596f, 0.45000008f, -0.30092400f,
+                    0.61734402f, 0.45000005f, -0.20737599f,
+                    0.64162797f, 0.45000005f, -0.10665195f,
+                    0.64999998f, 0.45000005f, 0.00000001f,
+                    0.00000000f, 0.60000002f, 0.10000001f,
+                    -0.01640800f, 0.59999996f, 0.09871200f,
+                    -0.03190400f, 0.60000008f, 0.09497602f,
+                    -0.04629600f, 0.60000008f, 0.08898401f,
+                    -0.05939200f, 0.60000002f, 0.08092801f,
+                    -0.07100000f, 0.60000002f, 0.07100001f,
+                    -0.08092801f, 0.60000002f, 0.05939201f,
+                    -0.08898400f, 0.60000002f, 0.04629601f,
+                    -0.09497601f, 0.60000002f, 0.03190401f,
+                    -0.09871200f, 0.60000002f, 0.01640800f,
+                    -0.10000000f, 0.60000002f, 0.00000001f,
+                    0.00000000f, 0.57959992f, 0.13970001f,
+                    -0.02292197f, 0.57959986f, 0.13790067f,
+                    -0.04456988f, 0.57959998f, 0.13268149f,
+                    -0.06467551f, 0.57959992f, 0.12431065f,
+                    -0.08297062f, 0.57959998f, 0.11305643f,
+                    -0.09918699f, 0.57959992f, 0.09918701f,
+                    -0.11305641f, 0.57959992f, 0.08297063f,
+                    -0.12431064f, 0.57959998f, 0.06467552f,
+                    -0.13268146f, 0.57959992f, 0.04456989f,
+                    -0.13790065f, 0.57959992f, 0.02292197f,
+                    -0.13969998f, 0.57959992f, 0.00000001f,
+                    0.00000000f, 0.56280005f, 0.19560002f,
+                    -0.03209405f, 0.56279999f, 0.19308068f,
+                    -0.06240423f, 0.56280011f, 0.18577307f,
+                    -0.09055499f, 0.56280005f, 0.17405272f,
+                    -0.11617076f, 0.56280005f, 0.15829518f,
+                    -0.13887601f, 0.56280005f, 0.13887602f,
+                    -0.15829518f, 0.56280005f, 0.11617076f,
+                    -0.17405272f, 0.56280005f, 0.09055499f,
+                    -0.18577307f, 0.56280005f, 0.06240423f,
+                    -0.19308069f, 0.56280005f, 0.03209404f,
+                    -0.19560002f, 0.56280005f, 0.00000001f,
+                    0.00000000f, 0.54869998f, 0.26289999f,
+                    -0.04313663f, 0.54869998f, 0.25951380f,
+                    -0.08387563f, 0.54869998f, 0.24969190f,
+                    -0.12171219f, 0.54870003f, 0.23393893f,
+                    -0.15614158f, 0.54869998f, 0.21275970f,
+                    -0.18665899f, 0.54869998f, 0.18665901f,
+                    -0.21275972f, 0.54869998f, 0.15614155f,
+                    -0.23393893f, 0.54869998f, 0.12171219f,
+                    -0.24969190f, 0.54869998f, 0.08387562f,
+                    -0.25951385f, 0.54869998f, 0.04313662f,
+                    -0.26289999f, 0.54869998f, 0.00000001f,
+                    0.00000000f, 0.53640002f, 0.33680001f,
+                    -0.05526214f, 0.53639996f, 0.33246198f,
+                    -0.10745268f, 0.53640002f, 0.31987920f,
+                    -0.15592493f, 0.53640002f, 0.29969811f,
+                    -0.20003228f, 0.53640002f, 0.27256551f,
+                    -0.23912801f, 0.53640002f, 0.23912801f,
+                    -0.27256551f, 0.53640002f, 0.20003226f,
+                    -0.29969811f, 0.53640008f, 0.15592495f,
+                    -0.31987917f, 0.53640008f, 0.10745268f,
+                    -0.33246201f, 0.53640002f, 0.05526213f,
+                    -0.33680001f, 0.53640002f, 0.00000001f,
+                    0.00000000f, 0.52499998f, 0.41250002f,
+                    -0.06768300f, 0.52499992f, 0.40718701f,
+                    -0.13160400f, 0.52499998f, 0.39177606f,
+                    -0.19097100f, 0.52499998f, 0.36705902f,
+                    -0.24499202f, 0.52499998f, 0.33382803f,
+                    -0.29287499f, 0.52499998f, 0.29287499f,
+                    -0.33382803f, 0.52499998f, 0.24499199f,
+                    -0.36705902f, 0.52499998f, 0.19097102f,
+                    -0.39177603f, 0.52499998f, 0.13160400f,
+                    -0.40718701f, 0.52499998f, 0.06768298f,
+                    -0.41250002f, 0.52499998f, 0.00000001f,
+                    0.00000000f, 0.51359999f, 0.48519999f,
+                    -0.07961161f, 0.51359999f, 0.47895056f,
+                    -0.15479822f, 0.51359999f, 0.46082360f,
+                    -0.22462820f, 0.51359999f, 0.43175036f,
+                    -0.28817001f, 0.51360005f, 0.39266267f,
+                    -0.34449199f, 0.51359999f, 0.34449199f,
+                    -0.39266264f, 0.51359999f, 0.28816998f,
+                    -0.43175036f, 0.51359999f, 0.22462821f,
+                    -0.46082354f, 0.51359999f, 0.15479821f,
+                    -0.47895062f, 0.51359999f, 0.07961159f,
+                    -0.48519999f, 0.51359999f, 0.00000001f,
+                    0.00000000f, 0.50129998f, 0.55009997f,
+                    -0.09026040f, 0.50129992f, 0.54301465f,
+                    -0.17550392f, 0.50129998f, 0.52246296f,
+                    -0.25467429f, 0.50129998f, 0.48950094f,
+                    -0.32671541f, 0.50129998f, 0.44518495f,
+                    -0.39057100f, 0.50129998f, 0.39057100f,
+                    -0.44518495f, 0.50129998f, 0.32671538f,
+                    -0.48950097f, 0.50129998f, 0.25467432f,
+                    -0.52246296f, 0.50129998f, 0.17550389f,
+                    -0.54301471f, 0.50129998f, 0.09026038f,
+                    -0.55009997f, 0.50129998f, 0.00000001f,
+                    0.00000000f, 0.48720002f, 0.60240000f,
+                    -0.09884179f, 0.48719996f, 0.59464109f,
+                    -0.19218971f, 0.48720005f, 0.57213551f,
+                    -0.27888709f, 0.48719999f, 0.53603959f,
+                    -0.35777742f, 0.48720002f, 0.48751029f,
+                    -0.42770401f, 0.48720002f, 0.42770401f,
+                    -0.48751026f, 0.48720002f, 0.35777742f,
+                    -0.53603959f, 0.48720005f, 0.27888709f,
+                    -0.57213545f, 0.48720002f, 0.19218969f,
+                    -0.59464109f, 0.48720002f, 0.09884176f,
+                    -0.60240000f, 0.48720002f, 0.00000001f,
+                    0.00000000f, 0.47040004f, 0.63730001f,
+                    -0.10456818f, 0.47039998f, 0.62909156f,
+                    -0.20332420f, 0.47040007f, 0.60528207f,
+                    -0.29504442f, 0.47040001f, 0.56709504f,
+                    -0.37850523f, 0.47040004f, 0.51575416f,
+                    -0.45248300f, 0.47040004f, 0.45248300f,
+                    -0.51575416f, 0.47040004f, 0.37850523f,
+                    -0.56709504f, 0.47040004f, 0.29504442f,
+                    -0.60528207f, 0.47040004f, 0.20332420f,
+                    -0.62909162f, 0.47040004f, 0.10456816f,
+                    -0.63730001f, 0.47040004f, 0.00000001f,
+                    0.00000000f, 0.45000005f, 0.64999998f,
+                    -0.10665200f, 0.44999996f, 0.64162791f,
+                    -0.20737600f, 0.45000011f, 0.61734402f,
+                    -0.30092400f, 0.45000002f, 0.57839590f,
+                    -0.38604802f, 0.45000008f, 0.52603197f,
+                    -0.46149999f, 0.45000005f, 0.46149999f,
+                    -0.52603197f, 0.45000005f, 0.38604796f,
+                    -0.57839596f, 0.45000008f, 0.30092400f,
+                    -0.61734402f, 0.45000005f, 0.20737599f,
+                    -0.64162797f, 0.45000005f, 0.10665197f,
+                    -0.64999998f, 0.45000005f, 0.00000001f,
+                    -0.10000000f, 0.60000002f, 0.00000001f,
+                    -0.09871199f, 0.59999996f, -0.01640799f,
+                    -0.09497602f, 0.60000008f, -0.03190399f,
+                    -0.08898400f, 0.60000008f, -0.04629599f,
+                    -0.08092801f, 0.60000002f, -0.05939199f,
+                    -0.07100000f, 0.60000002f, -0.07099999f,
+                    -0.05939200f, 0.60000002f, -0.08092800f,
+                    -0.04629600f, 0.60000002f, -0.08898400f,
+                    -0.03190400f, 0.60000002f, -0.09497599f,
+                    -0.01640799f, 0.60000002f, -0.09871200f,
+                    0.00000000f, 0.60000002f, -0.09999999f,
+                    -0.13969998f, 0.57959992f, 0.00000001f,
+                    -0.13790064f, 0.57959986f, -0.02292197f,
+                    -0.13268146f, 0.57959998f, -0.04456988f,
+                    -0.12431064f, 0.57959992f, -0.06467550f,
+                    -0.11305641f, 0.57959998f, -0.08297062f,
+                    -0.09918699f, 0.57959992f, -0.09918699f,
+                    -0.08297062f, 0.57959992f, -0.11305641f,
+                    -0.06467551f, 0.57959998f, -0.12431063f,
+                    -0.04456988f, 0.57959992f, -0.13268146f,
+                    -0.02292197f, 0.57959992f, -0.13790065f,
+                    0.00000000f, 0.57959992f, -0.13969998f,
+                    -0.19560002f, 0.56280005f, 0.00000001f,
+                    -0.19308068f, 0.56279999f, -0.03209404f,
+                    -0.18577307f, 0.56280011f, -0.06240422f,
+                    -0.17405272f, 0.56280005f, -0.09055497f,
+                    -0.15829518f, 0.56280005f, -0.11617076f,
+                    -0.13887602f, 0.56280005f, -0.13887601f,
+                    -0.11617076f, 0.56280005f, -0.15829517f,
+                    -0.09055499f, 0.56280005f, -0.17405272f,
+                    -0.06240423f, 0.56280005f, -0.18577306f,
+                    -0.03209404f, 0.56280005f, -0.19308066f,
+                    0.00000000f, 0.56280005f, -0.19560000f,
+                    -0.26289999f, 0.54869998f, 0.00000001f,
+                    -0.25951380f, 0.54869998f, -0.04313662f,
+                    -0.24969190f, 0.54869998f, -0.08387562f,
+                    -0.23393893f, 0.54870003f, -0.12171219f,
+                    -0.21275970f, 0.54869998f, -0.15614156f,
+                    -0.18665901f, 0.54869998f, -0.18665899f,
+                    -0.15614155f, 0.54869998f, -0.21275972f,
+                    -0.12171219f, 0.54869998f, -0.23393893f,
+                    -0.08387562f, 0.54869998f, -0.24969190f,
+                    -0.04313662f, 0.54869998f, -0.25951385f,
+                    0.00000000f, 0.54869998f, -0.26289999f,
+                    -0.33680001f, 0.53640002f, 0.00000001f,
+                    -0.33246198f, 0.53639996f, -0.05526214f,
+                    -0.31987920f, 0.53640002f, -0.10745268f,
+                    -0.29969811f, 0.53640002f, -0.15592493f,
+                    -0.27256551f, 0.53640002f, -0.20003226f,
+                    -0.23912801f, 0.53640002f, -0.23912801f,
+                    -0.20003226f, 0.53640002f, -0.27256551f,
+                    -0.15592495f, 0.53640008f, -0.29969811f,
+                    -0.10745268f, 0.53640008f, -0.31987917f,
+                    -0.05526213f, 0.53640002f, -0.33246201f,
+                    0.00000000f, 0.53640002f, -0.33680001f,
+                    -0.41250002f, 0.52499998f, 0.00000001f,
+                    -0.40718701f, 0.52499992f, -0.06768300f,
+                    -0.39177606f, 0.52499998f, -0.13160400f,
+                    -0.36705902f, 0.52499998f, -0.19097100f,
+                    -0.33382803f, 0.52499998f, -0.24499202f,
+                    -0.29287499f, 0.52499998f, -0.29287499f,
+                    -0.24499199f, 0.52499998f, -0.33382803f,
+                    -0.19097102f, 0.52499998f, -0.36705902f,
+                    -0.13160400f, 0.52499998f, -0.39177603f,
+                    -0.06768297f, 0.52499998f, -0.40718701f,
+                    0.00000000f, 0.52499998f, -0.41250002f,
+                    -0.48519999f, 0.51359999f, 0.00000001f,
+                    -0.47895056f, 0.51359999f, -0.07961161f,
+                    -0.46082360f, 0.51359999f, -0.15479822f,
+                    -0.43175036f, 0.51359999f, -0.22462820f,
+                    -0.39266267f, 0.51360005f, -0.28817001f,
+                    -0.34449199f, 0.51359999f, -0.34449199f,
+                    -0.28816998f, 0.51359999f, -0.39266264f,
+                    -0.22462821f, 0.51359999f, -0.43175036f,
+                    -0.15479821f, 0.51359999f, -0.46082354f,
+                    -0.07961158f, 0.51359999f, -0.47895062f,
+                    0.00000000f, 0.51359999f, -0.48519999f,
+                    -0.55009997f, 0.50129998f, 0.00000001f,
+                    -0.54301465f, 0.50129992f, -0.09026039f,
+                    -0.52246296f, 0.50129998f, -0.17550392f,
+                    -0.48950094f, 0.50129998f, -0.25467429f,
+                    -0.44518495f, 0.50129998f, -0.32671541f,
+                    -0.39057100f, 0.50129998f, -0.39057100f,
+                    -0.32671538f, 0.50129998f, -0.44518495f,
+                    -0.25467432f, 0.50129998f, -0.48950097f,
+                    -0.17550389f, 0.50129998f, -0.52246296f,
+                    -0.09026037f, 0.50129998f, -0.54301471f,
+                    0.00000000f, 0.50129998f, -0.55009997f,
+                    -0.60240000f, 0.48720002f, 0.00000001f,
+                    -0.59464109f, 0.48719996f, -0.09884178f,
+                    -0.57213551f, 0.48720005f, -0.19218971f,
+                    -0.53603959f, 0.48719999f, -0.27888709f,
+                    -0.48751029f, 0.48720002f, -0.35777742f,
+                    -0.42770401f, 0.48720002f, -0.42770401f,
+                    -0.35777742f, 0.48720002f, -0.48751026f,
+                    -0.27888709f, 0.48720005f, -0.53603959f,
+                    -0.19218969f, 0.48720002f, -0.57213545f,
+                    -0.09884176f, 0.48720002f, -0.59464109f,
+                    0.00000000f, 0.48720002f, -0.60240000f,
+                    -0.63730001f, 0.47040004f, 0.00000001f,
+                    -0.62909156f, 0.47039998f, -0.10456817f,
+                    -0.60528207f, 0.47040007f, -0.20332420f,
+                    -0.56709504f, 0.47040001f, -0.29504442f,
+                    -0.51575416f, 0.47040004f, -0.37850523f,
+                    -0.45248300f, 0.47040004f, -0.45248300f,
+                    -0.37850523f, 0.47040004f, -0.51575416f,
+                    -0.29504442f, 0.47040004f, -0.56709504f,
+                    -0.20332420f, 0.47040004f, -0.60528207f,
+                    -0.10456815f, 0.47040004f, -0.62909162f,
+                    0.00000000f, 0.47040004f, -0.63730001f,
+                    -0.64999998f, 0.45000005f, 0.00000001f,
+                    -0.64162791f, 0.44999996f, -0.10665199f,
+                    -0.61734402f, 0.45000011f, -0.20737600f,
+                    -0.57839590f, 0.45000002f, -0.30092400f,
+                    -0.52603197f, 0.45000008f, -0.38604802f,
+                    -0.46149999f, 0.45000005f, -0.46149999f,
+                    -0.38604796f, 0.45000005f, -0.52603197f,
+                    -0.30092400f, 0.45000008f, -0.57839596f,
+                    -0.20737599f, 0.45000005f, -0.61734402f,
+                    -0.10665196f, 0.45000005f, -0.64162797f,
+                    0.00000000f, 0.45000005f, -0.64999998f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000006f, -0.00000001f,
+                    0.00000000f, -0.75000006f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000006f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.74891251f, 0.19413748f,
+                    0.03185407f, -0.74891245f, 0.19163698f,
+                    0.06193763f, -0.74891251f, 0.18438403f,
+                    0.08987789f, -0.74891245f, 0.17275129f,
+                    0.11530213f, -0.74891257f, 0.15711159f,
+                    0.13783762f, -0.74891251f, 0.13783762f,
+                    0.15711159f, -0.74891251f, 0.11530213f,
+                    0.17275131f, -0.74891257f, 0.08987789f,
+                    0.18438402f, -0.74891257f, 0.06193761f,
+                    0.19163698f, -0.74891251f, 0.03185406f,
+                    0.19413748f, -0.74891251f, -0.00000001f,
+                    0.00000000f, -0.74580008f, 0.35160002f,
+                    0.05769053f, -0.74579996f, 0.34707141f,
+                    0.11217448f, -0.74580014f, 0.33393562f,
+                    0.16277674f, -0.74580008f, 0.31286776f,
+                    0.20882230f, -0.74580008f, 0.28454286f,
+                    0.24963601f, -0.74580008f, 0.24963601f,
+                    0.28454286f, -0.74580008f, 0.20882228f,
+                    0.31286776f, -0.74580008f, 0.16277674f,
+                    0.33393565f, -0.74580008f, 0.11217446f,
+                    0.34707141f, -0.74580002f, 0.05769050f,
+                    0.35160002f, -0.74580008f, -0.00000001f,
+                    0.00000000f, -0.74088752f, 0.47621247f,
+                    0.07813694f, -0.74088746f, 0.47007880f,
+                    0.15193085f, -0.74088758f, 0.45228758f,
+                    0.22046733f, -0.74088752f, 0.42375290f,
+                    0.28283215f, -0.74088758f, 0.38538927f,
+                    0.33811086f, -0.74088752f, 0.33811086f,
+                    0.38538924f, -0.74088752f, 0.28283212f,
+                    0.42375290f, -0.74088752f, 0.22046733f,
+                    0.45228755f, -0.74088752f, 0.15193082f,
+                    0.47007886f, -0.74088752f, 0.07813691f,
+                    0.47621247f, -0.74088752f, -0.00000001f,
+                    0.00000000f, -0.73440003f, 0.57179999f,
+                    0.09382094f, -0.73439997f, 0.56443512f,
+                    0.18242709f, -0.73440009f, 0.54307282f,
+                    0.26472056f, -0.73440003f, 0.50881052f,
+                    0.33960345f, -0.73440003f, 0.46274635f,
+                    0.40597799f, -0.73440003f, 0.40597799f,
+                    0.46274632f, -0.73440009f, 0.33960345f,
+                    0.50881052f, -0.73440003f, 0.26472053f,
+                    0.54307282f, -0.73440003f, 0.18242708f,
+                    0.56443524f, -0.73440003f, 0.09382091f,
+                    0.57179999f, -0.73440003f, -0.00000001f,
+                    0.00000000f, -0.72656250f, 0.64218748f,
+                    0.10537013f, -0.72656244f, 0.63391608f,
+                    0.20488352f, -0.72656256f, 0.60992402f,
+                    0.29730710f, -0.72656256f, 0.57144409f,
+                    0.38140801f, -0.72656256f, 0.51970953f,
+                    0.45595312f, -0.72656250f, 0.45595312f,
+                    0.51970953f, -0.72656250f, 0.38140798f,
+                    0.57144415f, -0.72656250f, 0.29730713f,
+                    0.60992396f, -0.72656250f, 0.20488349f,
+                    0.63391614f, -0.72656250f, 0.10537008f,
+                    0.64218748f, -0.72656250f, -0.00000001f,
+                    0.00000000f, -0.71759999f, 0.69119996f,
+                    0.11341208f, -0.71759993f, 0.68229729f,
+                    0.22052045f, -0.71760005f, 0.65647405f,
+                    0.31999791f, -0.71759999f, 0.61505735f,
+                    0.41051748f, -0.71759999f, 0.55937433f,
+                    0.49075195f, -0.71759999f, 0.49075198f,
+                    0.55937433f, -0.71759999f, 0.41051745f,
+                    0.61505735f, -0.71759999f, 0.31999797f,
+                    0.65647411f, -0.71759999f, 0.22052044f,
+                    0.68229735f, -0.71759999f, 0.11341204f,
+                    0.69119996f, -0.71759999f, -0.00000001f,
+                    0.00000000f, -0.70773757f, 0.72266257f,
+                    0.11857446f, -0.70773745f, 0.71335465f,
+                    0.23055826f, -0.70773757f, 0.68635601f,
+                    0.33456385f, -0.70773745f, 0.64305401f,
+                    0.42920375f, -0.70773751f, 0.58483636f,
+                    0.51309043f, -0.70773751f, 0.51309037f,
+                    0.58483636f, -0.70773751f, 0.42920372f,
+                    0.64305407f, -0.70773757f, 0.33456385f,
+                    0.68635601f, -0.70773751f, 0.23055825f,
+                    0.71335471f, -0.70773751f, 0.11857441f,
+                    0.72266257f, -0.70773751f, -0.00000001f,
+                    0.00000000f, -0.69720000f, 0.74039996f,
+                    0.12148482f, -0.69719994f, 0.73086351f,
+                    0.23621722f, -0.69720006f, 0.70320231f,
+                    0.34277558f, -0.69720000f, 0.65883750f,
+                    0.43973836f, -0.69720006f, 0.59919089f,
+                    0.52568400f, -0.69719994f, 0.52568400f,
+                    0.59919089f, -0.69720006f, 0.43973833f,
+                    0.65883750f, -0.69720000f, 0.34277558f,
+                    0.70320225f, -0.69720006f, 0.23621720f,
+                    0.73086363f, -0.69720000f, 0.12148478f,
+                    0.74039996f, -0.69720000f, -0.00000001f,
+                    0.00000000f, -0.68621248f, 0.74823749f,
+                    0.12277080f, -0.68621248f, 0.73860013f,
+                    0.23871772f, -0.68621254f, 0.71064609f,
+                    0.34640405f, -0.68621248f, 0.66581166f,
+                    0.44439322f, -0.68621248f, 0.60553366f,
+                    0.53124863f, -0.68621248f, 0.53124863f,
+                    0.60553366f, -0.68621248f, 0.44439319f,
+                    0.66581166f, -0.68621254f, 0.34640405f,
+                    0.71064603f, -0.68621254f, 0.23871769f,
+                    0.73860019f, -0.68621248f, 0.12277076f,
+                    0.74823749f, -0.68621248f, -0.00000001f,
+                    0.00000000f, -0.67500001f, 0.75000000f,
+                    0.12305999f, -0.67499995f, 0.74033999f,
+                    0.23928000f, -0.67500007f, 0.71232003f,
+                    0.34721997f, -0.67500001f, 0.66737998f,
+                    0.44544002f, -0.67500007f, 0.60696000f,
+                    0.53250003f, -0.67500007f, 0.53250003f,
+                    0.60696000f, -0.67500001f, 0.44543999f,
+                    0.66738003f, -0.67500001f, 0.34722000f,
+                    0.71231997f, -0.67500001f, 0.23927999f,
+                    0.74033999f, -0.67500001f, 0.12305995f,
+                    0.75000000f, -0.67500001f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000006f, -0.00000001f,
+                    0.00000000f, -0.75000006f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000006f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.19413748f, -0.74891251f, -0.00000001f,
+                    0.19163698f, -0.74891245f, -0.03185408f,
+                    0.18438403f, -0.74891251f, -0.06193763f,
+                    0.17275129f, -0.74891245f, -0.08987790f,
+                    0.15711159f, -0.74891257f, -0.11530215f,
+                    0.13783762f, -0.74891251f, -0.13783762f,
+                    0.11530213f, -0.74891251f, -0.15711159f,
+                    0.08987789f, -0.74891257f, -0.17275131f,
+                    0.06193762f, -0.74891257f, -0.18438402f,
+                    0.03185407f, -0.74891251f, -0.19163698f,
+                    0.00000000f, -0.74891251f, -0.19413748f,
+                    0.35160002f, -0.74580008f, -0.00000001f,
+                    0.34707141f, -0.74579996f, -0.05769053f,
+                    0.33393562f, -0.74580014f, -0.11217449f,
+                    0.31286776f, -0.74580008f, -0.16277674f,
+                    0.28454286f, -0.74580008f, -0.20882230f,
+                    0.24963601f, -0.74580008f, -0.24963601f,
+                    0.20882228f, -0.74580008f, -0.28454286f,
+                    0.16277674f, -0.74580008f, -0.31286776f,
+                    0.11217447f, -0.74580008f, -0.33393565f,
+                    0.05769051f, -0.74580002f, -0.34707141f,
+                    0.00000000f, -0.74580008f, -0.35160002f,
+                    0.47621247f, -0.74088752f, -0.00000001f,
+                    0.47007880f, -0.74088746f, -0.07813694f,
+                    0.45228758f, -0.74088758f, -0.15193085f,
+                    0.42375290f, -0.74088752f, -0.22046733f,
+                    0.38538927f, -0.74088758f, -0.28283215f,
+                    0.33811086f, -0.74088752f, -0.33811086f,
+                    0.28283212f, -0.74088752f, -0.38538924f,
+                    0.22046733f, -0.74088752f, -0.42375290f,
+                    0.15193082f, -0.74088752f, -0.45228755f,
+                    0.07813691f, -0.74088752f, -0.47007886f,
+                    0.00000000f, -0.74088752f, -0.47621247f,
+                    0.57179999f, -0.73440003f, -0.00000001f,
+                    0.56443512f, -0.73439997f, -0.09382095f,
+                    0.54307282f, -0.73440009f, -0.18242709f,
+                    0.50881052f, -0.73440003f, -0.26472056f,
+                    0.46274635f, -0.73440003f, -0.33960345f,
+                    0.40597799f, -0.73440003f, -0.40597799f,
+                    0.33960345f, -0.73440009f, -0.46274632f,
+                    0.26472053f, -0.73440003f, -0.50881052f,
+                    0.18242708f, -0.73440003f, -0.54307282f,
+                    0.09382091f, -0.73440003f, -0.56443524f,
+                    0.00000000f, -0.73440003f, -0.57179999f,
+                    0.64218748f, -0.72656250f, -0.00000001f,
+                    0.63391608f, -0.72656244f, -0.10537013f,
+                    0.60992402f, -0.72656256f, -0.20488352f,
+                    0.57144409f, -0.72656256f, -0.29730710f,
+                    0.51970953f, -0.72656256f, -0.38140801f,
+                    0.45595312f, -0.72656250f, -0.45595312f,
+                    0.38140798f, -0.72656250f, -0.51970953f,
+                    0.29730713f, -0.72656250f, -0.57144415f,
+                    0.20488349f, -0.72656250f, -0.60992396f,
+                    0.10537009f, -0.72656250f, -0.63391614f,
+                    0.00000000f, -0.72656250f, -0.64218748f,
+                    0.69119996f, -0.71759999f, -0.00000001f,
+                    0.68229729f, -0.71759993f, -0.11341209f,
+                    0.65647405f, -0.71760005f, -0.22052045f,
+                    0.61505735f, -0.71759999f, -0.31999791f,
+                    0.55937433f, -0.71759999f, -0.41051748f,
+                    0.49075198f, -0.71759999f, -0.49075195f,
+                    0.41051745f, -0.71759999f, -0.55937433f,
+                    0.31999797f, -0.71759999f, -0.61505735f,
+                    0.22052044f, -0.71759999f, -0.65647411f,
+                    0.11341204f, -0.71759999f, -0.68229735f,
+                    0.00000000f, -0.71759999f, -0.69119996f,
+                    0.72266257f, -0.70773751f, -0.00000001f,
+                    0.71335465f, -0.70773745f, -0.11857446f,
+                    0.68635601f, -0.70773757f, -0.23055826f,
+                    0.64305401f, -0.70773745f, -0.33456385f,
+                    0.58483636f, -0.70773751f, -0.42920375f,
+                    0.51309037f, -0.70773751f, -0.51309043f,
+                    0.42920372f, -0.70773751f, -0.58483636f,
+                    0.33456385f, -0.70773751f, -0.64305407f,
+                    0.23055825f, -0.70773751f, -0.68635601f,
+                    0.11857442f, -0.70773751f, -0.71335471f,
+                    0.00000000f, -0.70773751f, -0.72266257f,
+                    0.74039996f, -0.69720000f, -0.00000001f,
+                    0.73086351f, -0.69719994f, -0.12148483f,
+                    0.70320231f, -0.69720006f, -0.23621722f,
+                    0.65883750f, -0.69720000f, -0.34277558f,
+                    0.59919089f, -0.69720006f, -0.43973836f,
+                    0.52568400f, -0.69719994f, -0.52568400f,
+                    0.43973833f, -0.69720006f, -0.59919089f,
+                    0.34277558f, -0.69720000f, -0.65883750f,
+                    0.23621720f, -0.69720006f, -0.70320225f,
+                    0.12148479f, -0.69720000f, -0.73086363f,
+                    0.00000000f, -0.69720000f, -0.74039996f,
+                    0.74823749f, -0.68621248f, -0.00000001f,
+                    0.73860013f, -0.68621248f, -0.12277081f,
+                    0.71064609f, -0.68621254f, -0.23871772f,
+                    0.66581166f, -0.68621248f, -0.34640405f,
+                    0.60553366f, -0.68621248f, -0.44439322f,
+                    0.53124863f, -0.68621248f, -0.53124863f,
+                    0.44439319f, -0.68621248f, -0.60553366f,
+                    0.34640405f, -0.68621254f, -0.66581166f,
+                    0.23871769f, -0.68621254f, -0.71064603f,
+                    0.12277076f, -0.68621248f, -0.73860019f,
+                    0.00000000f, -0.68621248f, -0.74823749f,
+                    0.75000000f, -0.67500001f, -0.00000001f,
+                    0.74033999f, -0.67499995f, -0.12306000f,
+                    0.71232003f, -0.67500007f, -0.23928000f,
+                    0.66737998f, -0.67500001f, -0.34721997f,
+                    0.60696000f, -0.67500007f, -0.44544002f,
+                    0.53250003f, -0.67500007f, -0.53250003f,
+                    0.44543999f, -0.67500001f, -0.60696000f,
+                    0.34722000f, -0.67500001f, -0.66738003f,
+                    0.23927999f, -0.67500001f, -0.71231997f,
+                    0.12305996f, -0.67500001f, -0.74033999f,
+                    0.00000000f, -0.67500001f, -0.75000000f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000006f, -0.00000001f,
+                    0.00000000f, -0.75000006f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000006f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    -0.19413748f, -0.74891251f, -0.00000001f,
+                    -0.19163698f, -0.74891245f, 0.03185407f,
+                    -0.18438403f, -0.74891251f, 0.06193762f,
+                    -0.17275129f, -0.74891245f, 0.08987788f,
+                    -0.15711159f, -0.74891257f, 0.11530213f,
+                    -0.13783762f, -0.74891251f, 0.13783762f,
+                    -0.11530213f, -0.74891251f, 0.15711159f,
+                    -0.08987789f, -0.74891257f, 0.17275131f,
+                    -0.06193762f, -0.74891257f, 0.18438402f,
+                    -0.03185407f, -0.74891251f, 0.19163698f,
+                    0.00000000f, -0.74891251f, 0.19413748f,
+                    -0.35160002f, -0.74580008f, -0.00000001f,
+                    -0.34707141f, -0.74579996f, 0.05769052f,
+                    -0.33393562f, -0.74580014f, 0.11217447f,
+                    -0.31286776f, -0.74580008f, 0.16277674f,
+                    -0.28454286f, -0.74580008f, 0.20882230f,
+                    -0.24963601f, -0.74580008f, 0.24963601f,
+                    -0.20882228f, -0.74580008f, 0.28454286f,
+                    -0.16277674f, -0.74580008f, 0.31286776f,
+                    -0.11217447f, -0.74580008f, 0.33393565f,
+                    -0.05769051f, -0.74580002f, 0.34707141f,
+                    0.00000000f, -0.74580008f, 0.35160002f,
+                    -0.47621247f, -0.74088752f, -0.00000001f,
+                    -0.47007880f, -0.74088746f, 0.07813693f,
+                    -0.45228758f, -0.74088758f, 0.15193084f,
+                    -0.42375290f, -0.74088752f, 0.22046733f,
+                    -0.38538927f, -0.74088758f, 0.28283215f,
+                    -0.33811086f, -0.74088752f, 0.33811086f,
+                    -0.28283212f, -0.74088752f, 0.38538924f,
+                    -0.22046733f, -0.74088752f, 0.42375290f,
+                    -0.15193082f, -0.74088752f, 0.45228755f,
+                    -0.07813691f, -0.74088752f, 0.47007886f,
+                    0.00000000f, -0.74088752f, 0.47621247f,
+                    -0.57179999f, -0.73440003f, -0.00000001f,
+                    -0.56443512f, -0.73439997f, 0.09382094f,
+                    -0.54307282f, -0.73440009f, 0.18242709f,
+                    -0.50881052f, -0.73440003f, 0.26472056f,
+                    -0.46274635f, -0.73440003f, 0.33960345f,
+                    -0.40597799f, -0.73440003f, 0.40597799f,
+                    -0.33960345f, -0.73440009f, 0.46274632f,
+                    -0.26472053f, -0.73440003f, 0.50881052f,
+                    -0.18242708f, -0.73440003f, 0.54307282f,
+                    -0.09382091f, -0.73440003f, 0.56443524f,
+                    0.00000000f, -0.73440003f, 0.57179999f,
+                    -0.64218748f, -0.72656250f, -0.00000001f,
+                    -0.63391608f, -0.72656244f, 0.10537011f,
+                    -0.60992402f, -0.72656256f, 0.20488352f,
+                    -0.57144409f, -0.72656256f, 0.29730710f,
+                    -0.51970953f, -0.72656256f, 0.38140801f,
+                    -0.45595312f, -0.72656250f, 0.45595312f,
+                    -0.38140798f, -0.72656250f, 0.51970953f,
+                    -0.29730713f, -0.72656250f, 0.57144415f,
+                    -0.20488349f, -0.72656250f, 0.60992396f,
+                    -0.10537009f, -0.72656250f, 0.63391614f,
+                    0.00000000f, -0.72656250f, 0.64218748f,
+                    -0.69119996f, -0.71759999f, -0.00000001f,
+                    -0.68229729f, -0.71759993f, 0.11341207f,
+                    -0.65647405f, -0.71760005f, 0.22052045f,
+                    -0.61505735f, -0.71759999f, 0.31999791f,
+                    -0.55937433f, -0.71759999f, 0.41051748f,
+                    -0.49075198f, -0.71759999f, 0.49075195f,
+                    -0.41051745f, -0.71759999f, 0.55937433f,
+                    -0.31999797f, -0.71759999f, 0.61505735f,
+                    -0.22052044f, -0.71759999f, 0.65647411f,
+                    -0.11341204f, -0.71759999f, 0.68229735f,
+                    0.00000000f, -0.71759999f, 0.69119996f,
+                    -0.72266257f, -0.70773751f, -0.00000001f,
+                    -0.71335465f, -0.70773745f, 0.11857444f,
+                    -0.68635601f, -0.70773757f, 0.23055826f,
+                    -0.64305401f, -0.70773751f, 0.33456385f,
+                    -0.58483636f, -0.70773751f, 0.42920375f,
+                    -0.51309037f, -0.70773751f, 0.51309043f,
+                    -0.42920372f, -0.70773751f, 0.58483636f,
+                    -0.33456385f, -0.70773757f, 0.64305407f,
+                    -0.23055825f, -0.70773757f, 0.68635601f,
+                    -0.11857442f, -0.70773757f, 0.71335471f,
+                    0.00000000f, -0.70773757f, 0.72266257f,
+                    -0.74039996f, -0.69720000f, -0.00000001f,
+                    -0.73086351f, -0.69719994f, 0.12148482f,
+                    -0.70320231f, -0.69720006f, 0.23621722f,
+                    -0.65883750f, -0.69720000f, 0.34277558f,
+                    -0.59919089f, -0.69720006f, 0.43973836f,
+                    -0.52568400f, -0.69719994f, 0.52568400f,
+                    -0.43973833f, -0.69720006f, 0.59919089f,
+                    -0.34277558f, -0.69720000f, 0.65883750f,
+                    -0.23621720f, -0.69720006f, 0.70320225f,
+                    -0.12148479f, -0.69720000f, 0.73086363f,
+                    0.00000000f, -0.69720000f, 0.74039996f,
+                    -0.74823749f, -0.68621248f, -0.00000001f,
+                    -0.73860013f, -0.68621248f, 0.12277079f,
+                    -0.71064609f, -0.68621254f, 0.23871772f,
+                    -0.66581166f, -0.68621248f, 0.34640405f,
+                    -0.60553366f, -0.68621248f, 0.44439322f,
+                    -0.53124863f, -0.68621248f, 0.53124863f,
+                    -0.44439319f, -0.68621248f, 0.60553366f,
+                    -0.34640405f, -0.68621254f, 0.66581166f,
+                    -0.23871769f, -0.68621254f, 0.71064603f,
+                    -0.12277076f, -0.68621248f, 0.73860019f,
+                    0.00000000f, -0.68621248f, 0.74823749f,
+                    -0.75000000f, -0.67500001f, -0.00000001f,
+                    -0.74033999f, -0.67499995f, 0.12305998f,
+                    -0.71232003f, -0.67500007f, 0.23928000f,
+                    -0.66737998f, -0.67500001f, 0.34721997f,
+                    -0.60696000f, -0.67500007f, 0.44544002f,
+                    -0.53250003f, -0.67500007f, 0.53250003f,
+                    -0.44543999f, -0.67500001f, 0.60696000f,
+                    -0.34722000f, -0.67500001f, 0.66738003f,
+                    -0.23927999f, -0.67500001f, 0.71231997f,
+                    -0.12305996f, -0.67500001f, 0.74033999f,
+                    0.00000000f, -0.67500001f, 0.75000000f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000006f, -0.00000001f,
+                    0.00000000f, -0.75000006f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000006f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.75000000f, -0.00000001f,
+                    0.00000000f, -0.74891251f, -0.19413748f,
+                    -0.03185407f, -0.74891245f, -0.19163698f,
+                    -0.06193763f, -0.74891251f, -0.18438403f,
+                    -0.08987789f, -0.74891245f, -0.17275129f,
+                    -0.11530213f, -0.74891257f, -0.15711159f,
+                    -0.13783762f, -0.74891251f, -0.13783762f,
+                    -0.15711159f, -0.74891251f, -0.11530213f,
+                    -0.17275131f, -0.74891257f, -0.08987790f,
+                    -0.18438402f, -0.74891257f, -0.06193763f,
+                    -0.19163698f, -0.74891251f, -0.03185407f,
+                    -0.19413748f, -0.74891251f, -0.00000001f,
+                    0.00000000f, -0.74580008f, -0.35160002f,
+                    -0.05769053f, -0.74579996f, -0.34707141f,
+                    -0.11217448f, -0.74580014f, -0.33393562f,
+                    -0.16277674f, -0.74580008f, -0.31286776f,
+                    -0.20882230f, -0.74580008f, -0.28454286f,
+                    -0.24963601f, -0.74580008f, -0.24963601f,
+                    -0.28454286f, -0.74580008f, -0.20882228f,
+                    -0.31286776f, -0.74580008f, -0.16277674f,
+                    -0.33393565f, -0.74580008f, -0.11217447f,
+                    -0.34707141f, -0.74580002f, -0.05769052f,
+                    -0.35160002f, -0.74580008f, -0.00000001f,
+                    0.00000000f, -0.74088752f, -0.47621247f,
+                    -0.07813694f, -0.74088746f, -0.47007880f,
+                    -0.15193085f, -0.74088758f, -0.45228758f,
+                    -0.22046733f, -0.74088752f, -0.42375290f,
+                    -0.28283215f, -0.74088758f, -0.38538927f,
+                    -0.33811086f, -0.74088752f, -0.33811086f,
+                    -0.38538924f, -0.74088752f, -0.28283212f,
+                    -0.42375290f, -0.74088752f, -0.22046733f,
+                    -0.45228755f, -0.74088752f, -0.15193082f,
+                    -0.47007886f, -0.74088752f, -0.07813692f,
+                    -0.47621247f, -0.74088752f, -0.00000001f,
+                    0.00000000f, -0.73440003f, -0.57179999f,
+                    -0.09382094f, -0.73439997f, -0.56443512f,
+                    -0.18242709f, -0.73440009f, -0.54307282f,
+                    -0.26472056f, -0.73440003f, -0.50881052f,
+                    -0.33960345f, -0.73440003f, -0.46274635f,
+                    -0.40597799f, -0.73440003f, -0.40597799f,
+                    -0.46274632f, -0.73440009f, -0.33960345f,
+                    -0.50881052f, -0.73440003f, -0.26472053f,
+                    -0.54307282f, -0.73440003f, -0.18242708f,
+                    -0.56443524f, -0.73440003f, -0.09382092f,
+                    -0.57179999f, -0.73440003f, -0.00000001f,
+                    0.00000000f, -0.72656250f, -0.64218748f,
+                    -0.10537013f, -0.72656244f, -0.63391608f,
+                    -0.20488352f, -0.72656256f, -0.60992402f,
+                    -0.29730710f, -0.72656256f, -0.57144409f,
+                    -0.38140801f, -0.72656256f, -0.51970953f,
+                    -0.45595312f, -0.72656250f, -0.45595312f,
+                    -0.51970953f, -0.72656250f, -0.38140798f,
+                    -0.57144415f, -0.72656250f, -0.29730713f,
+                    -0.60992396f, -0.72656250f, -0.20488349f,
+                    -0.63391614f, -0.72656250f, -0.10537010f,
+                    -0.64218748f, -0.72656250f, -0.00000001f,
+                    0.00000000f, -0.71759999f, -0.69119996f,
+                    -0.11341208f, -0.71759993f, -0.68229729f,
+                    -0.22052045f, -0.71760005f, -0.65647405f,
+                    -0.31999791f, -0.71759999f, -0.61505735f,
+                    -0.41051748f, -0.71759999f, -0.55937433f,
+                    -0.49075195f, -0.71759999f, -0.49075198f,
+                    -0.55937433f, -0.71759999f, -0.41051745f,
+                    -0.61505735f, -0.71759999f, -0.31999797f,
+                    -0.65647411f, -0.71759999f, -0.22052044f,
+                    -0.68229735f, -0.71759999f, -0.11341205f,
+                    -0.69119996f, -0.71759999f, -0.00000001f,
+                    0.00000000f, -0.70773751f, -0.72266257f,
+                    -0.11857446f, -0.70773745f, -0.71335465f,
+                    -0.23055826f, -0.70773757f, -0.68635601f,
+                    -0.33456385f, -0.70773745f, -0.64305401f,
+                    -0.42920375f, -0.70773751f, -0.58483636f,
+                    -0.51309043f, -0.70773751f, -0.51309037f,
+                    -0.58483636f, -0.70773751f, -0.42920372f,
+                    -0.64305407f, -0.70773751f, -0.33456385f,
+                    -0.68635601f, -0.70773751f, -0.23055825f,
+                    -0.71335471f, -0.70773751f, -0.11857443f,
+                    -0.72266257f, -0.70773751f, -0.00000001f,
+                    0.00000000f, -0.69720000f, -0.74039996f,
+                    -0.12148482f, -0.69719994f, -0.73086351f,
+                    -0.23621722f, -0.69720006f, -0.70320231f,
+                    -0.34277558f, -0.69720000f, -0.65883750f,
+                    -0.43973836f, -0.69720006f, -0.59919089f,
+                    -0.52568400f, -0.69719994f, -0.52568400f,
+                    -0.59919089f, -0.69720006f, -0.43973833f,
+                    -0.65883750f, -0.69720000f, -0.34277558f,
+                    -0.70320225f, -0.69720006f, -0.23621720f,
+                    -0.73086363f, -0.69720000f, -0.12148479f,
+                    -0.74039996f, -0.69720000f, -0.00000001f,
+                    0.00000000f, -0.68621248f, -0.74823749f,
+                    -0.12277080f, -0.68621248f, -0.73860013f,
+                    -0.23871772f, -0.68621254f, -0.71064609f,
+                    -0.34640405f, -0.68621248f, -0.66581166f,
+                    -0.44439322f, -0.68621248f, -0.60553366f,
+                    -0.53124863f, -0.68621248f, -0.53124863f,
+                    -0.60553366f, -0.68621248f, -0.44439319f,
+                    -0.66581166f, -0.68621254f, -0.34640405f,
+                    -0.71064603f, -0.68621254f, -0.23871769f,
+                    -0.73860019f, -0.68621248f, -0.12277077f,
+                    -0.74823749f, -0.68621248f, -0.00000001f,
+                    0.00000000f, -0.67500001f, -0.75000000f,
+                    -0.12305999f, -0.67499995f, -0.74033999f,
+                    -0.23928000f, -0.67500007f, -0.71232003f,
+                    -0.34721997f, -0.67500001f, -0.66737998f,
+                    -0.44544002f, -0.67500007f, -0.60696000f,
+                    -0.53250003f, -0.67500007f, -0.53250003f,
+                    -0.60696000f, -0.67500001f, -0.44543999f,
+                    -0.66738003f, -0.67500001f, -0.34722000f,
+                    -0.71231997f, -0.67500001f, -0.23927999f,
+                    -0.74033999f, -0.67500001f, -0.12305997f,
+                    -0.75000000f, -0.67500001f, -0.00000001f,
+                    -0.80000001f, 0.26250005f, 0.00000000f,
+                    -0.79859996f, 0.26565003f, 0.04050000f,
+                    -0.79480010f, 0.27420005f, 0.07200000f,
+                    -0.78920001f, 0.28680006f, 0.09450001f,
+                    -0.78240001f, 0.30210006f, 0.10800001f,
+                    -0.77499998f, 0.31875002f, 0.11250000f,
+                    -0.76760000f, 0.33540002f, 0.10800000f,
+                    -0.76080006f, 0.35070002f, 0.09450001f,
+                    -0.75519997f, 0.36330000f, 0.07200000f,
+                    -0.75139999f, 0.37185001f, 0.04049999f,
+                    -0.75000000f, 0.37500000f, 0.00000000f,
+                    -0.90044993f, 0.26238751f, 0.00000000f,
+                    -0.90022725f, 0.26553434f, 0.04050000f,
+                    -0.89962322f, 0.27407584f, 0.07200000f,
+                    -0.89873272f, 0.28666320f, 0.09449999f,
+                    -0.89765161f, 0.30194792f, 0.10800000f,
+                    -0.89647496f, 0.31858125f, 0.11250000f,
+                    -0.89529836f, 0.33521461f, 0.10800000f,
+                    -0.89421713f, 0.35049930f, 0.09449999f,
+                    -0.89332676f, 0.36308670f, 0.07200000f,
+                    -0.89272249f, 0.37162814f, 0.04049999f,
+                    -0.89249992f, 0.37477499f, 0.00000000f,
+                    -0.99160004f, 0.26160005f, 0.00000000f,
+                    -0.99239522f, 0.26472485f, 0.04050001f,
+                    -0.99455369f, 0.27320647f, 0.07200002f,
+                    -0.99773449f, 0.28570563f, 0.09450001f,
+                    -1.00159693f, 0.30088326f, 0.10800002f,
+                    -1.00580013f, 0.31740004f, 0.11250001f,
+                    -1.01000333f, 0.33391684f, 0.10800001f,
+                    -1.01386571f, 0.34909445f, 0.09450001f,
+                    -1.01704645f, 0.36159366f, 0.07200001f,
+                    -1.01920485f, 0.37007523f, 0.04050000f,
+                    -1.02000010f, 0.37320003f, 0.00000000f,
+                    -1.07315004f, 0.25946254f, 0.00000000f,
+                    -1.07481182f, 0.26252747f, 0.04050001f,
+                    -1.07932246f, 0.27084666f, 0.07200002f,
+                    -1.08596969f, 0.28310645f, 0.09450001f,
+                    -1.09404123f, 0.29799330f, 0.10800002f,
+                    -1.10282505f, 0.31419379f, 0.11250001f,
+                    -1.11160886f, 0.33039424f, 0.10800001f,
+                    -1.11968040f, 0.34528112f, 0.09450001f,
+                    -1.12632775f, 0.35754091f, 0.07200001f,
+                    -1.13083827f, 0.36586004f, 0.04050000f,
+                    -1.13250005f, 0.36892501f, 0.00000000f,
+                    -1.14480007f, 0.25530005f, 0.00000000f,
+                    -1.14718556f, 0.25824845f, 0.04050000f,
+                    -1.15366089f, 0.26625127f, 0.07200000f,
+                    -1.16320336f, 0.27804485f, 0.09450001f,
+                    -1.17479050f, 0.29236567f, 0.10800001f,
+                    -1.18740010f, 0.30795002f, 0.11250000f,
+                    -1.20000958f, 0.32353443f, 0.10800000f,
+                    -1.21159685f, 0.33785522f, 0.09450001f,
+                    -1.22113919f, 0.34964880f, 0.07200000f,
+                    -1.22761440f, 0.35765159f, 0.04049999f,
+                    -1.23000002f, 0.36059999f, 0.00000000f,
+                    -1.20625007f, 0.24843754f, 0.00000000f,
+                    -1.20922494f, 0.25119376f, 0.04050000f,
+                    -1.21730018f, 0.25867507f, 0.07200000f,
+                    -1.22920001f, 0.26970002f, 0.09450001f,
+                    -1.24365008f, 0.28308752f, 0.10800001f,
+                    -1.25937510f, 0.29765627f, 0.11250000f,
+                    -1.27509999f, 0.31222504f, 0.10800000f,
+                    -1.28955007f, 0.32561252f, 0.09450001f,
+                    -1.30145001f, 0.33663750f, 0.07200000f,
+                    -1.30952501f, 0.34411874f, 0.04049999f,
+                    -1.31250000f, 0.34687501f, 0.00000000f,
+                    -1.25720000f, 0.23820004f, 0.00000000f,
+                    -1.26063836f, 0.24066961f, 0.04050000f,
+                    -1.26997125f, 0.24737285f, 0.07200000f,
+                    -1.28372490f, 0.25725123f, 0.09450001f,
+                    -1.30042577f, 0.26924643f, 0.10800001f,
+                    -1.31860006f, 0.28230003f, 0.11250000f,
+                    -1.33677435f, 0.29535359f, 0.10800000f,
+                    -1.35347521f, 0.30734879f, 0.09450001f,
+                    -1.36722875f, 0.31722721f, 0.07200000f,
+                    -1.37656152f, 0.32393038f, 0.04049999f,
+                    -1.38000000f, 0.32639998f, 0.00000000f,
+                    -1.29735005f, 0.22391254f, 0.00000000f,
+                    -1.30113411f, 0.22598207f, 0.04050000f,
+                    -1.31140578f, 0.23159945f, 0.07200000f,
+                    -1.32654238f, 0.23987763f, 0.09450001f,
+                    -1.34492302f, 0.24992974f, 0.10800001f,
+                    -1.36492503f, 0.26086879f, 0.11250000f,
+                    -1.38492727f, 0.27180785f, 0.10800000f,
+                    -1.40330780f, 0.28185993f, 0.09450001f,
+                    -1.41844463f, 0.29013813f, 0.07200000f,
+                    -1.42871594f, 0.29575545f, 0.04049999f,
+                    -1.43250012f, 0.29782501f, 0.00000000f,
+                    -1.32640004f, 0.20490001f, 0.00000000f,
+                    -1.33042073f, 0.20643719f, 0.04050000f,
+                    -1.34133446f, 0.21060961f, 0.07200000f,
+                    -1.35741770f, 0.21675842f, 0.09450001f,
+                    -1.37694728f, 0.22422481f, 0.10800001f,
+                    -1.39820004f, 0.23234999f, 0.11250000f,
+                    -1.41945279f, 0.24047519f, 0.10800000f,
+                    -1.43898249f, 0.24794158f, 0.09450001f,
+                    -1.45506573f, 0.25409040f, 0.07200000f,
+                    -1.46597922f, 0.25826281f, 0.04049999f,
+                    -1.47000003f, 0.25979999f, 0.00000000f,
+                    -1.34404993f, 0.18048748f, 0.00000000f,
+                    -1.34820640f, 0.18134111f, 0.04050000f,
+                    -1.35948884f, 0.18365818f, 0.07200000f,
+                    -1.37611520f, 0.18707278f, 0.09450001f,
+                    -1.39630449f, 0.19121909f, 0.10800001f,
+                    -1.41827500f, 0.19573122f, 0.11250000f,
+                    -1.44024563f, 0.20024337f, 0.10800000f,
+                    -1.46043491f, 0.20438966f, 0.09450001f,
+                    -1.47706127f, 0.20780426f, 0.07200000f,
+                    -1.48834348f, 0.21012132f, 0.04049999f,
+                    -1.49250007f, 0.21097496f, 0.00000000f,
+                    -1.35000002f, 0.14999998f, 0.00000000f,
+                    -1.35419989f, 0.14999996f, 0.04050000f,
+                    -1.36559999f, 0.14999999f, 0.07200000f,
+                    -1.38239992f, 0.14999998f, 0.09450001f,
+                    -1.40280008f, 0.14999999f, 0.10800001f,
+                    -1.42499995f, 0.14999998f, 0.11250000f,
+                    -1.44719994f, 0.14999998f, 0.10800000f,
+                    -1.46760011f, 0.14999998f, 0.09450001f,
+                    -1.48440003f, 0.14999998f, 0.07200000f,
+                    -1.49580002f, 0.14999998f, 0.04049999f,
+                    -1.50000000f, 0.14999998f, 0.00000000f,
+                    -0.75000000f, 0.37500000f, 0.00000000f,
+                    -0.75139999f, 0.37184998f, -0.04050000f,
+                    -0.75520003f, 0.36330003f, -0.07200000f,
+                    -0.76080000f, 0.35070002f, -0.09450001f,
+                    -0.76760006f, 0.33540004f, -0.10800001f,
+                    -0.77500004f, 0.31875002f, -0.11250000f,
+                    -0.78240001f, 0.30210003f, -0.10800000f,
+                    -0.78920001f, 0.28680006f, -0.09450001f,
+                    -0.79480004f, 0.27420002f, -0.07200000f,
+                    -0.79860002f, 0.26565003f, -0.04049999f,
+                    -0.80000001f, 0.26250005f, 0.00000000f,
+                    -0.89249992f, 0.37477499f, 0.00000000f,
+                    -0.89272243f, 0.37162811f, -0.04049999f,
+                    -0.89332676f, 0.36308670f, -0.07200000f,
+                    -0.89421707f, 0.35049930f, -0.09449999f,
+                    -0.89529842f, 0.33521461f, -0.10800000f,
+                    -0.89647490f, 0.31858125f, -0.11250000f,
+                    -0.89765155f, 0.30194789f, -0.10800000f,
+                    -0.89873278f, 0.28666323f, -0.09449999f,
+                    -0.89962316f, 0.27407581f, -0.07200000f,
+                    -0.90022731f, 0.26553437f, -0.04049998f,
+                    -0.90044993f, 0.26238751f, 0.00000000f,
+                    -1.02000010f, 0.37320003f, 0.00000000f,
+                    -1.01920474f, 0.37007520f, -0.04050000f,
+                    -1.01704657f, 0.36159366f, -0.07200001f,
+                    -1.01386571f, 0.34909445f, -0.09450001f,
+                    -1.01000333f, 0.33391684f, -0.10800002f,
+                    -1.00580001f, 0.31740004f, -0.11250001f,
+                    -1.00159681f, 0.30088323f, -0.10800001f,
+                    -0.99773443f, 0.28570566f, -0.09450001f,
+                    -0.99455369f, 0.27320647f, -0.07200001f,
+                    -0.99239522f, 0.26472485f, -0.04049999f,
+                    -0.99160004f, 0.26160005f, 0.00000000f,
+                    -1.13250005f, 0.36892501f, 0.00000000f,
+                    -1.13083816f, 0.36586002f, -0.04050000f,
+                    -1.12632775f, 0.35754091f, -0.07200001f,
+                    -1.11968040f, 0.34528109f, -0.09450001f,
+                    -1.11160886f, 0.33039424f, -0.10800002f,
+                    -1.10282505f, 0.31419379f, -0.11250001f,
+                    -1.09404123f, 0.29799333f, -0.10800001f,
+                    -1.08596969f, 0.28310645f, -0.09450001f,
+                    -1.07932246f, 0.27084661f, -0.07200001f,
+                    -1.07481182f, 0.26252750f, -0.04049999f,
+                    -1.07315004f, 0.25946254f, 0.00000000f,
+                    -1.23000002f, 0.36059999f, 0.00000000f,
+                    -1.22761440f, 0.35765156f, -0.04050000f,
+                    -1.22113931f, 0.34964883f, -0.07200000f,
+                    -1.21159685f, 0.33785522f, -0.09450001f,
+                    -1.20000970f, 0.32353443f, -0.10800001f,
+                    -1.18740010f, 0.30795002f, -0.11250000f,
+                    -1.17479038f, 0.29236564f, -0.10800000f,
+                    -1.16320324f, 0.27804482f, -0.09450001f,
+                    -1.15366089f, 0.26625124f, -0.07200000f,
+                    -1.14718568f, 0.25824845f, -0.04049999f,
+                    -1.14480007f, 0.25530005f, 0.00000000f,
+                    -1.31250000f, 0.34687501f, 0.00000000f,
+                    -1.30952489f, 0.34411871f, -0.04050000f,
+                    -1.30145013f, 0.33663750f, -0.07200000f,
+                    -1.28955019f, 0.32561252f, -0.09450001f,
+                    -1.27510011f, 0.31222504f, -0.10800001f,
+                    -1.25937498f, 0.29765630f, -0.11250000f,
+                    -1.24365008f, 0.28308752f, -0.10800000f,
+                    -1.22920012f, 0.26970005f, -0.09450001f,
+                    -1.21730018f, 0.25867504f, -0.07200000f,
+                    -1.20922506f, 0.25119379f, -0.04049999f,
+                    -1.20625007f, 0.24843754f, 0.00000000f,
+                    -1.38000000f, 0.32639998f, 0.00000000f,
+                    -1.37656140f, 0.32393035f, -0.04050000f,
+                    -1.36722875f, 0.31722721f, -0.07200000f,
+                    -1.35347521f, 0.30734879f, -0.09450001f,
+                    -1.33677447f, 0.29535362f, -0.10800001f,
+                    -1.31860006f, 0.28230000f, -0.11250000f,
+                    -1.30042553f, 0.26924643f, -0.10800000f,
+                    -1.28372478f, 0.25725123f, -0.09450001f,
+                    -1.26997125f, 0.24737284f, -0.07200000f,
+                    -1.26063836f, 0.24066964f, -0.04049999f,
+                    -1.25720000f, 0.23820004f, 0.00000000f,
+                    -1.43250012f, 0.29782501f, 0.00000000f,
+                    -1.42871583f, 0.29575542f, -0.04050000f,
+                    -1.41844463f, 0.29013813f, -0.07200000f,
+                    -1.40330768f, 0.28185993f, -0.09450001f,
+                    -1.38492751f, 0.27180785f, -0.10800001f,
+                    -1.36492515f, 0.26086876f, -0.11250000f,
+                    -1.34492302f, 0.24992973f, -0.10800000f,
+                    -1.32654250f, 0.23987764f, -0.09450001f,
+                    -1.31140566f, 0.23159944f, -0.07200000f,
+                    -1.30113423f, 0.22598210f, -0.04049999f,
+                    -1.29735005f, 0.22391254f, 0.00000000f,
+                    -1.47000003f, 0.25979999f, 0.00000000f,
+                    -1.46597922f, 0.25826275f, -0.04050000f,
+                    -1.45506561f, 0.25409040f, -0.07200000f,
+                    -1.43898249f, 0.24794158f, -0.09450001f,
+                    -1.41945291f, 0.24047521f, -0.10800001f,
+                    -1.39820004f, 0.23234999f, -0.11250000f,
+                    -1.37694728f, 0.22422481f, -0.10800000f,
+                    -1.35741770f, 0.21675842f, -0.09450001f,
+                    -1.34133446f, 0.21060961f, -0.07200000f,
+                    -1.33042085f, 0.20643722f, -0.04049999f,
+                    -1.32640004f, 0.20490001f, 0.00000000f,
+                    -1.49250007f, 0.21097496f, 0.00000000f,
+                    -1.48834324f, 0.21012127f, -0.04050000f,
+                    -1.47706139f, 0.20780428f, -0.07200000f,
+                    -1.46043479f, 0.20438966f, -0.09450001f,
+                    -1.44024563f, 0.20024338f, -0.10800001f,
+                    -1.41827488f, 0.19573122f, -0.11250000f,
+                    -1.39630437f, 0.19121908f, -0.10800000f,
+                    -1.37611520f, 0.18707278f, -0.09450001f,
+                    -1.35948884f, 0.18365818f, -0.07200000f,
+                    -1.34820652f, 0.18134113f, -0.04049999f,
+                    -1.34404993f, 0.18048748f, 0.00000000f,
+                    -1.50000000f, 0.14999998f, 0.00000000f,
+                    -1.49580002f, 0.14999996f, -0.04050000f,
+                    -1.48440015f, 0.14999999f, -0.07200000f,
+                    -1.46759999f, 0.14999998f, -0.09450001f,
+                    -1.44720006f, 0.14999999f, -0.10800001f,
+                    -1.42500007f, 0.14999998f, -0.11250000f,
+                    -1.40280008f, 0.14999998f, -0.10800000f,
+                    -1.38240004f, 0.14999998f, -0.09450001f,
+                    -1.36559999f, 0.14999998f, -0.07200000f,
+                    -1.35420001f, 0.14999998f, -0.04049999f,
+                    -1.35000002f, 0.14999998f, 0.00000000f,
+                    -1.35000002f, 0.14999998f, 0.00000000f,
+                    -1.35419989f, 0.14999996f, 0.04050000f,
+                    -1.36559999f, 0.14999999f, 0.07200000f,
+                    -1.38239992f, 0.14999998f, 0.09450001f,
+                    -1.40280008f, 0.14999999f, 0.10800001f,
+                    -1.42499995f, 0.14999998f, 0.11250000f,
+                    -1.44719994f, 0.14999998f, 0.10800000f,
+                    -1.46760011f, 0.14999998f, 0.09450001f,
+                    -1.48440003f, 0.14999998f, 0.07200000f,
+                    -1.49580002f, 0.14999998f, 0.04049999f,
+                    -1.50000000f, 0.14999998f, 0.00000000f,
+                    -1.34694993f, 0.11309998f, 0.00000000f,
+                    -1.35108757f, 0.11225944f, 0.04049999f,
+                    -1.36231863f, 0.10997803f, 0.07200000f,
+                    -1.37886941f, 0.10661592f, 0.09449999f,
+                    -1.39896679f, 0.10253337f, 0.10800000f,
+                    -1.42083752f, 0.09809060f, 0.11250000f,
+                    -1.44270813f, 0.09364782f, 0.10800000f,
+                    -1.46280551f, 0.08956527f, 0.09449999f,
+                    -1.47935629f, 0.08620317f, 0.07200000f,
+                    -1.49058723f, 0.08392174f, 0.04049999f,
+                    -1.49472487f, 0.08308122f, 0.00000000f,
+                    -1.33760011f, 0.07080000f, 0.00000000f,
+                    -1.34155357f, 0.06930479f, 0.04050000f,
+                    -1.35228503f, 0.06524640f, 0.07200002f,
+                    -1.36809933f, 0.05926559f, 0.09450001f,
+                    -1.38730252f, 0.05200320f, 0.10800002f,
+                    -1.40820003f, 0.04410000f, 0.11250001f,
+                    -1.42909765f, 0.03619679f, 0.10800001f,
+                    -1.44830084f, 0.02893440f, 0.09450001f,
+                    -1.46411526f, 0.02295359f, 0.07200001f,
+                    -1.47484648f, 0.01889519f, 0.04049999f,
+                    -1.47880006f, 0.01739999f, 0.00000000f,
+                    -1.32164991f, 0.02445000f, 0.00000000f,
+                    -1.32530177f, 0.02245132f, 0.04050000f,
+                    -1.33521414f, 0.01702635f, 0.07200002f,
+                    -1.34982169f, 0.00903165f, 0.09450001f,
+                    -1.36755967f, -0.00067620f, 0.10800002f,
+                    -1.38686240f, -0.01124063f, 0.11250001f,
+                    -1.40616536f, -0.02180506f, 0.10800001f,
+                    -1.42390323f, -0.03151290f, 0.09450001f,
+                    -1.43851089f, -0.03950761f, 0.07200001f,
+                    -1.44842303f, -0.04493258f, 0.04049999f,
+                    -1.45207500f, -0.04693126f, -0.00000000f,
+                    -1.29880011f, -0.02460000f, -0.00000000f,
+                    -1.30203676f, -0.02698559f, 0.04050000f,
+                    -1.31082261f, -0.03346080f, 0.07200000f,
+                    -1.32376969f, -0.04300320f, 0.09450001f,
+                    -1.33949125f, -0.05459040f, 0.10800001f,
+                    -1.35660005f, -0.06720001f, 0.11250000f,
+                    -1.37370884f, -0.07980961f, 0.10800000f,
+                    -1.38943052f, -0.09139681f, 0.09450001f,
+                    -1.40237761f, -0.10093921f, 0.07200000f,
+                    -1.41116321f, -0.10741441f, 0.04049999f,
+                    -1.41439998f, -0.10980001f, -0.00000000f,
+                    -1.26874995f, -0.07500000f, -0.00000000f,
+                    -1.27146244f, -0.07769062f, 0.04050000f,
+                    -1.27882504f, -0.08499375f, 0.07200000f,
+                    -1.28967500f, -0.09575625f, 0.09450001f,
+                    -1.30285001f, -0.10882500f, 0.10800001f,
+                    -1.31718755f, -0.12304687f, 0.11250000f,
+                    -1.33152497f, -0.13726875f, 0.10800000f,
+                    -1.34470010f, -0.15033749f, 0.09450001f,
+                    -1.35555005f, -0.16110000f, 0.07200000f,
+                    -1.36291242f, -0.16840312f, 0.04049999f,
+                    -1.36562502f, -0.17109375f, -0.00000000f,
+                    -1.23119998f, -0.12540001f, -0.00000000f,
+                    -1.23328304f, -0.12834840f, 0.04050000f,
+                    -1.23893762f, -0.13635120f, 0.07200000f,
+                    -1.24727035f, -0.14814480f, 0.09450001f,
+                    -1.25738895f, -0.16246562f, 0.10800001f,
+                    -1.26839995f, -0.17805001f, 0.11250000f,
+                    -1.27941120f, -0.19363439f, 0.10800000f,
+                    -1.28952956f, -0.20795521f, 0.09450001f,
+                    -1.29786241f, -0.21974881f, 0.07200000f,
+                    -1.30351675f, -0.22775160f, 0.04049999f,
+                    -1.30559993f, -0.23070000f, -0.00000000f,
+                    -1.18585014f, -0.17445001f, -0.00000000f,
+                    -1.18720317f, -0.17764355f, 0.04050000f,
+                    -1.19087601f, -0.18631189f, 0.07200000f,
+                    -1.19628823f, -0.19908616f, 0.09450001f,
+                    -1.20286059f, -0.21459784f, 0.10800001f,
+                    -1.21001256f, -0.23147814f, 0.11250000f,
+                    -1.21716475f, -0.24835847f, 0.10800000f,
+                    -1.22373688f, -0.26387012f, 0.09450001f,
+                    -1.22914934f, -0.27664441f, 0.07200000f,
+                    -1.23282194f, -0.28531271f, 0.04049999f,
+                    -1.23417509f, -0.28850627f, -0.00000000f,
+                    -1.13240004f, -0.22080001f, -0.00000000f,
+                    -1.13292634f, -0.22426079f, 0.04050000f,
+                    -1.13435543f, -0.23365441f, 0.07200000f,
+                    -1.13646090f, -0.24749762f, 0.09450001f,
+                    -1.13901782f, -0.26430723f, 0.10800001f,
+                    -1.14180005f, -0.28260002f, 0.11250000f,
+                    -1.14458251f, -0.30089283f, 0.10800000f,
+                    -1.14713919f, -0.31770241f, 0.09450001f,
+                    -1.14924490f, -0.33154562f, 0.07200000f,
+                    -1.15067363f, -0.34093922f, 0.04049999f,
+                    -1.15120006f, -0.34440002f, -0.00000000f,
+                    -1.07054996f, -0.26310003f, -0.00000000f,
+                    -1.07015729f, -0.26688471f, 0.04050000f,
+                    -1.06909144f, -0.27715757f, 0.07200000f,
+                    -1.06752050f, -0.29229647f, 0.09450001f,
+                    -1.06561327f, -0.31067947f, 0.10800001f,
+                    -1.06353748f, -0.33068439f, 0.11250000f,
+                    -1.06146181f, -0.35068941f, 0.10800000f,
+                    -1.05955434f, -0.36907232f, 0.09450001f,
+                    -1.05798364f, -0.38421124f, 0.07200000f,
+                    -1.05691767f, -0.39448404f, 0.04049999f,
+                    -1.05652499f, -0.39826876f, -0.00000000f,
+                    -1.00000000f, -0.30000001f, -0.00000000f,
+                    -0.99859989f, -0.30419996f, 0.04050000f,
+                    -0.99480003f, -0.31560001f, 0.07200000f,
+                    -0.98920000f, -0.33240002f, 0.09450001f,
+                    -0.98240000f, -0.35280001f, 0.10800001f,
+                    -0.97499996f, -0.37500000f, 0.11250000f,
+                    -0.96759999f, -0.39720002f, 0.10800000f,
+                    -0.96080005f, -0.41759998f, 0.09450001f,
+                    -0.95520002f, -0.43440002f, 0.07200000f,
+                    -0.95139992f, -0.44580001f, 0.04049999f,
+                    -0.94999999f, -0.44999999f, -0.00000001f,
+                    -1.50000000f, 0.14999998f, 0.00000000f,
+                    -1.49580002f, 0.14999996f, -0.04050000f,
+                    -1.48440015f, 0.14999999f, -0.07200000f,
+                    -1.46759999f, 0.14999998f, -0.09450001f,
+                    -1.44720006f, 0.14999999f, -0.10800001f,
+                    -1.42500007f, 0.14999998f, -0.11250000f,
+                    -1.40280008f, 0.14999998f, -0.10800000f,
+                    -1.38240004f, 0.14999998f, -0.09450001f,
+                    -1.36559999f, 0.14999998f, -0.07200000f,
+                    -1.35420001f, 0.14999998f, -0.04049999f,
+                    -1.35000002f, 0.14999998f, 0.00000000f,
+                    -1.49472487f, 0.08308122f, 0.00000000f,
+                    -1.49058712f, 0.08392174f, -0.04049999f,
+                    -1.47935617f, 0.08620317f, -0.07200000f,
+                    -1.46280551f, 0.08956527f, -0.09449999f,
+                    -1.44270813f, 0.09364782f, -0.10800000f,
+                    -1.42083728f, 0.09809060f, -0.11250000f,
+                    -1.39896679f, 0.10253337f, -0.10800000f,
+                    -1.37886930f, 0.10661593f, -0.09449999f,
+                    -1.36231852f, 0.10997803f, -0.07200000f,
+                    -1.35108757f, 0.11225945f, -0.04049999f,
+                    -1.34694993f, 0.11309998f, 0.00000000f,
+                    -1.47880006f, 0.01739999f, 0.00000000f,
+                    -1.47484636f, 0.01889519f, -0.04050000f,
+                    -1.46411538f, 0.02295360f, -0.07200002f,
+                    -1.44830084f, 0.02893439f, -0.09450001f,
+                    -1.42909777f, 0.03619680f, -0.10800002f,
+                    -1.40820003f, 0.04409999f, -0.11250001f,
+                    -1.38730240f, 0.05200320f, -0.10800001f,
+                    -1.36809933f, 0.05926560f, -0.09450001f,
+                    -1.35228491f, 0.06524640f, -0.07200001f,
+                    -1.34155369f, 0.06930479f, -0.04049999f,
+                    -1.33760011f, 0.07080000f, 0.00000000f,
+                    -1.45207500f, -0.04693126f, -0.00000000f,
+                    -1.44842303f, -0.04493257f, -0.04050000f,
+                    -1.43851078f, -0.03950761f, -0.07200002f,
+                    -1.42390323f, -0.03151290f, -0.09450001f,
+                    -1.40616548f, -0.02180506f, -0.10800002f,
+                    -1.38686240f, -0.01124063f, -0.11250001f,
+                    -1.36755955f, -0.00067620f, -0.10800001f,
+                    -1.34982181f, 0.00903165f, -0.09450001f,
+                    -1.33521414f, 0.01702635f, -0.07200001f,
+                    -1.32530189f, 0.02245133f, -0.04049999f,
+                    -1.32164991f, 0.02445000f, 0.00000000f,
+                    -1.41439998f, -0.10980001f, -0.00000000f,
+                    -1.41116297f, -0.10741439f, -0.04050000f,
+                    -1.40237772f, -0.10093922f, -0.07200000f,
+                    -1.38943040f, -0.09139680f, -0.09450001f,
+                    -1.37370884f, -0.07980961f, -0.10800001f,
+                    -1.35660017f, -0.06720000f, -0.11250000f,
+                    -1.33949125f, -0.05459040f, -0.10800000f,
+                    -1.32376981f, -0.04300320f, -0.09450001f,
+                    -1.31082249f, -0.03346080f, -0.07200000f,
+                    -1.30203688f, -0.02698559f, -0.04049999f,
+                    -1.29880011f, -0.02460000f, -0.00000000f,
+                    -1.36562502f, -0.17109375f, -0.00000000f,
+                    -1.36291230f, -0.16840309f, -0.04050000f,
+                    -1.35555005f, -0.16110000f, -0.07200000f,
+                    -1.34469998f, -0.15033749f, -0.09450001f,
+                    -1.33152509f, -0.13726877f, -0.10800001f,
+                    -1.31718755f, -0.12304687f, -0.11250000f,
+                    -1.30285001f, -0.10882499f, -0.10800000f,
+                    -1.28967500f, -0.09575625f, -0.09450001f,
+                    -1.27882504f, -0.08499375f, -0.07200000f,
+                    -1.27146244f, -0.07769062f, -0.04049999f,
+                    -1.26874995f, -0.07500000f, -0.00000000f,
+                    -1.30559993f, -0.23070000f, -0.00000000f,
+                    -1.30351651f, -0.22775158f, -0.04050000f,
+                    -1.29786241f, -0.21974881f, -0.07200000f,
+                    -1.28952956f, -0.20795520f, -0.09450001f,
+                    -1.27941132f, -0.19363441f, -0.10800001f,
+                    -1.26839995f, -0.17805001f, -0.11250000f,
+                    -1.25738883f, -0.16246560f, -0.10800000f,
+                    -1.24727035f, -0.14814481f, -0.09450001f,
+                    -1.23893762f, -0.13635120f, -0.07200000f,
+                    -1.23328316f, -0.12834841f, -0.04049999f,
+                    -1.23119998f, -0.12540001f, -0.00000000f,
+                    -1.23417509f, -0.28850627f, -0.00000000f,
+                    -1.23282194f, -0.28531265f, -0.04050000f,
+                    -1.22914934f, -0.27664444f, -0.07200000f,
+                    -1.22373676f, -0.26387009f, -0.09450001f,
+                    -1.21716475f, -0.24835847f, -0.10800001f,
+                    -1.21001267f, -0.23147814f, -0.11250000f,
+                    -1.20286047f, -0.21459781f, -0.10800000f,
+                    -1.19628835f, -0.19908617f, -0.09450001f,
+                    -1.19087601f, -0.18631187f, -0.07200000f,
+                    -1.18720317f, -0.17764360f, -0.04049999f,
+                    -1.18585014f, -0.17445001f, -0.00000000f,
+                    -1.15120006f, -0.34440002f, -0.00000000f,
+                    -1.15067351f, -0.34093919f, -0.04050000f,
+                    -1.14924490f, -0.33154565f, -0.07200001f,
+                    -1.14713919f, -0.31770241f, -0.09450001f,
+                    -1.14458263f, -0.30089283f, -0.10800001f,
+                    -1.14180017f, -0.28259999f, -0.11250000f,
+                    -1.13901758f, -0.26430720f, -0.10800000f,
+                    -1.13646090f, -0.24749762f, -0.09450001f,
+                    -1.13435531f, -0.23365441f, -0.07200000f,
+                    -1.13292646f, -0.22426081f, -0.04049999f,
+                    -1.13240004f, -0.22080001f, -0.00000000f,
+                    -1.05652499f, -0.39826876f, -0.00000000f,
+                    -1.05691755f, -0.39448401f, -0.04050000f,
+                    -1.05798364f, -0.38421121f, -0.07200001f,
+                    -1.05955434f, -0.36907232f, -0.09450001f,
+                    -1.06146181f, -0.35068938f, -0.10800001f,
+                    -1.06353748f, -0.33068442f, -0.11250000f,
+                    -1.06561315f, -0.31067941f, -0.10800000f,
+                    -1.06752062f, -0.29229647f, -0.09450001f,
+                    -1.06909132f, -0.27715760f, -0.07200000f,
+                    -1.07015729f, -0.26688474f, -0.04049999f,
+                    -1.07054996f, -0.26310003f, -0.00000000f,
+                    -0.94999999f, -0.44999999f, -0.00000001f,
+                    -0.95139986f, -0.44579995f, -0.04050000f,
+                    -0.95520008f, -0.43440005f, -0.07200001f,
+                    -0.96079999f, -0.41760001f, -0.09450001f,
+                    -0.96759999f, -0.39719999f, -0.10800001f,
+                    -0.97500002f, -0.37500000f, -0.11250000f,
+                    -0.98240000f, -0.35280001f, -0.10800000f,
+                    -0.98920006f, -0.33240002f, -0.09450001f,
+                    -0.99480003f, -0.31560001f, -0.07200000f,
+                    -0.99860001f, -0.30419999f, -0.04049999f,
+                    -1.00000000f, -0.30000001f, -0.00000000f,
+                    0.85000002f, -0.03750002f, -0.00000000f,
+                    0.84999996f, -0.04905002f, 0.08910000f,
+                    0.85000008f, -0.08040002f, 0.15840001f,
+                    0.85000002f, -0.12660003f, 0.20790000f,
+                    0.85000008f, -0.18270002f, 0.23760001f,
+                    0.85000002f, -0.24375001f, 0.24750000f,
+                    0.85000002f, -0.30480003f, 0.23760000f,
+                    0.85000002f, -0.36089998f, 0.20790002f,
+                    0.85000002f, -0.40710002f, 0.15840001f,
+                    0.85000002f, -0.43845001f, 0.08909997f,
+                    0.85000002f, -0.44999999f, -0.00000001f,
+                    0.96794993f, -0.02790002f, -0.00000000f,
+                    0.96969706f, -0.03838952f, 0.08755019f,
+                    0.97443962f, -0.06686102f, 0.15564480f,
+                    0.98142833f, -0.10881902f, 0.20428377f,
+                    0.98991477f, -0.15976802f, 0.23346718f,
+                    0.99914992f, -0.21521249f, 0.24319497f,
+                    1.00838506f, -0.27065700f, 0.23346716f,
+                    1.01687145f, -0.32160598f, 0.20428379f,
+                    1.02386022f, -0.36356401f, 0.15564479f,
+                    1.02860272f, -0.39203548f, 0.08755016f,
+                    1.03034985f, -0.40252498f, -0.00000000f,
+                    1.05560005f, -0.00120003f, -0.00000000f,
+                    1.05848956f, -0.01044003f, 0.08334360f,
+                    1.06633294f, -0.03552003f, 0.14816642f,
+                    1.07789123f, -0.07248003f, 0.19446841f,
+                    1.09192657f, -0.11736003f, 0.22224963f,
+                    1.10720015f, -0.16620001f, 0.23151001f,
+                    1.12247372f, -0.21504003f, 0.22224963f,
+                    1.13650894f, -0.25992000f, 0.19446844f,
+                    1.14806736f, -0.29688001f, 0.14816642f,
+                    1.15591049f, -0.32196000f, 0.08334358f,
+                    1.15880013f, -0.33120000f, -0.00000000f,
+                    1.11864996f, 0.03944998f, 0.00000000f,
+                    1.12222838f, 0.03158548f, 0.07714440f,
+                    1.13194120f, 0.01023899f, 0.13714561f,
+                    1.14625478f, -0.02121901f, 0.18000358f,
+                    1.16363573f, -0.05941800f, 0.20571840f,
+                    1.18255007f, -0.10098749f, 0.21428999f,
+                    1.20146441f, -0.14255700f, 0.20571840f,
+                    1.21884537f, -0.18075597f, 0.18000360f,
+                    1.23315883f, -0.21221398f, 0.13714559f,
+                    1.24287164f, -0.23356047f, 0.07714437f,
+                    1.24645007f, -0.24142496f, -0.00000000f,
+                    1.16280007f, 0.09089998f, 0.00000000f,
+                    1.16676486f, 0.08447397f, 0.06961680f,
+                    1.17752659f, 0.06703199f, 0.12376321f,
+                    1.19338572f, 0.04132799f, 0.16243920f,
+                    1.21264338f, 0.01011599f, 0.18564481f,
+                    1.23360014f, -0.02385001f, 0.19338000f,
+                    1.25455689f, -0.05781601f, 0.18564481f,
+                    1.27381444f, -0.08902800f, 0.16243921f,
+                    1.28967381f, -0.11473200f, 0.12376320f,
+                    1.30043530f, -0.13217400f, 0.06961678f,
+                    1.30440009f, -0.13859999f, -0.00000000f,
+                    1.19375002f, 0.14999998f, 0.00000000f,
+                    1.19794989f, 0.14501247f, 0.06142500f,
+                    1.20935011f, 0.13147499f, 0.10920001f,
+                    1.22614992f, 0.11152498f, 0.14332500f,
+                    1.24654996f, 0.08730000f, 0.16380000f,
+                    1.26874995f, 0.06093749f, 0.17062500f,
+                    1.29094982f, 0.03457499f, 0.16380000f,
+                    1.31134987f, 0.01035001f, 0.14332500f,
+                    1.32814991f, -0.00959999f, 0.10920000f,
+                    1.33954990f, -0.02313749f, 0.06142498f,
+                    1.34374988f, -0.02812499f, -0.00000000f,
+                    1.21719992f, 0.21360001f, 0.00000000f,
+                    1.22163498f, 0.20998798f, 0.05323320f,
+                    1.23367357f, 0.20018403f, 0.09463681f,
+                    1.25141430f, 0.18573602f, 0.12421080f,
+                    1.27295685f, 0.16819203f, 0.14195521f,
+                    1.29639995f, 0.14910004f, 0.14787000f,
+                    1.31984317f, 0.13000804f, 0.14195520f,
+                    1.34138560f, 0.11246406f, 0.12421081f,
+                    1.35912633f, 0.09801605f, 0.09463680f,
+                    1.37116480f, 0.08821206f, 0.05323318f,
+                    1.37559998f, 0.08460006f, 0.00000000f,
+                    1.23885000f, 0.27855000f, 0.00000000f,
+                    1.24367154f, 0.27618748f, 0.04570561f,
+                    1.25675893f, 0.26977503f, 0.08125442f,
+                    1.27604520f, 0.26032501f, 0.10664641f,
+                    1.29946446f, 0.24885003f, 0.12188162f,
+                    1.32495010f, 0.23636252f, 0.12696001f,
+                    1.35043573f, 0.22387503f, 0.12188161f,
+                    1.37385488f, 0.21240003f, 0.10664642f,
+                    1.39314127f, 0.20295003f, 0.08125441f,
+                    1.40622854f, 0.19653754f, 0.04570559f,
+                    1.41105008f, 0.19417503f, 0.00000000f,
+                    1.26440001f, 0.34170002f, 0.00000000f,
+                    1.26991034f, 0.34039798f, 0.03950640f,
+                    1.28486729f, 0.33686405f, 0.07023361f,
+                    1.30690885f, 0.33165601f, 0.09218160f,
+                    1.33367372f, 0.32533202f, 0.10535040f,
+                    1.36280000f, 0.31845003f, 0.10974000f,
+                    1.39192641f, 0.31156802f, 0.10535040f,
+                    1.41869128f, 0.30524403f, 0.09218161f,
+                    1.44073272f, 0.30003607f, 0.07023360f,
+                    1.45568967f, 0.29650205f, 0.03950639f,
+                    1.46120000f, 0.29520005f, 0.00000000f,
+                    1.29955006f, 0.39990005f, 0.00000000f,
+                    1.30620289f, 0.39940652f, 0.03529980f,
+                    1.32426059f, 0.39806706f, 0.06275520f,
+                    1.35087168f, 0.39609307f, 0.08236619f,
+                    1.38318527f, 0.39369607f, 0.09413280f,
+                    1.41835010f, 0.39108756f, 0.09805499f,
+                    1.45351493f, 0.38847905f, 0.09413280f,
+                    1.48582840f, 0.38608208f, 0.08236620f,
+                    1.51243973f, 0.38410807f, 0.06275519f,
+                    1.53049719f, 0.38276857f, 0.03529979f,
+                    1.53715003f, 0.38227507f, 0.00000000f,
+                    1.35000002f, 0.45000005f, 0.00000001f,
+                    1.35839975f, 0.44999996f, 0.03375000f,
+                    1.38120008f, 0.45000011f, 0.06000001f,
+                    1.41480005f, 0.45000002f, 0.07875000f,
+                    1.45560014f, 0.45000008f, 0.09000000f,
+                    1.50000000f, 0.45000005f, 0.09375000f,
+                    1.54439998f, 0.45000005f, 0.09000000f,
+                    1.58520007f, 0.45000008f, 0.07875000f,
+                    1.61880004f, 0.45000005f, 0.06000000f,
+                    1.64159989f, 0.45000005f, 0.03374999f,
+                    1.64999998f, 0.45000005f, 0.00000001f,
+                    0.85000002f, -0.44999999f, -0.00000001f,
+                    0.84999996f, -0.43844995f, -0.08910000f,
+                    0.85000008f, -0.40710002f, -0.15840001f,
+                    0.85000002f, -0.36089998f, -0.20790000f,
+                    0.85000008f, -0.30480000f, -0.23760001f,
+                    0.85000002f, -0.24375001f, -0.24750000f,
+                    0.85000002f, -0.18269999f, -0.23760000f,
+                    0.85000002f, -0.12660003f, -0.20790002f,
+                    0.85000002f, -0.08040002f, -0.15840001f,
+                    0.85000002f, -0.04905001f, -0.08909997f,
+                    0.85000002f, -0.03750002f, -0.00000000f,
+                    1.03034985f, -0.40252498f, -0.00000000f,
+                    1.02860260f, -0.39203545f, -0.08755019f,
+                    1.02386034f, -0.36356398f, -0.15564480f,
+                    1.01687145f, -0.32160601f, -0.20428377f,
+                    1.00838506f, -0.27065703f, -0.23346718f,
+                    0.99914992f, -0.21521249f, -0.24319497f,
+                    0.98991477f, -0.15976799f, -0.23346716f,
+                    0.98142833f, -0.10881902f, -0.20428379f,
+                    0.97443956f, -0.06686102f, -0.15564479f,
+                    0.96969712f, -0.03838951f, -0.08755016f,
+                    0.96794993f, -0.02790002f, -0.00000000f,
+                    1.15880013f, -0.33120000f, -0.00000000f,
+                    1.15591037f, -0.32195994f, -0.08334360f,
+                    1.14806736f, -0.29688004f, -0.14816642f,
+                    1.13650882f, -0.25992000f, -0.19446841f,
+                    1.12247384f, -0.21504001f, -0.22224963f,
+                    1.10720003f, -0.16620003f, -0.23151001f,
+                    1.09192646f, -0.11736001f, -0.22224963f,
+                    1.07789123f, -0.07248002f, -0.19446844f,
+                    1.06633282f, -0.03552002f, -0.14816642f,
+                    1.05848956f, -0.01044002f, -0.08334358f,
+                    1.05560005f, -0.00120003f, -0.00000000f,
+                    1.24645007f, -0.24142496f, -0.00000000f,
+                    1.24287152f, -0.23356044f, -0.07714440f,
+                    1.23315895f, -0.21221398f, -0.13714561f,
+                    1.21884525f, -0.18075597f, -0.18000358f,
+                    1.20146453f, -0.14255700f, -0.20571840f,
+                    1.18254995f, -0.10098749f, -0.21428999f,
+                    1.16363561f, -0.05941799f, -0.20571840f,
+                    1.14625478f, -0.02121901f, -0.18000360f,
+                    1.13194120f, 0.01023899f, -0.13714559f,
+                    1.12222838f, 0.03158549f, -0.07714437f,
+                    1.11864996f, 0.03944998f, 0.00000000f,
+                    1.30440009f, -0.13859999f, -0.00000000f,
+                    1.30043507f, -0.13217399f, -0.06961680f,
+                    1.28967369f, -0.11473200f, -0.12376321f,
+                    1.27381444f, -0.08902799f, -0.16243920f,
+                    1.25455701f, -0.05781601f, -0.18564481f,
+                    1.23360002f, -0.02385001f, -0.19338000f,
+                    1.21264338f, 0.01011600f, -0.18564481f,
+                    1.19338572f, 0.04132798f, -0.16243921f,
+                    1.17752647f, 0.06703199f, -0.12376320f,
+                    1.16676486f, 0.08447399f, -0.06961678f,
+                    1.16280007f, 0.09089998f, 0.00000000f,
+                    1.34374988f, -0.02812499f, -0.00000000f,
+                    1.33954978f, -0.02313748f, -0.06142500f,
+                    1.32814991f, -0.00959999f, -0.10920001f,
+                    1.31134987f, 0.01035001f, -0.14332500f,
+                    1.29095006f, 0.03457500f, -0.16380000f,
+                    1.26874995f, 0.06093749f, -0.17062500f,
+                    1.24654996f, 0.08730000f, -0.16380000f,
+                    1.22615004f, 0.11152498f, -0.14332500f,
+                    1.20935011f, 0.13147499f, -0.10920000f,
+                    1.19795001f, 0.14501248f, -0.06142498f,
+                    1.19375002f, 0.14999998f, 0.00000000f,
+                    1.37559998f, 0.08460006f, 0.00000000f,
+                    1.37116480f, 0.08821206f, -0.05323320f,
+                    1.35912645f, 0.09801605f, -0.09463680f,
+                    1.34138560f, 0.11246406f, -0.12421079f,
+                    1.31984317f, 0.13000806f, -0.14195520f,
+                    1.29639995f, 0.14910004f, -0.14786999f,
+                    1.27295685f, 0.16819203f, -0.14195520f,
+                    1.25141430f, 0.18573603f, -0.12421080f,
+                    1.23367357f, 0.20018402f, -0.09463680f,
+                    1.22163510f, 0.20998801f, -0.05323318f,
+                    1.21719992f, 0.21360001f, 0.00000000f,
+                    1.41105008f, 0.19417503f, 0.00000000f,
+                    1.40622830f, 0.19653751f, -0.04570560f,
+                    1.39314139f, 0.20295003f, -0.08125441f,
+                    1.37385488f, 0.21240002f, -0.10664640f,
+                    1.35043585f, 0.22387503f, -0.12188162f,
+                    1.32494998f, 0.23636252f, -0.12696001f,
+                    1.29946434f, 0.24885002f, -0.12188160f,
+                    1.27604532f, 0.26032501f, -0.10664640f,
+                    1.25675893f, 0.26977503f, -0.08125440f,
+                    1.24367166f, 0.27618751f, -0.04570558f,
+                    1.23885000f, 0.27855000f, 0.00000000f,
+                    1.46120000f, 0.29520005f, 0.00000000f,
+                    1.45568943f, 0.29650205f, -0.03950639f,
+                    1.44073284f, 0.30003604f, -0.07023360f,
+                    1.41869116f, 0.30524403f, -0.09218159f,
+                    1.39192653f, 0.31156805f, -0.10535039f,
+                    1.36280012f, 0.31845003f, -0.10973999f,
+                    1.33367360f, 0.32533202f, -0.10535039f,
+                    1.30690885f, 0.33165604f, -0.09218159f,
+                    1.28486729f, 0.33686402f, -0.07023359f,
+                    1.26991045f, 0.34039801f, -0.03950638f,
+                    1.26440001f, 0.34170002f, 0.00000000f,
+                    1.53715003f, 0.38227507f, 0.00000000f,
+                    1.53049695f, 0.38276854f, -0.03529979f,
+                    1.51243961f, 0.38410810f, -0.06275519f,
+                    1.48582840f, 0.38608205f, -0.08236619f,
+                    1.45351493f, 0.38847908f, -0.09413280f,
+                    1.41835010f, 0.39108753f, -0.09805499f,
+                    1.38318527f, 0.39369607f, -0.09413280f,
+                    1.35087168f, 0.39609307f, -0.08236620f,
+                    1.32426047f, 0.39806706f, -0.06275519f,
+                    1.30620289f, 0.39940655f, -0.03529978f,
+                    1.29955006f, 0.39990005f, 0.00000000f,
+                    1.64999998f, 0.45000005f, 0.00000001f,
+                    1.64159989f, 0.44999996f, -0.03374999f,
+                    1.61880016f, 0.45000011f, -0.05999999f,
+                    1.58519995f, 0.45000002f, -0.07874999f,
+                    1.54440010f, 0.45000008f, -0.09000000f,
+                    1.50000000f, 0.45000005f, -0.09374999f,
+                    1.45560002f, 0.45000005f, -0.08999999f,
+                    1.41480005f, 0.45000008f, -0.07875000f,
+                    1.38120008f, 0.45000005f, -0.05999999f,
+                    1.35839999f, 0.45000005f, -0.03374998f,
+                    1.35000002f, 0.45000005f, 0.00000001f,
+                    1.35000002f, 0.45000005f, 0.00000001f,
+                    1.35839975f, 0.44999996f, 0.03375000f,
+                    1.38120008f, 0.45000011f, 0.06000001f,
+                    1.41480005f, 0.45000002f, 0.07875000f,
+                    1.45560014f, 0.45000008f, 0.09000000f,
+                    1.50000000f, 0.45000005f, 0.09375000f,
+                    1.54439998f, 0.45000005f, 0.09000000f,
+                    1.58520007f, 0.45000008f, 0.07875000f,
+                    1.61880004f, 0.45000005f, 0.06000000f,
+                    1.64159989f, 0.45000005f, 0.03374999f,
+                    1.64999998f, 0.45000005f, 0.00000001f,
+                    1.36489987f, 0.46012494f, 0.00000001f,
+                    1.37370336f, 0.46020287f, 0.03337200f,
+                    1.39759886f, 0.46041453f, 0.05932800f,
+                    1.43281305f, 0.46072635f, 0.07786799f,
+                    1.47557306f, 0.46110505f, 0.08899199f,
+                    1.52210617f, 0.46151716f, 0.09269999f,
+                    1.56863928f, 0.46192923f, 0.08899198f,
+                    1.61139929f, 0.46230793f, 0.07786799f,
+                    1.64661360f, 0.46261978f, 0.05932799f,
+                    1.67050874f, 0.46283138f, 0.03337199f,
+                    1.67931235f, 0.46290934f, 0.00000001f,
+                    1.37919998f, 0.46800002f, 0.00000001f,
+                    1.38818228f, 0.46815118f, 0.03234600f,
+                    1.41256332f, 0.46856162f, 0.05750401f,
+                    1.44849277f, 0.46916643f, 0.07547401f,
+                    1.49212170f, 0.46990088f, 0.08625601f,
+                    1.53960001f, 0.47070009f, 0.08985001f,
+                    1.58707845f, 0.47149926f, 0.08625601f,
+                    1.63070726f, 0.47223371f, 0.07547401f,
+                    1.66663694f, 0.47283852f, 0.05750401f,
+                    1.69101763f, 0.47324890f, 0.03234600f,
+                    1.70000005f, 0.47340012f, 0.00000001f,
+                    1.39230001f, 0.47362497f, 0.00000001f,
+                    1.40126371f, 0.47383994f, 0.03083400f,
+                    1.42559445f, 0.47442356f, 0.05481601f,
+                    1.46144974f, 0.47528344f, 0.07194600f,
+                    1.50498855f, 0.47632772f, 0.08222401f,
+                    1.55236876f, 0.47746408f, 0.08565000f,
+                    1.59974909f, 0.47860044f, 0.08222400f,
+                    1.64328790f, 0.47964469f, 0.07194600f,
+                    1.67914319f, 0.48050463f, 0.05481600f,
+                    1.70347357f, 0.48108816f, 0.03083399f,
+                    1.71243751f, 0.48130316f, 0.00000001f,
+                    1.40359998f, 0.47700000f, 0.00000001f,
+                    1.41237509f, 0.47726458f, 0.02899800f,
+                    1.43619370f, 0.47798279f, 0.05155201f,
+                    1.47129452f, 0.47904122f, 0.06766201f,
+                    1.51391697f, 0.48032647f, 0.07732801f,
+                    1.56030011f, 0.48172504f, 0.08055001f,
+                    1.60668325f, 0.48312366f, 0.07732800f,
+                    1.64930582f, 0.48440886f, 0.06766201f,
+                    1.68440676f, 0.48546728f, 0.05155201f,
+                    1.70822501f, 0.48618549f, 0.02899799f,
+                    1.71700025f, 0.48645008f, 0.00000001f,
+                    1.41249990f, 0.47812498f, 0.00000001f,
+                    1.42094362f, 0.47842023f, 0.02700000f,
+                    1.44386256f, 0.47922191f, 0.04800001f,
+                    1.47763753f, 0.48040313f, 0.06300000f,
+                    1.51865005f, 0.48183754f, 0.07200000f,
+                    1.56328130f, 0.48339847f, 0.07500000f,
+                    1.60791266f, 0.48495942f, 0.07200000f,
+                    1.64892507f, 0.48639381f, 0.06300000f,
+                    1.68270016f, 0.48757505f, 0.04800000f,
+                    1.70561898f, 0.48837662f, 0.02700000f,
+                    1.71406269f, 0.48867193f, 0.00000001f,
+                    1.41839993f, 0.47700000f, 0.00000001f,
+                    1.42639649f, 0.47730237f, 0.02500200f,
+                    1.44810247f, 0.47812322f, 0.04444801f,
+                    1.48008955f, 0.47933280f, 0.05833800f,
+                    1.51893127f, 0.48080164f, 0.06667200f,
+                    1.56120014f, 0.48240003f, 0.06945001f,
+                    1.60346889f, 0.48399845f, 0.06667200f,
+                    1.64231050f, 0.48546726f, 0.05833801f,
+                    1.67429769f, 0.48667687f, 0.04444801f,
+                    1.69600332f, 0.48749766f, 0.02500200f,
+                    1.70400012f, 0.48780006f, 0.00000001f,
+                    1.42070007f, 0.47362500f, 0.00000001f,
+                    1.42816150f, 0.47390610f, 0.02316600f,
+                    1.44841480f, 0.47466925f, 0.04118401f,
+                    1.47826135f, 0.47579375f, 0.05405401f,
+                    1.51450372f, 0.47715935f, 0.06177600f,
+                    1.55394375f, 0.47864535f, 0.06435001f,
+                    1.59338403f, 0.48013136f, 0.06177600f,
+                    1.62962627f, 0.48149687f, 0.05405401f,
+                    1.65947294f, 0.48262146f, 0.04118400f,
+                    1.67972589f, 0.48338455f, 0.02316600f,
+                    1.68718755f, 0.48366567f, 0.00000001f,
+                    1.41880012f, 0.46799999f, 0.00000001f,
+                    1.42566562f, 0.46822673f, 0.02165400f,
+                    1.44430101f, 0.46884245f, 0.03849601f,
+                    1.47176325f, 0.46974963f, 0.05052601f,
+                    1.50511050f, 0.47085124f, 0.05774401f,
+                    1.54139996f, 0.47205001f, 0.06015000f,
+                    1.57768965f, 0.47324884f, 0.05774400f,
+                    1.61103690f, 0.47435045f, 0.05052601f,
+                    1.63849926f, 0.47525764f, 0.03849601f,
+                    1.65713453f, 0.47587326f, 0.02165400f,
+                    1.66400003f, 0.47610006f, 0.00000001f,
+                    1.41209996f, 0.46012503f, 0.00000001f,
+                    1.41833580f, 0.46025965f, 0.02062801f,
+                    1.43526208f, 0.46062523f, 0.03667201f,
+                    1.46020591f, 0.46116382f, 0.04813201f,
+                    1.49049485f, 0.46181795f, 0.05500801f,
+                    1.52345622f, 0.46252972f, 0.05730001f,
+                    1.55641770f, 0.46324152f, 0.05500801f,
+                    1.58670676f, 0.46389559f, 0.04813201f,
+                    1.61165047f, 0.46443427f, 0.03667201f,
+                    1.62857664f, 0.46479973f, 0.02062800f,
+                    1.63481259f, 0.46493441f, 0.00000001f,
+                    1.39999998f, 0.45000005f, 0.00000001f,
+                    1.40559983f, 0.44999996f, 0.02025001f,
+                    1.42079997f, 0.45000011f, 0.03600001f,
+                    1.44319999f, 0.45000002f, 0.04725001f,
+                    1.47040009f, 0.45000008f, 0.05400001f,
+                    1.50000000f, 0.45000005f, 0.05625001f,
+                    1.52960002f, 0.45000005f, 0.05400001f,
+                    1.55680001f, 0.45000008f, 0.04725001f,
+                    1.57920003f, 0.45000005f, 0.03600001f,
+                    1.59440005f, 0.45000005f, 0.02025000f,
+                    1.60000002f, 0.45000005f, 0.00000001f,
+                    1.64999998f, 0.45000005f, 0.00000001f,
+                    1.64159989f, 0.44999996f, -0.03374999f,
+                    1.61880016f, 0.45000011f, -0.05999999f,
+                    1.58519995f, 0.45000002f, -0.07874999f,
+                    1.54440010f, 0.45000008f, -0.09000000f,
+                    1.50000000f, 0.45000005f, -0.09374999f,
+                    1.45560002f, 0.45000005f, -0.08999999f,
+                    1.41480005f, 0.45000008f, -0.07875000f,
+                    1.38120008f, 0.45000005f, -0.05999999f,
+                    1.35839999f, 0.45000005f, -0.03374998f,
+                    1.35000002f, 0.45000005f, 0.00000001f,
+                    1.67931235f, 0.46290934f, 0.00000001f,
+                    1.67050874f, 0.46283132f, -0.03337199f,
+                    1.64661360f, 0.46261978f, -0.05932799f,
+                    1.61139941f, 0.46230793f, -0.07786798f,
+                    1.56863928f, 0.46192926f, -0.08899198f,
+                    1.52210605f, 0.46151716f, -0.09269998f,
+                    1.47557306f, 0.46110505f, -0.08899198f,
+                    1.43281293f, 0.46072638f, -0.07786798f,
+                    1.39759874f, 0.46041453f, -0.05932799f,
+                    1.37370336f, 0.46020290f, -0.03337198f,
+                    1.36489987f, 0.46012494f, 0.00000001f,
+                    1.70000005f, 0.47340012f, 0.00000001f,
+                    1.69101751f, 0.47324887f, -0.03234600f,
+                    1.66663706f, 0.47283855f, -0.05750400f,
+                    1.63070726f, 0.47223368f, -0.07547399f,
+                    1.58707845f, 0.47149929f, -0.08625600f,
+                    1.53960001f, 0.47070006f, -0.08985000f,
+                    1.49212158f, 0.46990088f, -0.08625600f,
+                    1.44849288f, 0.46916646f, -0.07547400f,
+                    1.41256320f, 0.46856165f, -0.05750399f,
+                    1.38818240f, 0.46815124f, -0.03234598f,
+                    1.37919998f, 0.46800002f, 0.00000001f,
+                    1.71243751f, 0.48130316f, 0.00000001f,
+                    1.70347345f, 0.48108810f, -0.03083399f,
+                    1.67914343f, 0.48050466f, -0.05481599f,
+                    1.64328778f, 0.47964469f, -0.07194599f,
+                    1.59974921f, 0.47860047f, -0.08222400f,
+                    1.55236864f, 0.47746405f, -0.08564999f,
+                    1.50498843f, 0.47632769f, -0.08222400f,
+                    1.46144986f, 0.47528344f, -0.07194600f,
+                    1.42559433f, 0.47442353f, -0.05481599f,
+                    1.40126395f, 0.47383997f, -0.03083398f,
+                    1.39230001f, 0.47362497f, 0.00000001f,
+                    1.71700025f, 0.48645008f, 0.00000001f,
+                    1.70822489f, 0.48618543f, -0.02899799f,
+                    1.68440676f, 0.48546731f, -0.05155200f,
+                    1.64930582f, 0.48440889f, -0.06766199f,
+                    1.60668337f, 0.48312369f, -0.07732800f,
+                    1.56030011f, 0.48172504f, -0.08055000f,
+                    1.51391685f, 0.48032641f, -0.07732800f,
+                    1.47129452f, 0.47904122f, -0.06766200f,
+                    1.43619370f, 0.47798282f, -0.05155200f,
+                    1.41237521f, 0.47726461f, -0.02899799f,
+                    1.40359998f, 0.47700000f, 0.00000001f,
+                    1.71406269f, 0.48867193f, 0.00000001f,
+                    1.70561886f, 0.48837656f, -0.02699999f,
+                    1.68270016f, 0.48757505f, -0.04800000f,
+                    1.64892519f, 0.48639381f, -0.06299999f,
+                    1.60791278f, 0.48495948f, -0.07200000f,
+                    1.56328130f, 0.48339844f, -0.07500000f,
+                    1.51865005f, 0.48183751f, -0.07200000f,
+                    1.47763753f, 0.48040313f, -0.06299999f,
+                    1.44386244f, 0.47922188f, -0.04799999f,
+                    1.42094362f, 0.47842029f, -0.02699998f,
+                    1.41249990f, 0.47812498f, 0.00000001f,
+                    1.70400012f, 0.48780006f, 0.00000001f,
+                    1.69600320f, 0.48749760f, -0.02500199f,
+                    1.67429769f, 0.48667687f, -0.04444800f,
+                    1.64231050f, 0.48546728f, -0.05833799f,
+                    1.60346901f, 0.48399848f, -0.06667200f,
+                    1.56120002f, 0.48240003f, -0.06944999f,
+                    1.51893127f, 0.48080158f, -0.06667199f,
+                    1.48008966f, 0.47933280f, -0.05833799f,
+                    1.44810236f, 0.47812319f, -0.04444799f,
+                    1.42639661f, 0.47730240f, -0.02500199f,
+                    1.41839993f, 0.47700000f, 0.00000001f,
+                    1.68718755f, 0.48366567f, 0.00000001f,
+                    1.67972589f, 0.48338449f, -0.02316599f,
+                    1.65947306f, 0.48262149f, -0.04118400f,
+                    1.62962627f, 0.48149690f, -0.05405399f,
+                    1.59338415f, 0.48013139f, -0.06177600f,
+                    1.55394387f, 0.47864535f, -0.06434999f,
+                    1.51450372f, 0.47715932f, -0.06177600f,
+                    1.47826147f, 0.47579378f, -0.05405399f,
+                    1.44841480f, 0.47466925f, -0.04118399f,
+                    1.42816162f, 0.47390613f, -0.02316599f,
+                    1.42070007f, 0.47362500f, 0.00000001f,
+                    1.66400003f, 0.47610006f, 0.00000001f,
+                    1.65713418f, 0.47587320f, -0.02165399f,
+                    1.63849938f, 0.47525769f, -0.03849600f,
+                    1.61103702f, 0.47435045f, -0.05052599f,
+                    1.57768977f, 0.47324887f, -0.05774400f,
+                    1.54140007f, 0.47205001f, -0.06015000f,
+                    1.50511050f, 0.47085121f, -0.05774400f,
+                    1.47176337f, 0.46974963f, -0.05052600f,
+                    1.44430089f, 0.46884239f, -0.03849599f,
+                    1.42566574f, 0.46822679f, -0.02165399f,
+                    1.41880012f, 0.46799999f, 0.00000001f,
+                    1.63481259f, 0.46493441f, 0.00000001f,
+                    1.62857652f, 0.46479967f, -0.02062799f,
+                    1.61165059f, 0.46443427f, -0.03667200f,
+                    1.58670664f, 0.46389559f, -0.04813199f,
+                    1.55641782f, 0.46324155f, -0.05500800f,
+                    1.52345634f, 0.46252972f, -0.05730000f,
+                    1.49049485f, 0.46181795f, -0.05500799f,
+                    1.46020591f, 0.46116385f, -0.04813200f,
+                    1.43526220f, 0.46062523f, -0.03667200f,
+                    1.41833591f, 0.46025968f, -0.02062799f,
+                    1.41209996f, 0.46012503f, 0.00000001f,
+                    1.60000002f, 0.45000005f, 0.00000001f,
+                    1.59439981f, 0.44999996f, -0.02024999f,
+                    1.57920015f, 0.45000011f, -0.03600000f,
+                    1.55680001f, 0.45000002f, -0.04725000f,
+                    1.52960002f, 0.45000008f, -0.05400000f,
+                    1.50000000f, 0.45000005f, -0.05625000f,
+                    1.47039998f, 0.45000005f, -0.05400000f,
+                    1.44320011f, 0.45000008f, -0.04725000f,
+                    1.42079997f, 0.45000005f, -0.03599999f,
+                    1.40559995f, 0.45000005f, -0.02024999f,
+                    1.39999998f, 0.45000005f, 0.00000001f,
+            };
+
+    std::vector<uint32_t> m_teapotIndices =
+            {
+                    0, 1, 11, 11, 1, 12,
+                    1, 2, 12, 12, 2, 13,
+                    2, 3, 13, 13, 3, 14,
+                    3, 4, 14, 14, 4, 15,
+                    4, 5, 15, 15, 5, 16,
+                    5, 6, 16, 16, 6, 17,
+                    6, 7, 17, 17, 7, 18,
+                    7, 8, 18, 18, 8, 19,
+                    8, 9, 19, 19, 9, 20,
+                    9, 10, 20, 20, 10, 21,
+                    11, 12, 22, 22, 12, 23,
+                    12, 13, 23, 23, 13, 24,
+                    13, 14, 24, 24, 14, 25,
+                    14, 15, 25, 25, 15, 26,
+                    15, 16, 26, 26, 16, 27,
+                    16, 17, 27, 27, 17, 28,
+                    17, 18, 28, 28, 18, 29,
+                    18, 19, 29, 29, 19, 30,
+                    19, 20, 30, 30, 20, 31,
+                    20, 21, 31, 31, 21, 32,
+                    22, 23, 33, 33, 23, 34,
+                    23, 24, 34, 34, 24, 35,
+                    24, 25, 35, 35, 25, 36,
+                    25, 26, 36, 36, 26, 37,
+                    26, 27, 37, 37, 27, 38,
+                    27, 28, 38, 38, 28, 39,
+                    28, 29, 39, 39, 29, 40,
+                    29, 30, 40, 40, 30, 41,
+                    30, 31, 41, 41, 31, 42,
+                    31, 32, 42, 42, 32, 43,
+                    33, 34, 44, 44, 34, 45,
+                    34, 35, 45, 45, 35, 46,
+                    35, 36, 46, 46, 36, 47,
+                    36, 37, 47, 47, 37, 48,
+                    37, 38, 48, 48, 38, 49,
+                    38, 39, 49, 49, 39, 50,
+                    39, 40, 50, 50, 40, 51,
+                    40, 41, 51, 51, 41, 52,
+                    41, 42, 52, 52, 42, 53,
+                    42, 43, 53, 53, 43, 54,
+                    44, 45, 55, 55, 45, 56,
+                    45, 46, 56, 56, 46, 57,
+                    46, 47, 57, 57, 47, 58,
+                    47, 48, 58, 58, 48, 59,
+                    48, 49, 59, 59, 49, 60,
+                    49, 50, 60, 60, 50, 61,
+                    50, 51, 61, 61, 51, 62,
+                    51, 52, 62, 62, 52, 63,
+                    52, 53, 63, 63, 53, 64,
+                    53, 54, 64, 64, 54, 65,
+                    55, 56, 66, 66, 56, 67,
+                    56, 57, 67, 67, 57, 68,
+                    57, 58, 68, 68, 58, 69,
+                    58, 59, 69, 69, 59, 70,
+                    59, 60, 70, 70, 60, 71,
+                    60, 61, 71, 71, 61, 72,
+                    61, 62, 72, 72, 62, 73,
+                    62, 63, 73, 73, 63, 74,
+                    63, 64, 74, 74, 64, 75,
+                    64, 65, 75, 75, 65, 76,
+                    66, 67, 77, 77, 67, 78,
+                    67, 68, 78, 78, 68, 79,
+                    68, 69, 79, 79, 69, 80,
+                    69, 70, 80, 80, 70, 81,
+                    70, 71, 81, 81, 71, 82,
+                    71, 72, 82, 82, 72, 83,
+                    72, 73, 83, 83, 73, 84,
+                    73, 74, 84, 84, 74, 85,
+                    74, 75, 85, 85, 75, 86,
+                    75, 76, 86, 86, 76, 87,
+                    77, 78, 88, 88, 78, 89,
+                    78, 79, 89, 89, 79, 90,
+                    79, 80, 90, 90, 80, 91,
+                    80, 81, 91, 91, 81, 92,
+                    81, 82, 92, 92, 82, 93,
+                    82, 83, 93, 93, 83, 94,
+                    83, 84, 94, 94, 84, 95,
+                    84, 85, 95, 95, 85, 96,
+                    85, 86, 96, 96, 86, 97,
+                    86, 87, 97, 97, 87, 98,
+                    88, 89, 99, 99, 89, 100,
+                    89, 90, 100, 100, 90, 101,
+                    90, 91, 101, 101, 91, 102,
+                    91, 92, 102, 102, 92, 103,
+                    92, 93, 103, 103, 93, 104,
+                    93, 94, 104, 104, 94, 105,
+                    94, 95, 105, 105, 95, 106,
+                    95, 96, 106, 106, 96, 107,
+                    96, 97, 107, 107, 97, 108,
+                    97, 98, 108, 108, 98, 109,
+                    99, 100, 110, 110, 100, 111,
+                    100, 101, 111, 111, 101, 112,
+                    101, 102, 112, 112, 102, 113,
+                    102, 103, 113, 113, 103, 114,
+                    103, 104, 114, 114, 104, 115,
+                    104, 105, 115, 115, 105, 116,
+                    105, 106, 116, 116, 106, 117,
+                    106, 107, 117, 117, 107, 118,
+                    107, 108, 118, 118, 108, 119,
+                    108, 109, 119, 119, 109, 120,
+                    121, 122, 132, 132, 122, 133,
+                    122, 123, 133, 133, 123, 134,
+                    123, 124, 134, 134, 124, 135,
+                    124, 125, 135, 135, 125, 136,
+                    125, 126, 136, 136, 126, 137,
+                    126, 127, 137, 137, 127, 138,
+                    127, 128, 138, 138, 128, 139,
+                    128, 129, 139, 139, 129, 140,
+                    129, 130, 140, 140, 130, 141,
+                    130, 131, 141, 141, 131, 142,
+                    132, 133, 143, 143, 133, 144,
+                    133, 134, 144, 144, 134, 145,
+                    134, 135, 145, 145, 135, 146,
+                    135, 136, 146, 146, 136, 147,
+                    136, 137, 147, 147, 137, 148,
+                    137, 138, 148, 148, 138, 149,
+                    138, 139, 149, 149, 139, 150,
+                    139, 140, 150, 150, 140, 151,
+                    140, 141, 151, 151, 141, 152,
+                    141, 142, 152, 152, 142, 153,
+                    143, 144, 154, 154, 144, 155,
+                    144, 145, 155, 155, 145, 156,
+                    145, 146, 156, 156, 146, 157,
+                    146, 147, 157, 157, 147, 158,
+                    147, 148, 158, 158, 148, 159,
+                    148, 149, 159, 159, 149, 160,
+                    149, 150, 160, 160, 150, 161,
+                    150, 151, 161, 161, 151, 162,
+                    151, 152, 162, 162, 152, 163,
+                    152, 153, 163, 163, 153, 164,
+                    154, 155, 165, 165, 155, 166,
+                    155, 156, 166, 166, 156, 167,
+                    156, 157, 167, 167, 157, 168,
+                    157, 158, 168, 168, 158, 169,
+                    158, 159, 169, 169, 159, 170,
+                    159, 160, 170, 170, 160, 171,
+                    160, 161, 171, 171, 161, 172,
+                    161, 162, 172, 172, 162, 173,
+                    162, 163, 173, 173, 163, 174,
+                    163, 164, 174, 174, 164, 175,
+                    165, 166, 176, 176, 166, 177,
+                    166, 167, 177, 177, 167, 178,
+                    167, 168, 178, 178, 168, 179,
+                    168, 169, 179, 179, 169, 180,
+                    169, 170, 180, 180, 170, 181,
+                    170, 171, 181, 181, 171, 182,
+                    171, 172, 182, 182, 172, 183,
+                    172, 173, 183, 183, 173, 184,
+                    173, 174, 184, 184, 174, 185,
+                    174, 175, 185, 185, 175, 186,
+                    176, 177, 187, 187, 177, 188,
+                    177, 178, 188, 188, 178, 189,
+                    178, 179, 189, 189, 179, 190,
+                    179, 180, 190, 190, 180, 191,
+                    180, 181, 191, 191, 181, 192,
+                    181, 182, 192, 192, 182, 193,
+                    182, 183, 193, 193, 183, 194,
+                    183, 184, 194, 194, 184, 195,
+                    184, 185, 195, 195, 185, 196,
+                    185, 186, 196, 196, 186, 197,
+                    187, 188, 198, 198, 188, 199,
+                    188, 189, 199, 199, 189, 200,
+                    189, 190, 200, 200, 190, 201,
+                    190, 191, 201, 201, 191, 202,
+                    191, 192, 202, 202, 192, 203,
+                    192, 193, 203, 203, 193, 204,
+                    193, 194, 204, 204, 194, 205,
+                    194, 195, 205, 205, 195, 206,
+                    195, 196, 206, 206, 196, 207,
+                    196, 197, 207, 207, 197, 208,
+                    198, 199, 209, 209, 199, 210,
+                    199, 200, 210, 210, 200, 211,
+                    200, 201, 211, 211, 201, 212,
+                    201, 202, 212, 212, 202, 213,
+                    202, 203, 213, 213, 203, 214,
+                    203, 204, 214, 214, 204, 215,
+                    204, 205, 215, 215, 205, 216,
+                    205, 206, 216, 216, 206, 217,
+                    206, 207, 217, 217, 207, 218,
+                    207, 208, 218, 218, 208, 219,
+                    209, 210, 220, 220, 210, 221,
+                    210, 211, 221, 221, 211, 222,
+                    211, 212, 222, 222, 212, 223,
+                    212, 213, 223, 223, 213, 224,
+                    213, 214, 224, 224, 214, 225,
+                    214, 215, 225, 225, 215, 226,
+                    215, 216, 226, 226, 216, 227,
+                    216, 217, 227, 227, 217, 228,
+                    217, 218, 228, 228, 218, 229,
+                    218, 219, 229, 229, 219, 230,
+                    220, 221, 231, 231, 221, 232,
+                    221, 222, 232, 232, 222, 233,
+                    222, 223, 233, 233, 223, 234,
+                    223, 224, 234, 234, 224, 235,
+                    224, 225, 235, 235, 225, 236,
+                    225, 226, 236, 236, 226, 237,
+                    226, 227, 237, 237, 227, 238,
+                    227, 228, 238, 238, 228, 239,
+                    228, 229, 239, 239, 229, 240,
+                    229, 230, 240, 240, 230, 241,
+                    242, 243, 253, 253, 243, 254,
+                    243, 244, 254, 254, 244, 255,
+                    244, 245, 255, 255, 245, 256,
+                    245, 246, 256, 256, 246, 257,
+                    246, 247, 257, 257, 247, 258,
+                    247, 248, 258, 258, 248, 259,
+                    248, 249, 259, 259, 249, 260,
+                    249, 250, 260, 260, 250, 261,
+                    250, 251, 261, 261, 251, 262,
+                    251, 252, 262, 262, 252, 263,
+                    253, 254, 264, 264, 254, 265,
+                    254, 255, 265, 265, 255, 266,
+                    255, 256, 266, 266, 256, 267,
+                    256, 257, 267, 267, 257, 268,
+                    257, 258, 268, 268, 258, 269,
+                    258, 259, 269, 269, 259, 270,
+                    259, 260, 270, 270, 260, 271,
+                    260, 261, 271, 271, 261, 272,
+                    261, 262, 272, 272, 262, 273,
+                    262, 263, 273, 273, 263, 274,
+                    264, 265, 275, 275, 265, 276,
+                    265, 266, 276, 276, 266, 277,
+                    266, 267, 277, 277, 267, 278,
+                    267, 268, 278, 278, 268, 279,
+                    268, 269, 279, 279, 269, 280,
+                    269, 270, 280, 280, 270, 281,
+                    270, 271, 281, 281, 271, 282,
+                    271, 272, 282, 282, 272, 283,
+                    272, 273, 283, 283, 273, 284,
+                    273, 274, 284, 284, 274, 285,
+                    275, 276, 286, 286, 276, 287,
+                    276, 277, 287, 287, 277, 288,
+                    277, 278, 288, 288, 278, 289,
+                    278, 279, 289, 289, 279, 290,
+                    279, 280, 290, 290, 280, 291,
+                    280, 281, 291, 291, 281, 292,
+                    281, 282, 292, 292, 282, 293,
+                    282, 283, 293, 293, 283, 294,
+                    283, 284, 294, 294, 284, 295,
+                    284, 285, 295, 295, 285, 296,
+                    286, 287, 297, 297, 287, 298,
+                    287, 288, 298, 298, 288, 299,
+                    288, 289, 299, 299, 289, 300,
+                    289, 290, 300, 300, 290, 301,
+                    290, 291, 301, 301, 291, 302,
+                    291, 292, 302, 302, 292, 303,
+                    292, 293, 303, 303, 293, 304,
+                    293, 294, 304, 304, 294, 305,
+                    294, 295, 305, 305, 295, 306,
+                    295, 296, 306, 306, 296, 307,
+                    297, 298, 308, 308, 298, 309,
+                    298, 299, 309, 309, 299, 310,
+                    299, 300, 310, 310, 300, 311,
+                    300, 301, 311, 311, 301, 312,
+                    301, 302, 312, 312, 302, 313,
+                    302, 303, 313, 313, 303, 314,
+                    303, 304, 314, 314, 304, 315,
+                    304, 305, 315, 315, 305, 316,
+                    305, 306, 316, 316, 306, 317,
+                    306, 307, 317, 317, 307, 318,
+                    308, 309, 319, 319, 309, 320,
+                    309, 310, 320, 320, 310, 321,
+                    310, 311, 321, 321, 311, 322,
+                    311, 312, 322, 322, 312, 323,
+                    312, 313, 323, 323, 313, 324,
+                    313, 314, 324, 324, 314, 325,
+                    314, 315, 325, 325, 315, 326,
+                    315, 316, 326, 326, 316, 327,
+                    316, 317, 327, 327, 317, 328,
+                    317, 318, 328, 328, 318, 329,
+                    319, 320, 330, 330, 320, 331,
+                    320, 321, 331, 331, 321, 332,
+                    321, 322, 332, 332, 322, 333,
+                    322, 323, 333, 333, 323, 334,
+                    323, 324, 334, 334, 324, 335,
+                    324, 325, 335, 335, 325, 336,
+                    325, 326, 336, 336, 326, 337,
+                    326, 327, 337, 337, 327, 338,
+                    327, 328, 338, 338, 328, 339,
+                    328, 329, 339, 339, 329, 340,
+                    330, 331, 341, 341, 331, 342,
+                    331, 332, 342, 342, 332, 343,
+                    332, 333, 343, 343, 333, 344,
+                    333, 334, 344, 344, 334, 345,
+                    334, 335, 345, 345, 335, 346,
+                    335, 336, 346, 346, 336, 347,
+                    336, 337, 347, 347, 337, 348,
+                    337, 338, 348, 348, 338, 349,
+                    338, 339, 349, 349, 339, 350,
+                    339, 340, 350, 350, 340, 351,
+                    341, 342, 352, 352, 342, 353,
+                    342, 343, 353, 353, 343, 354,
+                    343, 344, 354, 354, 344, 355,
+                    344, 345, 355, 355, 345, 356,
+                    345, 346, 356, 356, 346, 357,
+                    346, 347, 357, 357, 347, 358,
+                    347, 348, 358, 358, 348, 359,
+                    348, 349, 359, 359, 349, 360,
+                    349, 350, 360, 360, 350, 361,
+                    350, 351, 361, 361, 351, 362,
+                    363, 364, 374, 374, 364, 375,
+                    364, 365, 375, 375, 365, 376,
+                    365, 366, 376, 376, 366, 377,
+                    366, 367, 377, 377, 367, 378,
+                    367, 368, 378, 378, 368, 379,
+                    368, 369, 379, 379, 369, 380,
+                    369, 370, 380, 380, 370, 381,
+                    370, 371, 381, 381, 371, 382,
+                    371, 372, 382, 382, 372, 383,
+                    372, 373, 383, 383, 373, 384,
+                    374, 375, 385, 385, 375, 386,
+                    375, 376, 386, 386, 376, 387,
+                    376, 377, 387, 387, 377, 388,
+                    377, 378, 388, 388, 378, 389,
+                    378, 379, 389, 389, 379, 390,
+                    379, 380, 390, 390, 380, 391,
+                    380, 381, 391, 391, 381, 392,
+                    381, 382, 392, 392, 382, 393,
+                    382, 383, 393, 393, 383, 394,
+                    383, 384, 394, 394, 384, 395,
+                    385, 386, 396, 396, 386, 397,
+                    386, 387, 397, 397, 387, 398,
+                    387, 388, 398, 398, 388, 399,
+                    388, 389, 399, 399, 389, 400,
+                    389, 390, 400, 400, 390, 401,
+                    390, 391, 401, 401, 391, 402,
+                    391, 392, 402, 402, 392, 403,
+                    392, 393, 403, 403, 393, 404,
+                    393, 394, 404, 404, 394, 405,
+                    394, 395, 405, 405, 395, 406,
+                    396, 397, 407, 407, 397, 408,
+                    397, 398, 408, 408, 398, 409,
+                    398, 399, 409, 409, 399, 410,
+                    399, 400, 410, 410, 400, 411,
+                    400, 401, 411, 411, 401, 412,
+                    401, 402, 412, 412, 402, 413,
+                    402, 403, 413, 413, 403, 414,
+                    403, 404, 414, 414, 404, 415,
+                    404, 405, 415, 415, 405, 416,
+                    405, 406, 416, 416, 406, 417,
+                    407, 408, 418, 418, 408, 419,
+                    408, 409, 419, 419, 409, 420,
+                    409, 410, 420, 420, 410, 421,
+                    410, 411, 421, 421, 411, 422,
+                    411, 412, 422, 422, 412, 423,
+                    412, 413, 423, 423, 413, 424,
+                    413, 414, 424, 424, 414, 425,
+                    414, 415, 425, 425, 415, 426,
+                    415, 416, 426, 426, 416, 427,
+                    416, 417, 427, 427, 417, 428,
+                    418, 419, 429, 429, 419, 430,
+                    419, 420, 430, 430, 420, 431,
+                    420, 421, 431, 431, 421, 432,
+                    421, 422, 432, 432, 422, 433,
+                    422, 423, 433, 433, 423, 434,
+                    423, 424, 434, 434, 424, 435,
+                    424, 425, 435, 435, 425, 436,
+                    425, 426, 436, 436, 426, 437,
+                    426, 427, 437, 437, 427, 438,
+                    427, 428, 438, 438, 428, 439,
+                    429, 430, 440, 440, 430, 441,
+                    430, 431, 441, 441, 431, 442,
+                    431, 432, 442, 442, 432, 443,
+                    432, 433, 443, 443, 433, 444,
+                    433, 434, 444, 444, 434, 445,
+                    434, 435, 445, 445, 435, 446,
+                    435, 436, 446, 446, 436, 447,
+                    436, 437, 447, 447, 437, 448,
+                    437, 438, 448, 448, 438, 449,
+                    438, 439, 449, 449, 439, 450,
+                    440, 441, 451, 451, 441, 452,
+                    441, 442, 452, 452, 442, 453,
+                    442, 443, 453, 453, 443, 454,
+                    443, 444, 454, 454, 444, 455,
+                    444, 445, 455, 455, 445, 456,
+                    445, 446, 456, 456, 446, 457,
+                    446, 447, 457, 457, 447, 458,
+                    447, 448, 458, 458, 448, 459,
+                    448, 449, 459, 459, 449, 460,
+                    449, 450, 460, 460, 450, 461,
+                    451, 452, 462, 462, 452, 463,
+                    452, 453, 463, 463, 453, 464,
+                    453, 454, 464, 464, 454, 465,
+                    454, 455, 465, 465, 455, 466,
+                    455, 456, 466, 466, 456, 467,
+                    456, 457, 467, 467, 457, 468,
+                    457, 458, 468, 468, 458, 469,
+                    458, 459, 469, 469, 459, 470,
+                    459, 460, 470, 470, 460, 471,
+                    460, 461, 471, 471, 461, 472,
+                    462, 463, 473, 473, 463, 474,
+                    463, 464, 474, 474, 464, 475,
+                    464, 465, 475, 475, 465, 476,
+                    465, 466, 476, 476, 466, 477,
+                    466, 467, 477, 477, 467, 478,
+                    467, 468, 478, 478, 468, 479,
+                    468, 469, 479, 479, 469, 480,
+                    469, 470, 480, 480, 470, 481,
+                    470, 471, 481, 481, 471, 482,
+                    471, 472, 482, 482, 472, 483,
+                    484, 485, 495, 495, 485, 496,
+                    485, 486, 496, 496, 486, 497,
+                    486, 487, 497, 497, 487, 498,
+                    487, 488, 498, 498, 488, 499,
+                    488, 489, 499, 499, 489, 500,
+                    489, 490, 500, 500, 490, 501,
+                    490, 491, 501, 501, 491, 502,
+                    491, 492, 502, 502, 492, 503,
+                    492, 493, 503, 503, 493, 504,
+                    493, 494, 504, 504, 494, 505,
+                    495, 496, 506, 506, 496, 507,
+                    496, 497, 507, 507, 497, 508,
+                    497, 498, 508, 508, 498, 509,
+                    498, 499, 509, 509, 499, 510,
+                    499, 500, 510, 510, 500, 511,
+                    500, 501, 511, 511, 501, 512,
+                    501, 502, 512, 512, 502, 513,
+                    502, 503, 513, 513, 503, 514,
+                    503, 504, 514, 514, 504, 515,
+                    504, 505, 515, 515, 505, 516,
+                    506, 507, 517, 517, 507, 518,
+                    507, 508, 518, 518, 508, 519,
+                    508, 509, 519, 519, 509, 520,
+                    509, 510, 520, 520, 510, 521,
+                    510, 511, 521, 521, 511, 522,
+                    511, 512, 522, 522, 512, 523,
+                    512, 513, 523, 523, 513, 524,
+                    513, 514, 524, 524, 514, 525,
+                    514, 515, 525, 525, 515, 526,
+                    515, 516, 526, 526, 516, 527,
+                    517, 518, 528, 528, 518, 529,
+                    518, 519, 529, 529, 519, 530,
+                    519, 520, 530, 530, 520, 531,
+                    520, 521, 531, 531, 521, 532,
+                    521, 522, 532, 532, 522, 533,
+                    522, 523, 533, 533, 523, 534,
+                    523, 524, 534, 534, 524, 535,
+                    524, 525, 535, 535, 525, 536,
+                    525, 526, 536, 536, 526, 537,
+                    526, 527, 537, 537, 527, 538,
+                    528, 529, 539, 539, 529, 540,
+                    529, 530, 540, 540, 530, 541,
+                    530, 531, 541, 541, 531, 542,
+                    531, 532, 542, 542, 532, 543,
+                    532, 533, 543, 543, 533, 544,
+                    533, 534, 544, 544, 534, 545,
+                    534, 535, 545, 545, 535, 546,
+                    535, 536, 546, 546, 536, 547,
+                    536, 537, 547, 547, 537, 548,
+                    537, 538, 548, 548, 538, 549,
+                    539, 540, 550, 550, 540, 551,
+                    540, 541, 551, 551, 541, 552,
+                    541, 542, 552, 552, 542, 553,
+                    542, 543, 553, 553, 543, 554,
+                    543, 544, 554, 554, 544, 555,
+                    544, 545, 555, 555, 545, 556,
+                    545, 546, 556, 556, 546, 557,
+                    546, 547, 557, 557, 547, 558,
+                    547, 548, 558, 558, 548, 559,
+                    548, 549, 559, 559, 549, 560,
+                    550, 551, 561, 561, 551, 562,
+                    551, 552, 562, 562, 552, 563,
+                    552, 553, 563, 563, 553, 564,
+                    553, 554, 564, 564, 554, 565,
+                    554, 555, 565, 565, 555, 566,
+                    555, 556, 566, 566, 556, 567,
+                    556, 557, 567, 567, 557, 568,
+                    557, 558, 568, 568, 558, 569,
+                    558, 559, 569, 569, 559, 570,
+                    559, 560, 570, 570, 560, 571,
+                    561, 562, 572, 572, 562, 573,
+                    562, 563, 573, 573, 563, 574,
+                    563, 564, 574, 574, 564, 575,
+                    564, 565, 575, 575, 565, 576,
+                    565, 566, 576, 576, 566, 577,
+                    566, 567, 577, 577, 567, 578,
+                    567, 568, 578, 578, 568, 579,
+                    568, 569, 579, 579, 569, 580,
+                    569, 570, 580, 580, 570, 581,
+                    570, 571, 581, 581, 571, 582,
+                    572, 573, 583, 583, 573, 584,
+                    573, 574, 584, 584, 574, 585,
+                    574, 575, 585, 585, 575, 586,
+                    575, 576, 586, 586, 576, 587,
+                    576, 577, 587, 587, 577, 588,
+                    577, 578, 588, 588, 578, 589,
+                    578, 579, 589, 589, 579, 590,
+                    579, 580, 590, 590, 580, 591,
+                    580, 581, 591, 591, 581, 592,
+                    581, 582, 592, 592, 582, 593,
+                    583, 584, 594, 594, 584, 595,
+                    584, 585, 595, 595, 585, 596,
+                    585, 586, 596, 596, 586, 597,
+                    586, 587, 597, 597, 587, 598,
+                    587, 588, 598, 598, 588, 599,
+                    588, 589, 599, 599, 589, 600,
+                    589, 590, 600, 600, 590, 601,
+                    590, 591, 601, 601, 591, 602,
+                    591, 592, 602, 602, 592, 603,
+                    592, 593, 603, 603, 593, 604,
+                    605, 606, 616, 616, 606, 617,
+                    606, 607, 617, 617, 607, 618,
+                    607, 608, 618, 618, 608, 619,
+                    608, 609, 619, 619, 609, 620,
+                    609, 610, 620, 620, 610, 621,
+                    610, 611, 621, 621, 611, 622,
+                    611, 612, 622, 622, 612, 623,
+                    612, 613, 623, 623, 613, 624,
+                    613, 614, 624, 624, 614, 625,
+                    614, 615, 625, 625, 615, 626,
+                    616, 617, 627, 627, 617, 628,
+                    617, 618, 628, 628, 618, 629,
+                    618, 619, 629, 629, 619, 630,
+                    619, 620, 630, 630, 620, 631,
+                    620, 621, 631, 631, 621, 632,
+                    621, 622, 632, 632, 622, 633,
+                    622, 623, 633, 633, 623, 634,
+                    623, 624, 634, 634, 624, 635,
+                    624, 625, 635, 635, 625, 636,
+                    625, 626, 636, 636, 626, 637,
+                    627, 628, 638, 638, 628, 639,
+                    628, 629, 639, 639, 629, 640,
+                    629, 630, 640, 640, 630, 641,
+                    630, 631, 641, 641, 631, 642,
+                    631, 632, 642, 642, 632, 643,
+                    632, 633, 643, 643, 633, 644,
+                    633, 634, 644, 644, 634, 645,
+                    634, 635, 645, 645, 635, 646,
+                    635, 636, 646, 646, 636, 647,
+                    636, 637, 647, 647, 637, 648,
+                    638, 639, 649, 649, 639, 650,
+                    639, 640, 650, 650, 640, 651,
+                    640, 641, 651, 651, 641, 652,
+                    641, 642, 652, 652, 642, 653,
+                    642, 643, 653, 653, 643, 654,
+                    643, 644, 654, 654, 644, 655,
+                    644, 645, 655, 655, 645, 656,
+                    645, 646, 656, 656, 646, 657,
+                    646, 647, 657, 657, 647, 658,
+                    647, 648, 658, 658, 648, 659,
+                    649, 650, 660, 660, 650, 661,
+                    650, 651, 661, 661, 651, 662,
+                    651, 652, 662, 662, 652, 663,
+                    652, 653, 663, 663, 653, 664,
+                    653, 654, 664, 664, 654, 665,
+                    654, 655, 665, 665, 655, 666,
+                    655, 656, 666, 666, 656, 667,
+                    656, 657, 667, 667, 657, 668,
+                    657, 658, 668, 668, 658, 669,
+                    658, 659, 669, 669, 659, 670,
+                    660, 661, 671, 671, 661, 672,
+                    661, 662, 672, 672, 662, 673,
+                    662, 663, 673, 673, 663, 674,
+                    663, 664, 674, 674, 664, 675,
+                    664, 665, 675, 675, 665, 676,
+                    665, 666, 676, 676, 666, 677,
+                    666, 667, 677, 677, 667, 678,
+                    667, 668, 678, 678, 668, 679,
+                    668, 669, 679, 679, 669, 680,
+                    669, 670, 680, 680, 670, 681,
+                    671, 672, 682, 682, 672, 683,
+                    672, 673, 683, 683, 673, 684,
+                    673, 674, 684, 684, 674, 685,
+                    674, 675, 685, 685, 675, 686,
+                    675, 676, 686, 686, 676, 687,
+                    676, 677, 687, 687, 677, 688,
+                    677, 678, 688, 688, 678, 689,
+                    678, 679, 689, 689, 679, 690,
+                    679, 680, 690, 690, 680, 691,
+                    680, 681, 691, 691, 681, 692,
+                    682, 683, 693, 693, 683, 694,
+                    683, 684, 694, 694, 684, 695,
+                    684, 685, 695, 695, 685, 696,
+                    685, 686, 696, 696, 686, 697,
+                    686, 687, 697, 697, 687, 698,
+                    687, 688, 698, 698, 688, 699,
+                    688, 689, 699, 699, 689, 700,
+                    689, 690, 700, 700, 690, 701,
+                    690, 691, 701, 701, 691, 702,
+                    691, 692, 702, 702, 692, 703,
+                    693, 694, 704, 704, 694, 705,
+                    694, 695, 705, 705, 695, 706,
+                    695, 696, 706, 706, 696, 707,
+                    696, 697, 707, 707, 697, 708,
+                    697, 698, 708, 708, 698, 709,
+                    698, 699, 709, 709, 699, 710,
+                    699, 700, 710, 710, 700, 711,
+                    700, 701, 711, 711, 701, 712,
+                    701, 702, 712, 712, 702, 713,
+                    702, 703, 713, 713, 703, 714,
+                    704, 705, 715, 715, 705, 716,
+                    705, 706, 716, 716, 706, 717,
+                    706, 707, 717, 717, 707, 718,
+                    707, 708, 718, 718, 708, 719,
+                    708, 709, 719, 719, 709, 720,
+                    709, 710, 720, 720, 710, 721,
+                    710, 711, 721, 721, 711, 722,
+                    711, 712, 722, 722, 712, 723,
+                    712, 713, 723, 723, 713, 724,
+                    713, 714, 724, 724, 714, 725,
+                    726, 727, 737, 737, 727, 738,
+                    727, 728, 738, 738, 728, 739,
+                    728, 729, 739, 739, 729, 740,
+                    729, 730, 740, 740, 730, 741,
+                    730, 731, 741, 741, 731, 742,
+                    731, 732, 742, 742, 732, 743,
+                    732, 733, 743, 743, 733, 744,
+                    733, 734, 744, 744, 734, 745,
+                    734, 735, 745, 745, 735, 746,
+                    735, 736, 746, 746, 736, 747,
+                    737, 738, 748, 748, 738, 749,
+                    738, 739, 749, 749, 739, 750,
+                    739, 740, 750, 750, 740, 751,
+                    740, 741, 751, 751, 741, 752,
+                    741, 742, 752, 752, 742, 753,
+                    742, 743, 753, 753, 743, 754,
+                    743, 744, 754, 754, 744, 755,
+                    744, 745, 755, 755, 745, 756,
+                    745, 746, 756, 756, 746, 757,
+                    746, 747, 757, 757, 747, 758,
+                    748, 749, 759, 759, 749, 760,
+                    749, 750, 760, 760, 750, 761,
+                    750, 751, 761, 761, 751, 762,
+                    751, 752, 762, 762, 752, 763,
+                    752, 753, 763, 763, 753, 764,
+                    753, 754, 764, 764, 754, 765,
+                    754, 755, 765, 765, 755, 766,
+                    755, 756, 766, 766, 756, 767,
+                    756, 757, 767, 767, 757, 768,
+                    757, 758, 768, 768, 758, 769,
+                    759, 760, 770, 770, 760, 771,
+                    760, 761, 771, 771, 761, 772,
+                    761, 762, 772, 772, 762, 773,
+                    762, 763, 773, 773, 763, 774,
+                    763, 764, 774, 774, 764, 775,
+                    764, 765, 775, 775, 765, 776,
+                    765, 766, 776, 776, 766, 777,
+                    766, 767, 777, 777, 767, 778,
+                    767, 768, 778, 778, 768, 779,
+                    768, 769, 779, 779, 769, 780,
+                    770, 771, 781, 781, 771, 782,
+                    771, 772, 782, 782, 772, 783,
+                    772, 773, 783, 783, 773, 784,
+                    773, 774, 784, 784, 774, 785,
+                    774, 775, 785, 785, 775, 786,
+                    775, 776, 786, 786, 776, 787,
+                    776, 777, 787, 787, 777, 788,
+                    777, 778, 788, 788, 778, 789,
+                    778, 779, 789, 789, 779, 790,
+                    779, 780, 790, 790, 780, 791,
+                    781, 782, 792, 792, 782, 793,
+                    782, 783, 793, 793, 783, 794,
+                    783, 784, 794, 794, 784, 795,
+                    784, 785, 795, 795, 785, 796,
+                    785, 786, 796, 796, 786, 797,
+                    786, 787, 797, 797, 787, 798,
+                    787, 788, 798, 798, 788, 799,
+                    788, 789, 799, 799, 789, 800,
+                    789, 790, 800, 800, 790, 801,
+                    790, 791, 801, 801, 791, 802,
+                    792, 793, 803, 803, 793, 804,
+                    793, 794, 804, 804, 794, 805,
+                    794, 795, 805, 805, 795, 806,
+                    795, 796, 806, 806, 796, 807,
+                    796, 797, 807, 807, 797, 808,
+                    797, 798, 808, 808, 798, 809,
+                    798, 799, 809, 809, 799, 810,
+                    799, 800, 810, 810, 800, 811,
+                    800, 801, 811, 811, 801, 812,
+                    801, 802, 812, 812, 802, 813,
+                    803, 804, 814, 814, 804, 815,
+                    804, 805, 815, 815, 805, 816,
+                    805, 806, 816, 816, 806, 817,
+                    806, 807, 817, 817, 807, 818,
+                    807, 808, 818, 818, 808, 819,
+                    808, 809, 819, 819, 809, 820,
+                    809, 810, 820, 820, 810, 821,
+                    810, 811, 821, 821, 811, 822,
+                    811, 812, 822, 822, 812, 823,
+                    812, 813, 823, 823, 813, 824,
+                    814, 815, 825, 825, 815, 826,
+                    815, 816, 826, 826, 816, 827,
+                    816, 817, 827, 827, 817, 828,
+                    817, 818, 828, 828, 818, 829,
+                    818, 819, 829, 829, 819, 830,
+                    819, 820, 830, 830, 820, 831,
+                    820, 821, 831, 831, 821, 832,
+                    821, 822, 832, 832, 822, 833,
+                    822, 823, 833, 833, 823, 834,
+                    823, 824, 834, 834, 824, 835,
+                    825, 826, 836, 836, 826, 837,
+                    826, 827, 837, 837, 827, 838,
+                    827, 828, 838, 838, 828, 839,
+                    828, 829, 839, 839, 829, 840,
+                    829, 830, 840, 840, 830, 841,
+                    830, 831, 841, 841, 831, 842,
+                    831, 832, 842, 842, 832, 843,
+                    832, 833, 843, 843, 833, 844,
+                    833, 834, 844, 844, 834, 845,
+                    834, 835, 845, 845, 835, 846,
+                    847, 848, 858, 858, 848, 859,
+                    848, 849, 859, 859, 849, 860,
+                    849, 850, 860, 860, 850, 861,
+                    850, 851, 861, 861, 851, 862,
+                    851, 852, 862, 862, 852, 863,
+                    852, 853, 863, 863, 853, 864,
+                    853, 854, 864, 864, 854, 865,
+                    854, 855, 865, 865, 855, 866,
+                    855, 856, 866, 866, 856, 867,
+                    856, 857, 867, 867, 857, 868,
+                    858, 859, 869, 869, 859, 870,
+                    859, 860, 870, 870, 860, 871,
+                    860, 861, 871, 871, 861, 872,
+                    861, 862, 872, 872, 862, 873,
+                    862, 863, 873, 873, 863, 874,
+                    863, 864, 874, 874, 864, 875,
+                    864, 865, 875, 875, 865, 876,
+                    865, 866, 876, 876, 866, 877,
+                    866, 867, 877, 877, 867, 878,
+                    867, 868, 878, 878, 868, 879,
+                    869, 870, 880, 880, 870, 881,
+                    870, 871, 881, 881, 871, 882,
+                    871, 872, 882, 882, 872, 883,
+                    872, 873, 883, 883, 873, 884,
+                    873, 874, 884, 884, 874, 885,
+                    874, 875, 885, 885, 875, 886,
+                    875, 876, 886, 886, 876, 887,
+                    876, 877, 887, 887, 877, 888,
+                    877, 878, 888, 888, 878, 889,
+                    878, 879, 889, 889, 879, 890,
+                    880, 881, 891, 891, 881, 892,
+                    881, 882, 892, 892, 882, 893,
+                    882, 883, 893, 893, 883, 894,
+                    883, 884, 894, 894, 884, 895,
+                    884, 885, 895, 895, 885, 896,
+                    885, 886, 896, 896, 886, 897,
+                    886, 887, 897, 897, 887, 898,
+                    887, 888, 898, 898, 888, 899,
+                    888, 889, 899, 899, 889, 900,
+                    889, 890, 900, 900, 890, 901,
+                    891, 892, 902, 902, 892, 903,
+                    892, 893, 903, 903, 893, 904,
+                    893, 894, 904, 904, 894, 905,
+                    894, 895, 905, 905, 895, 906,
+                    895, 896, 906, 906, 896, 907,
+                    896, 897, 907, 907, 897, 908,
+                    897, 898, 908, 908, 898, 909,
+                    898, 899, 909, 909, 899, 910,
+                    899, 900, 910, 910, 900, 911,
+                    900, 901, 911, 911, 901, 912,
+                    902, 903, 913, 913, 903, 914,
+                    903, 904, 914, 914, 904, 915,
+                    904, 905, 915, 915, 905, 916,
+                    905, 906, 916, 916, 906, 917,
+                    906, 907, 917, 917, 907, 918,
+                    907, 908, 918, 918, 908, 919,
+                    908, 909, 919, 919, 909, 920,
+                    909, 910, 920, 920, 910, 921,
+                    910, 911, 921, 921, 911, 922,
+                    911, 912, 922, 922, 912, 923,
+                    913, 914, 924, 924, 914, 925,
+                    914, 915, 925, 925, 915, 926,
+                    915, 916, 926, 926, 916, 927,
+                    916, 917, 927, 927, 917, 928,
+                    917, 918, 928, 928, 918, 929,
+                    918, 919, 929, 929, 919, 930,
+                    919, 920, 930, 930, 920, 931,
+                    920, 921, 931, 931, 921, 932,
+                    921, 922, 932, 932, 922, 933,
+                    922, 923, 933, 933, 923, 934,
+                    924, 925, 935, 935, 925, 936,
+                    925, 926, 936, 936, 926, 937,
+                    926, 927, 937, 937, 927, 938,
+                    927, 928, 938, 938, 928, 939,
+                    928, 929, 939, 939, 929, 940,
+                    929, 930, 940, 940, 930, 941,
+                    930, 931, 941, 941, 931, 942,
+                    931, 932, 942, 942, 932, 943,
+                    932, 933, 943, 943, 933, 944,
+                    933, 934, 944, 944, 934, 945,
+                    935, 936, 946, 946, 936, 947,
+                    936, 937, 947, 947, 937, 948,
+                    937, 938, 948, 948, 938, 949,
+                    938, 939, 949, 949, 939, 950,
+                    939, 940, 950, 950, 940, 951,
+                    940, 941, 951, 951, 941, 952,
+                    941, 942, 952, 952, 942, 953,
+                    942, 943, 953, 953, 943, 954,
+                    943, 944, 954, 954, 944, 955,
+                    944, 945, 955, 955, 945, 956,
+                    946, 947, 957, 957, 947, 958,
+                    947, 948, 958, 958, 948, 959,
+                    948, 949, 959, 959, 949, 960,
+                    949, 950, 960, 960, 950, 961,
+                    950, 951, 961, 961, 951, 962,
+                    951, 952, 962, 962, 952, 963,
+                    952, 953, 963, 963, 953, 964,
+                    953, 954, 964, 964, 954, 965,
+                    954, 955, 965, 965, 955, 966,
+                    955, 956, 966, 966, 956, 967,
+                    968, 969, 979, 979, 969, 980,
+                    969, 970, 980, 980, 970, 981,
+                    970, 971, 981, 981, 971, 982,
+                    971, 972, 982, 982, 972, 983,
+                    972, 973, 983, 983, 973, 984,
+                    973, 974, 984, 984, 974, 985,
+                    974, 975, 985, 985, 975, 986,
+                    975, 976, 986, 986, 976, 987,
+                    976, 977, 987, 987, 977, 988,
+                    977, 978, 988, 988, 978, 989,
+                    979, 980, 990, 990, 980, 991,
+                    980, 981, 991, 991, 981, 992,
+                    981, 982, 992, 992, 982, 993,
+                    982, 983, 993, 993, 983, 994,
+                    983, 984, 994, 994, 984, 995,
+                    984, 985, 995, 995, 985, 996,
+                    985, 986, 996, 996, 986, 997,
+                    986, 987, 997, 997, 987, 998,
+                    987, 988, 998, 998, 988, 999,
+                    988, 989, 999, 999, 989, 1000,
+                    990, 991, 1001, 1001, 991, 1002,
+                    991, 992, 1002, 1002, 992, 1003,
+                    992, 993, 1003, 1003, 993, 1004,
+                    993, 994, 1004, 1004, 994, 1005,
+                    994, 995, 1005, 1005, 995, 1006,
+                    995, 996, 1006, 1006, 996, 1007,
+                    996, 997, 1007, 1007, 997, 1008,
+                    997, 998, 1008, 1008, 998, 1009,
+                    998, 999, 1009, 1009, 999, 1010,
+                    999, 1000, 1010, 1010, 1000, 1011,
+                    1001, 1002, 1012, 1012, 1002, 1013,
+                    1002, 1003, 1013, 1013, 1003, 1014,
+                    1003, 1004, 1014, 1014, 1004, 1015,
+                    1004, 1005, 1015, 1015, 1005, 1016,
+                    1005, 1006, 1016, 1016, 1006, 1017,
+                    1006, 1007, 1017, 1017, 1007, 1018,
+                    1007, 1008, 1018, 1018, 1008, 1019,
+                    1008, 1009, 1019, 1019, 1009, 1020,
+                    1009, 1010, 1020, 1020, 1010, 1021,
+                    1010, 1011, 1021, 1021, 1011, 1022,
+                    1012, 1013, 1023, 1023, 1013, 1024,
+                    1013, 1014, 1024, 1024, 1014, 1025,
+                    1014, 1015, 1025, 1025, 1015, 1026,
+                    1015, 1016, 1026, 1026, 1016, 1027,
+                    1016, 1017, 1027, 1027, 1017, 1028,
+                    1017, 1018, 1028, 1028, 1018, 1029,
+                    1018, 1019, 1029, 1029, 1019, 1030,
+                    1019, 1020, 1030, 1030, 1020, 1031,
+                    1020, 1021, 1031, 1031, 1021, 1032,
+                    1021, 1022, 1032, 1032, 1022, 1033,
+                    1023, 1024, 1034, 1034, 1024, 1035,
+                    1024, 1025, 1035, 1035, 1025, 1036,
+                    1025, 1026, 1036, 1036, 1026, 1037,
+                    1026, 1027, 1037, 1037, 1027, 1038,
+                    1027, 1028, 1038, 1038, 1028, 1039,
+                    1028, 1029, 1039, 1039, 1029, 1040,
+                    1029, 1030, 1040, 1040, 1030, 1041,
+                    1030, 1031, 1041, 1041, 1031, 1042,
+                    1031, 1032, 1042, 1042, 1032, 1043,
+                    1032, 1033, 1043, 1043, 1033, 1044,
+                    1034, 1035, 1045, 1045, 1035, 1046,
+                    1035, 1036, 1046, 1046, 1036, 1047,
+                    1036, 1037, 1047, 1047, 1037, 1048,
+                    1037, 1038, 1048, 1048, 1038, 1049,
+                    1038, 1039, 1049, 1049, 1039, 1050,
+                    1039, 1040, 1050, 1050, 1040, 1051,
+                    1040, 1041, 1051, 1051, 1041, 1052,
+                    1041, 1042, 1052, 1052, 1042, 1053,
+                    1042, 1043, 1053, 1053, 1043, 1054,
+                    1043, 1044, 1054, 1054, 1044, 1055,
+                    1045, 1046, 1056, 1056, 1046, 1057,
+                    1046, 1047, 1057, 1057, 1047, 1058,
+                    1047, 1048, 1058, 1058, 1048, 1059,
+                    1048, 1049, 1059, 1059, 1049, 1060,
+                    1049, 1050, 1060, 1060, 1050, 1061,
+                    1050, 1051, 1061, 1061, 1051, 1062,
+                    1051, 1052, 1062, 1062, 1052, 1063,
+                    1052, 1053, 1063, 1063, 1053, 1064,
+                    1053, 1054, 1064, 1064, 1054, 1065,
+                    1054, 1055, 1065, 1065, 1055, 1066,
+                    1056, 1057, 1067, 1067, 1057, 1068,
+                    1057, 1058, 1068, 1068, 1058, 1069,
+                    1058, 1059, 1069, 1069, 1059, 1070,
+                    1059, 1060, 1070, 1070, 1060, 1071,
+                    1060, 1061, 1071, 1071, 1061, 1072,
+                    1061, 1062, 1072, 1072, 1062, 1073,
+                    1062, 1063, 1073, 1073, 1063, 1074,
+                    1063, 1064, 1074, 1074, 1064, 1075,
+                    1064, 1065, 1075, 1075, 1065, 1076,
+                    1065, 1066, 1076, 1076, 1066, 1077,
+                    1067, 1068, 1078, 1078, 1068, 1079,
+                    1068, 1069, 1079, 1079, 1069, 1080,
+                    1069, 1070, 1080, 1080, 1070, 1081,
+                    1070, 1071, 1081, 1081, 1071, 1082,
+                    1071, 1072, 1082, 1082, 1072, 1083,
+                    1072, 1073, 1083, 1083, 1073, 1084,
+                    1073, 1074, 1084, 1084, 1074, 1085,
+                    1074, 1075, 1085, 1085, 1075, 1086,
+                    1075, 1076, 1086, 1086, 1076, 1087,
+                    1076, 1077, 1087, 1087, 1077, 1088,
+                    1089, 1090, 1100, 1100, 1090, 1101,
+                    1090, 1091, 1101, 1101, 1091, 1102,
+                    1091, 1092, 1102, 1102, 1092, 1103,
+                    1092, 1093, 1103, 1103, 1093, 1104,
+                    1093, 1094, 1104, 1104, 1094, 1105,
+                    1094, 1095, 1105, 1105, 1095, 1106,
+                    1095, 1096, 1106, 1106, 1096, 1107,
+                    1096, 1097, 1107, 1107, 1097, 1108,
+                    1097, 1098, 1108, 1108, 1098, 1109,
+                    1098, 1099, 1109, 1109, 1099, 1110,
+                    1100, 1101, 1111, 1111, 1101, 1112,
+                    1101, 1102, 1112, 1112, 1102, 1113,
+                    1102, 1103, 1113, 1113, 1103, 1114,
+                    1103, 1104, 1114, 1114, 1104, 1115,
+                    1104, 1105, 1115, 1115, 1105, 1116,
+                    1105, 1106, 1116, 1116, 1106, 1117,
+                    1106, 1107, 1117, 1117, 1107, 1118,
+                    1107, 1108, 1118, 1118, 1108, 1119,
+                    1108, 1109, 1119, 1119, 1109, 1120,
+                    1109, 1110, 1120, 1120, 1110, 1121,
+                    1111, 1112, 1122, 1122, 1112, 1123,
+                    1112, 1113, 1123, 1123, 1113, 1124,
+                    1113, 1114, 1124, 1124, 1114, 1125,
+                    1114, 1115, 1125, 1125, 1115, 1126,
+                    1115, 1116, 1126, 1126, 1116, 1127,
+                    1116, 1117, 1127, 1127, 1117, 1128,
+                    1117, 1118, 1128, 1128, 1118, 1129,
+                    1118, 1119, 1129, 1129, 1119, 1130,
+                    1119, 1120, 1130, 1130, 1120, 1131,
+                    1120, 1121, 1131, 1131, 1121, 1132,
+                    1122, 1123, 1133, 1133, 1123, 1134,
+                    1123, 1124, 1134, 1134, 1124, 1135,
+                    1124, 1125, 1135, 1135, 1125, 1136,
+                    1125, 1126, 1136, 1136, 1126, 1137,
+                    1126, 1127, 1137, 1137, 1127, 1138,
+                    1127, 1128, 1138, 1138, 1128, 1139,
+                    1128, 1129, 1139, 1139, 1129, 1140,
+                    1129, 1130, 1140, 1140, 1130, 1141,
+                    1130, 1131, 1141, 1141, 1131, 1142,
+                    1131, 1132, 1142, 1142, 1132, 1143,
+                    1133, 1134, 1144, 1144, 1134, 1145,
+                    1134, 1135, 1145, 1145, 1135, 1146,
+                    1135, 1136, 1146, 1146, 1136, 1147,
+                    1136, 1137, 1147, 1147, 1137, 1148,
+                    1137, 1138, 1148, 1148, 1138, 1149,
+                    1138, 1139, 1149, 1149, 1139, 1150,
+                    1139, 1140, 1150, 1150, 1140, 1151,
+                    1140, 1141, 1151, 1151, 1141, 1152,
+                    1141, 1142, 1152, 1152, 1142, 1153,
+                    1142, 1143, 1153, 1153, 1143, 1154,
+                    1144, 1145, 1155, 1155, 1145, 1156,
+                    1145, 1146, 1156, 1156, 1146, 1157,
+                    1146, 1147, 1157, 1157, 1147, 1158,
+                    1147, 1148, 1158, 1158, 1148, 1159,
+                    1148, 1149, 1159, 1159, 1149, 1160,
+                    1149, 1150, 1160, 1160, 1150, 1161,
+                    1150, 1151, 1161, 1161, 1151, 1162,
+                    1151, 1152, 1162, 1162, 1152, 1163,
+                    1152, 1153, 1163, 1163, 1153, 1164,
+                    1153, 1154, 1164, 1164, 1154, 1165,
+                    1155, 1156, 1166, 1166, 1156, 1167,
+                    1156, 1157, 1167, 1167, 1157, 1168,
+                    1157, 1158, 1168, 1168, 1158, 1169,
+                    1158, 1159, 1169, 1169, 1159, 1170,
+                    1159, 1160, 1170, 1170, 1160, 1171,
+                    1160, 1161, 1171, 1171, 1161, 1172,
+                    1161, 1162, 1172, 1172, 1162, 1173,
+                    1162, 1163, 1173, 1173, 1163, 1174,
+                    1163, 1164, 1174, 1174, 1164, 1175,
+                    1164, 1165, 1175, 1175, 1165, 1176,
+                    1166, 1167, 1177, 1177, 1167, 1178,
+                    1167, 1168, 1178, 1178, 1168, 1179,
+                    1168, 1169, 1179, 1179, 1169, 1180,
+                    1169, 1170, 1180, 1180, 1170, 1181,
+                    1170, 1171, 1181, 1181, 1171, 1182,
+                    1171, 1172, 1182, 1182, 1172, 1183,
+                    1172, 1173, 1183, 1183, 1173, 1184,
+                    1173, 1174, 1184, 1184, 1174, 1185,
+                    1174, 1175, 1185, 1185, 1175, 1186,
+                    1175, 1176, 1186, 1186, 1176, 1187,
+                    1177, 1178, 1188, 1188, 1178, 1189,
+                    1178, 1179, 1189, 1189, 1179, 1190,
+                    1179, 1180, 1190, 1190, 1180, 1191,
+                    1180, 1181, 1191, 1191, 1181, 1192,
+                    1181, 1182, 1192, 1192, 1182, 1193,
+                    1182, 1183, 1193, 1193, 1183, 1194,
+                    1183, 1184, 1194, 1194, 1184, 1195,
+                    1184, 1185, 1195, 1195, 1185, 1196,
+                    1185, 1186, 1196, 1196, 1186, 1197,
+                    1186, 1187, 1197, 1197, 1187, 1198,
+                    1188, 1189, 1199, 1199, 1189, 1200,
+                    1189, 1190, 1200, 1200, 1190, 1201,
+                    1190, 1191, 1201, 1201, 1191, 1202,
+                    1191, 1192, 1202, 1202, 1192, 1203,
+                    1192, 1193, 1203, 1203, 1193, 1204,
+                    1193, 1194, 1204, 1204, 1194, 1205,
+                    1194, 1195, 1205, 1205, 1195, 1206,
+                    1195, 1196, 1206, 1206, 1196, 1207,
+                    1196, 1197, 1207, 1207, 1197, 1208,
+                    1197, 1198, 1208, 1208, 1198, 1209,
+                    1210, 1211, 1221, 1221, 1211, 1222,
+                    1211, 1212, 1222, 1222, 1212, 1223,
+                    1212, 1213, 1223, 1223, 1213, 1224,
+                    1213, 1214, 1224, 1224, 1214, 1225,
+                    1214, 1215, 1225, 1225, 1215, 1226,
+                    1215, 1216, 1226, 1226, 1216, 1227,
+                    1216, 1217, 1227, 1227, 1217, 1228,
+                    1217, 1218, 1228, 1228, 1218, 1229,
+                    1218, 1219, 1229, 1229, 1219, 1230,
+                    1219, 1220, 1230, 1230, 1220, 1231,
+                    1221, 1222, 1232, 1232, 1222, 1233,
+                    1222, 1223, 1233, 1233, 1223, 1234,
+                    1223, 1224, 1234, 1234, 1224, 1235,
+                    1224, 1225, 1235, 1235, 1225, 1236,
+                    1225, 1226, 1236, 1236, 1226, 1237,
+                    1226, 1227, 1237, 1237, 1227, 1238,
+                    1227, 1228, 1238, 1238, 1228, 1239,
+                    1228, 1229, 1239, 1239, 1229, 1240,
+                    1229, 1230, 1240, 1240, 1230, 1241,
+                    1230, 1231, 1241, 1241, 1231, 1242,
+                    1232, 1233, 1243, 1243, 1233, 1244,
+                    1233, 1234, 1244, 1244, 1234, 1245,
+                    1234, 1235, 1245, 1245, 1235, 1246,
+                    1235, 1236, 1246, 1246, 1236, 1247,
+                    1236, 1237, 1247, 1247, 1237, 1248,
+                    1237, 1238, 1248, 1248, 1238, 1249,
+                    1238, 1239, 1249, 1249, 1239, 1250,
+                    1239, 1240, 1250, 1250, 1240, 1251,
+                    1240, 1241, 1251, 1251, 1241, 1252,
+                    1241, 1242, 1252, 1252, 1242, 1253,
+                    1243, 1244, 1254, 1254, 1244, 1255,
+                    1244, 1245, 1255, 1255, 1245, 1256,
+                    1245, 1246, 1256, 1256, 1246, 1257,
+                    1246, 1247, 1257, 1257, 1247, 1258,
+                    1247, 1248, 1258, 1258, 1248, 1259,
+                    1248, 1249, 1259, 1259, 1249, 1260,
+                    1249, 1250, 1260, 1260, 1250, 1261,
+                    1250, 1251, 1261, 1261, 1251, 1262,
+                    1251, 1252, 1262, 1262, 1252, 1263,
+                    1252, 1253, 1263, 1263, 1253, 1264,
+                    1254, 1255, 1265, 1265, 1255, 1266,
+                    1255, 1256, 1266, 1266, 1256, 1267,
+                    1256, 1257, 1267, 1267, 1257, 1268,
+                    1257, 1258, 1268, 1268, 1258, 1269,
+                    1258, 1259, 1269, 1269, 1259, 1270,
+                    1259, 1260, 1270, 1270, 1260, 1271,
+                    1260, 1261, 1271, 1271, 1261, 1272,
+                    1261, 1262, 1272, 1272, 1262, 1273,
+                    1262, 1263, 1273, 1273, 1263, 1274,
+                    1263, 1264, 1274, 1274, 1264, 1275,
+                    1265, 1266, 1276, 1276, 1266, 1277,
+                    1266, 1267, 1277, 1277, 1267, 1278,
+                    1267, 1268, 1278, 1278, 1268, 1279,
+                    1268, 1269, 1279, 1279, 1269, 1280,
+                    1269, 1270, 1280, 1280, 1270, 1281,
+                    1270, 1271, 1281, 1281, 1271, 1282,
+                    1271, 1272, 1282, 1282, 1272, 1283,
+                    1272, 1273, 1283, 1283, 1273, 1284,
+                    1273, 1274, 1284, 1284, 1274, 1285,
+                    1274, 1275, 1285, 1285, 1275, 1286,
+                    1276, 1277, 1287, 1287, 1277, 1288,
+                    1277, 1278, 1288, 1288, 1278, 1289,
+                    1278, 1279, 1289, 1289, 1279, 1290,
+                    1279, 1280, 1290, 1290, 1280, 1291,
+                    1280, 1281, 1291, 1291, 1281, 1292,
+                    1281, 1282, 1292, 1292, 1282, 1293,
+                    1282, 1283, 1293, 1293, 1283, 1294,
+                    1283, 1284, 1294, 1294, 1284, 1295,
+                    1284, 1285, 1295, 1295, 1285, 1296,
+                    1285, 1286, 1296, 1296, 1286, 1297,
+                    1287, 1288, 1298, 1298, 1288, 1299,
+                    1288, 1289, 1299, 1299, 1289, 1300,
+                    1289, 1290, 1300, 1300, 1290, 1301,
+                    1290, 1291, 1301, 1301, 1291, 1302,
+                    1291, 1292, 1302, 1302, 1292, 1303,
+                    1292, 1293, 1303, 1303, 1293, 1304,
+                    1293, 1294, 1304, 1304, 1294, 1305,
+                    1294, 1295, 1305, 1305, 1295, 1306,
+                    1295, 1296, 1306, 1306, 1296, 1307,
+                    1296, 1297, 1307, 1307, 1297, 1308,
+                    1298, 1299, 1309, 1309, 1299, 1310,
+                    1299, 1300, 1310, 1310, 1300, 1311,
+                    1300, 1301, 1311, 1311, 1301, 1312,
+                    1301, 1302, 1312, 1312, 1302, 1313,
+                    1302, 1303, 1313, 1313, 1303, 1314,
+                    1303, 1304, 1314, 1314, 1304, 1315,
+                    1304, 1305, 1315, 1315, 1305, 1316,
+                    1305, 1306, 1316, 1316, 1306, 1317,
+                    1306, 1307, 1317, 1317, 1307, 1318,
+                    1307, 1308, 1318, 1318, 1308, 1319,
+                    1309, 1310, 1320, 1320, 1310, 1321,
+                    1310, 1311, 1321, 1321, 1311, 1322,
+                    1311, 1312, 1322, 1322, 1312, 1323,
+                    1312, 1313, 1323, 1323, 1313, 1324,
+                    1313, 1314, 1324, 1324, 1314, 1325,
+                    1314, 1315, 1325, 1325, 1315, 1326,
+                    1315, 1316, 1326, 1326, 1316, 1327,
+                    1316, 1317, 1327, 1327, 1317, 1328,
+                    1317, 1318, 1328, 1328, 1318, 1329,
+                    1318, 1319, 1329, 1329, 1319, 1330,
+                    1331, 1332, 1342, 1342, 1332, 1343,
+                    1332, 1333, 1343, 1343, 1333, 1344,
+                    1333, 1334, 1344, 1344, 1334, 1345,
+                    1334, 1335, 1345, 1345, 1335, 1346,
+                    1335, 1336, 1346, 1346, 1336, 1347,
+                    1336, 1337, 1347, 1347, 1337, 1348,
+                    1337, 1338, 1348, 1348, 1338, 1349,
+                    1338, 1339, 1349, 1349, 1339, 1350,
+                    1339, 1340, 1350, 1350, 1340, 1351,
+                    1340, 1341, 1351, 1351, 1341, 1352,
+                    1342, 1343, 1353, 1353, 1343, 1354,
+                    1343, 1344, 1354, 1354, 1344, 1355,
+                    1344, 1345, 1355, 1355, 1345, 1356,
+                    1345, 1346, 1356, 1356, 1346, 1357,
+                    1346, 1347, 1357, 1357, 1347, 1358,
+                    1347, 1348, 1358, 1358, 1348, 1359,
+                    1348, 1349, 1359, 1359, 1349, 1360,
+                    1349, 1350, 1360, 1360, 1350, 1361,
+                    1350, 1351, 1361, 1361, 1351, 1362,
+                    1351, 1352, 1362, 1362, 1352, 1363,
+                    1353, 1354, 1364, 1364, 1354, 1365,
+                    1354, 1355, 1365, 1365, 1355, 1366,
+                    1355, 1356, 1366, 1366, 1356, 1367,
+                    1356, 1357, 1367, 1367, 1357, 1368,
+                    1357, 1358, 1368, 1368, 1358, 1369,
+                    1358, 1359, 1369, 1369, 1359, 1370,
+                    1359, 1360, 1370, 1370, 1360, 1371,
+                    1360, 1361, 1371, 1371, 1361, 1372,
+                    1361, 1362, 1372, 1372, 1362, 1373,
+                    1362, 1363, 1373, 1373, 1363, 1374,
+                    1364, 1365, 1375, 1375, 1365, 1376,
+                    1365, 1366, 1376, 1376, 1366, 1377,
+                    1366, 1367, 1377, 1377, 1367, 1378,
+                    1367, 1368, 1378, 1378, 1368, 1379,
+                    1368, 1369, 1379, 1379, 1369, 1380,
+                    1369, 1370, 1380, 1380, 1370, 1381,
+                    1370, 1371, 1381, 1381, 1371, 1382,
+                    1371, 1372, 1382, 1382, 1372, 1383,
+                    1372, 1373, 1383, 1383, 1373, 1384,
+                    1373, 1374, 1384, 1384, 1374, 1385,
+                    1375, 1376, 1386, 1386, 1376, 1387,
+                    1376, 1377, 1387, 1387, 1377, 1388,
+                    1377, 1378, 1388, 1388, 1378, 1389,
+                    1378, 1379, 1389, 1389, 1379, 1390,
+                    1379, 1380, 1390, 1390, 1380, 1391,
+                    1380, 1381, 1391, 1391, 1381, 1392,
+                    1381, 1382, 1392, 1392, 1382, 1393,
+                    1382, 1383, 1393, 1393, 1383, 1394,
+                    1383, 1384, 1394, 1394, 1384, 1395,
+                    1384, 1385, 1395, 1395, 1385, 1396,
+                    1386, 1387, 1397, 1397, 1387, 1398,
+                    1387, 1388, 1398, 1398, 1388, 1399,
+                    1388, 1389, 1399, 1399, 1389, 1400,
+                    1389, 1390, 1400, 1400, 1390, 1401,
+                    1390, 1391, 1401, 1401, 1391, 1402,
+                    1391, 1392, 1402, 1402, 1392, 1403,
+                    1392, 1393, 1403, 1403, 1393, 1404,
+                    1393, 1394, 1404, 1404, 1394, 1405,
+                    1394, 1395, 1405, 1405, 1395, 1406,
+                    1395, 1396, 1406, 1406, 1396, 1407,
+                    1397, 1398, 1408, 1408, 1398, 1409,
+                    1398, 1399, 1409, 1409, 1399, 1410,
+                    1399, 1400, 1410, 1410, 1400, 1411,
+                    1400, 1401, 1411, 1411, 1401, 1412,
+                    1401, 1402, 1412, 1412, 1402, 1413,
+                    1402, 1403, 1413, 1413, 1403, 1414,
+                    1403, 1404, 1414, 1414, 1404, 1415,
+                    1404, 1405, 1415, 1415, 1405, 1416,
+                    1405, 1406, 1416, 1416, 1406, 1417,
+                    1406, 1407, 1417, 1417, 1407, 1418,
+                    1408, 1409, 1419, 1419, 1409, 1420,
+                    1409, 1410, 1420, 1420, 1410, 1421,
+                    1410, 1411, 1421, 1421, 1411, 1422,
+                    1411, 1412, 1422, 1422, 1412, 1423,
+                    1412, 1413, 1423, 1423, 1413, 1424,
+                    1413, 1414, 1424, 1424, 1414, 1425,
+                    1414, 1415, 1425, 1425, 1415, 1426,
+                    1415, 1416, 1426, 1426, 1416, 1427,
+                    1416, 1417, 1427, 1427, 1417, 1428,
+                    1417, 1418, 1428, 1428, 1418, 1429,
+                    1419, 1420, 1430, 1430, 1420, 1431,
+                    1420, 1421, 1431, 1431, 1421, 1432,
+                    1421, 1422, 1432, 1432, 1422, 1433,
+                    1422, 1423, 1433, 1433, 1423, 1434,
+                    1423, 1424, 1434, 1434, 1424, 1435,
+                    1424, 1425, 1435, 1435, 1425, 1436,
+                    1425, 1426, 1436, 1436, 1426, 1437,
+                    1426, 1427, 1437, 1437, 1427, 1438,
+                    1427, 1428, 1438, 1438, 1428, 1439,
+                    1428, 1429, 1439, 1439, 1429, 1440,
+                    1430, 1431, 1441, 1441, 1431, 1442,
+                    1431, 1432, 1442, 1442, 1432, 1443,
+                    1432, 1433, 1443, 1443, 1433, 1444,
+                    1433, 1434, 1444, 1444, 1434, 1445,
+                    1434, 1435, 1445, 1445, 1435, 1446,
+                    1435, 1436, 1446, 1446, 1436, 1447,
+                    1436, 1437, 1447, 1447, 1437, 1448,
+                    1437, 1438, 1448, 1448, 1438, 1449,
+                    1438, 1439, 1449, 1449, 1439, 1450,
+                    1439, 1440, 1450, 1450, 1440, 1451,
+                    1452, 1453, 1463, 1463, 1453, 1464,
+                    1453, 1454, 1464, 1464, 1454, 1465,
+                    1454, 1455, 1465, 1465, 1455, 1466,
+                    1455, 1456, 1466, 1466, 1456, 1467,
+                    1456, 1457, 1467, 1467, 1457, 1468,
+                    1457, 1458, 1468, 1468, 1458, 1469,
+                    1458, 1459, 1469, 1469, 1459, 1470,
+                    1459, 1460, 1470, 1470, 1460, 1471,
+                    1460, 1461, 1471, 1471, 1461, 1472,
+                    1461, 1462, 1472, 1472, 1462, 1473,
+                    1463, 1464, 1474, 1474, 1464, 1475,
+                    1464, 1465, 1475, 1475, 1465, 1476,
+                    1465, 1466, 1476, 1476, 1466, 1477,
+                    1466, 1467, 1477, 1477, 1467, 1478,
+                    1467, 1468, 1478, 1478, 1468, 1479,
+                    1468, 1469, 1479, 1479, 1469, 1480,
+                    1469, 1470, 1480, 1480, 1470, 1481,
+                    1470, 1471, 1481, 1481, 1471, 1482,
+                    1471, 1472, 1482, 1482, 1472, 1483,
+                    1472, 1473, 1483, 1483, 1473, 1484,
+                    1474, 1475, 1485, 1485, 1475, 1486,
+                    1475, 1476, 1486, 1486, 1476, 1487,
+                    1476, 1477, 1487, 1487, 1477, 1488,
+                    1477, 1478, 1488, 1488, 1478, 1489,
+                    1478, 1479, 1489, 1489, 1479, 1490,
+                    1479, 1480, 1490, 1490, 1480, 1491,
+                    1480, 1481, 1491, 1491, 1481, 1492,
+                    1481, 1482, 1492, 1492, 1482, 1493,
+                    1482, 1483, 1493, 1493, 1483, 1494,
+                    1483, 1484, 1494, 1494, 1484, 1495,
+                    1485, 1486, 1496, 1496, 1486, 1497,
+                    1486, 1487, 1497, 1497, 1487, 1498,
+                    1487, 1488, 1498, 1498, 1488, 1499,
+                    1488, 1489, 1499, 1499, 1489, 1500,
+                    1489, 1490, 1500, 1500, 1490, 1501,
+                    1490, 1491, 1501, 1501, 1491, 1502,
+                    1491, 1492, 1502, 1502, 1492, 1503,
+                    1492, 1493, 1503, 1503, 1493, 1504,
+                    1493, 1494, 1504, 1504, 1494, 1505,
+                    1494, 1495, 1505, 1505, 1495, 1506,
+                    1496, 1497, 1507, 1507, 1497, 1508,
+                    1497, 1498, 1508, 1508, 1498, 1509,
+                    1498, 1499, 1509, 1509, 1499, 1510,
+                    1499, 1500, 1510, 1510, 1500, 1511,
+                    1500, 1501, 1511, 1511, 1501, 1512,
+                    1501, 1502, 1512, 1512, 1502, 1513,
+                    1502, 1503, 1513, 1513, 1503, 1514,
+                    1503, 1504, 1514, 1514, 1504, 1515,
+                    1504, 1505, 1515, 1515, 1505, 1516,
+                    1505, 1506, 1516, 1516, 1506, 1517,
+                    1507, 1508, 1518, 1518, 1508, 1519,
+                    1508, 1509, 1519, 1519, 1509, 1520,
+                    1509, 1510, 1520, 1520, 1510, 1521,
+                    1510, 1511, 1521, 1521, 1511, 1522,
+                    1511, 1512, 1522, 1522, 1512, 1523,
+                    1512, 1513, 1523, 1523, 1513, 1524,
+                    1513, 1514, 1524, 1524, 1514, 1525,
+                    1514, 1515, 1525, 1525, 1515, 1526,
+                    1515, 1516, 1526, 1526, 1516, 1527,
+                    1516, 1517, 1527, 1527, 1517, 1528,
+                    1518, 1519, 1529, 1529, 1519, 1530,
+                    1519, 1520, 1530, 1530, 1520, 1531,
+                    1520, 1521, 1531, 1531, 1521, 1532,
+                    1521, 1522, 1532, 1532, 1522, 1533,
+                    1522, 1523, 1533, 1533, 1523, 1534,
+                    1523, 1524, 1534, 1534, 1524, 1535,
+                    1524, 1525, 1535, 1535, 1525, 1536,
+                    1525, 1526, 1536, 1536, 1526, 1537,
+                    1526, 1527, 1537, 1537, 1527, 1538,
+                    1527, 1528, 1538, 1538, 1528, 1539,
+                    1529, 1530, 1540, 1540, 1530, 1541,
+                    1530, 1531, 1541, 1541, 1531, 1542,
+                    1531, 1532, 1542, 1542, 1532, 1543,
+                    1532, 1533, 1543, 1543, 1533, 1544,
+                    1533, 1534, 1544, 1544, 1534, 1545,
+                    1534, 1535, 1545, 1545, 1535, 1546,
+                    1535, 1536, 1546, 1546, 1536, 1547,
+                    1536, 1537, 1547, 1547, 1537, 1548,
+                    1537, 1538, 1548, 1548, 1538, 1549,
+                    1538, 1539, 1549, 1549, 1539, 1550,
+                    1540, 1541, 1551, 1551, 1541, 1552,
+                    1541, 1542, 1552, 1552, 1542, 1553,
+                    1542, 1543, 1553, 1553, 1543, 1554,
+                    1543, 1544, 1554, 1554, 1544, 1555,
+                    1544, 1545, 1555, 1555, 1545, 1556,
+                    1545, 1546, 1556, 1556, 1546, 1557,
+                    1546, 1547, 1557, 1557, 1547, 1558,
+                    1547, 1548, 1558, 1558, 1548, 1559,
+                    1548, 1549, 1559, 1559, 1549, 1560,
+                    1549, 1550, 1560, 1560, 1550, 1561,
+                    1551, 1552, 1562, 1562, 1552, 1563,
+                    1552, 1553, 1563, 1563, 1553, 1564,
+                    1553, 1554, 1564, 1564, 1554, 1565,
+                    1554, 1555, 1565, 1565, 1555, 1566,
+                    1555, 1556, 1566, 1566, 1556, 1567,
+                    1556, 1557, 1567, 1567, 1557, 1568,
+                    1557, 1558, 1568, 1568, 1558, 1569,
+                    1558, 1559, 1569, 1569, 1559, 1570,
+                    1559, 1560, 1570, 1570, 1560, 1571,
+                    1560, 1561, 1571, 1571, 1561, 1572,
+                    1573, 1574, 1584, 1584, 1574, 1585,
+                    1574, 1575, 1585, 1585, 1575, 1586,
+                    1575, 1576, 1586, 1586, 1576, 1587,
+                    1576, 1577, 1587, 1587, 1577, 1588,
+                    1577, 1578, 1588, 1588, 1578, 1589,
+                    1578, 1579, 1589, 1589, 1579, 1590,
+                    1579, 1580, 1590, 1590, 1580, 1591,
+                    1580, 1581, 1591, 1591, 1581, 1592,
+                    1581, 1582, 1592, 1592, 1582, 1593,
+                    1582, 1583, 1593, 1593, 1583, 1594,
+                    1584, 1585, 1595, 1595, 1585, 1596,
+                    1585, 1586, 1596, 1596, 1586, 1597,
+                    1586, 1587, 1597, 1597, 1587, 1598,
+                    1587, 1588, 1598, 1598, 1588, 1599,
+                    1588, 1589, 1599, 1599, 1589, 1600,
+                    1589, 1590, 1600, 1600, 1590, 1601,
+                    1590, 1591, 1601, 1601, 1591, 1602,
+                    1591, 1592, 1602, 1602, 1592, 1603,
+                    1592, 1593, 1603, 1603, 1593, 1604,
+                    1593, 1594, 1604, 1604, 1594, 1605,
+                    1595, 1596, 1606, 1606, 1596, 1607,
+                    1596, 1597, 1607, 1607, 1597, 1608,
+                    1597, 1598, 1608, 1608, 1598, 1609,
+                    1598, 1599, 1609, 1609, 1599, 1610,
+                    1599, 1600, 1610, 1610, 1600, 1611,
+                    1600, 1601, 1611, 1611, 1601, 1612,
+                    1601, 1602, 1612, 1612, 1602, 1613,
+                    1602, 1603, 1613, 1613, 1603, 1614,
+                    1603, 1604, 1614, 1614, 1604, 1615,
+                    1604, 1605, 1615, 1615, 1605, 1616,
+                    1606, 1607, 1617, 1617, 1607, 1618,
+                    1607, 1608, 1618, 1618, 1608, 1619,
+                    1608, 1609, 1619, 1619, 1609, 1620,
+                    1609, 1610, 1620, 1620, 1610, 1621,
+                    1610, 1611, 1621, 1621, 1611, 1622,
+                    1611, 1612, 1622, 1622, 1612, 1623,
+                    1612, 1613, 1623, 1623, 1613, 1624,
+                    1613, 1614, 1624, 1624, 1614, 1625,
+                    1614, 1615, 1625, 1625, 1615, 1626,
+                    1615, 1616, 1626, 1626, 1616, 1627,
+                    1617, 1618, 1628, 1628, 1618, 1629,
+                    1618, 1619, 1629, 1629, 1619, 1630,
+                    1619, 1620, 1630, 1630, 1620, 1631,
+                    1620, 1621, 1631, 1631, 1621, 1632,
+                    1621, 1622, 1632, 1632, 1622, 1633,
+                    1622, 1623, 1633, 1633, 1623, 1634,
+                    1623, 1624, 1634, 1634, 1624, 1635,
+                    1624, 1625, 1635, 1635, 1625, 1636,
+                    1625, 1626, 1636, 1636, 1626, 1637,
+                    1626, 1627, 1637, 1637, 1627, 1638,
+                    1628, 1629, 1639, 1639, 1629, 1640,
+                    1629, 1630, 1640, 1640, 1630, 1641,
+                    1630, 1631, 1641, 1641, 1631, 1642,
+                    1631, 1632, 1642, 1642, 1632, 1643,
+                    1632, 1633, 1643, 1643, 1633, 1644,
+                    1633, 1634, 1644, 1644, 1634, 1645,
+                    1634, 1635, 1645, 1645, 1635, 1646,
+                    1635, 1636, 1646, 1646, 1636, 1647,
+                    1636, 1637, 1647, 1647, 1637, 1648,
+                    1637, 1638, 1648, 1648, 1638, 1649,
+                    1639, 1640, 1650, 1650, 1640, 1651,
+                    1640, 1641, 1651, 1651, 1641, 1652,
+                    1641, 1642, 1652, 1652, 1642, 1653,
+                    1642, 1643, 1653, 1653, 1643, 1654,
+                    1643, 1644, 1654, 1654, 1644, 1655,
+                    1644, 1645, 1655, 1655, 1645, 1656,
+                    1645, 1646, 1656, 1656, 1646, 1657,
+                    1646, 1647, 1657, 1657, 1647, 1658,
+                    1647, 1648, 1658, 1658, 1648, 1659,
+                    1648, 1649, 1659, 1659, 1649, 1660,
+                    1650, 1651, 1661, 1661, 1651, 1662,
+                    1651, 1652, 1662, 1662, 1652, 1663,
+                    1652, 1653, 1663, 1663, 1653, 1664,
+                    1653, 1654, 1664, 1664, 1654, 1665,
+                    1654, 1655, 1665, 1665, 1655, 1666,
+                    1655, 1656, 1666, 1666, 1656, 1667,
+                    1656, 1657, 1667, 1667, 1657, 1668,
+                    1657, 1658, 1668, 1668, 1658, 1669,
+                    1658, 1659, 1669, 1669, 1659, 1670,
+                    1659, 1660, 1670, 1670, 1660, 1671,
+                    1661, 1662, 1672, 1672, 1662, 1673,
+                    1662, 1663, 1673, 1673, 1663, 1674,
+                    1663, 1664, 1674, 1674, 1664, 1675,
+                    1664, 1665, 1675, 1675, 1665, 1676,
+                    1665, 1666, 1676, 1676, 1666, 1677,
+                    1666, 1667, 1677, 1677, 1667, 1678,
+                    1667, 1668, 1678, 1678, 1668, 1679,
+                    1668, 1669, 1679, 1679, 1669, 1680,
+                    1669, 1670, 1680, 1680, 1670, 1681,
+                    1670, 1671, 1681, 1681, 1671, 1682,
+                    1672, 1673, 1683, 1683, 1673, 1684,
+                    1673, 1674, 1684, 1684, 1674, 1685,
+                    1674, 1675, 1685, 1685, 1675, 1686,
+                    1675, 1676, 1686, 1686, 1676, 1687,
+                    1676, 1677, 1687, 1687, 1677, 1688,
+                    1677, 1678, 1688, 1688, 1678, 1689,
+                    1678, 1679, 1689, 1689, 1679, 1690,
+                    1679, 1680, 1690, 1690, 1680, 1691,
+                    1680, 1681, 1691, 1691, 1681, 1692,
+                    1681, 1682, 1692, 1692, 1682, 1693,
+                    1694, 1695, 1705, 1705, 1695, 1706,
+                    1695, 1696, 1706, 1706, 1696, 1707,
+                    1696, 1697, 1707, 1707, 1697, 1708,
+                    1697, 1698, 1708, 1708, 1698, 1709,
+                    1698, 1699, 1709, 1709, 1699, 1710,
+                    1699, 1700, 1710, 1710, 1700, 1711,
+                    1700, 1701, 1711, 1711, 1701, 1712,
+                    1701, 1702, 1712, 1712, 1702, 1713,
+                    1702, 1703, 1713, 1713, 1703, 1714,
+                    1703, 1704, 1714, 1714, 1704, 1715,
+                    1705, 1706, 1716, 1716, 1706, 1717,
+                    1706, 1707, 1717, 1717, 1707, 1718,
+                    1707, 1708, 1718, 1718, 1708, 1719,
+                    1708, 1709, 1719, 1719, 1709, 1720,
+                    1709, 1710, 1720, 1720, 1710, 1721,
+                    1710, 1711, 1721, 1721, 1711, 1722,
+                    1711, 1712, 1722, 1722, 1712, 1723,
+                    1712, 1713, 1723, 1723, 1713, 1724,
+                    1713, 1714, 1724, 1724, 1714, 1725,
+                    1714, 1715, 1725, 1725, 1715, 1726,
+                    1716, 1717, 1727, 1727, 1717, 1728,
+                    1717, 1718, 1728, 1728, 1718, 1729,
+                    1718, 1719, 1729, 1729, 1719, 1730,
+                    1719, 1720, 1730, 1730, 1720, 1731,
+                    1720, 1721, 1731, 1731, 1721, 1732,
+                    1721, 1722, 1732, 1732, 1722, 1733,
+                    1722, 1723, 1733, 1733, 1723, 1734,
+                    1723, 1724, 1734, 1734, 1724, 1735,
+                    1724, 1725, 1735, 1735, 1725, 1736,
+                    1725, 1726, 1736, 1736, 1726, 1737,
+                    1727, 1728, 1738, 1738, 1728, 1739,
+                    1728, 1729, 1739, 1739, 1729, 1740,
+                    1729, 1730, 1740, 1740, 1730, 1741,
+                    1730, 1731, 1741, 1741, 1731, 1742,
+                    1731, 1732, 1742, 1742, 1732, 1743,
+                    1732, 1733, 1743, 1743, 1733, 1744,
+                    1733, 1734, 1744, 1744, 1734, 1745,
+                    1734, 1735, 1745, 1745, 1735, 1746,
+                    1735, 1736, 1746, 1746, 1736, 1747,
+                    1736, 1737, 1747, 1747, 1737, 1748,
+                    1738, 1739, 1749, 1749, 1739, 1750,
+                    1739, 1740, 1750, 1750, 1740, 1751,
+                    1740, 1741, 1751, 1751, 1741, 1752,
+                    1741, 1742, 1752, 1752, 1742, 1753,
+                    1742, 1743, 1753, 1753, 1743, 1754,
+                    1743, 1744, 1754, 1754, 1744, 1755,
+                    1744, 1745, 1755, 1755, 1745, 1756,
+                    1745, 1746, 1756, 1756, 1746, 1757,
+                    1746, 1747, 1757, 1757, 1747, 1758,
+                    1747, 1748, 1758, 1758, 1748, 1759,
+                    1749, 1750, 1760, 1760, 1750, 1761,
+                    1750, 1751, 1761, 1761, 1751, 1762,
+                    1751, 1752, 1762, 1762, 1752, 1763,
+                    1752, 1753, 1763, 1763, 1753, 1764,
+                    1753, 1754, 1764, 1764, 1754, 1765,
+                    1754, 1755, 1765, 1765, 1755, 1766,
+                    1755, 1756, 1766, 1766, 1756, 1767,
+                    1756, 1757, 1767, 1767, 1757, 1768,
+                    1757, 1758, 1768, 1768, 1758, 1769,
+                    1758, 1759, 1769, 1769, 1759, 1770,
+                    1760, 1761, 1771, 1771, 1761, 1772,
+                    1761, 1762, 1772, 1772, 1762, 1773,
+                    1762, 1763, 1773, 1773, 1763, 1774,
+                    1763, 1764, 1774, 1774, 1764, 1775,
+                    1764, 1765, 1775, 1775, 1765, 1776,
+                    1765, 1766, 1776, 1776, 1766, 1777,
+                    1766, 1767, 1777, 1777, 1767, 1778,
+                    1767, 1768, 1778, 1778, 1768, 1779,
+                    1768, 1769, 1779, 1779, 1769, 1780,
+                    1769, 1770, 1780, 1780, 1770, 1781,
+                    1771, 1772, 1782, 1782, 1772, 1783,
+                    1772, 1773, 1783, 1783, 1773, 1784,
+                    1773, 1774, 1784, 1784, 1774, 1785,
+                    1774, 1775, 1785, 1785, 1775, 1786,
+                    1775, 1776, 1786, 1786, 1776, 1787,
+                    1776, 1777, 1787, 1787, 1777, 1788,
+                    1777, 1778, 1788, 1788, 1778, 1789,
+                    1778, 1779, 1789, 1789, 1779, 1790,
+                    1779, 1780, 1790, 1790, 1780, 1791,
+                    1780, 1781, 1791, 1791, 1781, 1792,
+                    1782, 1783, 1793, 1793, 1783, 1794,
+                    1783, 1784, 1794, 1794, 1784, 1795,
+                    1784, 1785, 1795, 1795, 1785, 1796,
+                    1785, 1786, 1796, 1796, 1786, 1797,
+                    1786, 1787, 1797, 1797, 1787, 1798,
+                    1787, 1788, 1798, 1798, 1788, 1799,
+                    1788, 1789, 1799, 1799, 1789, 1800,
+                    1789, 1790, 1800, 1800, 1790, 1801,
+                    1790, 1791, 1801, 1801, 1791, 1802,
+                    1791, 1792, 1802, 1802, 1792, 1803,
+                    1793, 1794, 1804, 1804, 1794, 1805,
+                    1794, 1795, 1805, 1805, 1795, 1806,
+                    1795, 1796, 1806, 1806, 1796, 1807,
+                    1796, 1797, 1807, 1807, 1797, 1808,
+                    1797, 1798, 1808, 1808, 1798, 1809,
+                    1798, 1799, 1809, 1809, 1799, 1810,
+                    1799, 1800, 1810, 1810, 1800, 1811,
+                    1800, 1801, 1811, 1811, 1801, 1812,
+                    1801, 1802, 1812, 1812, 1802, 1813,
+                    1802, 1803, 1813, 1813, 1803, 1814,
+                    1815, 1816, 1826, 1826, 1816, 1827,
+                    1816, 1817, 1827, 1827, 1817, 1828,
+                    1817, 1818, 1828, 1828, 1818, 1829,
+                    1818, 1819, 1829, 1829, 1819, 1830,
+                    1819, 1820, 1830, 1830, 1820, 1831,
+                    1820, 1821, 1831, 1831, 1821, 1832,
+                    1821, 1822, 1832, 1832, 1822, 1833,
+                    1822, 1823, 1833, 1833, 1823, 1834,
+                    1823, 1824, 1834, 1834, 1824, 1835,
+                    1824, 1825, 1835, 1835, 1825, 1836,
+                    1826, 1827, 1837, 1837, 1827, 1838,
+                    1827, 1828, 1838, 1838, 1828, 1839,
+                    1828, 1829, 1839, 1839, 1829, 1840,
+                    1829, 1830, 1840, 1840, 1830, 1841,
+                    1830, 1831, 1841, 1841, 1831, 1842,
+                    1831, 1832, 1842, 1842, 1832, 1843,
+                    1832, 1833, 1843, 1843, 1833, 1844,
+                    1833, 1834, 1844, 1844, 1834, 1845,
+                    1834, 1835, 1845, 1845, 1835, 1846,
+                    1835, 1836, 1846, 1846, 1836, 1847,
+                    1837, 1838, 1848, 1848, 1838, 1849,
+                    1838, 1839, 1849, 1849, 1839, 1850,
+                    1839, 1840, 1850, 1850, 1840, 1851,
+                    1840, 1841, 1851, 1851, 1841, 1852,
+                    1841, 1842, 1852, 1852, 1842, 1853,
+                    1842, 1843, 1853, 1853, 1843, 1854,
+                    1843, 1844, 1854, 1854, 1844, 1855,
+                    1844, 1845, 1855, 1855, 1845, 1856,
+                    1845, 1846, 1856, 1856, 1846, 1857,
+                    1846, 1847, 1857, 1857, 1847, 1858,
+                    1848, 1849, 1859, 1859, 1849, 1860,
+                    1849, 1850, 1860, 1860, 1850, 1861,
+                    1850, 1851, 1861, 1861, 1851, 1862,
+                    1851, 1852, 1862, 1862, 1852, 1863,
+                    1852, 1853, 1863, 1863, 1853, 1864,
+                    1853, 1854, 1864, 1864, 1854, 1865,
+                    1854, 1855, 1865, 1865, 1855, 1866,
+                    1855, 1856, 1866, 1866, 1856, 1867,
+                    1856, 1857, 1867, 1867, 1857, 1868,
+                    1857, 1858, 1868, 1868, 1858, 1869,
+                    1859, 1860, 1870, 1870, 1860, 1871,
+                    1860, 1861, 1871, 1871, 1861, 1872,
+                    1861, 1862, 1872, 1872, 1862, 1873,
+                    1862, 1863, 1873, 1873, 1863, 1874,
+                    1863, 1864, 1874, 1874, 1864, 1875,
+                    1864, 1865, 1875, 1875, 1865, 1876,
+                    1865, 1866, 1876, 1876, 1866, 1877,
+                    1866, 1867, 1877, 1877, 1867, 1878,
+                    1867, 1868, 1878, 1878, 1868, 1879,
+                    1868, 1869, 1879, 1879, 1869, 1880,
+                    1870, 1871, 1881, 1881, 1871, 1882,
+                    1871, 1872, 1882, 1882, 1872, 1883,
+                    1872, 1873, 1883, 1883, 1873, 1884,
+                    1873, 1874, 1884, 1884, 1874, 1885,
+                    1874, 1875, 1885, 1885, 1875, 1886,
+                    1875, 1876, 1886, 1886, 1876, 1887,
+                    1876, 1877, 1887, 1887, 1877, 1888,
+                    1877, 1878, 1888, 1888, 1878, 1889,
+                    1878, 1879, 1889, 1889, 1879, 1890,
+                    1879, 1880, 1890, 1890, 1880, 1891,
+                    1881, 1882, 1892, 1892, 1882, 1893,
+                    1882, 1883, 1893, 1893, 1883, 1894,
+                    1883, 1884, 1894, 1894, 1884, 1895,
+                    1884, 1885, 1895, 1895, 1885, 1896,
+                    1885, 1886, 1896, 1896, 1886, 1897,
+                    1886, 1887, 1897, 1897, 1887, 1898,
+                    1887, 1888, 1898, 1898, 1888, 1899,
+                    1888, 1889, 1899, 1899, 1889, 1900,
+                    1889, 1890, 1900, 1900, 1890, 1901,
+                    1890, 1891, 1901, 1901, 1891, 1902,
+                    1892, 1893, 1903, 1903, 1893, 1904,
+                    1893, 1894, 1904, 1904, 1894, 1905,
+                    1894, 1895, 1905, 1905, 1895, 1906,
+                    1895, 1896, 1906, 1906, 1896, 1907,
+                    1896, 1897, 1907, 1907, 1897, 1908,
+                    1897, 1898, 1908, 1908, 1898, 1909,
+                    1898, 1899, 1909, 1909, 1899, 1910,
+                    1899, 1900, 1910, 1910, 1900, 1911,
+                    1900, 1901, 1911, 1911, 1901, 1912,
+                    1901, 1902, 1912, 1912, 1902, 1913,
+                    1903, 1904, 1914, 1914, 1904, 1915,
+                    1904, 1905, 1915, 1915, 1905, 1916,
+                    1905, 1906, 1916, 1916, 1906, 1917,
+                    1906, 1907, 1917, 1917, 1907, 1918,
+                    1907, 1908, 1918, 1918, 1908, 1919,
+                    1908, 1909, 1919, 1919, 1909, 1920,
+                    1909, 1910, 1920, 1920, 1910, 1921,
+                    1910, 1911, 1921, 1921, 1911, 1922,
+                    1911, 1912, 1922, 1922, 1912, 1923,
+                    1912, 1913, 1923, 1923, 1913, 1924,
+                    1914, 1915, 1925, 1925, 1915, 1926,
+                    1915, 1916, 1926, 1926, 1916, 1927,
+                    1916, 1917, 1927, 1927, 1917, 1928,
+                    1917, 1918, 1928, 1928, 1918, 1929,
+                    1918, 1919, 1929, 1929, 1919, 1930,
+                    1919, 1920, 1930, 1930, 1920, 1931,
+                    1920, 1921, 1931, 1931, 1921, 1932,
+                    1921, 1922, 1932, 1932, 1922, 1933,
+                    1922, 1923, 1933, 1933, 1923, 1934,
+                    1923, 1924, 1934, 1934, 1924, 1935,
+                    1936, 1937, 1947, 1947, 1937, 1948,
+                    1937, 1938, 1948, 1948, 1938, 1949,
+                    1938, 1939, 1949, 1949, 1939, 1950,
+                    1939, 1940, 1950, 1950, 1940, 1951,
+                    1940, 1941, 1951, 1951, 1941, 1952,
+                    1941, 1942, 1952, 1952, 1942, 1953,
+                    1942, 1943, 1953, 1953, 1943, 1954,
+                    1943, 1944, 1954, 1954, 1944, 1955,
+                    1944, 1945, 1955, 1955, 1945, 1956,
+                    1945, 1946, 1956, 1956, 1946, 1957,
+                    1947, 1948, 1958, 1958, 1948, 1959,
+                    1948, 1949, 1959, 1959, 1949, 1960,
+                    1949, 1950, 1960, 1960, 1950, 1961,
+                    1950, 1951, 1961, 1961, 1951, 1962,
+                    1951, 1952, 1962, 1962, 1952, 1963,
+                    1952, 1953, 1963, 1963, 1953, 1964,
+                    1953, 1954, 1964, 1964, 1954, 1965,
+                    1954, 1955, 1965, 1965, 1955, 1966,
+                    1955, 1956, 1966, 1966, 1956, 1967,
+                    1956, 1957, 1967, 1967, 1957, 1968,
+                    1958, 1959, 1969, 1969, 1959, 1970,
+                    1959, 1960, 1970, 1970, 1960, 1971,
+                    1960, 1961, 1971, 1971, 1961, 1972,
+                    1961, 1962, 1972, 1972, 1962, 1973,
+                    1962, 1963, 1973, 1973, 1963, 1974,
+                    1963, 1964, 1974, 1974, 1964, 1975,
+                    1964, 1965, 1975, 1975, 1965, 1976,
+                    1965, 1966, 1976, 1976, 1966, 1977,
+                    1966, 1967, 1977, 1977, 1967, 1978,
+                    1967, 1968, 1978, 1978, 1968, 1979,
+                    1969, 1970, 1980, 1980, 1970, 1981,
+                    1970, 1971, 1981, 1981, 1971, 1982,
+                    1971, 1972, 1982, 1982, 1972, 1983,
+                    1972, 1973, 1983, 1983, 1973, 1984,
+                    1973, 1974, 1984, 1984, 1974, 1985,
+                    1974, 1975, 1985, 1985, 1975, 1986,
+                    1975, 1976, 1986, 1986, 1976, 1987,
+                    1976, 1977, 1987, 1987, 1977, 1988,
+                    1977, 1978, 1988, 1988, 1978, 1989,
+                    1978, 1979, 1989, 1989, 1979, 1990,
+                    1980, 1981, 1991, 1991, 1981, 1992,
+                    1981, 1982, 1992, 1992, 1982, 1993,
+                    1982, 1983, 1993, 1993, 1983, 1994,
+                    1983, 1984, 1994, 1994, 1984, 1995,
+                    1984, 1985, 1995, 1995, 1985, 1996,
+                    1985, 1986, 1996, 1996, 1986, 1997,
+                    1986, 1987, 1997, 1997, 1987, 1998,
+                    1987, 1988, 1998, 1998, 1988, 1999,
+                    1988, 1989, 1999, 1999, 1989, 2000,
+                    1989, 1990, 2000, 2000, 1990, 2001,
+                    1991, 1992, 2002, 2002, 1992, 2003,
+                    1992, 1993, 2003, 2003, 1993, 2004,
+                    1993, 1994, 2004, 2004, 1994, 2005,
+                    1994, 1995, 2005, 2005, 1995, 2006,
+                    1995, 1996, 2006, 2006, 1996, 2007,
+                    1996, 1997, 2007, 2007, 1997, 2008,
+                    1997, 1998, 2008, 2008, 1998, 2009,
+                    1998, 1999, 2009, 2009, 1999, 2010,
+                    1999, 2000, 2010, 2010, 2000, 2011,
+                    2000, 2001, 2011, 2011, 2001, 2012,
+                    2002, 2003, 2013, 2013, 2003, 2014,
+                    2003, 2004, 2014, 2014, 2004, 2015,
+                    2004, 2005, 2015, 2015, 2005, 2016,
+                    2005, 2006, 2016, 2016, 2006, 2017,
+                    2006, 2007, 2017, 2017, 2007, 2018,
+                    2007, 2008, 2018, 2018, 2008, 2019,
+                    2008, 2009, 2019, 2019, 2009, 2020,
+                    2009, 2010, 2020, 2020, 2010, 2021,
+                    2010, 2011, 2021, 2021, 2011, 2022,
+                    2011, 2012, 2022, 2022, 2012, 2023,
+                    2013, 2014, 2024, 2024, 2014, 2025,
+                    2014, 2015, 2025, 2025, 2015, 2026,
+                    2015, 2016, 2026, 2026, 2016, 2027,
+                    2016, 2017, 2027, 2027, 2017, 2028,
+                    2017, 2018, 2028, 2028, 2018, 2029,
+                    2018, 2019, 2029, 2029, 2019, 2030,
+                    2019, 2020, 2030, 2030, 2020, 2031,
+                    2020, 2021, 2031, 2031, 2021, 2032,
+                    2021, 2022, 2032, 2032, 2022, 2033,
+                    2022, 2023, 2033, 2033, 2023, 2034,
+                    2024, 2025, 2035, 2035, 2025, 2036,
+                    2025, 2026, 2036, 2036, 2026, 2037,
+                    2026, 2027, 2037, 2037, 2027, 2038,
+                    2027, 2028, 2038, 2038, 2028, 2039,
+                    2028, 2029, 2039, 2039, 2029, 2040,
+                    2029, 2030, 2040, 2040, 2030, 2041,
+                    2030, 2031, 2041, 2041, 2031, 2042,
+                    2031, 2032, 2042, 2042, 2032, 2043,
+                    2032, 2033, 2043, 2043, 2033, 2044,
+                    2033, 2034, 2044, 2044, 2034, 2045,
+                    2035, 2036, 2046, 2046, 2036, 2047,
+                    2036, 2037, 2047, 2047, 2037, 2048,
+                    2037, 2038, 2048, 2048, 2038, 2049,
+                    2038, 2039, 2049, 2049, 2039, 2050,
+                    2039, 2040, 2050, 2050, 2040, 2051,
+                    2040, 2041, 2051, 2051, 2041, 2052,
+                    2041, 2042, 2052, 2052, 2042, 2053,
+                    2042, 2043, 2053, 2053, 2043, 2054,
+                    2043, 2044, 2054, 2054, 2044, 2055,
+                    2044, 2045, 2055, 2055, 2045, 2056,
+                    2057, 2058, 2068, 2068, 2058, 2069,
+                    2058, 2059, 2069, 2069, 2059, 2070,
+                    2059, 2060, 2070, 2070, 2060, 2071,
+                    2060, 2061, 2071, 2071, 2061, 2072,
+                    2061, 2062, 2072, 2072, 2062, 2073,
+                    2062, 2063, 2073, 2073, 2063, 2074,
+                    2063, 2064, 2074, 2074, 2064, 2075,
+                    2064, 2065, 2075, 2075, 2065, 2076,
+                    2065, 2066, 2076, 2076, 2066, 2077,
+                    2066, 2067, 2077, 2077, 2067, 2078,
+                    2068, 2069, 2079, 2079, 2069, 2080,
+                    2069, 2070, 2080, 2080, 2070, 2081,
+                    2070, 2071, 2081, 2081, 2071, 2082,
+                    2071, 2072, 2082, 2082, 2072, 2083,
+                    2072, 2073, 2083, 2083, 2073, 2084,
+                    2073, 2074, 2084, 2084, 2074, 2085,
+                    2074, 2075, 2085, 2085, 2075, 2086,
+                    2075, 2076, 2086, 2086, 2076, 2087,
+                    2076, 2077, 2087, 2087, 2077, 2088,
+                    2077, 2078, 2088, 2088, 2078, 2089,
+                    2079, 2080, 2090, 2090, 2080, 2091,
+                    2080, 2081, 2091, 2091, 2081, 2092,
+                    2081, 2082, 2092, 2092, 2082, 2093,
+                    2082, 2083, 2093, 2093, 2083, 2094,
+                    2083, 2084, 2094, 2094, 2084, 2095,
+                    2084, 2085, 2095, 2095, 2085, 2096,
+                    2085, 2086, 2096, 2096, 2086, 2097,
+                    2086, 2087, 2097, 2097, 2087, 2098,
+                    2087, 2088, 2098, 2098, 2088, 2099,
+                    2088, 2089, 2099, 2099, 2089, 2100,
+                    2090, 2091, 2101, 2101, 2091, 2102,
+                    2091, 2092, 2102, 2102, 2092, 2103,
+                    2092, 2093, 2103, 2103, 2093, 2104,
+                    2093, 2094, 2104, 2104, 2094, 2105,
+                    2094, 2095, 2105, 2105, 2095, 2106,
+                    2095, 2096, 2106, 2106, 2096, 2107,
+                    2096, 2097, 2107, 2107, 2097, 2108,
+                    2097, 2098, 2108, 2108, 2098, 2109,
+                    2098, 2099, 2109, 2109, 2099, 2110,
+                    2099, 2100, 2110, 2110, 2100, 2111,
+                    2101, 2102, 2112, 2112, 2102, 2113,
+                    2102, 2103, 2113, 2113, 2103, 2114,
+                    2103, 2104, 2114, 2114, 2104, 2115,
+                    2104, 2105, 2115, 2115, 2105, 2116,
+                    2105, 2106, 2116, 2116, 2106, 2117,
+                    2106, 2107, 2117, 2117, 2107, 2118,
+                    2107, 2108, 2118, 2118, 2108, 2119,
+                    2108, 2109, 2119, 2119, 2109, 2120,
+                    2109, 2110, 2120, 2120, 2110, 2121,
+                    2110, 2111, 2121, 2121, 2111, 2122,
+                    2112, 2113, 2123, 2123, 2113, 2124,
+                    2113, 2114, 2124, 2124, 2114, 2125,
+                    2114, 2115, 2125, 2125, 2115, 2126,
+                    2115, 2116, 2126, 2126, 2116, 2127,
+                    2116, 2117, 2127, 2127, 2117, 2128,
+                    2117, 2118, 2128, 2128, 2118, 2129,
+                    2118, 2119, 2129, 2129, 2119, 2130,
+                    2119, 2120, 2130, 2130, 2120, 2131,
+                    2120, 2121, 2131, 2131, 2121, 2132,
+                    2121, 2122, 2132, 2132, 2122, 2133,
+                    2123, 2124, 2134, 2134, 2124, 2135,
+                    2124, 2125, 2135, 2135, 2125, 2136,
+                    2125, 2126, 2136, 2136, 2126, 2137,
+                    2126, 2127, 2137, 2137, 2127, 2138,
+                    2127, 2128, 2138, 2138, 2128, 2139,
+                    2128, 2129, 2139, 2139, 2129, 2140,
+                    2129, 2130, 2140, 2140, 2130, 2141,
+                    2130, 2131, 2141, 2141, 2131, 2142,
+                    2131, 2132, 2142, 2142, 2132, 2143,
+                    2132, 2133, 2143, 2143, 2133, 2144,
+                    2134, 2135, 2145, 2145, 2135, 2146,
+                    2135, 2136, 2146, 2146, 2136, 2147,
+                    2136, 2137, 2147, 2147, 2137, 2148,
+                    2137, 2138, 2148, 2148, 2138, 2149,
+                    2138, 2139, 2149, 2149, 2139, 2150,
+                    2139, 2140, 2150, 2150, 2140, 2151,
+                    2140, 2141, 2151, 2151, 2141, 2152,
+                    2141, 2142, 2152, 2152, 2142, 2153,
+                    2142, 2143, 2153, 2153, 2143, 2154,
+                    2143, 2144, 2154, 2154, 2144, 2155,
+                    2145, 2146, 2156, 2156, 2146, 2157,
+                    2146, 2147, 2157, 2157, 2147, 2158,
+                    2147, 2148, 2158, 2158, 2148, 2159,
+                    2148, 2149, 2159, 2159, 2149, 2160,
+                    2149, 2150, 2160, 2160, 2150, 2161,
+                    2150, 2151, 2161, 2161, 2151, 2162,
+                    2151, 2152, 2162, 2162, 2152, 2163,
+                    2152, 2153, 2163, 2163, 2153, 2164,
+                    2153, 2154, 2164, 2164, 2154, 2165,
+                    2154, 2155, 2165, 2165, 2155, 2166,
+                    2156, 2157, 2167, 2167, 2157, 2168,
+                    2157, 2158, 2168, 2168, 2158, 2169,
+                    2158, 2159, 2169, 2169, 2159, 2170,
+                    2159, 2160, 2170, 2170, 2160, 2171,
+                    2160, 2161, 2171, 2171, 2161, 2172,
+                    2161, 2162, 2172, 2172, 2162, 2173,
+                    2162, 2163, 2173, 2173, 2163, 2174,
+                    2163, 2164, 2174, 2174, 2164, 2175,
+                    2164, 2165, 2175, 2175, 2165, 2176,
+                    2165, 2166, 2176, 2176, 2166, 2177,
+                    2178, 2179, 2189, 2189, 2179, 2190,
+                    2179, 2180, 2190, 2190, 2180, 2191,
+                    2180, 2181, 2191, 2191, 2181, 2192,
+                    2181, 2182, 2192, 2192, 2182, 2193,
+                    2182, 2183, 2193, 2193, 2183, 2194,
+                    2183, 2184, 2194, 2194, 2184, 2195,
+                    2184, 2185, 2195, 2195, 2185, 2196,
+                    2185, 2186, 2196, 2196, 2186, 2197,
+                    2186, 2187, 2197, 2197, 2187, 2198,
+                    2187, 2188, 2198, 2198, 2188, 2199,
+                    2189, 2190, 2200, 2200, 2190, 2201,
+                    2190, 2191, 2201, 2201, 2191, 2202,
+                    2191, 2192, 2202, 2202, 2192, 2203,
+                    2192, 2193, 2203, 2203, 2193, 2204,
+                    2193, 2194, 2204, 2204, 2194, 2205,
+                    2194, 2195, 2205, 2205, 2195, 2206,
+                    2195, 2196, 2206, 2206, 2196, 2207,
+                    2196, 2197, 2207, 2207, 2197, 2208,
+                    2197, 2198, 2208, 2208, 2198, 2209,
+                    2198, 2199, 2209, 2209, 2199, 2210,
+                    2200, 2201, 2211, 2211, 2201, 2212,
+                    2201, 2202, 2212, 2212, 2202, 2213,
+                    2202, 2203, 2213, 2213, 2203, 2214,
+                    2203, 2204, 2214, 2214, 2204, 2215,
+                    2204, 2205, 2215, 2215, 2205, 2216,
+                    2205, 2206, 2216, 2216, 2206, 2217,
+                    2206, 2207, 2217, 2217, 2207, 2218,
+                    2207, 2208, 2218, 2218, 2208, 2219,
+                    2208, 2209, 2219, 2219, 2209, 2220,
+                    2209, 2210, 2220, 2220, 2210, 2221,
+                    2211, 2212, 2222, 2222, 2212, 2223,
+                    2212, 2213, 2223, 2223, 2213, 2224,
+                    2213, 2214, 2224, 2224, 2214, 2225,
+                    2214, 2215, 2225, 2225, 2215, 2226,
+                    2215, 2216, 2226, 2226, 2216, 2227,
+                    2216, 2217, 2227, 2227, 2217, 2228,
+                    2217, 2218, 2228, 2228, 2218, 2229,
+                    2218, 2219, 2229, 2229, 2219, 2230,
+                    2219, 2220, 2230, 2230, 2220, 2231,
+                    2220, 2221, 2231, 2231, 2221, 2232,
+                    2222, 2223, 2233, 2233, 2223, 2234,
+                    2223, 2224, 2234, 2234, 2224, 2235,
+                    2224, 2225, 2235, 2235, 2225, 2236,
+                    2225, 2226, 2236, 2236, 2226, 2237,
+                    2226, 2227, 2237, 2237, 2227, 2238,
+                    2227, 2228, 2238, 2238, 2228, 2239,
+                    2228, 2229, 2239, 2239, 2229, 2240,
+                    2229, 2230, 2240, 2240, 2230, 2241,
+                    2230, 2231, 2241, 2241, 2231, 2242,
+                    2231, 2232, 2242, 2242, 2232, 2243,
+                    2233, 2234, 2244, 2244, 2234, 2245,
+                    2234, 2235, 2245, 2245, 2235, 2246,
+                    2235, 2236, 2246, 2246, 2236, 2247,
+                    2236, 2237, 2247, 2247, 2237, 2248,
+                    2237, 2238, 2248, 2248, 2238, 2249,
+                    2238, 2239, 2249, 2249, 2239, 2250,
+                    2239, 2240, 2250, 2250, 2240, 2251,
+                    2240, 2241, 2251, 2251, 2241, 2252,
+                    2241, 2242, 2252, 2252, 2242, 2253,
+                    2242, 2243, 2253, 2253, 2243, 2254,
+                    2244, 2245, 2255, 2255, 2245, 2256,
+                    2245, 2246, 2256, 2256, 2246, 2257,
+                    2246, 2247, 2257, 2257, 2247, 2258,
+                    2247, 2248, 2258, 2258, 2248, 2259,
+                    2248, 2249, 2259, 2259, 2249, 2260,
+                    2249, 2250, 2260, 2260, 2250, 2261,
+                    2250, 2251, 2261, 2261, 2251, 2262,
+                    2251, 2252, 2262, 2262, 2252, 2263,
+                    2252, 2253, 2263, 2263, 2253, 2264,
+                    2253, 2254, 2264, 2264, 2254, 2265,
+                    2255, 2256, 2266, 2266, 2256, 2267,
+                    2256, 2257, 2267, 2267, 2257, 2268,
+                    2257, 2258, 2268, 2268, 2258, 2269,
+                    2258, 2259, 2269, 2269, 2259, 2270,
+                    2259, 2260, 2270, 2270, 2260, 2271,
+                    2260, 2261, 2271, 2271, 2261, 2272,
+                    2261, 2262, 2272, 2272, 2262, 2273,
+                    2262, 2263, 2273, 2273, 2263, 2274,
+                    2263, 2264, 2274, 2274, 2264, 2275,
+                    2264, 2265, 2275, 2275, 2265, 2276,
+                    2266, 2267, 2277, 2277, 2267, 2278,
+                    2267, 2268, 2278, 2278, 2268, 2279,
+                    2268, 2269, 2279, 2279, 2269, 2280,
+                    2269, 2270, 2280, 2280, 2270, 2281,
+                    2270, 2271, 2281, 2281, 2271, 2282,
+                    2271, 2272, 2282, 2282, 2272, 2283,
+                    2272, 2273, 2283, 2283, 2273, 2284,
+                    2273, 2274, 2284, 2284, 2274, 2285,
+                    2274, 2275, 2285, 2285, 2275, 2286,
+                    2275, 2276, 2286, 2286, 2276, 2287,
+                    2277, 2278, 2288, 2288, 2278, 2289,
+                    2278, 2279, 2289, 2289, 2279, 2290,
+                    2279, 2280, 2290, 2290, 2280, 2291,
+                    2280, 2281, 2291, 2291, 2281, 2292,
+                    2281, 2282, 2292, 2292, 2282, 2293,
+                    2282, 2283, 2293, 2293, 2283, 2294,
+                    2283, 2284, 2294, 2294, 2284, 2295,
+                    2284, 2285, 2295, 2295, 2285, 2296,
+                    2285, 2286, 2296, 2296, 2286, 2297,
+                    2286, 2287, 2297, 2297, 2287, 2298,
+                    2299, 2300, 2310, 2310, 2300, 2311,
+                    2300, 2301, 2311, 2311, 2301, 2312,
+                    2301, 2302, 2312, 2312, 2302, 2313,
+                    2302, 2303, 2313, 2313, 2303, 2314,
+                    2303, 2304, 2314, 2314, 2304, 2315,
+                    2304, 2305, 2315, 2315, 2305, 2316,
+                    2305, 2306, 2316, 2316, 2306, 2317,
+                    2306, 2307, 2317, 2317, 2307, 2318,
+                    2307, 2308, 2318, 2318, 2308, 2319,
+                    2308, 2309, 2319, 2319, 2309, 2320,
+                    2310, 2311, 2321, 2321, 2311, 2322,
+                    2311, 2312, 2322, 2322, 2312, 2323,
+                    2312, 2313, 2323, 2323, 2313, 2324,
+                    2313, 2314, 2324, 2324, 2314, 2325,
+                    2314, 2315, 2325, 2325, 2315, 2326,
+                    2315, 2316, 2326, 2326, 2316, 2327,
+                    2316, 2317, 2327, 2327, 2317, 2328,
+                    2317, 2318, 2328, 2328, 2318, 2329,
+                    2318, 2319, 2329, 2329, 2319, 2330,
+                    2319, 2320, 2330, 2330, 2320, 2331,
+                    2321, 2322, 2332, 2332, 2322, 2333,
+                    2322, 2323, 2333, 2333, 2323, 2334,
+                    2323, 2324, 2334, 2334, 2324, 2335,
+                    2324, 2325, 2335, 2335, 2325, 2336,
+                    2325, 2326, 2336, 2336, 2326, 2337,
+                    2326, 2327, 2337, 2337, 2327, 2338,
+                    2327, 2328, 2338, 2338, 2328, 2339,
+                    2328, 2329, 2339, 2339, 2329, 2340,
+                    2329, 2330, 2340, 2340, 2330, 2341,
+                    2330, 2331, 2341, 2341, 2331, 2342,
+                    2332, 2333, 2343, 2343, 2333, 2344,
+                    2333, 2334, 2344, 2344, 2334, 2345,
+                    2334, 2335, 2345, 2345, 2335, 2346,
+                    2335, 2336, 2346, 2346, 2336, 2347,
+                    2336, 2337, 2347, 2347, 2337, 2348,
+                    2337, 2338, 2348, 2348, 2338, 2349,
+                    2338, 2339, 2349, 2349, 2339, 2350,
+                    2339, 2340, 2350, 2350, 2340, 2351,
+                    2340, 2341, 2351, 2351, 2341, 2352,
+                    2341, 2342, 2352, 2352, 2342, 2353,
+                    2343, 2344, 2354, 2354, 2344, 2355,
+                    2344, 2345, 2355, 2355, 2345, 2356,
+                    2345, 2346, 2356, 2356, 2346, 2357,
+                    2346, 2347, 2357, 2357, 2347, 2358,
+                    2347, 2348, 2358, 2358, 2348, 2359,
+                    2348, 2349, 2359, 2359, 2349, 2360,
+                    2349, 2350, 2360, 2360, 2350, 2361,
+                    2350, 2351, 2361, 2361, 2351, 2362,
+                    2351, 2352, 2362, 2362, 2352, 2363,
+                    2352, 2353, 2363, 2363, 2353, 2364,
+                    2354, 2355, 2365, 2365, 2355, 2366,
+                    2355, 2356, 2366, 2366, 2356, 2367,
+                    2356, 2357, 2367, 2367, 2357, 2368,
+                    2357, 2358, 2368, 2368, 2358, 2369,
+                    2358, 2359, 2369, 2369, 2359, 2370,
+                    2359, 2360, 2370, 2370, 2360, 2371,
+                    2360, 2361, 2371, 2371, 2361, 2372,
+                    2361, 2362, 2372, 2372, 2362, 2373,
+                    2362, 2363, 2373, 2373, 2363, 2374,
+                    2363, 2364, 2374, 2374, 2364, 2375,
+                    2365, 2366, 2376, 2376, 2366, 2377,
+                    2366, 2367, 2377, 2377, 2367, 2378,
+                    2367, 2368, 2378, 2378, 2368, 2379,
+                    2368, 2369, 2379, 2379, 2369, 2380,
+                    2369, 2370, 2380, 2380, 2370, 2381,
+                    2370, 2371, 2381, 2381, 2371, 2382,
+                    2371, 2372, 2382, 2382, 2372, 2383,
+                    2372, 2373, 2383, 2383, 2373, 2384,
+                    2373, 2374, 2384, 2384, 2374, 2385,
+                    2374, 2375, 2385, 2385, 2375, 2386,
+                    2376, 2377, 2387, 2387, 2377, 2388,
+                    2377, 2378, 2388, 2388, 2378, 2389,
+                    2378, 2379, 2389, 2389, 2379, 2390,
+                    2379, 2380, 2390, 2390, 2380, 2391,
+                    2380, 2381, 2391, 2391, 2381, 2392,
+                    2381, 2382, 2392, 2392, 2382, 2393,
+                    2382, 2383, 2393, 2393, 2383, 2394,
+                    2383, 2384, 2394, 2394, 2384, 2395,
+                    2384, 2385, 2395, 2395, 2385, 2396,
+                    2385, 2386, 2396, 2396, 2386, 2397,
+                    2387, 2388, 2398, 2398, 2388, 2399,
+                    2388, 2389, 2399, 2399, 2389, 2400,
+                    2389, 2390, 2400, 2400, 2390, 2401,
+                    2390, 2391, 2401, 2401, 2391, 2402,
+                    2391, 2392, 2402, 2402, 2392, 2403,
+                    2392, 2393, 2403, 2403, 2393, 2404,
+                    2393, 2394, 2404, 2404, 2394, 2405,
+                    2394, 2395, 2405, 2405, 2395, 2406,
+                    2395, 2396, 2406, 2406, 2396, 2407,
+                    2396, 2397, 2407, 2407, 2397, 2408,
+                    2398, 2399, 2409, 2409, 2399, 2410,
+                    2399, 2400, 2410, 2410, 2400, 2411,
+                    2400, 2401, 2411, 2411, 2401, 2412,
+                    2401, 2402, 2412, 2412, 2402, 2413,
+                    2402, 2403, 2413, 2413, 2403, 2414,
+                    2403, 2404, 2414, 2414, 2404, 2415,
+                    2404, 2405, 2415, 2415, 2405, 2416,
+                    2405, 2406, 2416, 2416, 2406, 2417,
+                    2406, 2407, 2417, 2417, 2407, 2418,
+                    2407, 2408, 2418, 2418, 2408, 2419,
+                    2420, 2421, 2431, 2431, 2421, 2432,
+                    2421, 2422, 2432, 2432, 2422, 2433,
+                    2422, 2423, 2433, 2433, 2423, 2434,
+                    2423, 2424, 2434, 2434, 2424, 2435,
+                    2424, 2425, 2435, 2435, 2425, 2436,
+                    2425, 2426, 2436, 2436, 2426, 2437,
+                    2426, 2427, 2437, 2437, 2427, 2438,
+                    2427, 2428, 2438, 2438, 2428, 2439,
+                    2428, 2429, 2439, 2439, 2429, 2440,
+                    2429, 2430, 2440, 2440, 2430, 2441,
+                    2431, 2432, 2442, 2442, 2432, 2443,
+                    2432, 2433, 2443, 2443, 2433, 2444,
+                    2433, 2434, 2444, 2444, 2434, 2445,
+                    2434, 2435, 2445, 2445, 2435, 2446,
+                    2435, 2436, 2446, 2446, 2436, 2447,
+                    2436, 2437, 2447, 2447, 2437, 2448,
+                    2437, 2438, 2448, 2448, 2438, 2449,
+                    2438, 2439, 2449, 2449, 2439, 2450,
+                    2439, 2440, 2450, 2450, 2440, 2451,
+                    2440, 2441, 2451, 2451, 2441, 2452,
+                    2442, 2443, 2453, 2453, 2443, 2454,
+                    2443, 2444, 2454, 2454, 2444, 2455,
+                    2444, 2445, 2455, 2455, 2445, 2456,
+                    2445, 2446, 2456, 2456, 2446, 2457,
+                    2446, 2447, 2457, 2457, 2447, 2458,
+                    2447, 2448, 2458, 2458, 2448, 2459,
+                    2448, 2449, 2459, 2459, 2449, 2460,
+                    2449, 2450, 2460, 2460, 2450, 2461,
+                    2450, 2451, 2461, 2461, 2451, 2462,
+                    2451, 2452, 2462, 2462, 2452, 2463,
+                    2453, 2454, 2464, 2464, 2454, 2465,
+                    2454, 2455, 2465, 2465, 2455, 2466,
+                    2455, 2456, 2466, 2466, 2456, 2467,
+                    2456, 2457, 2467, 2467, 2457, 2468,
+                    2457, 2458, 2468, 2468, 2458, 2469,
+                    2458, 2459, 2469, 2469, 2459, 2470,
+                    2459, 2460, 2470, 2470, 2460, 2471,
+                    2460, 2461, 2471, 2471, 2461, 2472,
+                    2461, 2462, 2472, 2472, 2462, 2473,
+                    2462, 2463, 2473, 2473, 2463, 2474,
+                    2464, 2465, 2475, 2475, 2465, 2476,
+                    2465, 2466, 2476, 2476, 2466, 2477,
+                    2466, 2467, 2477, 2477, 2467, 2478,
+                    2467, 2468, 2478, 2478, 2468, 2479,
+                    2468, 2469, 2479, 2479, 2469, 2480,
+                    2469, 2470, 2480, 2480, 2470, 2481,
+                    2470, 2471, 2481, 2481, 2471, 2482,
+                    2471, 2472, 2482, 2482, 2472, 2483,
+                    2472, 2473, 2483, 2483, 2473, 2484,
+                    2473, 2474, 2484, 2484, 2474, 2485,
+                    2475, 2476, 2486, 2486, 2476, 2487,
+                    2476, 2477, 2487, 2487, 2477, 2488,
+                    2477, 2478, 2488, 2488, 2478, 2489,
+                    2478, 2479, 2489, 2489, 2479, 2490,
+                    2479, 2480, 2490, 2490, 2480, 2491,
+                    2480, 2481, 2491, 2491, 2481, 2492,
+                    2481, 2482, 2492, 2492, 2482, 2493,
+                    2482, 2483, 2493, 2493, 2483, 2494,
+                    2483, 2484, 2494, 2494, 2484, 2495,
+                    2484, 2485, 2495, 2495, 2485, 2496,
+                    2486, 2487, 2497, 2497, 2487, 2498,
+                    2487, 2488, 2498, 2498, 2488, 2499,
+                    2488, 2489, 2499, 2499, 2489, 2500,
+                    2489, 2490, 2500, 2500, 2490, 2501,
+                    2490, 2491, 2501, 2501, 2491, 2502,
+                    2491, 2492, 2502, 2502, 2492, 2503,
+                    2492, 2493, 2503, 2503, 2493, 2504,
+                    2493, 2494, 2504, 2504, 2494, 2505,
+                    2494, 2495, 2505, 2505, 2495, 2506,
+                    2495, 2496, 2506, 2506, 2496, 2507,
+                    2497, 2498, 2508, 2508, 2498, 2509,
+                    2498, 2499, 2509, 2509, 2499, 2510,
+                    2499, 2500, 2510, 2510, 2500, 2511,
+                    2500, 2501, 2511, 2511, 2501, 2512,
+                    2501, 2502, 2512, 2512, 2502, 2513,
+                    2502, 2503, 2513, 2513, 2503, 2514,
+                    2503, 2504, 2514, 2514, 2504, 2515,
+                    2504, 2505, 2515, 2515, 2505, 2516,
+                    2505, 2506, 2516, 2516, 2506, 2517,
+                    2506, 2507, 2517, 2517, 2507, 2518,
+                    2508, 2509, 2519, 2519, 2509, 2520,
+                    2509, 2510, 2520, 2520, 2510, 2521,
+                    2510, 2511, 2521, 2521, 2511, 2522,
+                    2511, 2512, 2522, 2522, 2512, 2523,
+                    2512, 2513, 2523, 2523, 2513, 2524,
+                    2513, 2514, 2524, 2524, 2514, 2525,
+                    2514, 2515, 2525, 2525, 2515, 2526,
+                    2515, 2516, 2526, 2526, 2516, 2527,
+                    2516, 2517, 2527, 2527, 2517, 2528,
+                    2517, 2518, 2528, 2528, 2518, 2529,
+                    2519, 2520, 2530, 2530, 2520, 2531,
+                    2520, 2521, 2531, 2531, 2521, 2532,
+                    2521, 2522, 2532, 2532, 2522, 2533,
+                    2522, 2523, 2533, 2533, 2523, 2534,
+                    2523, 2524, 2534, 2534, 2524, 2535,
+                    2524, 2525, 2535, 2535, 2525, 2536,
+                    2525, 2526, 2536, 2536, 2526, 2537,
+                    2526, 2527, 2537, 2537, 2527, 2538,
+                    2527, 2528, 2538, 2538, 2528, 2539,
+                    2528, 2529, 2539, 2539, 2529, 2540,
+                    2541, 2542, 2552, 2552, 2542, 2553,
+                    2542, 2543, 2553, 2553, 2543, 2554,
+                    2543, 2544, 2554, 2554, 2544, 2555,
+                    2544, 2545, 2555, 2555, 2545, 2556,
+                    2545, 2546, 2556, 2556, 2546, 2557,
+                    2546, 2547, 2557, 2557, 2547, 2558,
+                    2547, 2548, 2558, 2558, 2548, 2559,
+                    2548, 2549, 2559, 2559, 2549, 2560,
+                    2549, 2550, 2560, 2560, 2550, 2561,
+                    2550, 2551, 2561, 2561, 2551, 2562,
+                    2552, 2553, 2563, 2563, 2553, 2564,
+                    2553, 2554, 2564, 2564, 2554, 2565,
+                    2554, 2555, 2565, 2565, 2555, 2566,
+                    2555, 2556, 2566, 2566, 2556, 2567,
+                    2556, 2557, 2567, 2567, 2557, 2568,
+                    2557, 2558, 2568, 2568, 2558, 2569,
+                    2558, 2559, 2569, 2569, 2559, 2570,
+                    2559, 2560, 2570, 2570, 2560, 2571,
+                    2560, 2561, 2571, 2571, 2561, 2572,
+                    2561, 2562, 2572, 2572, 2562, 2573,
+                    2563, 2564, 2574, 2574, 2564, 2575,
+                    2564, 2565, 2575, 2575, 2565, 2576,
+                    2565, 2566, 2576, 2576, 2566, 2577,
+                    2566, 2567, 2577, 2577, 2567, 2578,
+                    2567, 2568, 2578, 2578, 2568, 2579,
+                    2568, 2569, 2579, 2579, 2569, 2580,
+                    2569, 2570, 2580, 2580, 2570, 2581,
+                    2570, 2571, 2581, 2581, 2571, 2582,
+                    2571, 2572, 2582, 2582, 2572, 2583,
+                    2572, 2573, 2583, 2583, 2573, 2584,
+                    2574, 2575, 2585, 2585, 2575, 2586,
+                    2575, 2576, 2586, 2586, 2576, 2587,
+                    2576, 2577, 2587, 2587, 2577, 2588,
+                    2577, 2578, 2588, 2588, 2578, 2589,
+                    2578, 2579, 2589, 2589, 2579, 2590,
+                    2579, 2580, 2590, 2590, 2580, 2591,
+                    2580, 2581, 2591, 2591, 2581, 2592,
+                    2581, 2582, 2592, 2592, 2582, 2593,
+                    2582, 2583, 2593, 2593, 2583, 2594,
+                    2583, 2584, 2594, 2594, 2584, 2595,
+                    2585, 2586, 2596, 2596, 2586, 2597,
+                    2586, 2587, 2597, 2597, 2587, 2598,
+                    2587, 2588, 2598, 2598, 2588, 2599,
+                    2588, 2589, 2599, 2599, 2589, 2600,
+                    2589, 2590, 2600, 2600, 2590, 2601,
+                    2590, 2591, 2601, 2601, 2591, 2602,
+                    2591, 2592, 2602, 2602, 2592, 2603,
+                    2592, 2593, 2603, 2603, 2593, 2604,
+                    2593, 2594, 2604, 2604, 2594, 2605,
+                    2594, 2595, 2605, 2605, 2595, 2606,
+                    2596, 2597, 2607, 2607, 2597, 2608,
+                    2597, 2598, 2608, 2608, 2598, 2609,
+                    2598, 2599, 2609, 2609, 2599, 2610,
+                    2599, 2600, 2610, 2610, 2600, 2611,
+                    2600, 2601, 2611, 2611, 2601, 2612,
+                    2601, 2602, 2612, 2612, 2602, 2613,
+                    2602, 2603, 2613, 2613, 2603, 2614,
+                    2603, 2604, 2614, 2614, 2604, 2615,
+                    2604, 2605, 2615, 2615, 2605, 2616,
+                    2605, 2606, 2616, 2616, 2606, 2617,
+                    2607, 2608, 2618, 2618, 2608, 2619,
+                    2608, 2609, 2619, 2619, 2609, 2620,
+                    2609, 2610, 2620, 2620, 2610, 2621,
+                    2610, 2611, 2621, 2621, 2611, 2622,
+                    2611, 2612, 2622, 2622, 2612, 2623,
+                    2612, 2613, 2623, 2623, 2613, 2624,
+                    2613, 2614, 2624, 2624, 2614, 2625,
+                    2614, 2615, 2625, 2625, 2615, 2626,
+                    2615, 2616, 2626, 2626, 2616, 2627,
+                    2616, 2617, 2627, 2627, 2617, 2628,
+                    2618, 2619, 2629, 2629, 2619, 2630,
+                    2619, 2620, 2630, 2630, 2620, 2631,
+                    2620, 2621, 2631, 2631, 2621, 2632,
+                    2621, 2622, 2632, 2632, 2622, 2633,
+                    2622, 2623, 2633, 2633, 2623, 2634,
+                    2623, 2624, 2634, 2634, 2624, 2635,
+                    2624, 2625, 2635, 2635, 2625, 2636,
+                    2625, 2626, 2636, 2636, 2626, 2637,
+                    2626, 2627, 2637, 2637, 2627, 2638,
+                    2627, 2628, 2638, 2638, 2628, 2639,
+                    2629, 2630, 2640, 2640, 2630, 2641,
+                    2630, 2631, 2641, 2641, 2631, 2642,
+                    2631, 2632, 2642, 2642, 2632, 2643,
+                    2632, 2633, 2643, 2643, 2633, 2644,
+                    2633, 2634, 2644, 2644, 2634, 2645,
+                    2634, 2635, 2645, 2645, 2635, 2646,
+                    2635, 2636, 2646, 2646, 2636, 2647,
+                    2636, 2637, 2647, 2647, 2637, 2648,
+                    2637, 2638, 2648, 2648, 2638, 2649,
+                    2638, 2639, 2649, 2649, 2639, 2650,
+                    2640, 2641, 2651, 2651, 2641, 2652,
+                    2641, 2642, 2652, 2652, 2642, 2653,
+                    2642, 2643, 2653, 2653, 2643, 2654,
+                    2643, 2644, 2654, 2654, 2644, 2655,
+                    2644, 2645, 2655, 2655, 2645, 2656,
+                    2645, 2646, 2656, 2656, 2646, 2657,
+                    2646, 2647, 2657, 2657, 2647, 2658,
+                    2647, 2648, 2658, 2658, 2648, 2659,
+                    2648, 2649, 2659, 2659, 2649, 2660,
+                    2649, 2650, 2660, 2660, 2650, 2661,
+                    2662, 2663, 2673, 2673, 2663, 2674,
+                    2663, 2664, 2674, 2674, 2664, 2675,
+                    2664, 2665, 2675, 2675, 2665, 2676,
+                    2665, 2666, 2676, 2676, 2666, 2677,
+                    2666, 2667, 2677, 2677, 2667, 2678,
+                    2667, 2668, 2678, 2678, 2668, 2679,
+                    2668, 2669, 2679, 2679, 2669, 2680,
+                    2669, 2670, 2680, 2680, 2670, 2681,
+                    2670, 2671, 2681, 2681, 2671, 2682,
+                    2671, 2672, 2682, 2682, 2672, 2683,
+                    2673, 2674, 2684, 2684, 2674, 2685,
+                    2674, 2675, 2685, 2685, 2675, 2686,
+                    2675, 2676, 2686, 2686, 2676, 2687,
+                    2676, 2677, 2687, 2687, 2677, 2688,
+                    2677, 2678, 2688, 2688, 2678, 2689,
+                    2678, 2679, 2689, 2689, 2679, 2690,
+                    2679, 2680, 2690, 2690, 2680, 2691,
+                    2680, 2681, 2691, 2691, 2681, 2692,
+                    2681, 2682, 2692, 2692, 2682, 2693,
+                    2682, 2683, 2693, 2693, 2683, 2694,
+                    2684, 2685, 2695, 2695, 2685, 2696,
+                    2685, 2686, 2696, 2696, 2686, 2697,
+                    2686, 2687, 2697, 2697, 2687, 2698,
+                    2687, 2688, 2698, 2698, 2688, 2699,
+                    2688, 2689, 2699, 2699, 2689, 2700,
+                    2689, 2690, 2700, 2700, 2690, 2701,
+                    2690, 2691, 2701, 2701, 2691, 2702,
+                    2691, 2692, 2702, 2702, 2692, 2703,
+                    2692, 2693, 2703, 2703, 2693, 2704,
+                    2693, 2694, 2704, 2704, 2694, 2705,
+                    2695, 2696, 2706, 2706, 2696, 2707,
+                    2696, 2697, 2707, 2707, 2697, 2708,
+                    2697, 2698, 2708, 2708, 2698, 2709,
+                    2698, 2699, 2709, 2709, 2699, 2710,
+                    2699, 2700, 2710, 2710, 2700, 2711,
+                    2700, 2701, 2711, 2711, 2701, 2712,
+                    2701, 2702, 2712, 2712, 2702, 2713,
+                    2702, 2703, 2713, 2713, 2703, 2714,
+                    2703, 2704, 2714, 2714, 2704, 2715,
+                    2704, 2705, 2715, 2715, 2705, 2716,
+                    2706, 2707, 2717, 2717, 2707, 2718,
+                    2707, 2708, 2718, 2718, 2708, 2719,
+                    2708, 2709, 2719, 2719, 2709, 2720,
+                    2709, 2710, 2720, 2720, 2710, 2721,
+                    2710, 2711, 2721, 2721, 2711, 2722,
+                    2711, 2712, 2722, 2722, 2712, 2723,
+                    2712, 2713, 2723, 2723, 2713, 2724,
+                    2713, 2714, 2724, 2724, 2714, 2725,
+                    2714, 2715, 2725, 2725, 2715, 2726,
+                    2715, 2716, 2726, 2726, 2716, 2727,
+                    2717, 2718, 2728, 2728, 2718, 2729,
+                    2718, 2719, 2729, 2729, 2719, 2730,
+                    2719, 2720, 2730, 2730, 2720, 2731,
+                    2720, 2721, 2731, 2731, 2721, 2732,
+                    2721, 2722, 2732, 2732, 2722, 2733,
+                    2722, 2723, 2733, 2733, 2723, 2734,
+                    2723, 2724, 2734, 2734, 2724, 2735,
+                    2724, 2725, 2735, 2735, 2725, 2736,
+                    2725, 2726, 2736, 2736, 2726, 2737,
+                    2726, 2727, 2737, 2737, 2727, 2738,
+                    2728, 2729, 2739, 2739, 2729, 2740,
+                    2729, 2730, 2740, 2740, 2730, 2741,
+                    2730, 2731, 2741, 2741, 2731, 2742,
+                    2731, 2732, 2742, 2742, 2732, 2743,
+                    2732, 2733, 2743, 2743, 2733, 2744,
+                    2733, 2734, 2744, 2744, 2734, 2745,
+                    2734, 2735, 2745, 2745, 2735, 2746,
+                    2735, 2736, 2746, 2746, 2736, 2747,
+                    2736, 2737, 2747, 2747, 2737, 2748,
+                    2737, 2738, 2748, 2748, 2738, 2749,
+                    2739, 2740, 2750, 2750, 2740, 2751,
+                    2740, 2741, 2751, 2751, 2741, 2752,
+                    2741, 2742, 2752, 2752, 2742, 2753,
+                    2742, 2743, 2753, 2753, 2743, 2754,
+                    2743, 2744, 2754, 2754, 2744, 2755,
+                    2744, 2745, 2755, 2755, 2745, 2756,
+                    2745, 2746, 2756, 2756, 2746, 2757,
+                    2746, 2747, 2757, 2757, 2747, 2758,
+                    2747, 2748, 2758, 2758, 2748, 2759,
+                    2748, 2749, 2759, 2759, 2749, 2760,
+                    2750, 2751, 2761, 2761, 2751, 2762,
+                    2751, 2752, 2762, 2762, 2752, 2763,
+                    2752, 2753, 2763, 2763, 2753, 2764,
+                    2753, 2754, 2764, 2764, 2754, 2765,
+                    2754, 2755, 2765, 2765, 2755, 2766,
+                    2755, 2756, 2766, 2766, 2756, 2767,
+                    2756, 2757, 2767, 2767, 2757, 2768,
+                    2757, 2758, 2768, 2768, 2758, 2769,
+                    2758, 2759, 2769, 2769, 2759, 2770,
+                    2759, 2760, 2770, 2770, 2760, 2771,
+                    2761, 2762, 2772, 2772, 2762, 2773,
+                    2762, 2763, 2773, 2773, 2763, 2774,
+                    2763, 2764, 2774, 2774, 2764, 2775,
+                    2764, 2765, 2775, 2775, 2765, 2776,
+                    2765, 2766, 2776, 2776, 2766, 2777,
+                    2766, 2767, 2777, 2777, 2767, 2778,
+                    2767, 2768, 2778, 2778, 2768, 2779,
+                    2768, 2769, 2779, 2779, 2769, 2780,
+                    2769, 2770, 2780, 2780, 2770, 2781,
+                    2770, 2771, 2781, 2781, 2771, 2782,
+                    2783, 2784, 2794, 2794, 2784, 2795,
+                    2784, 2785, 2795, 2795, 2785, 2796,
+                    2785, 2786, 2796, 2796, 2786, 2797,
+                    2786, 2787, 2797, 2797, 2787, 2798,
+                    2787, 2788, 2798, 2798, 2788, 2799,
+                    2788, 2789, 2799, 2799, 2789, 2800,
+                    2789, 2790, 2800, 2800, 2790, 2801,
+                    2790, 2791, 2801, 2801, 2791, 2802,
+                    2791, 2792, 2802, 2802, 2792, 2803,
+                    2792, 2793, 2803, 2803, 2793, 2804,
+                    2794, 2795, 2805, 2805, 2795, 2806,
+                    2795, 2796, 2806, 2806, 2796, 2807,
+                    2796, 2797, 2807, 2807, 2797, 2808,
+                    2797, 2798, 2808, 2808, 2798, 2809,
+                    2798, 2799, 2809, 2809, 2799, 2810,
+                    2799, 2800, 2810, 2810, 2800, 2811,
+                    2800, 2801, 2811, 2811, 2801, 2812,
+                    2801, 2802, 2812, 2812, 2802, 2813,
+                    2802, 2803, 2813, 2813, 2803, 2814,
+                    2803, 2804, 2814, 2814, 2804, 2815,
+                    2805, 2806, 2816, 2816, 2806, 2817,
+                    2806, 2807, 2817, 2817, 2807, 2818,
+                    2807, 2808, 2818, 2818, 2808, 2819,
+                    2808, 2809, 2819, 2819, 2809, 2820,
+                    2809, 2810, 2820, 2820, 2810, 2821,
+                    2810, 2811, 2821, 2821, 2811, 2822,
+                    2811, 2812, 2822, 2822, 2812, 2823,
+                    2812, 2813, 2823, 2823, 2813, 2824,
+                    2813, 2814, 2824, 2824, 2814, 2825,
+                    2814, 2815, 2825, 2825, 2815, 2826,
+                    2816, 2817, 2827, 2827, 2817, 2828,
+                    2817, 2818, 2828, 2828, 2818, 2829,
+                    2818, 2819, 2829, 2829, 2819, 2830,
+                    2819, 2820, 2830, 2830, 2820, 2831,
+                    2820, 2821, 2831, 2831, 2821, 2832,
+                    2821, 2822, 2832, 2832, 2822, 2833,
+                    2822, 2823, 2833, 2833, 2823, 2834,
+                    2823, 2824, 2834, 2834, 2824, 2835,
+                    2824, 2825, 2835, 2835, 2825, 2836,
+                    2825, 2826, 2836, 2836, 2826, 2837,
+                    2827, 2828, 2838, 2838, 2828, 2839,
+                    2828, 2829, 2839, 2839, 2829, 2840,
+                    2829, 2830, 2840, 2840, 2830, 2841,
+                    2830, 2831, 2841, 2841, 2831, 2842,
+                    2831, 2832, 2842, 2842, 2832, 2843,
+                    2832, 2833, 2843, 2843, 2833, 2844,
+                    2833, 2834, 2844, 2844, 2834, 2845,
+                    2834, 2835, 2845, 2845, 2835, 2846,
+                    2835, 2836, 2846, 2846, 2836, 2847,
+                    2836, 2837, 2847, 2847, 2837, 2848,
+                    2838, 2839, 2849, 2849, 2839, 2850,
+                    2839, 2840, 2850, 2850, 2840, 2851,
+                    2840, 2841, 2851, 2851, 2841, 2852,
+                    2841, 2842, 2852, 2852, 2842, 2853,
+                    2842, 2843, 2853, 2853, 2843, 2854,
+                    2843, 2844, 2854, 2854, 2844, 2855,
+                    2844, 2845, 2855, 2855, 2845, 2856,
+                    2845, 2846, 2856, 2856, 2846, 2857,
+                    2846, 2847, 2857, 2857, 2847, 2858,
+                    2847, 2848, 2858, 2858, 2848, 2859,
+                    2849, 2850, 2860, 2860, 2850, 2861,
+                    2850, 2851, 2861, 2861, 2851, 2862,
+                    2851, 2852, 2862, 2862, 2852, 2863,
+                    2852, 2853, 2863, 2863, 2853, 2864,
+                    2853, 2854, 2864, 2864, 2854, 2865,
+                    2854, 2855, 2865, 2865, 2855, 2866,
+                    2855, 2856, 2866, 2866, 2856, 2867,
+                    2856, 2857, 2867, 2867, 2857, 2868,
+                    2857, 2858, 2868, 2868, 2858, 2869,
+                    2858, 2859, 2869, 2869, 2859, 2870,
+                    2860, 2861, 2871, 2871, 2861, 2872,
+                    2861, 2862, 2872, 2872, 2862, 2873,
+                    2862, 2863, 2873, 2873, 2863, 2874,
+                    2863, 2864, 2874, 2874, 2864, 2875,
+                    2864, 2865, 2875, 2875, 2865, 2876,
+                    2865, 2866, 2876, 2876, 2866, 2877,
+                    2866, 2867, 2877, 2877, 2867, 2878,
+                    2867, 2868, 2878, 2878, 2868, 2879,
+                    2868, 2869, 2879, 2879, 2869, 2880,
+                    2869, 2870, 2880, 2880, 2870, 2881,
+                    2871, 2872, 2882, 2882, 2872, 2883,
+                    2872, 2873, 2883, 2883, 2873, 2884,
+                    2873, 2874, 2884, 2884, 2874, 2885,
+                    2874, 2875, 2885, 2885, 2875, 2886,
+                    2875, 2876, 2886, 2886, 2876, 2887,
+                    2876, 2877, 2887, 2887, 2877, 2888,
+                    2877, 2878, 2888, 2888, 2878, 2889,
+                    2878, 2879, 2889, 2889, 2879, 2890,
+                    2879, 2880, 2890, 2890, 2880, 2891,
+                    2880, 2881, 2891, 2891, 2881, 2892,
+                    2882, 2883, 2893, 2893, 2883, 2894,
+                    2883, 2884, 2894, 2894, 2884, 2895,
+                    2884, 2885, 2895, 2895, 2885, 2896,
+                    2885, 2886, 2896, 2896, 2886, 2897,
+                    2886, 2887, 2897, 2897, 2887, 2898,
+                    2887, 2888, 2898, 2898, 2888, 2899,
+                    2888, 2889, 2899, 2899, 2889, 2900,
+                    2889, 2890, 2900, 2900, 2890, 2901,
+                    2890, 2891, 2901, 2901, 2891, 2902,
+                    2891, 2892, 2902, 2902, 2892, 2903,
+                    2904, 2905, 2915, 2915, 2905, 2916,
+                    2905, 2906, 2916, 2916, 2906, 2917,
+                    2906, 2907, 2917, 2917, 2907, 2918,
+                    2907, 2908, 2918, 2918, 2908, 2919,
+                    2908, 2909, 2919, 2919, 2909, 2920,
+                    2909, 2910, 2920, 2920, 2910, 2921,
+                    2910, 2911, 2921, 2921, 2911, 2922,
+                    2911, 2912, 2922, 2922, 2912, 2923,
+                    2912, 2913, 2923, 2923, 2913, 2924,
+                    2913, 2914, 2924, 2924, 2914, 2925,
+                    2915, 2916, 2926, 2926, 2916, 2927,
+                    2916, 2917, 2927, 2927, 2917, 2928,
+                    2917, 2918, 2928, 2928, 2918, 2929,
+                    2918, 2919, 2929, 2929, 2919, 2930,
+                    2919, 2920, 2930, 2930, 2920, 2931,
+                    2920, 2921, 2931, 2931, 2921, 2932,
+                    2921, 2922, 2932, 2932, 2922, 2933,
+                    2922, 2923, 2933, 2933, 2923, 2934,
+                    2923, 2924, 2934, 2934, 2924, 2935,
+                    2924, 2925, 2935, 2935, 2925, 2936,
+                    2926, 2927, 2937, 2937, 2927, 2938,
+                    2927, 2928, 2938, 2938, 2928, 2939,
+                    2928, 2929, 2939, 2939, 2929, 2940,
+                    2929, 2930, 2940, 2940, 2930, 2941,
+                    2930, 2931, 2941, 2941, 2931, 2942,
+                    2931, 2932, 2942, 2942, 2932, 2943,
+                    2932, 2933, 2943, 2943, 2933, 2944,
+                    2933, 2934, 2944, 2944, 2934, 2945,
+                    2934, 2935, 2945, 2945, 2935, 2946,
+                    2935, 2936, 2946, 2946, 2936, 2947,
+                    2937, 2938, 2948, 2948, 2938, 2949,
+                    2938, 2939, 2949, 2949, 2939, 2950,
+                    2939, 2940, 2950, 2950, 2940, 2951,
+                    2940, 2941, 2951, 2951, 2941, 2952,
+                    2941, 2942, 2952, 2952, 2942, 2953,
+                    2942, 2943, 2953, 2953, 2943, 2954,
+                    2943, 2944, 2954, 2954, 2944, 2955,
+                    2944, 2945, 2955, 2955, 2945, 2956,
+                    2945, 2946, 2956, 2956, 2946, 2957,
+                    2946, 2947, 2957, 2957, 2947, 2958,
+                    2948, 2949, 2959, 2959, 2949, 2960,
+                    2949, 2950, 2960, 2960, 2950, 2961,
+                    2950, 2951, 2961, 2961, 2951, 2962,
+                    2951, 2952, 2962, 2962, 2952, 2963,
+                    2952, 2953, 2963, 2963, 2953, 2964,
+                    2953, 2954, 2964, 2964, 2954, 2965,
+                    2954, 2955, 2965, 2965, 2955, 2966,
+                    2955, 2956, 2966, 2966, 2956, 2967,
+                    2956, 2957, 2967, 2967, 2957, 2968,
+                    2957, 2958, 2968, 2968, 2958, 2969,
+                    2959, 2960, 2970, 2970, 2960, 2971,
+                    2960, 2961, 2971, 2971, 2961, 2972,
+                    2961, 2962, 2972, 2972, 2962, 2973,
+                    2962, 2963, 2973, 2973, 2963, 2974,
+                    2963, 2964, 2974, 2974, 2964, 2975,
+                    2964, 2965, 2975, 2975, 2965, 2976,
+                    2965, 2966, 2976, 2976, 2966, 2977,
+                    2966, 2967, 2977, 2977, 2967, 2978,
+                    2967, 2968, 2978, 2978, 2968, 2979,
+                    2968, 2969, 2979, 2979, 2969, 2980,
+                    2970, 2971, 2981, 2981, 2971, 2982,
+                    2971, 2972, 2982, 2982, 2972, 2983,
+                    2972, 2973, 2983, 2983, 2973, 2984,
+                    2973, 2974, 2984, 2984, 2974, 2985,
+                    2974, 2975, 2985, 2985, 2975, 2986,
+                    2975, 2976, 2986, 2986, 2976, 2987,
+                    2976, 2977, 2987, 2987, 2977, 2988,
+                    2977, 2978, 2988, 2988, 2978, 2989,
+                    2978, 2979, 2989, 2989, 2979, 2990,
+                    2979, 2980, 2990, 2990, 2980, 2991,
+                    2981, 2982, 2992, 2992, 2982, 2993,
+                    2982, 2983, 2993, 2993, 2983, 2994,
+                    2983, 2984, 2994, 2994, 2984, 2995,
+                    2984, 2985, 2995, 2995, 2985, 2996,
+                    2985, 2986, 2996, 2996, 2986, 2997,
+                    2986, 2987, 2997, 2997, 2987, 2998,
+                    2987, 2988, 2998, 2998, 2988, 2999,
+                    2988, 2989, 2999, 2999, 2989, 3000,
+                    2989, 2990, 3000, 3000, 2990, 3001,
+                    2990, 2991, 3001, 3001, 2991, 3002,
+                    2992, 2993, 3003, 3003, 2993, 3004,
+                    2993, 2994, 3004, 3004, 2994, 3005,
+                    2994, 2995, 3005, 3005, 2995, 3006,
+                    2995, 2996, 3006, 3006, 2996, 3007,
+                    2996, 2997, 3007, 3007, 2997, 3008,
+                    2997, 2998, 3008, 3008, 2998, 3009,
+                    2998, 2999, 3009, 3009, 2999, 3010,
+                    2999, 3000, 3010, 3010, 3000, 3011,
+                    3000, 3001, 3011, 3011, 3001, 3012,
+                    3001, 3002, 3012, 3012, 3002, 3013,
+                    3003, 3004, 3014, 3014, 3004, 3015,
+                    3004, 3005, 3015, 3015, 3005, 3016,
+                    3005, 3006, 3016, 3016, 3006, 3017,
+                    3006, 3007, 3017, 3017, 3007, 3018,
+                    3007, 3008, 3018, 3018, 3008, 3019,
+                    3008, 3009, 3019, 3019, 3009, 3020,
+                    3009, 3010, 3020, 3020, 3010, 3021,
+                    3010, 3011, 3021, 3021, 3011, 3022,
+                    3011, 3012, 3022, 3022, 3012, 3023,
+                    3012, 3013, 3023, 3023, 3013, 3024,
+                    3025, 3026, 3036, 3036, 3026, 3037,
+                    3026, 3027, 3037, 3037, 3027, 3038,
+                    3027, 3028, 3038, 3038, 3028, 3039,
+                    3028, 3029, 3039, 3039, 3029, 3040,
+                    3029, 3030, 3040, 3040, 3030, 3041,
+                    3030, 3031, 3041, 3041, 3031, 3042,
+                    3031, 3032, 3042, 3042, 3032, 3043,
+                    3032, 3033, 3043, 3043, 3033, 3044,
+                    3033, 3034, 3044, 3044, 3034, 3045,
+                    3034, 3035, 3045, 3045, 3035, 3046,
+                    3036, 3037, 3047, 3047, 3037, 3048,
+                    3037, 3038, 3048, 3048, 3038, 3049,
+                    3038, 3039, 3049, 3049, 3039, 3050,
+                    3039, 3040, 3050, 3050, 3040, 3051,
+                    3040, 3041, 3051, 3051, 3041, 3052,
+                    3041, 3042, 3052, 3052, 3042, 3053,
+                    3042, 3043, 3053, 3053, 3043, 3054,
+                    3043, 3044, 3054, 3054, 3044, 3055,
+                    3044, 3045, 3055, 3055, 3045, 3056,
+                    3045, 3046, 3056, 3056, 3046, 3057,
+                    3047, 3048, 3058, 3058, 3048, 3059,
+                    3048, 3049, 3059, 3059, 3049, 3060,
+                    3049, 3050, 3060, 3060, 3050, 3061,
+                    3050, 3051, 3061, 3061, 3051, 3062,
+                    3051, 3052, 3062, 3062, 3052, 3063,
+                    3052, 3053, 3063, 3063, 3053, 3064,
+                    3053, 3054, 3064, 3064, 3054, 3065,
+                    3054, 3055, 3065, 3065, 3055, 3066,
+                    3055, 3056, 3066, 3066, 3056, 3067,
+                    3056, 3057, 3067, 3067, 3057, 3068,
+                    3058, 3059, 3069, 3069, 3059, 3070,
+                    3059, 3060, 3070, 3070, 3060, 3071,
+                    3060, 3061, 3071, 3071, 3061, 3072,
+                    3061, 3062, 3072, 3072, 3062, 3073,
+                    3062, 3063, 3073, 3073, 3063, 3074,
+                    3063, 3064, 3074, 3074, 3064, 3075,
+                    3064, 3065, 3075, 3075, 3065, 3076,
+                    3065, 3066, 3076, 3076, 3066, 3077,
+                    3066, 3067, 3077, 3077, 3067, 3078,
+                    3067, 3068, 3078, 3078, 3068, 3079,
+                    3069, 3070, 3080, 3080, 3070, 3081,
+                    3070, 3071, 3081, 3081, 3071, 3082,
+                    3071, 3072, 3082, 3082, 3072, 3083,
+                    3072, 3073, 3083, 3083, 3073, 3084,
+                    3073, 3074, 3084, 3084, 3074, 3085,
+                    3074, 3075, 3085, 3085, 3075, 3086,
+                    3075, 3076, 3086, 3086, 3076, 3087,
+                    3076, 3077, 3087, 3087, 3077, 3088,
+                    3077, 3078, 3088, 3088, 3078, 3089,
+                    3078, 3079, 3089, 3089, 3079, 3090,
+                    3080, 3081, 3091, 3091, 3081, 3092,
+                    3081, 3082, 3092, 3092, 3082, 3093,
+                    3082, 3083, 3093, 3093, 3083, 3094,
+                    3083, 3084, 3094, 3094, 3084, 3095,
+                    3084, 3085, 3095, 3095, 3085, 3096,
+                    3085, 3086, 3096, 3096, 3086, 3097,
+                    3086, 3087, 3097, 3097, 3087, 3098,
+                    3087, 3088, 3098, 3098, 3088, 3099,
+                    3088, 3089, 3099, 3099, 3089, 3100,
+                    3089, 3090, 3100, 3100, 3090, 3101,
+                    3091, 3092, 3102, 3102, 3092, 3103,
+                    3092, 3093, 3103, 3103, 3093, 3104,
+                    3093, 3094, 3104, 3104, 3094, 3105,
+                    3094, 3095, 3105, 3105, 3095, 3106,
+                    3095, 3096, 3106, 3106, 3096, 3107,
+                    3096, 3097, 3107, 3107, 3097, 3108,
+                    3097, 3098, 3108, 3108, 3098, 3109,
+                    3098, 3099, 3109, 3109, 3099, 3110,
+                    3099, 3100, 3110, 3110, 3100, 3111,
+                    3100, 3101, 3111, 3111, 3101, 3112,
+                    3102, 3103, 3113, 3113, 3103, 3114,
+                    3103, 3104, 3114, 3114, 3104, 3115,
+                    3104, 3105, 3115, 3115, 3105, 3116,
+                    3105, 3106, 3116, 3116, 3106, 3117,
+                    3106, 3107, 3117, 3117, 3107, 3118,
+                    3107, 3108, 3118, 3118, 3108, 3119,
+                    3108, 3109, 3119, 3119, 3109, 3120,
+                    3109, 3110, 3120, 3120, 3110, 3121,
+                    3110, 3111, 3121, 3121, 3111, 3122,
+                    3111, 3112, 3122, 3122, 3112, 3123,
+                    3113, 3114, 3124, 3124, 3114, 3125,
+                    3114, 3115, 3125, 3125, 3115, 3126,
+                    3115, 3116, 3126, 3126, 3116, 3127,
+                    3116, 3117, 3127, 3127, 3117, 3128,
+                    3117, 3118, 3128, 3128, 3118, 3129,
+                    3118, 3119, 3129, 3129, 3119, 3130,
+                    3119, 3120, 3130, 3130, 3120, 3131,
+                    3120, 3121, 3131, 3131, 3121, 3132,
+                    3121, 3122, 3132, 3132, 3122, 3133,
+                    3122, 3123, 3133, 3133, 3123, 3134,
+                    3124, 3125, 3135, 3135, 3125, 3136,
+                    3125, 3126, 3136, 3136, 3126, 3137,
+                    3126, 3127, 3137, 3137, 3127, 3138,
+                    3127, 3128, 3138, 3138, 3128, 3139,
+                    3128, 3129, 3139, 3139, 3129, 3140,
+                    3129, 3130, 3140, 3140, 3130, 3141,
+                    3130, 3131, 3141, 3141, 3131, 3142,
+                    3131, 3132, 3142, 3142, 3132, 3143,
+                    3132, 3133, 3143, 3143, 3133, 3144,
+                    3133, 3134, 3144, 3144, 3134, 3145,
+                    3146, 3147, 3157, 3157, 3147, 3158,
+                    3147, 3148, 3158, 3158, 3148, 3159,
+                    3148, 3149, 3159, 3159, 3149, 3160,
+                    3149, 3150, 3160, 3160, 3150, 3161,
+                    3150, 3151, 3161, 3161, 3151, 3162,
+                    3151, 3152, 3162, 3162, 3152, 3163,
+                    3152, 3153, 3163, 3163, 3153, 3164,
+                    3153, 3154, 3164, 3164, 3154, 3165,
+                    3154, 3155, 3165, 3165, 3155, 3166,
+                    3155, 3156, 3166, 3166, 3156, 3167,
+                    3157, 3158, 3168, 3168, 3158, 3169,
+                    3158, 3159, 3169, 3169, 3159, 3170,
+                    3159, 3160, 3170, 3170, 3160, 3171,
+                    3160, 3161, 3171, 3171, 3161, 3172,
+                    3161, 3162, 3172, 3172, 3162, 3173,
+                    3162, 3163, 3173, 3173, 3163, 3174,
+                    3163, 3164, 3174, 3174, 3164, 3175,
+                    3164, 3165, 3175, 3175, 3165, 3176,
+                    3165, 3166, 3176, 3176, 3166, 3177,
+                    3166, 3167, 3177, 3177, 3167, 3178,
+                    3168, 3169, 3179, 3179, 3169, 3180,
+                    3169, 3170, 3180, 3180, 3170, 3181,
+                    3170, 3171, 3181, 3181, 3171, 3182,
+                    3171, 3172, 3182, 3182, 3172, 3183,
+                    3172, 3173, 3183, 3183, 3173, 3184,
+                    3173, 3174, 3184, 3184, 3174, 3185,
+                    3174, 3175, 3185, 3185, 3175, 3186,
+                    3175, 3176, 3186, 3186, 3176, 3187,
+                    3176, 3177, 3187, 3187, 3177, 3188,
+                    3177, 3178, 3188, 3188, 3178, 3189,
+                    3179, 3180, 3190, 3190, 3180, 3191,
+                    3180, 3181, 3191, 3191, 3181, 3192,
+                    3181, 3182, 3192, 3192, 3182, 3193,
+                    3182, 3183, 3193, 3193, 3183, 3194,
+                    3183, 3184, 3194, 3194, 3184, 3195,
+                    3184, 3185, 3195, 3195, 3185, 3196,
+                    3185, 3186, 3196, 3196, 3186, 3197,
+                    3186, 3187, 3197, 3197, 3187, 3198,
+                    3187, 3188, 3198, 3198, 3188, 3199,
+                    3188, 3189, 3199, 3199, 3189, 3200,
+                    3190, 3191, 3201, 3201, 3191, 3202,
+                    3191, 3192, 3202, 3202, 3192, 3203,
+                    3192, 3193, 3203, 3203, 3193, 3204,
+                    3193, 3194, 3204, 3204, 3194, 3205,
+                    3194, 3195, 3205, 3205, 3195, 3206,
+                    3195, 3196, 3206, 3206, 3196, 3207,
+                    3196, 3197, 3207, 3207, 3197, 3208,
+                    3197, 3198, 3208, 3208, 3198, 3209,
+                    3198, 3199, 3209, 3209, 3199, 3210,
+                    3199, 3200, 3210, 3210, 3200, 3211,
+                    3201, 3202, 3212, 3212, 3202, 3213,
+                    3202, 3203, 3213, 3213, 3203, 3214,
+                    3203, 3204, 3214, 3214, 3204, 3215,
+                    3204, 3205, 3215, 3215, 3205, 3216,
+                    3205, 3206, 3216, 3216, 3206, 3217,
+                    3206, 3207, 3217, 3217, 3207, 3218,
+                    3207, 3208, 3218, 3218, 3208, 3219,
+                    3208, 3209, 3219, 3219, 3209, 3220,
+                    3209, 3210, 3220, 3220, 3210, 3221,
+                    3210, 3211, 3221, 3221, 3211, 3222,
+                    3212, 3213, 3223, 3223, 3213, 3224,
+                    3213, 3214, 3224, 3224, 3214, 3225,
+                    3214, 3215, 3225, 3225, 3215, 3226,
+                    3215, 3216, 3226, 3226, 3216, 3227,
+                    3216, 3217, 3227, 3227, 3217, 3228,
+                    3217, 3218, 3228, 3228, 3218, 3229,
+                    3218, 3219, 3229, 3229, 3219, 3230,
+                    3219, 3220, 3230, 3230, 3220, 3231,
+                    3220, 3221, 3231, 3231, 3221, 3232,
+                    3221, 3222, 3232, 3232, 3222, 3233,
+                    3223, 3224, 3234, 3234, 3224, 3235,
+                    3224, 3225, 3235, 3235, 3225, 3236,
+                    3225, 3226, 3236, 3236, 3226, 3237,
+                    3226, 3227, 3237, 3237, 3227, 3238,
+                    3227, 3228, 3238, 3238, 3228, 3239,
+                    3228, 3229, 3239, 3239, 3229, 3240,
+                    3229, 3230, 3240, 3240, 3230, 3241,
+                    3230, 3231, 3241, 3241, 3231, 3242,
+                    3231, 3232, 3242, 3242, 3232, 3243,
+                    3232, 3233, 3243, 3243, 3233, 3244,
+                    3234, 3235, 3245, 3245, 3235, 3246,
+                    3235, 3236, 3246, 3246, 3236, 3247,
+                    3236, 3237, 3247, 3247, 3237, 3248,
+                    3237, 3238, 3248, 3248, 3238, 3249,
+                    3238, 3239, 3249, 3249, 3239, 3250,
+                    3239, 3240, 3250, 3250, 3240, 3251,
+                    3240, 3241, 3251, 3251, 3241, 3252,
+                    3241, 3242, 3252, 3252, 3242, 3253,
+                    3242, 3243, 3253, 3253, 3243, 3254,
+                    3243, 3244, 3254, 3254, 3244, 3255,
+                    3245, 3246, 3256, 3256, 3246, 3257,
+                    3246, 3247, 3257, 3257, 3247, 3258,
+                    3247, 3248, 3258, 3258, 3248, 3259,
+                    3248, 3249, 3259, 3259, 3249, 3260,
+                    3249, 3250, 3260, 3260, 3250, 3261,
+                    3250, 3251, 3261, 3261, 3251, 3262,
+                    3251, 3252, 3262, 3262, 3252, 3263,
+                    3252, 3253, 3263, 3263, 3253, 3264,
+                    3253, 3254, 3264, 3264, 3254, 3265,
+                    3254, 3255, 3265, 3265, 3255, 3266,
+                    3267, 3268, 3278, 3278, 3268, 3279,
+                    3268, 3269, 3279, 3279, 3269, 3280,
+                    3269, 3270, 3280, 3280, 3270, 3281,
+                    3270, 3271, 3281, 3281, 3271, 3282,
+                    3271, 3272, 3282, 3282, 3272, 3283,
+                    3272, 3273, 3283, 3283, 3273, 3284,
+                    3273, 3274, 3284, 3284, 3274, 3285,
+                    3274, 3275, 3285, 3285, 3275, 3286,
+                    3275, 3276, 3286, 3286, 3276, 3287,
+                    3276, 3277, 3287, 3287, 3277, 3288,
+                    3278, 3279, 3289, 3289, 3279, 3290,
+                    3279, 3280, 3290, 3290, 3280, 3291,
+                    3280, 3281, 3291, 3291, 3281, 3292,
+                    3281, 3282, 3292, 3292, 3282, 3293,
+                    3282, 3283, 3293, 3293, 3283, 3294,
+                    3283, 3284, 3294, 3294, 3284, 3295,
+                    3284, 3285, 3295, 3295, 3285, 3296,
+                    3285, 3286, 3296, 3296, 3286, 3297,
+                    3286, 3287, 3297, 3297, 3287, 3298,
+                    3287, 3288, 3298, 3298, 3288, 3299,
+                    3289, 3290, 3300, 3300, 3290, 3301,
+                    3290, 3291, 3301, 3301, 3291, 3302,
+                    3291, 3292, 3302, 3302, 3292, 3303,
+                    3292, 3293, 3303, 3303, 3293, 3304,
+                    3293, 3294, 3304, 3304, 3294, 3305,
+                    3294, 3295, 3305, 3305, 3295, 3306,
+                    3295, 3296, 3306, 3306, 3296, 3307,
+                    3296, 3297, 3307, 3307, 3297, 3308,
+                    3297, 3298, 3308, 3308, 3298, 3309,
+                    3298, 3299, 3309, 3309, 3299, 3310,
+                    3300, 3301, 3311, 3311, 3301, 3312,
+                    3301, 3302, 3312, 3312, 3302, 3313,
+                    3302, 3303, 3313, 3313, 3303, 3314,
+                    3303, 3304, 3314, 3314, 3304, 3315,
+                    3304, 3305, 3315, 3315, 3305, 3316,
+                    3305, 3306, 3316, 3316, 3306, 3317,
+                    3306, 3307, 3317, 3317, 3307, 3318,
+                    3307, 3308, 3318, 3318, 3308, 3319,
+                    3308, 3309, 3319, 3319, 3309, 3320,
+                    3309, 3310, 3320, 3320, 3310, 3321,
+                    3311, 3312, 3322, 3322, 3312, 3323,
+                    3312, 3313, 3323, 3323, 3313, 3324,
+                    3313, 3314, 3324, 3324, 3314, 3325,
+                    3314, 3315, 3325, 3325, 3315, 3326,
+                    3315, 3316, 3326, 3326, 3316, 3327,
+                    3316, 3317, 3327, 3327, 3317, 3328,
+                    3317, 3318, 3328, 3328, 3318, 3329,
+                    3318, 3319, 3329, 3329, 3319, 3330,
+                    3319, 3320, 3330, 3330, 3320, 3331,
+                    3320, 3321, 3331, 3331, 3321, 3332,
+                    3322, 3323, 3333, 3333, 3323, 3334,
+                    3323, 3324, 3334, 3334, 3324, 3335,
+                    3324, 3325, 3335, 3335, 3325, 3336,
+                    3325, 3326, 3336, 3336, 3326, 3337,
+                    3326, 3327, 3337, 3337, 3327, 3338,
+                    3327, 3328, 3338, 3338, 3328, 3339,
+                    3328, 3329, 3339, 3339, 3329, 3340,
+                    3329, 3330, 3340, 3340, 3330, 3341,
+                    3330, 3331, 3341, 3341, 3331, 3342,
+                    3331, 3332, 3342, 3342, 3332, 3343,
+                    3333, 3334, 3344, 3344, 3334, 3345,
+                    3334, 3335, 3345, 3345, 3335, 3346,
+                    3335, 3336, 3346, 3346, 3336, 3347,
+                    3336, 3337, 3347, 3347, 3337, 3348,
+                    3337, 3338, 3348, 3348, 3338, 3349,
+                    3338, 3339, 3349, 3349, 3339, 3350,
+                    3339, 3340, 3350, 3350, 3340, 3351,
+                    3340, 3341, 3351, 3351, 3341, 3352,
+                    3341, 3342, 3352, 3352, 3342, 3353,
+                    3342, 3343, 3353, 3353, 3343, 3354,
+                    3344, 3345, 3355, 3355, 3345, 3356,
+                    3345, 3346, 3356, 3356, 3346, 3357,
+                    3346, 3347, 3357, 3357, 3347, 3358,
+                    3347, 3348, 3358, 3358, 3348, 3359,
+                    3348, 3349, 3359, 3359, 3349, 3360,
+                    3349, 3350, 3360, 3360, 3350, 3361,
+                    3350, 3351, 3361, 3361, 3351, 3362,
+                    3351, 3352, 3362, 3362, 3352, 3363,
+                    3352, 3353, 3363, 3363, 3353, 3364,
+                    3353, 3354, 3364, 3364, 3354, 3365,
+                    3355, 3356, 3366, 3366, 3356, 3367,
+                    3356, 3357, 3367, 3367, 3357, 3368,
+                    3357, 3358, 3368, 3368, 3358, 3369,
+                    3358, 3359, 3369, 3369, 3359, 3370,
+                    3359, 3360, 3370, 3370, 3360, 3371,
+                    3360, 3361, 3371, 3371, 3361, 3372,
+                    3361, 3362, 3372, 3372, 3362, 3373,
+                    3362, 3363, 3373, 3373, 3363, 3374,
+                    3363, 3364, 3374, 3374, 3364, 3375,
+                    3364, 3365, 3375, 3375, 3365, 3376,
+                    3366, 3367, 3377, 3377, 3367, 3378,
+                    3367, 3368, 3378, 3378, 3368, 3379,
+                    3368, 3369, 3379, 3379, 3369, 3380,
+                    3369, 3370, 3380, 3380, 3370, 3381,
+                    3370, 3371, 3381, 3381, 3371, 3382,
+                    3371, 3372, 3382, 3382, 3372, 3383,
+                    3372, 3373, 3383, 3383, 3373, 3384,
+                    3373, 3374, 3384, 3384, 3374, 3385,
+                    3374, 3375, 3385, 3385, 3375, 3386,
+                    3375, 3376, 3386, 3386, 3376, 3387,
+                    3388, 3389, 3399, 3399, 3389, 3400,
+                    3389, 3390, 3400, 3400, 3390, 3401,
+                    3390, 3391, 3401, 3401, 3391, 3402,
+                    3391, 3392, 3402, 3402, 3392, 3403,
+                    3392, 3393, 3403, 3403, 3393, 3404,
+                    3393, 3394, 3404, 3404, 3394, 3405,
+                    3394, 3395, 3405, 3405, 3395, 3406,
+                    3395, 3396, 3406, 3406, 3396, 3407,
+                    3396, 3397, 3407, 3407, 3397, 3408,
+                    3397, 3398, 3408, 3408, 3398, 3409,
+                    3399, 3400, 3410, 3410, 3400, 3411,
+                    3400, 3401, 3411, 3411, 3401, 3412,
+                    3401, 3402, 3412, 3412, 3402, 3413,
+                    3402, 3403, 3413, 3413, 3403, 3414,
+                    3403, 3404, 3414, 3414, 3404, 3415,
+                    3404, 3405, 3415, 3415, 3405, 3416,
+                    3405, 3406, 3416, 3416, 3406, 3417,
+                    3406, 3407, 3417, 3417, 3407, 3418,
+                    3407, 3408, 3418, 3418, 3408, 3419,
+                    3408, 3409, 3419, 3419, 3409, 3420,
+                    3410, 3411, 3421, 3421, 3411, 3422,
+                    3411, 3412, 3422, 3422, 3412, 3423,
+                    3412, 3413, 3423, 3423, 3413, 3424,
+                    3413, 3414, 3424, 3424, 3414, 3425,
+                    3414, 3415, 3425, 3425, 3415, 3426,
+                    3415, 3416, 3426, 3426, 3416, 3427,
+                    3416, 3417, 3427, 3427, 3417, 3428,
+                    3417, 3418, 3428, 3428, 3418, 3429,
+                    3418, 3419, 3429, 3429, 3419, 3430,
+                    3419, 3420, 3430, 3430, 3420, 3431,
+                    3421, 3422, 3432, 3432, 3422, 3433,
+                    3422, 3423, 3433, 3433, 3423, 3434,
+                    3423, 3424, 3434, 3434, 3424, 3435,
+                    3424, 3425, 3435, 3435, 3425, 3436,
+                    3425, 3426, 3436, 3436, 3426, 3437,
+                    3426, 3427, 3437, 3437, 3427, 3438,
+                    3427, 3428, 3438, 3438, 3428, 3439,
+                    3428, 3429, 3439, 3439, 3429, 3440,
+                    3429, 3430, 3440, 3440, 3430, 3441,
+                    3430, 3431, 3441, 3441, 3431, 3442,
+                    3432, 3433, 3443, 3443, 3433, 3444,
+                    3433, 3434, 3444, 3444, 3434, 3445,
+                    3434, 3435, 3445, 3445, 3435, 3446,
+                    3435, 3436, 3446, 3446, 3436, 3447,
+                    3436, 3437, 3447, 3447, 3437, 3448,
+                    3437, 3438, 3448, 3448, 3438, 3449,
+                    3438, 3439, 3449, 3449, 3439, 3450,
+                    3439, 3440, 3450, 3450, 3440, 3451,
+                    3440, 3441, 3451, 3451, 3441, 3452,
+                    3441, 3442, 3452, 3452, 3442, 3453,
+                    3443, 3444, 3454, 3454, 3444, 3455,
+                    3444, 3445, 3455, 3455, 3445, 3456,
+                    3445, 3446, 3456, 3456, 3446, 3457,
+                    3446, 3447, 3457, 3457, 3447, 3458,
+                    3447, 3448, 3458, 3458, 3448, 3459,
+                    3448, 3449, 3459, 3459, 3449, 3460,
+                    3449, 3450, 3460, 3460, 3450, 3461,
+                    3450, 3451, 3461, 3461, 3451, 3462,
+                    3451, 3452, 3462, 3462, 3452, 3463,
+                    3452, 3453, 3463, 3463, 3453, 3464,
+                    3454, 3455, 3465, 3465, 3455, 3466,
+                    3455, 3456, 3466, 3466, 3456, 3467,
+                    3456, 3457, 3467, 3467, 3457, 3468,
+                    3457, 3458, 3468, 3468, 3458, 3469,
+                    3458, 3459, 3469, 3469, 3459, 3470,
+                    3459, 3460, 3470, 3470, 3460, 3471,
+                    3460, 3461, 3471, 3471, 3461, 3472,
+                    3461, 3462, 3472, 3472, 3462, 3473,
+                    3462, 3463, 3473, 3473, 3463, 3474,
+                    3463, 3464, 3474, 3474, 3464, 3475,
+                    3465, 3466, 3476, 3476, 3466, 3477,
+                    3466, 3467, 3477, 3477, 3467, 3478,
+                    3467, 3468, 3478, 3478, 3468, 3479,
+                    3468, 3469, 3479, 3479, 3469, 3480,
+                    3469, 3470, 3480, 3480, 3470, 3481,
+                    3470, 3471, 3481, 3481, 3471, 3482,
+                    3471, 3472, 3482, 3482, 3472, 3483,
+                    3472, 3473, 3483, 3483, 3473, 3484,
+                    3473, 3474, 3484, 3484, 3474, 3485,
+                    3474, 3475, 3485, 3485, 3475, 3486,
+                    3476, 3477, 3487, 3487, 3477, 3488,
+                    3477, 3478, 3488, 3488, 3478, 3489,
+                    3478, 3479, 3489, 3489, 3479, 3490,
+                    3479, 3480, 3490, 3490, 3480, 3491,
+                    3480, 3481, 3491, 3491, 3481, 3492,
+                    3481, 3482, 3492, 3492, 3482, 3493,
+                    3482, 3483, 3493, 3493, 3483, 3494,
+                    3483, 3484, 3494, 3494, 3484, 3495,
+                    3484, 3485, 3495, 3495, 3485, 3496,
+                    3485, 3486, 3496, 3496, 3486, 3497,
+                    3487, 3488, 3498, 3498, 3488, 3499,
+                    3488, 3489, 3499, 3499, 3489, 3500,
+                    3489, 3490, 3500, 3500, 3490, 3501,
+                    3490, 3491, 3501, 3501, 3491, 3502,
+                    3491, 3492, 3502, 3502, 3492, 3503,
+                    3492, 3493, 3503, 3503, 3493, 3504,
+                    3493, 3494, 3504, 3504, 3494, 3505,
+                    3494, 3495, 3505, 3505, 3495, 3506,
+                    3495, 3496, 3506, 3506, 3496, 3507,
+                    3496, 3497, 3507, 3507, 3497, 3508,
+                    3509, 3510, 3520, 3520, 3510, 3521,
+                    3510, 3511, 3521, 3521, 3511, 3522,
+                    3511, 3512, 3522, 3522, 3512, 3523,
+                    3512, 3513, 3523, 3523, 3513, 3524,
+                    3513, 3514, 3524, 3524, 3514, 3525,
+                    3514, 3515, 3525, 3525, 3515, 3526,
+                    3515, 3516, 3526, 3526, 3516, 3527,
+                    3516, 3517, 3527, 3527, 3517, 3528,
+                    3517, 3518, 3528, 3528, 3518, 3529,
+                    3518, 3519, 3529, 3529, 3519, 3530,
+                    3520, 3521, 3531, 3531, 3521, 3532,
+                    3521, 3522, 3532, 3532, 3522, 3533,
+                    3522, 3523, 3533, 3533, 3523, 3534,
+                    3523, 3524, 3534, 3534, 3524, 3535,
+                    3524, 3525, 3535, 3535, 3525, 3536,
+                    3525, 3526, 3536, 3536, 3526, 3537,
+                    3526, 3527, 3537, 3537, 3527, 3538,
+                    3527, 3528, 3538, 3538, 3528, 3539,
+                    3528, 3529, 3539, 3539, 3529, 3540,
+                    3529, 3530, 3540, 3540, 3530, 3541,
+                    3531, 3532, 3542, 3542, 3532, 3543,
+                    3532, 3533, 3543, 3543, 3533, 3544,
+                    3533, 3534, 3544, 3544, 3534, 3545,
+                    3534, 3535, 3545, 3545, 3535, 3546,
+                    3535, 3536, 3546, 3546, 3536, 3547,
+                    3536, 3537, 3547, 3547, 3537, 3548,
+                    3537, 3538, 3548, 3548, 3538, 3549,
+                    3538, 3539, 3549, 3549, 3539, 3550,
+                    3539, 3540, 3550, 3550, 3540, 3551,
+                    3540, 3541, 3551, 3551, 3541, 3552,
+                    3542, 3543, 3553, 3553, 3543, 3554,
+                    3543, 3544, 3554, 3554, 3544, 3555,
+                    3544, 3545, 3555, 3555, 3545, 3556,
+                    3545, 3546, 3556, 3556, 3546, 3557,
+                    3546, 3547, 3557, 3557, 3547, 3558,
+                    3547, 3548, 3558, 3558, 3548, 3559,
+                    3548, 3549, 3559, 3559, 3549, 3560,
+                    3549, 3550, 3560, 3560, 3550, 3561,
+                    3550, 3551, 3561, 3561, 3551, 3562,
+                    3551, 3552, 3562, 3562, 3552, 3563,
+                    3553, 3554, 3564, 3564, 3554, 3565,
+                    3554, 3555, 3565, 3565, 3555, 3566,
+                    3555, 3556, 3566, 3566, 3556, 3567,
+                    3556, 3557, 3567, 3567, 3557, 3568,
+                    3557, 3558, 3568, 3568, 3558, 3569,
+                    3558, 3559, 3569, 3569, 3559, 3570,
+                    3559, 3560, 3570, 3570, 3560, 3571,
+                    3560, 3561, 3571, 3571, 3561, 3572,
+                    3561, 3562, 3572, 3572, 3562, 3573,
+                    3562, 3563, 3573, 3573, 3563, 3574,
+                    3564, 3565, 3575, 3575, 3565, 3576,
+                    3565, 3566, 3576, 3576, 3566, 3577,
+                    3566, 3567, 3577, 3577, 3567, 3578,
+                    3567, 3568, 3578, 3578, 3568, 3579,
+                    3568, 3569, 3579, 3579, 3569, 3580,
+                    3569, 3570, 3580, 3580, 3570, 3581,
+                    3570, 3571, 3581, 3581, 3571, 3582,
+                    3571, 3572, 3582, 3582, 3572, 3583,
+                    3572, 3573, 3583, 3583, 3573, 3584,
+                    3573, 3574, 3584, 3584, 3574, 3585,
+                    3575, 3576, 3586, 3586, 3576, 3587,
+                    3576, 3577, 3587, 3587, 3577, 3588,
+                    3577, 3578, 3588, 3588, 3578, 3589,
+                    3578, 3579, 3589, 3589, 3579, 3590,
+                    3579, 3580, 3590, 3590, 3580, 3591,
+                    3580, 3581, 3591, 3591, 3581, 3592,
+                    3581, 3582, 3592, 3592, 3582, 3593,
+                    3582, 3583, 3593, 3593, 3583, 3594,
+                    3583, 3584, 3594, 3594, 3584, 3595,
+                    3584, 3585, 3595, 3595, 3585, 3596,
+                    3586, 3587, 3597, 3597, 3587, 3598,
+                    3587, 3588, 3598, 3598, 3588, 3599,
+                    3588, 3589, 3599, 3599, 3589, 3600,
+                    3589, 3590, 3600, 3600, 3590, 3601,
+                    3590, 3591, 3601, 3601, 3591, 3602,
+                    3591, 3592, 3602, 3602, 3592, 3603,
+                    3592, 3593, 3603, 3603, 3593, 3604,
+                    3593, 3594, 3604, 3604, 3594, 3605,
+                    3594, 3595, 3605, 3605, 3595, 3606,
+                    3595, 3596, 3606, 3606, 3596, 3607,
+                    3597, 3598, 3608, 3608, 3598, 3609,
+                    3598, 3599, 3609, 3609, 3599, 3610,
+                    3599, 3600, 3610, 3610, 3600, 3611,
+                    3600, 3601, 3611, 3611, 3601, 3612,
+                    3601, 3602, 3612, 3612, 3602, 3613,
+                    3602, 3603, 3613, 3613, 3603, 3614,
+                    3603, 3604, 3614, 3614, 3604, 3615,
+                    3604, 3605, 3615, 3615, 3605, 3616,
+                    3605, 3606, 3616, 3616, 3606, 3617,
+                    3606, 3607, 3617, 3617, 3607, 3618,
+                    3608, 3609, 3619, 3619, 3609, 3620,
+                    3609, 3610, 3620, 3620, 3610, 3621,
+                    3610, 3611, 3621, 3621, 3611, 3622,
+                    3611, 3612, 3622, 3622, 3612, 3623,
+                    3612, 3613, 3623, 3623, 3613, 3624,
+                    3613, 3614, 3624, 3624, 3614, 3625,
+                    3614, 3615, 3625, 3625, 3615, 3626,
+                    3615, 3616, 3626, 3626, 3616, 3627,
+                    3616, 3617, 3627, 3627, 3617, 3628,
+                    3617, 3618, 3628, 3628, 3618, 3629,
+                    3630, 3631, 3641, 3641, 3631, 3642,
+                    3631, 3632, 3642, 3642, 3632, 3643,
+                    3632, 3633, 3643, 3643, 3633, 3644,
+                    3633, 3634, 3644, 3644, 3634, 3645,
+                    3634, 3635, 3645, 3645, 3635, 3646,
+                    3635, 3636, 3646, 3646, 3636, 3647,
+                    3636, 3637, 3647, 3647, 3637, 3648,
+                    3637, 3638, 3648, 3648, 3638, 3649,
+                    3638, 3639, 3649, 3649, 3639, 3650,
+                    3639, 3640, 3650, 3650, 3640, 3651,
+                    3641, 3642, 3652, 3652, 3642, 3653,
+                    3642, 3643, 3653, 3653, 3643, 3654,
+                    3643, 3644, 3654, 3654, 3644, 3655,
+                    3644, 3645, 3655, 3655, 3645, 3656,
+                    3645, 3646, 3656, 3656, 3646, 3657,
+                    3646, 3647, 3657, 3657, 3647, 3658,
+                    3647, 3648, 3658, 3658, 3648, 3659,
+                    3648, 3649, 3659, 3659, 3649, 3660,
+                    3649, 3650, 3660, 3660, 3650, 3661,
+                    3650, 3651, 3661, 3661, 3651, 3662,
+                    3652, 3653, 3663, 3663, 3653, 3664,
+                    3653, 3654, 3664, 3664, 3654, 3665,
+                    3654, 3655, 3665, 3665, 3655, 3666,
+                    3655, 3656, 3666, 3666, 3656, 3667,
+                    3656, 3657, 3667, 3667, 3657, 3668,
+                    3657, 3658, 3668, 3668, 3658, 3669,
+                    3658, 3659, 3669, 3669, 3659, 3670,
+                    3659, 3660, 3670, 3670, 3660, 3671,
+                    3660, 3661, 3671, 3671, 3661, 3672,
+                    3661, 3662, 3672, 3672, 3662, 3673,
+                    3663, 3664, 3674, 3674, 3664, 3675,
+                    3664, 3665, 3675, 3675, 3665, 3676,
+                    3665, 3666, 3676, 3676, 3666, 3677,
+                    3666, 3667, 3677, 3677, 3667, 3678,
+                    3667, 3668, 3678, 3678, 3668, 3679,
+                    3668, 3669, 3679, 3679, 3669, 3680,
+                    3669, 3670, 3680, 3680, 3670, 3681,
+                    3670, 3671, 3681, 3681, 3671, 3682,
+                    3671, 3672, 3682, 3682, 3672, 3683,
+                    3672, 3673, 3683, 3683, 3673, 3684,
+                    3674, 3675, 3685, 3685, 3675, 3686,
+                    3675, 3676, 3686, 3686, 3676, 3687,
+                    3676, 3677, 3687, 3687, 3677, 3688,
+                    3677, 3678, 3688, 3688, 3678, 3689,
+                    3678, 3679, 3689, 3689, 3679, 3690,
+                    3679, 3680, 3690, 3690, 3680, 3691,
+                    3680, 3681, 3691, 3691, 3681, 3692,
+                    3681, 3682, 3692, 3692, 3682, 3693,
+                    3682, 3683, 3693, 3693, 3683, 3694,
+                    3683, 3684, 3694, 3694, 3684, 3695,
+                    3685, 3686, 3696, 3696, 3686, 3697,
+                    3686, 3687, 3697, 3697, 3687, 3698,
+                    3687, 3688, 3698, 3698, 3688, 3699,
+                    3688, 3689, 3699, 3699, 3689, 3700,
+                    3689, 3690, 3700, 3700, 3690, 3701,
+                    3690, 3691, 3701, 3701, 3691, 3702,
+                    3691, 3692, 3702, 3702, 3692, 3703,
+                    3692, 3693, 3703, 3703, 3693, 3704,
+                    3693, 3694, 3704, 3704, 3694, 3705,
+                    3694, 3695, 3705, 3705, 3695, 3706,
+                    3696, 3697, 3707, 3707, 3697, 3708,
+                    3697, 3698, 3708, 3708, 3698, 3709,
+                    3698, 3699, 3709, 3709, 3699, 3710,
+                    3699, 3700, 3710, 3710, 3700, 3711,
+                    3700, 3701, 3711, 3711, 3701, 3712,
+                    3701, 3702, 3712, 3712, 3702, 3713,
+                    3702, 3703, 3713, 3713, 3703, 3714,
+                    3703, 3704, 3714, 3714, 3704, 3715,
+                    3704, 3705, 3715, 3715, 3705, 3716,
+                    3705, 3706, 3716, 3716, 3706, 3717,
+                    3707, 3708, 3718, 3718, 3708, 3719,
+                    3708, 3709, 3719, 3719, 3709, 3720,
+                    3709, 3710, 3720, 3720, 3710, 3721,
+                    3710, 3711, 3721, 3721, 3711, 3722,
+                    3711, 3712, 3722, 3722, 3712, 3723,
+                    3712, 3713, 3723, 3723, 3713, 3724,
+                    3713, 3714, 3724, 3724, 3714, 3725,
+                    3714, 3715, 3725, 3725, 3715, 3726,
+                    3715, 3716, 3726, 3726, 3716, 3727,
+                    3716, 3717, 3727, 3727, 3717, 3728,
+                    3718, 3719, 3729, 3729, 3719, 3730,
+                    3719, 3720, 3730, 3730, 3720, 3731,
+                    3720, 3721, 3731, 3731, 3721, 3732,
+                    3721, 3722, 3732, 3732, 3722, 3733,
+                    3722, 3723, 3733, 3733, 3723, 3734,
+                    3723, 3724, 3734, 3734, 3724, 3735,
+                    3724, 3725, 3735, 3735, 3725, 3736,
+                    3725, 3726, 3736, 3736, 3726, 3737,
+                    3726, 3727, 3737, 3737, 3727, 3738,
+                    3727, 3728, 3738, 3738, 3728, 3739,
+                    3729, 3730, 3740, 3740, 3730, 3741,
+                    3730, 3731, 3741, 3741, 3731, 3742,
+                    3731, 3732, 3742, 3742, 3732, 3743,
+                    3732, 3733, 3743, 3743, 3733, 3744,
+                    3733, 3734, 3744, 3744, 3734, 3745,
+                    3734, 3735, 3745, 3745, 3735, 3746,
+                    3735, 3736, 3746, 3746, 3736, 3747,
+                    3736, 3737, 3747, 3747, 3737, 3748,
+                    3737, 3738, 3748, 3748, 3738, 3749,
+                    3738, 3739, 3749, 3749, 3739, 3750,
+                    3751, 3752, 3762, 3762, 3752, 3763,
+                    3752, 3753, 3763, 3763, 3753, 3764,
+                    3753, 3754, 3764, 3764, 3754, 3765,
+                    3754, 3755, 3765, 3765, 3755, 3766,
+                    3755, 3756, 3766, 3766, 3756, 3767,
+                    3756, 3757, 3767, 3767, 3757, 3768,
+                    3757, 3758, 3768, 3768, 3758, 3769,
+                    3758, 3759, 3769, 3769, 3759, 3770,
+                    3759, 3760, 3770, 3770, 3760, 3771,
+                    3760, 3761, 3771, 3771, 3761, 3772,
+                    3762, 3763, 3773, 3773, 3763, 3774,
+                    3763, 3764, 3774, 3774, 3764, 3775,
+                    3764, 3765, 3775, 3775, 3765, 3776,
+                    3765, 3766, 3776, 3776, 3766, 3777,
+                    3766, 3767, 3777, 3777, 3767, 3778,
+                    3767, 3768, 3778, 3778, 3768, 3779,
+                    3768, 3769, 3779, 3779, 3769, 3780,
+                    3769, 3770, 3780, 3780, 3770, 3781,
+                    3770, 3771, 3781, 3781, 3771, 3782,
+                    3771, 3772, 3782, 3782, 3772, 3783,
+                    3773, 3774, 3784, 3784, 3774, 3785,
+                    3774, 3775, 3785, 3785, 3775, 3786,
+                    3775, 3776, 3786, 3786, 3776, 3787,
+                    3776, 3777, 3787, 3787, 3777, 3788,
+                    3777, 3778, 3788, 3788, 3778, 3789,
+                    3778, 3779, 3789, 3789, 3779, 3790,
+                    3779, 3780, 3790, 3790, 3780, 3791,
+                    3780, 3781, 3791, 3791, 3781, 3792,
+                    3781, 3782, 3792, 3792, 3782, 3793,
+                    3782, 3783, 3793, 3793, 3783, 3794,
+                    3784, 3785, 3795, 3795, 3785, 3796,
+                    3785, 3786, 3796, 3796, 3786, 3797,
+                    3786, 3787, 3797, 3797, 3787, 3798,
+                    3787, 3788, 3798, 3798, 3788, 3799,
+                    3788, 3789, 3799, 3799, 3789, 3800,
+                    3789, 3790, 3800, 3800, 3790, 3801,
+                    3790, 3791, 3801, 3801, 3791, 3802,
+                    3791, 3792, 3802, 3802, 3792, 3803,
+                    3792, 3793, 3803, 3803, 3793, 3804,
+                    3793, 3794, 3804, 3804, 3794, 3805,
+                    3795, 3796, 3806, 3806, 3796, 3807,
+                    3796, 3797, 3807, 3807, 3797, 3808,
+                    3797, 3798, 3808, 3808, 3798, 3809,
+                    3798, 3799, 3809, 3809, 3799, 3810,
+                    3799, 3800, 3810, 3810, 3800, 3811,
+                    3800, 3801, 3811, 3811, 3801, 3812,
+                    3801, 3802, 3812, 3812, 3802, 3813,
+                    3802, 3803, 3813, 3813, 3803, 3814,
+                    3803, 3804, 3814, 3814, 3804, 3815,
+                    3804, 3805, 3815, 3815, 3805, 3816,
+                    3806, 3807, 3817, 3817, 3807, 3818,
+                    3807, 3808, 3818, 3818, 3808, 3819,
+                    3808, 3809, 3819, 3819, 3809, 3820,
+                    3809, 3810, 3820, 3820, 3810, 3821,
+                    3810, 3811, 3821, 3821, 3811, 3822,
+                    3811, 3812, 3822, 3822, 3812, 3823,
+                    3812, 3813, 3823, 3823, 3813, 3824,
+                    3813, 3814, 3824, 3824, 3814, 3825,
+                    3814, 3815, 3825, 3825, 3815, 3826,
+                    3815, 3816, 3826, 3826, 3816, 3827,
+                    3817, 3818, 3828, 3828, 3818, 3829,
+                    3818, 3819, 3829, 3829, 3819, 3830,
+                    3819, 3820, 3830, 3830, 3820, 3831,
+                    3820, 3821, 3831, 3831, 3821, 3832,
+                    3821, 3822, 3832, 3832, 3822, 3833,
+                    3822, 3823, 3833, 3833, 3823, 3834,
+                    3823, 3824, 3834, 3834, 3824, 3835,
+                    3824, 3825, 3835, 3835, 3825, 3836,
+                    3825, 3826, 3836, 3836, 3826, 3837,
+                    3826, 3827, 3837, 3837, 3827, 3838,
+                    3828, 3829, 3839, 3839, 3829, 3840,
+                    3829, 3830, 3840, 3840, 3830, 3841,
+                    3830, 3831, 3841, 3841, 3831, 3842,
+                    3831, 3832, 3842, 3842, 3832, 3843,
+                    3832, 3833, 3843, 3843, 3833, 3844,
+                    3833, 3834, 3844, 3844, 3834, 3845,
+                    3834, 3835, 3845, 3845, 3835, 3846,
+                    3835, 3836, 3846, 3846, 3836, 3847,
+                    3836, 3837, 3847, 3847, 3837, 3848,
+                    3837, 3838, 3848, 3848, 3838, 3849,
+                    3839, 3840, 3850, 3850, 3840, 3851,
+                    3840, 3841, 3851, 3851, 3841, 3852,
+                    3841, 3842, 3852, 3852, 3842, 3853,
+                    3842, 3843, 3853, 3853, 3843, 3854,
+                    3843, 3844, 3854, 3854, 3844, 3855,
+                    3844, 3845, 3855, 3855, 3845, 3856,
+                    3845, 3846, 3856, 3856, 3846, 3857,
+                    3846, 3847, 3857, 3857, 3847, 3858,
+                    3847, 3848, 3858, 3858, 3848, 3859,
+                    3848, 3849, 3859, 3859, 3849, 3860,
+                    3850, 3851, 3861, 3861, 3851, 3862,
+                    3851, 3852, 3862, 3862, 3852, 3863,
+                    3852, 3853, 3863, 3863, 3853, 3864,
+                    3853, 3854, 3864, 3864, 3854, 3865,
+                    3854, 3855, 3865, 3865, 3855, 3866,
+                    3855, 3856, 3866, 3866, 3856, 3867,
+                    3856, 3857, 3867, 3867, 3857, 3868,
+                    3857, 3858, 3868, 3868, 3858, 3869,
+                    3858, 3859, 3869, 3869, 3859, 3870,
+                    3859, 3860, 3870, 3870, 3860, 3871,
+            };
+
+};
diff --git a/projects/sph/.gitignore b/projects/sph/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..3968dc30067c92efde65779bc5eebecf167dfc75
--- /dev/null
+++ b/projects/sph/.gitignore
@@ -0,0 +1 @@
+sph
\ No newline at end of file
diff --git a/projects/sph/CMakeLists.txt b/projects/sph/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..685004f7be9d8f4ef3e0409543dbb0f964d55e9c
--- /dev/null
+++ b/projects/sph/CMakeLists.txt
@@ -0,0 +1,31 @@
+cmake_minimum_required(VERSION 3.16)
+project(sph)
+
+# setting c++ standard for the project
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+# adding source files to the project
+add_project(sph
+		src/main.cpp
+		src/Particle.hpp 
+		src/Particle.cpp
+		src/PipelineInit.hpp
+		src/PipelineInit.cpp)
+
+# including headers of dependencies and the VkCV framework
+target_include_directories(sph SYSTEM BEFORE PRIVATE
+		${vkcv_include}
+		${vkcv_includes}
+		${vkcv_testing_include}
+		${vkcv_camera_include}
+		${vkcv_shader_compiler_include}
+		${vkcv_effects_include})
+
+# linking with libraries from all dependencies and the VkCV framework
+target_link_libraries(sph
+		vkcv
+		vkcv_testing
+		vkcv_camera
+		vkcv_shader_compiler
+		vkcv_effects)
diff --git a/projects/sph/README.md b/projects/sph/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..90e0452998158031b10022407cd3312f54beea9a
--- /dev/null
+++ b/projects/sph/README.md
@@ -0,0 +1,10 @@
+# SPH
+An example project to show the implementation of an SPH fluid simulation with the VkCV framework
+
+![Screenshot](../../screenshots/sph.png)
+
+## Details
+
+The project shows off an example implementation of an SPH fluid simulation which are widely used
+in computer graphics to simulate the behavior of fluids with a discrete amount of particles. The
+implementation is able to run in realtime allowing interactive use.
diff --git a/projects/sph/shaders/flip.comp b/projects/sph/shaders/flip.comp
new file mode 100644
index 0000000000000000000000000000000000000000..f5cd7bf3ea5ed8d04de970c816989230421c0b52
--- /dev/null
+++ b/projects/sph/shaders/flip.comp
@@ -0,0 +1,53 @@
+#version 450 core
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+layout(local_size_x = 256) in;
+
+struct Particle
+{
+    vec3 position;
+    float padding;
+    vec3 velocity;
+    float density;
+    vec3 force;
+    float pressure;
+};
+
+layout(std430, binding = 1) readonly buffer buffer_inParticle
+{
+    Particle inParticle[];
+};
+
+layout(std430, binding = 0) writeonly buffer buffer_outParticle
+{
+    Particle outParticle[];
+};
+
+layout( push_constant ) uniform constants{
+    float h;
+    float mass;
+    float gasConstant;
+    float offset;
+    float gravity;
+    float viscosity;
+    float ABSORBTION;
+    float dt;
+    vec3 gravityDir;
+    float particleCount;
+};
+
+void main() {
+    uint id = gl_GlobalInvocationID.x;
+
+    if(id >= int(particleCount))
+    {
+        return;
+    }
+    
+    outParticle[id].force = inParticle[id].force;
+    outParticle[id].density = inParticle[id].density;
+    outParticle[id].pressure = inParticle[id].pressure;
+    outParticle[id].position =  inParticle[id].position;
+    outParticle[id].velocity =  inParticle[id].velocity;
+}
diff --git a/projects/sph/shaders/force.comp b/projects/sph/shaders/force.comp
new file mode 100644
index 0000000000000000000000000000000000000000..694ddbdf7009d4e493a5f8dd8ba462e42d1e8b68
--- /dev/null
+++ b/projects/sph/shaders/force.comp
@@ -0,0 +1,152 @@
+#version 450 core
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+const float PI = 3.1415926535897932384626433832795;
+
+layout(local_size_x = 256) in;
+
+struct Particle
+{
+    vec3 position;
+    float padding;
+    vec3 velocity;
+    float density;
+    vec3 force;
+    float pressure;
+    
+};
+
+layout(std430, binding = 1) readonly buffer buffer_inParticle
+{
+    Particle inParticle[];
+};
+
+layout(std430, binding = 0) writeonly buffer buffer_outParticle
+{
+    Particle outParticle[];
+};
+
+layout( push_constant ) uniform constants{
+    float h;
+    float mass;
+    float gasConstant;
+    float offset;
+    float gravity;
+    float viscosity;
+    float ABSORBTION;
+    float dt;
+    vec3 gravityDir;
+    float particleCount;
+};
+
+float grad_spiky(float r)
+{
+    return -45.f / (PI * pow(h, 6)) * pow((h-r), 2);
+}
+
+float laplacian(float r)
+{
+    return (45.f / (PI * pow(h,6)) * (h - r));
+}
+
+vec3 pressureForce = vec3(0, 0, 0);
+vec3 viscosityForce = vec3(0, 0, 0); 
+vec3 externalForce = vec3(0, 0, 0);
+
+struct ParticleData
+{
+    vec3 position;
+    float density;
+    vec3 velocity;
+    float pressure;
+};
+
+shared ParticleData particle_data [256];
+
+void main() {
+    uint id = gl_GlobalInvocationID.x;
+
+    if (id >= int(particleCount)) {
+        particle_data[gl_LocalInvocationIndex].position = vec3(0.0f);
+        particle_data[gl_LocalInvocationIndex].density  = 0.0f;
+        particle_data[gl_LocalInvocationIndex].velocity = vec3(0.0f);
+        particle_data[gl_LocalInvocationIndex].pressure = 0.0f;
+    } else {
+        particle_data[gl_LocalInvocationIndex].position = inParticle[id].position;
+        particle_data[gl_LocalInvocationIndex].density  = inParticle[id].density;
+        particle_data[gl_LocalInvocationIndex].velocity = inParticle[id].velocity;
+        particle_data[gl_LocalInvocationIndex].pressure = inParticle[id].pressure;
+    }
+
+    uint index_offset = gl_WorkGroupID.x * gl_WorkGroupSize.x;
+    uint group_size = min(index_offset + gl_WorkGroupSize.x, int(particleCount)) - index_offset;
+
+    memoryBarrierShared();
+    barrier();
+
+    const float h6 = pow(h, 6);
+    externalForce = particle_data[gl_LocalInvocationIndex].density * gravity * vec3(-gravityDir.x,gravityDir.y,gravityDir.z);
+
+    for(uint j = 1; j < group_size; j++) {
+        uint i = (gl_LocalInvocationIndex + j) % group_size;
+
+        vec3 dir = particle_data[gl_LocalInvocationIndex].position - particle_data[i].position;
+        float dist = length(dir);
+
+        if ((dist > 0.0f) && (dist <= h))
+        {
+            const float h_dist = (h - dist);
+
+            float laplacian = 45.f / (PI * h6) * h_dist;
+            float grad_spiky = -1.0f * laplacian * h_dist;
+
+            pressureForce += mass * -(particle_data[gl_LocalInvocationIndex].pressure + particle_data[i].pressure)/(2.f * particle_data[i].density) * grad_spiky * normalize(dir);
+            viscosityForce += mass * (particle_data[i].velocity - particle_data[gl_LocalInvocationIndex].velocity)/particle_data[i].density * laplacian;
+        }
+    }
+
+    for(uint i = 0; i < index_offset; i++)
+    {
+        vec3 dir = particle_data[gl_LocalInvocationIndex].position - inParticle[i].position;
+        float dist = length(dir);
+
+        if ((dist > 0.0f) && (dist <= h))
+        {
+            const float h_dist = (h - dist);
+
+            float laplacian = 45.f / (PI * h6) * h_dist;
+            float grad_spiky = -1.0f * laplacian * h_dist;
+
+            pressureForce += mass * -(particle_data[gl_LocalInvocationIndex].pressure + inParticle[i].pressure)/(2.f * inParticle[i].density) * grad_spiky * normalize(dir);
+            viscosityForce += mass * (inParticle[i].velocity - particle_data[gl_LocalInvocationIndex].velocity)/inParticle[i].density * laplacian;
+        }
+    }
+
+    for(uint i = index_offset + group_size; i < int(particleCount); i++)
+    {
+        vec3 dir = particle_data[gl_LocalInvocationIndex].position - inParticle[i].position;
+        float dist = length(dir);
+
+        if ((dist > 0.0f) && (dist <= h))
+        {
+            const float h_dist = (h - dist);
+
+            float laplacian = 45.f / (PI * h6) * h_dist;
+            float grad_spiky = -1.0f * laplacian * h_dist;
+
+            pressureForce += mass * -(particle_data[gl_LocalInvocationIndex].pressure + inParticle[i].pressure)/(2.f * inParticle[i].density) * grad_spiky * normalize(dir);
+            viscosityForce += mass * (inParticle[i].velocity - particle_data[gl_LocalInvocationIndex].velocity)/inParticle[i].density * laplacian;
+        }
+    }
+
+    viscosityForce *= viscosity;
+
+    if (id < int(particleCount)) {
+        outParticle[id].force = externalForce + pressureForce + viscosityForce;
+        outParticle[id].density = particle_data[gl_LocalInvocationIndex].density;
+        outParticle[id].pressure = particle_data[gl_LocalInvocationIndex].pressure;
+        outParticle[id].position = particle_data[gl_LocalInvocationIndex].position;
+        outParticle[id].velocity = particle_data[gl_LocalInvocationIndex].velocity;
+    }
+}
diff --git a/projects/sph/shaders/particleShading.inc b/projects/sph/shaders/particleShading.inc
new file mode 100644
index 0000000000000000000000000000000000000000..b2d1832b9ccd6ba05a585b59bdfdedd4729e80f8
--- /dev/null
+++ b/projects/sph/shaders/particleShading.inc
@@ -0,0 +1,6 @@
+float circleFactor(vec2 triangleCoordinates){
+    // percentage of distance from center to circle edge
+    float p = clamp((0.4 - length(triangleCoordinates)) / 0.4, 0, 1);
+    // remapping for nice falloff
+    return sqrt(p);
+}
\ No newline at end of file
diff --git a/projects/sph/shaders/particle_params.inc b/projects/sph/shaders/particle_params.inc
new file mode 100644
index 0000000000000000000000000000000000000000..e35cce13be1bc84f045da276fe46666d0b852984
--- /dev/null
+++ b/projects/sph/shaders/particle_params.inc
@@ -0,0 +1,8 @@
+#define h 0.4
+#define mass 0.15
+#define gasConstant 3000
+#define offset 1800
+#define gravity -1000
+#define viscosity 1500
+#define ABSORBTION 0.9
+#define dt 0.0005
diff --git a/projects/sph/shaders/pressure.comp b/projects/sph/shaders/pressure.comp
new file mode 100644
index 0000000000000000000000000000000000000000..8fa2e4762bddb3b9b28d8a3c184ceaaf7ab4421c
--- /dev/null
+++ b/projects/sph/shaders/pressure.comp
@@ -0,0 +1,94 @@
+#version 450 core
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+const float PI = 3.1415926535897932384626433832795;
+
+layout(local_size_x = 256) in;
+
+struct Particle
+{
+    vec3 position;
+    float padding;
+    vec3 velocity;
+    float density;
+    vec3 force;
+    float pressure;
+    
+};
+
+layout(std430, binding = 0) readonly buffer buffer_inParticle
+{
+    Particle inParticle[];
+};
+
+layout(std430, binding = 1) writeonly buffer buffer_outParticle
+{
+    Particle outParticle[];
+};
+
+layout( push_constant ) uniform constants{
+    float h;
+    float mass;
+    float gasConstant;
+    float offset;
+    float gravity;
+    float viscosity;
+    float ABSORBTION;
+    float dt;
+    vec3 gravityDir;
+    float particleCount;
+};
+
+float poly6(float r)    
+{
+    return (315.f * pow((pow(h,2)-pow(r,2)), 3)/(64.f*PI*pow(h, 9))) * int(r<=h);
+}
+
+float densitySum = 0.f;
+
+shared vec3 position_data [256];
+
+void main() {
+    
+    uint id = gl_GlobalInvocationID.x;
+
+    if (id >= int(particleCount)) {
+        position_data[gl_LocalInvocationIndex] = vec3(0.0f);
+    } else {
+        position_data[gl_LocalInvocationIndex] = inParticle[id].position;
+    }
+
+    uint index_offset = gl_WorkGroupID.x * gl_WorkGroupSize.x;
+    uint group_size = min(index_offset + gl_WorkGroupSize.x, int(particleCount)) - index_offset;
+
+    memoryBarrierShared();
+    barrier();
+
+    for(uint j = 1; j < group_size; j++) {
+        uint i = (gl_LocalInvocationIndex + j) % group_size;
+
+        float dist = distance(position_data[gl_LocalInvocationIndex], position_data[i]);
+        densitySum += mass * poly6(dist);
+    }
+
+    for(uint i = 0; i < index_offset; i++)
+    {
+        float dist = distance(position_data[gl_LocalInvocationIndex], inParticle[i].position);
+        densitySum += mass * poly6(dist);
+    }
+
+    for(uint i = index_offset + group_size; i < int(particleCount); i++)
+    {
+        float dist = distance(position_data[gl_LocalInvocationIndex], inParticle[i].position);
+        densitySum += mass * poly6(dist);
+    }
+
+    if (id < int(particleCount)) {
+        outParticle[id].density = max(densitySum, 0.0000001f);
+        outParticle[id].pressure = max((densitySum - offset), 0.0000001f) * gasConstant;
+        outParticle[id].position = position_data[gl_LocalInvocationIndex];
+        outParticle[id].velocity = inParticle[id].velocity;
+        outParticle[id].force = inParticle[id].force;
+    }
+}
diff --git a/projects/sph/shaders/shader.vert b/projects/sph/shaders/shader.vert
new file mode 100644
index 0000000000000000000000000000000000000000..f5531ffa4f26d3652e8e35971c16af6dda2e3b45
--- /dev/null
+++ b/projects/sph/shaders/shader.vert
@@ -0,0 +1,49 @@
+#version 460 core
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 particle;
+
+struct Particle
+{
+    vec3 position;
+    float padding;
+    vec3 velocity;
+    float density;
+    vec3 force;
+    float pressure;
+};
+
+layout(std430, binding = 2) readonly buffer buffer_inParticle1
+{
+    Particle inParticle1[];
+};
+
+layout(std430, binding = 3) readonly buffer buffer_inParticle2
+{
+    Particle inParticle2[];
+};
+
+layout( push_constant ) uniform constants{
+    mat4 view;
+    mat4 projection;
+};
+
+layout(location = 0) out vec2 passTriangleCoordinates;
+layout(location = 1) out vec3 passVelocity;
+
+void main()
+{
+    int id = gl_InstanceIndex;
+    passVelocity = inParticle1[id].velocity;
+    
+    // particle position in view space
+    vec4 positionView = view * vec4(inParticle1[id].position, 1);
+    // by adding the triangle position in view space the mesh is always camera facing
+    positionView.xyz += particle;
+    // multiply with projection matrix for final position
+	gl_Position = projection * positionView;
+    
+    // 0.01 corresponds to vertex position size in main
+    float normalizationDivider  = 0.012;
+    passTriangleCoordinates     = particle.xy / normalizationDivider;
+}
\ No newline at end of file
diff --git a/projects/sph/shaders/shader_water.frag b/projects/sph/shaders/shader_water.frag
new file mode 100644
index 0000000000000000000000000000000000000000..2aee4ec692a2ada060a77389099b2c279e9c338c
--- /dev/null
+++ b/projects/sph/shaders/shader_water.frag
@@ -0,0 +1,28 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+#include "particleShading.inc"
+
+layout(location = 0) in vec2 passTriangleCoordinates;
+layout(location = 1) in vec3 passVelocity;
+
+layout(location = 0) out vec3 outColor;
+
+layout(set=0, binding=0) uniform uColor {
+	vec4 color;
+} Color;
+
+layout(set=0,binding=1) uniform uPosition{
+	vec2 position;
+} Position;
+
+void main()
+{
+    float p = length(passVelocity)/100.f;
+    outColor = vec3(0.f+p/3.f, 0.05f+p/2.f, 0.4f+p);
+
+    // make the triangle look like a circle
+   outColor *= circleFactor(passTriangleCoordinates);
+
+}
diff --git a/projects/sph/shaders/tonemapping.comp b/projects/sph/shaders/tonemapping.comp
new file mode 100644
index 0000000000000000000000000000000000000000..26f0232d66e3475afdd1266c0cc6288b47ed1c38
--- /dev/null
+++ b/projects/sph/shaders/tonemapping.comp
@@ -0,0 +1,19 @@
+#version 440
+
+layout(set=0, binding=0, rgba16f)   uniform image2D inImage;
+layout(set=0, binding=1, rgba8)     uniform image2D outImage;
+
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+void main(){
+
+    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(inImage)))){
+        return;
+    }
+    ivec2 uv            = ivec2(gl_GlobalInvocationID.xy);
+    vec3 linearColor    = imageLoad(inImage, uv).rgb;
+    vec3 tonemapped     = linearColor / (dot(linearColor, vec3(0.21, 0.71, 0.08)) + 1); // reinhard tonemapping
+    vec3 gammaCorrected = pow(tonemapped, vec3(1.f / 2.2f));
+    imageStore(outImage, uv, vec4(gammaCorrected, 0.f));
+}
\ No newline at end of file
diff --git a/projects/sph/shaders/updateData.comp b/projects/sph/shaders/updateData.comp
new file mode 100644
index 0000000000000000000000000000000000000000..3c2321b0d2fcfdd7e6691da2df7e7609af8eb6cf
--- /dev/null
+++ b/projects/sph/shaders/updateData.comp
@@ -0,0 +1,101 @@
+#version 450 core
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+layout(local_size_x = 256) in;
+
+struct Particle
+{
+    vec3 position;
+    float padding;
+    vec3 velocity;
+    float density;
+    vec3 force;
+    float pressure;
+    
+};
+
+layout(std430, binding = 0) readonly buffer buffer_inParticle
+{
+    Particle inParticle[];
+};
+
+layout(std430, binding = 1) writeonly buffer buffer_outParticle
+{
+    Particle outParticle[];
+};
+
+layout( push_constant ) uniform constants{
+    float h;
+    float mass;
+    float gasConstant;
+    float offset;
+    float gravity;
+    float viscosity;
+    float ABSORBTION;
+    float dt;
+    vec3 gravityDir;
+    float particleCount;
+};
+
+void main() {
+
+    uint id = gl_GlobalInvocationID.x;
+
+    if(id >= int(particleCount))
+    {
+        return;
+    }
+
+    vec3 accel = inParticle[id].force / inParticle[id].density;
+    vec3 vel_new = inParticle[id].velocity + (dt * accel);
+
+    vec3 out_force = inParticle[id].force;
+    float out_density = inParticle[id].density;
+    float out_pressure = inParticle[id].pressure;
+
+    vec3 pos_new = inParticle[id].position + (dt * vel_new);
+
+    // Überprüfe Randbedingungen x
+    if (inParticle[id].position.x < -1.0)
+    {
+        vel_new = reflect(vel_new.xyz, vec3(1.f,0.f,0.f)) * ABSORBTION;
+        pos_new.x = -1.0 + 0.01f;
+    }
+    else if (inParticle[id].position.x > 1.0)
+    {
+        vel_new = reflect(vel_new,vec3(1.f,0.f,0.f)) * ABSORBTION;
+        pos_new.x = 1.0 - 0.01f;
+    }
+
+    // Überprüfe Randbedingungen y
+    if (inParticle[id].position.y < -1.0)
+    {
+        vel_new = reflect(vel_new,vec3(0.f,1.f,0.f)) * ABSORBTION;
+        pos_new.y = -1.0 + 0.01f;
+
+    }
+    else if (inParticle[id].position.y > 1.0)
+    {
+        vel_new = reflect(vel_new,vec3(0.f,1.f,0.f)) * ABSORBTION;
+        pos_new.y = 1.0 - 0.01f;
+    }
+
+    // Überprüfe Randbedingungen z
+    if (inParticle[id].position.z < -1.0 )
+    {
+        vel_new = reflect(vel_new,vec3(0.f,0.f,1.f)) * ABSORBTION;
+        pos_new.z = -1.0 + 0.01f;
+    }
+    else if (inParticle[id].position.z > 1.0 )
+    {
+        vel_new = reflect(vel_new,vec3(0.f,0.f,1.f)) * ABSORBTION;
+        pos_new.z = 1.0 - 0.01f;
+    }
+
+    outParticle[id].force = out_force;
+    outParticle[id].density = out_density;
+    outParticle[id].pressure = out_pressure;
+    outParticle[id].position = pos_new;
+    outParticle[id].velocity = vel_new;
+}
diff --git a/projects/sph/src/Particle.cpp b/projects/sph/src/Particle.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..329236989b7cab502e7a4e1bb5aa27869bed53cb
--- /dev/null
+++ b/projects/sph/src/Particle.cpp
@@ -0,0 +1,39 @@
+
+#include "Particle.hpp"
+
+Particle::Particle(glm::vec3 position, glm::vec3 velocity)
+: m_position(position),
+  m_velocity(velocity)
+{
+    m_density = 0.f;
+    m_force = glm::vec3(0.f);
+    m_pressure = 0.f;
+}
+
+const glm::vec3& Particle::getPosition()const{
+    return m_position;
+}
+
+void Particle::setPosition( const glm::vec3 pos ){
+    m_position = pos;
+}
+
+const glm::vec3& Particle::getVelocity()const{
+    return m_velocity;
+}
+
+void Particle::setVelocity( const glm::vec3 vel ){
+    m_velocity = vel;
+}
+
+const float& Particle::getDensity()const {
+    return m_density;
+}
+
+const glm::vec3& Particle::getForce()const {
+    return m_force;
+}
+
+const float& Particle::getPressure()const {
+    return m_pressure;
+}
\ No newline at end of file
diff --git a/projects/sph/src/Particle.hpp b/projects/sph/src/Particle.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6c4ab50b74cc4544976318c23e36f4b91989ee66
--- /dev/null
+++ b/projects/sph/src/Particle.hpp
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <glm/glm.hpp>
+
+class Particle {
+
+public:
+    Particle(glm::vec3 position, glm::vec3 velocity);
+
+    const glm::vec3& getPosition()const;
+
+    void setPosition( const glm::vec3 pos );
+
+    const glm::vec3& getVelocity()const;
+
+    void setVelocity( const glm::vec3 vel );
+
+    const float& getDensity()const;
+
+    const glm::vec3& getForce()const;
+
+    const float& getPressure()const;
+
+
+
+private:
+    // all properties of the Particle
+    glm::vec3 m_position;
+    float m_padding1;
+    glm::vec3 m_velocity;
+    float m_density;
+    glm::vec3 m_force;
+    float m_pressure;
+};
diff --git a/projects/sph/src/PipelineInit.cpp b/projects/sph/src/PipelineInit.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e507f1edebf6e39fb65bf81dbd4cf4915602313b
--- /dev/null
+++ b/projects/sph/src/PipelineInit.cpp
@@ -0,0 +1,28 @@
+#include "PipelineInit.hpp"
+
+vkcv::DescriptorSetHandle PipelineInit::ComputePipelineInit(vkcv::Core *pCore, vkcv::ShaderStage shaderStage, std::filesystem::path includePath,
+                                vkcv::ComputePipelineHandle &pipeline) {
+    vkcv::ShaderProgram shaderProgram;
+    vkcv::shader::GLSLCompiler compiler;
+    compiler.compile(shaderStage, includePath,
+                     [&](vkcv::ShaderStage shaderStage1, const std::filesystem::path& path1) {shaderProgram.addShader(shaderStage1, path1);
+    });
+    vkcv::DescriptorSetLayoutHandle descriptorSetLayout = pCore->createDescriptorSetLayout(
+            shaderProgram.getReflectedDescriptors().at(0));
+    vkcv::DescriptorSetHandle descriptorSet = pCore->createDescriptorSet(descriptorSetLayout);
+
+    const std::vector<vkcv::VertexAttachment> vertexAttachments = shaderProgram.getVertexAttachments();
+
+    std::vector<vkcv::VertexBinding> bindings;
+    for (size_t i = 0; i < vertexAttachments.size(); i++) {
+        bindings.push_back(vkcv::createVertexBinding(i, { vertexAttachments[i] }));
+    }
+    const vkcv::VertexLayout layout { bindings };
+
+    pipeline = pCore->createComputePipeline({
+            shaderProgram,
+            { descriptorSetLayout }
+	});
+
+    return  descriptorSet;
+}
\ No newline at end of file
diff --git a/projects/sph/src/PipelineInit.hpp b/projects/sph/src/PipelineInit.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..54979cfdaffb2aba0915f87552e75e6d2ad3a958
--- /dev/null
+++ b/projects/sph/src/PipelineInit.hpp
@@ -0,0 +1,20 @@
+#pragma once
+#include <vkcv/Core.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+#include <fstream>
+
+class PipelineInit{
+public:
+    /**
+     * Helperfunction to initialize a compute Pipeline. Goal is to reduce repetitive code in main.
+     * @param pCore Pointer to core object
+     * @param shaderStage Type of shaderstage - currently only supports COMPUTE
+     * @param includePath filepath to shaderprogram
+     * @param pipeline handle of the pipeline that is to be initialized. This handle is replaced with the handle of the generated pipeline
+     * @return returns the descriptorset handle from the generated descriptorset of the reflected shader
+     */
+    static vkcv::DescriptorSetHandle ComputePipelineInit(vkcv::Core *pCore,
+                                                         vkcv::ShaderStage shaderStage,
+                                                         std::filesystem::path includePath,
+                                                         vkcv::ComputePipelineHandle& pipeline);
+};
diff --git a/projects/sph/src/main.cpp b/projects/sph/src/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..989201a996757e79f8fe29e78f8517c1354a14dc
--- /dev/null
+++ b/projects/sph/src/main.cpp
@@ -0,0 +1,429 @@
+#include <iostream>
+#include <vkcv/Core.hpp>
+#include <vkcv/Buffer.hpp>
+#include <vkcv/Pass.hpp>
+#include <vkcv/camera/CameraManager.hpp>
+#include <chrono>
+#include <random>
+#include <ctime>
+#include <vkcv/shader/GLSLCompiler.hpp>
+#include <vkcv/effects/BloomAndFlaresEffect.hpp>
+#include "PipelineInit.hpp"
+#include "Particle.hpp"
+
+int main(int argc, const char **argv) {
+    const std::string applicationName = "SPH";
+
+    // creating core object that will handle all vulkan objects
+    vkcv::Core core = vkcv::Core::create(
+        applicationName,
+        VK_MAKE_VERSION(0, 0, 1),
+        { vk::QueueFlagBits::eTransfer, vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },
+        { VK_KHR_SWAPCHAIN_EXTENSION_NAME }
+    );
+
+    vkcv::WindowHandle windowHandle = core.createWindow(applicationName, 1280, 720, true);
+    vkcv::Window& window = core.getWindow(windowHandle);
+
+    vkcv::camera::CameraManager cameraManager(window);
+
+    auto particleIndexBuffer = vkcv::buffer<uint16_t>(core, vkcv::BufferType::INDEX, 3);
+    uint16_t indices[3] = {0, 1, 2};
+    particleIndexBuffer.fill(&indices[0], sizeof(indices));
+
+    vk::Format colorFormat = vk::Format::eR16G16B16A16Sfloat;
+    vkcv::PassHandle particlePass = vkcv::passFormat(core, colorFormat);
+
+    //rotation
+    float rotationx = 0;
+    float rotationy = 0;
+
+    // params  
+    float param_h = 0.20;
+    float param_mass = 0.03;
+    float param_gasConstant = 3500;
+    float param_offset = 200;
+    float param_gravity = -5000;
+    float param_viscosity = 10;
+    float param_ABSORBTION = 0.5;
+    float param_dt = 0.0005;
+
+    if (!particlePass)
+    {
+        std::cout << "Error. Could not create renderpass. Exiting." << std::endl;
+        return EXIT_FAILURE;
+    }
+	vkcv::shader::GLSLCompiler compiler;
+
+    // pressure shader --> computes the pressure for all particles
+    vkcv::ComputePipelineHandle pressurePipeline;
+    vkcv::DescriptorSetHandle pressureDescriptorSet = PipelineInit::ComputePipelineInit(&core, vkcv::ShaderStage::COMPUTE,
+                                                                                        "shaders/pressure.comp", pressurePipeline);
+    // force shader --> computes the force that effects the particles
+    vkcv::ComputePipelineHandle forcePipeline;
+    vkcv::DescriptorSetHandle forceDescriptorSet = PipelineInit::ComputePipelineInit(&core, vkcv::ShaderStage::COMPUTE,
+                                                                                     "shaders/force.comp", forcePipeline);
+
+    // update data shader --> applies the force on all particles and updates their position
+    vkcv::ComputePipelineHandle updateDataPipeline;
+    vkcv::DescriptorSetHandle updateDataDescriptorSet = PipelineInit::ComputePipelineInit(&core, vkcv::ShaderStage::COMPUTE,
+                                                                                          "shaders/updateData.comp", updateDataPipeline);
+
+    // flip shader --> flips input and output buffer
+    vkcv::ComputePipelineHandle flipPipeline;
+    vkcv::DescriptorSetHandle flipDescriptorSet = PipelineInit::ComputePipelineInit(&core, vkcv::ShaderStage::COMPUTE,
+                                                                                    "shaders/flip.comp", flipPipeline);
+
+    // particle rendering shaders
+    vkcv::ShaderProgram particleShaderProgram{};
+    compiler.compile(vkcv::ShaderStage::VERTEX, "shaders/shader.vert", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+        particleShaderProgram.addShader(shaderStage, path);
+    });
+    compiler.compile(vkcv::ShaderStage::FRAGMENT, "shaders/shader_water.frag", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+        particleShaderProgram.addShader(shaderStage, path);
+    });
+
+    // generating descriptorsets from shader reflection
+    vkcv::DescriptorSetLayoutHandle descriptorSetLayout = core.createDescriptorSetLayout(
+            particleShaderProgram.getReflectedDescriptors().at(0));
+    vkcv::DescriptorSetHandle descriptorSet = core.createDescriptorSet(descriptorSetLayout);
+
+    vkcv::Buffer<glm::vec3> vertexBuffer = vkcv::buffer<glm::vec3>(
+			core,
+            vkcv::BufferType::VERTEX,
+            3
+    );
+    const std::vector<vkcv::VertexAttachment> vertexAttachments = particleShaderProgram.getVertexAttachments();
+
+    const std::vector<vkcv::VertexBufferBinding> vertexBufferBindings = {
+            vkcv::vertexBufferBinding(vertexBuffer.getHandle())
+	};
+
+    std::vector<vkcv::VertexBinding> bindings;
+    for (size_t i = 0; i < vertexAttachments.size(); i++) {
+        bindings.push_back(vkcv::createVertexBinding(i, {vertexAttachments[i]}));
+    }
+
+    const vkcv::VertexLayout particleLayout { bindings };
+
+    // initializing graphics pipeline
+    vkcv::GraphicsPipelineConfig particlePipelineDefinition (
+            particleShaderProgram,
+            particlePass,
+            {particleLayout},
+            {descriptorSetLayout}
+	);
+	
+    particlePipelineDefinition.setBlendMode(vkcv::BlendMode::Additive);
+
+    const std::vector<glm::vec3> vertices = {glm::vec3(-0.012, 0.012, 0),
+                                             glm::vec3(0.012, 0.012, 0),
+                                             glm::vec3(0, -0.012, 0)};
+
+    vertexBuffer.fill(vertices);
+
+    vkcv::GraphicsPipelineHandle particlePipeline = core.createGraphicsPipeline(particlePipelineDefinition);
+
+    vkcv::Buffer<glm::vec4> color = vkcv::buffer<glm::vec4>(
+			core,
+            vkcv::BufferType::UNIFORM,
+            1
+    );
+
+    vkcv::Buffer<glm::vec2> position = vkcv::buffer<glm::vec2>(
+			core,
+            vkcv::BufferType::UNIFORM,
+            1
+    );
+
+    // generating particles
+    int numberParticles = 20000;
+    std::vector<Particle> particles;
+    for (int i = 0; i < numberParticles; i++) {
+        const float lo = 0.6;
+        const float hi = 0.9;
+        const float vlo = 0;
+        const float vhi = 70;
+        float x = lo + static_cast <float> (rand()) /( static_cast <float> (RAND_MAX/(hi-lo)));
+        float y = lo + static_cast <float> (rand()) /( static_cast <float> (RAND_MAX/(hi-lo)));
+        float z = lo + static_cast <float> (rand()) /( static_cast <float> (RAND_MAX/(hi-lo)));
+        float vx = vlo + static_cast <float> (rand()) /( static_cast <float> (RAND_MAX/(vhi-vlo)));
+        float vy = vlo + static_cast <float> (rand()) /( static_cast <float> (RAND_MAX/(vhi-vlo)));
+        float vz = vlo + static_cast <float> (rand()) /( static_cast <float> (RAND_MAX/(vhi-vlo)));
+        glm::vec3 pos = glm::vec3(x,y,z);
+        glm::vec3 vel = glm::vec3(vx,vy,vz);
+        //glm::vec3 vel = glm::vec3(0.0,0.0,0.0);
+        particles.push_back(Particle(pos, vel));
+    }
+
+    // creating and filling particle buffer
+    vkcv::Buffer<Particle> particleBuffer1 = vkcv::buffer<Particle>(
+			core,
+            vkcv::BufferType::STORAGE,
+            numberParticles
+    );
+
+    vkcv::Buffer<Particle> particleBuffer2 = vkcv::buffer<Particle>(
+			core,
+			vkcv::BufferType::STORAGE,
+			numberParticles
+    );
+
+    particleBuffer1.fill(particles);
+	particleBuffer2.fill(particles);
+
+    vkcv::DescriptorWrites setWrites;
+    setWrites.writeUniformBuffer(0, color.getHandle()).writeUniformBuffer(1, position.getHandle());
+    setWrites.writeStorageBuffer(2, particleBuffer1.getHandle()).writeStorageBuffer(3, particleBuffer2.getHandle());
+    core.writeDescriptorSet(descriptorSet, setWrites);
+
+    vkcv::DescriptorWrites computeWrites;
+    computeWrites.writeStorageBuffer(
+			0, particleBuffer1.getHandle()
+	).writeStorageBuffer(
+			1, particleBuffer2.getHandle()
+	);
+    
+    core.writeDescriptorSet(pressureDescriptorSet, computeWrites);
+	core.writeDescriptorSet(forceDescriptorSet, computeWrites);
+    core.writeDescriptorSet(updateDataDescriptorSet, computeWrites);
+	core.writeDescriptorSet(flipDescriptorSet, computeWrites);
+
+    // error message if creation of one pipeline failed
+    if (!particlePipeline || !pressurePipeline || !forcePipeline || !updateDataPipeline || !flipPipeline)
+    {
+        std::cout << "Error. Could not create at least one pipeline. Exiting." << std::endl;
+        return EXIT_FAILURE;
+    }
+
+    const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+
+	vkcv::VertexData vertexData (vertexBufferBindings);
+	vertexData.setIndexBuffer(particleIndexBuffer.getHandle());
+	vertexData.setCount(particleIndexBuffer.getCount());
+
+    auto pos = glm::vec2(0.f);
+	
+	vkcv::InstanceDrawcall drawcall (vertexData, numberParticles);
+	drawcall.useDescriptorSet(0, descriptorSet);
+	
+    glm::vec4 colorData = glm::vec4(1.0f, 1.0f, 0.0f, 1.0f);
+    auto camHandle0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+    auto camHandle1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+
+    cameraManager.getCamera(camHandle0).setNearFar(0.1f, 30.0f);
+	cameraManager.getCamera(camHandle0).setPosition(glm::vec3(0.0f, 0.0f, -2.5f));
+	
+    cameraManager.setActiveCamera(camHandle1);
+	
+	cameraManager.getCamera(camHandle1).setNearFar(0.1f, 30.0f);
+    cameraManager.getCamera(camHandle1).setPosition(glm::vec3(0.0f, 0.0f, -2.5f));
+    cameraManager.getCamera(camHandle1).setCenter(glm::vec3(0.0f, 0.0f, 0.0f));
+
+	const auto swapchainExtent = core.getSwapchainExtent(window.getSwapchain());
+	
+	vkcv::ImageConfig colorBufferConfig (
+			swapchainExtent.width,
+			swapchainExtent.height
+	);
+	
+	colorBufferConfig.setSupportingStorage(true);
+	colorBufferConfig.setSupportingColorAttachment(true);
+	
+    vkcv::ImageHandle colorBuffer = core.createImage(
+			colorFormat,
+			colorBufferConfig
+	);
+	
+	vkcv::effects::BloomAndFlaresEffect bloomAndFlares (core);
+	bloomAndFlares.setUpsamplingLimit(3);
+
+    //tone mapping shader & pipeline
+    vkcv::ComputePipelineHandle tonemappingPipe;
+    vkcv::DescriptorSetHandle tonemappingDescriptor = PipelineInit::ComputePipelineInit(
+			&core,
+			vkcv::ShaderStage::COMPUTE,
+			"shaders/tonemapping.comp",
+			tonemappingPipe
+	);
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
+		if ((core.getImageWidth(colorBuffer) != swapchainWidth) ||
+			(core.getImageHeight(colorBuffer) != swapchainHeight)) {
+			colorBufferConfig.setWidth(swapchainWidth);
+			colorBufferConfig.setHeight(swapchainHeight);
+			
+			colorBuffer = core.createImage(
+					colorFormat,
+					colorBufferConfig
+			);
+		}
+
+        color.fill(&colorData);
+        position.fill(&pos);
+
+        cameraManager.update(dt);
+
+        // split view and projection to allow for easy billboarding in shader
+        struct {
+			glm::mat4 view;
+			glm::mat4 projection;
+        } renderingMatrices;
+
+        glm::vec3 gravityDir = glm::rotate(glm::mat4(1.0), glm::radians(rotationx), glm::vec3(0.f,0.f,1.f)) * glm::vec4(0.f,1.f,0.f,0.f);
+        gravityDir = glm::rotate(glm::mat4(1.0), glm::radians(rotationy), glm::vec3(0.f,1.f,0.f)) * glm::vec4(gravityDir,0.f);
+
+        renderingMatrices.view = cameraManager.getActiveCamera().getView();
+        renderingMatrices.view = glm::rotate(renderingMatrices.view, glm::radians(rotationx), glm::vec3(0.f, 0.f, 1.f));
+        renderingMatrices.view = glm::rotate(renderingMatrices.view, glm::radians(rotationy), glm::vec3(0.f, 1.f, 0.f));
+        renderingMatrices.projection = cameraManager.getActiveCamera().getProjection();
+
+        // keybindings rotation
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_LEFT) == GLFW_PRESS)
+            rotationx += dt * 50;
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_RIGHT) == GLFW_PRESS)
+            rotationx -= dt * 50;
+        
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_UP) == GLFW_PRESS)
+            rotationy += dt * 50;
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_DOWN) == GLFW_PRESS)
+            rotationy -= dt * 50;
+
+        // keybindings params
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_T) == GLFW_PRESS)
+            param_h += dt * 0.2;
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_G) == GLFW_PRESS)
+            param_h -= dt * 0.2;
+
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_Y) == GLFW_PRESS)
+            param_mass += dt * 0.2;
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_H) == GLFW_PRESS)
+            param_mass -= dt * 0.2;
+
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_U) == GLFW_PRESS)
+            param_gasConstant += dt * 1500.0;
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_J) == GLFW_PRESS)
+            param_gasConstant -= dt * 1500.0;
+
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_I) == GLFW_PRESS)
+            param_offset += dt * 400.0;
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_K) == GLFW_PRESS)
+            param_offset -= dt * 400.0;
+
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_O) == GLFW_PRESS)
+            param_viscosity = 50;
+        if (glfwGetKey(window.getWindow(), GLFW_KEY_L) == GLFW_PRESS)
+            param_viscosity = 1200;
+        
+
+        auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
+
+        glm::vec4 pushData[3] = {
+            glm::vec4(param_h,param_mass,param_gasConstant,param_offset),
+            glm::vec4(param_gravity,param_viscosity,param_ABSORBTION,param_dt),
+            glm::vec4(gravityDir.x,gravityDir.y,gravityDir.z,(float)numberParticles)
+        };
+
+        std::cout << "h: " << param_h << " | mass: " << param_mass << " | gasConstant: " << param_gasConstant << " | offset: " << param_offset << " | viscosity: " << param_viscosity << std::endl;
+
+        vkcv::PushConstants pushConstantsCompute (sizeof(pushData));
+        pushConstantsCompute.appendDrawcall(pushData);
+
+        const auto computeDispatchCount = vkcv::dispatchInvocations(numberParticles, 256);
+
+        // computing pressure pipeline
+        core.recordComputeDispatchToCmdStream(
+				cmdStream,
+				pressurePipeline,
+				computeDispatchCount,
+				{ vkcv::useDescriptorSet(0, pressureDescriptorSet) },
+				pushConstantsCompute
+		);
+
+        core.recordBufferMemoryBarrier(cmdStream, particleBuffer1.getHandle());
+		core.recordBufferMemoryBarrier(cmdStream, particleBuffer2.getHandle());
+
+        // computing force pipeline
+		core.recordComputeDispatchToCmdStream(
+				cmdStream,
+				forcePipeline,
+				computeDispatchCount,
+				{ vkcv::useDescriptorSet(0, forceDescriptorSet) },
+				pushConstantsCompute
+		);
+
+		core.recordBufferMemoryBarrier(cmdStream, particleBuffer1.getHandle());
+		core.recordBufferMemoryBarrier(cmdStream, particleBuffer2.getHandle());
+
+        // computing update data pipeline
+        core.recordComputeDispatchToCmdStream(
+				cmdStream,
+				updateDataPipeline,
+				computeDispatchCount,
+				{ vkcv::useDescriptorSet(0, updateDataDescriptorSet) },
+				pushConstantsCompute
+		);
+
+        core.recordBufferMemoryBarrier(cmdStream, particleBuffer1.getHandle());
+        core.recordBufferMemoryBarrier(cmdStream, particleBuffer2.getHandle());
+
+        // computing flip pipeline
+        core.recordComputeDispatchToCmdStream(
+				cmdStream,
+				flipPipeline,
+				computeDispatchCount,
+				{ vkcv::useDescriptorSet(0, flipDescriptorSet) },
+				pushConstantsCompute
+		);
+
+        core.recordBufferMemoryBarrier(cmdStream, particleBuffer1.getHandle());
+        core.recordBufferMemoryBarrier(cmdStream, particleBuffer2.getHandle());
+
+
+        // bloomAndFlares & tonemapping
+        vkcv::PushConstants pushConstantsDraw (sizeof(renderingMatrices));
+        pushConstantsDraw.appendDrawcall(renderingMatrices);
+        
+        core.recordDrawcallsToCmdStream(
+                cmdStream,
+                particlePipeline,
+				pushConstantsDraw,
+                { drawcall },
+                { colorBuffer },
+                windowHandle
+		);
+	
+		bloomAndFlares.recordEffect(cmdStream, colorBuffer, colorBuffer);
+
+        core.prepareImageForStorage(cmdStream, colorBuffer);
+        core.prepareImageForStorage(cmdStream, swapchainInput);
+
+        vkcv::DescriptorWrites tonemappingDescriptorWrites;
+        tonemappingDescriptorWrites.writeStorageImage(
+				0, colorBuffer
+		).writeStorageImage(
+				1, swapchainInput
+		);
+		
+        core.writeDescriptorSet(tonemappingDescriptor, tonemappingDescriptorWrites);
+
+        const auto tonemappingDispatchCount = vkcv::dispatchInvocations(
+				vkcv::DispatchSize(swapchainWidth, swapchainHeight),
+				vkcv::DispatchSize(8, 8)
+		);
+
+        core.recordComputeDispatchToCmdStream(
+            cmdStream, 
+            tonemappingPipe, 
+            tonemappingDispatchCount, 
+            { vkcv::useDescriptorSet(0, tonemappingDescriptor) },
+            vkcv::PushConstants(0)
+		);
+
+        core.prepareSwapchainImageForPresent(cmdStream);
+        core.submitCommandStream(cmdStream);
+    });
+
+    return 0;
+}
diff --git a/projects/voxelization/CMakeLists.txt b/projects/voxelization/CMakeLists.txt
index bc87996096226af4e3f3d05c3e10bb287c61cc8d..34178021a517485e54ef8dcafe0f170a834e5186 100644
--- a/projects/voxelization/CMakeLists.txt
+++ b/projects/voxelization/CMakeLists.txt
@@ -2,31 +2,39 @@ cmake_minimum_required(VERSION 3.16)
 project(voxelization)
 
 # setting c++ standard for the project
-set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD 20)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
-# this should fix the execution path to load local files from the project
-set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
-
 # adding source files to the project
-add_executable(voxelization src/main.cpp)
+add_project(voxelization src/main.cpp)
 
 target_sources(voxelization PRIVATE
     src/Voxelization.hpp
-    src/Voxelization.cpp)
-
-# this should fix the execution path to load local files from the project (for MSVC)
-if(MSVC)
-	set_target_properties(voxelization PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-	set_target_properties(voxelization PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-    
-    # in addition to setting the output directory, the working directory has to be set
-	# by default visual studio sets the working directory to the build directory, when using the debugger
-	set_target_properties(voxelization PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-endif()
+    src/Voxelization.cpp
+    src/ShadowMapping.hpp
+    src/ShadowMapping.cpp)
 
 # including headers of dependencies and the VkCV framework
-target_include_directories(voxelization SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include} ${vkcv_gui_include})
+target_include_directories(voxelization SYSTEM BEFORE PRIVATE
+		${vkcv_include}
+		${vkcv_includes}
+		${vkcv_asset_loader_include}
+		${vkcv_camera_include}
+		${vkcv_shader_compiler_include}
+		${vkcv_gui_include}
+		${vkcv_upscaling_include}
+		${vkcv_effects_include}
+		${vkcv_algorithm_include})
 
 # linking with libraries from all dependencies and the VkCV framework
-target_link_libraries(voxelization vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera vkcv_shader_compiler vkcv_gui)
+target_link_libraries(voxelization
+		vkcv
+		${vkcv_libraries}
+		vkcv_asset_loader
+		${vkcv_asset_loader_libraries}
+		vkcv_camera
+		vkcv_shader_compiler
+		vkcv_gui
+		vkcv_upscaling
+		vkcv_effects
+		vkcv_algorithm)
diff --git a/projects/voxelization/README.md b/projects/voxelization/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..569b911ade302d8dfbd55ce0b31e9c28738c6b41
--- /dev/null
+++ b/projects/voxelization/README.md
@@ -0,0 +1,21 @@
+# Voxelization
+An example project to show a volumetric global illumination technique with the VkCV framework
+
+![Screenshot](../../screenshots/voxelization.png)
+
+## Details
+
+The project utilizes multiple Vulkan extensions (some of them optionally) to implement multiple 
+graphics effects in an advanced visualization of a whole scene. Together this brings visual 
+features like shadow maps, indirect lighting via discrete voxels and multiple post-processing 
+effects like bloom and lens-flares to the screen. To make all of this available in realtime even 
+on low powered hardware, it is possible to utilize multiple different upscaling methods.
+
+## Extensions
+
+Here is a list of the used extensions:
+
+- [VK_EXT_descriptor_indexing](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_descriptor_indexing.html)
+- [VK_KHR_shader_subgroup_extended_types](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_shader_subgroup_extended_types.html)
+- [VK_KHR_shader_float16_int8](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_shader_float16_int8.html)
+- [VK_KHR_16bit_storage](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_16bit_storage.html)
diff --git a/projects/voxelization/assets/RadialLUT.png b/projects/voxelization/assets/RadialLUT.png
new file mode 100644
index 0000000000000000000000000000000000000000..8b7056cf2a35c4d41f142e52bbc48dd1a91e4758
--- /dev/null
+++ b/projects/voxelization/assets/RadialLUT.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:70d59d4e9c1ce2a077ed60c19c8c4665bf0723c952612a2ca8ec32c55f9ec498
+size 900
diff --git a/projects/voxelization/assets/Sponza/Sponza.bin b/projects/voxelization/assets/Sponza/Sponza.bin
new file mode 100644
index 0000000000000000000000000000000000000000..eb0523cb55746451c1b20f25fb4ecfed22ef6047
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Sponza.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:232d9c216b72dc0ee6089bc070cf8a12cabecd6a00c1f04aea78ac361da53839
+size 10819832
diff --git a/projects/voxelization/assets/Sponza/Sponza.gltf b/projects/voxelization/assets/Sponza/Sponza.gltf
new file mode 100644
index 0000000000000000000000000000000000000000..18d697f622eab38c3b3089a56c1680ff4a443171
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Sponza.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:748597f56b7228dddbe4c5d1cb84a35d645941933faf89c4c0f81dd9b602291e
+size 73667
diff --git a/projects/voxelization/assets/Sponza/Textures/Arch_Diff.jpg b/projects/voxelization/assets/Sponza/Textures/Arch_Diff.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..95900bdf9c8d57666b92929109a6750dc04a548b
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Arch_Diff.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e8b68080eb4c5709536dfb7858d595d51d4bb725003e11b0f383acf76a7e05a3
+size 521947
diff --git a/projects/voxelization/assets/Sponza/Textures/Arch_Norm.jpg b/projects/voxelization/assets/Sponza/Textures/Arch_Norm.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..28e407a7c96acc4fbc9e75c7c8d6399914d409e3
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Arch_Norm.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:86defcb4c007b80b9d23bcc9fdccd994f6a68f710eb75f11396d92faa2fbe368
+size 206244
diff --git a/projects/voxelization/assets/Sponza/Textures/Arch_Spec.jpg b/projects/voxelization/assets/Sponza/Textures/Arch_Spec.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d1e0df9e5b0613ee476440b7e86d23535d5f4d05
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Arch_Spec.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:259b6659cb2681e06ef04becd49298d379f2f25b2cc468acf0c2ade4d190637c
+size 570081
diff --git a/projects/voxelization/assets/Sponza/Textures/Background_Albedo.png b/projects/voxelization/assets/Sponza/Textures/Background_Albedo.png
new file mode 100644
index 0000000000000000000000000000000000000000..668c3b6d389e9675bf8e482a51936fe1dff0c889
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Background_Albedo.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9a64b98be19acc30b95d071fb9dd1f0eaaf066612a2c757b32f8b7487f4600e9
+size 1653594
diff --git a/projects/voxelization/assets/Sponza/Textures/Background_Normal.png b/projects/voxelization/assets/Sponza/Textures/Background_Normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..6f03475bdcf3d26f1b152c30680998fbb6dec321
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Background_Normal.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4e7fe693f80bbf4b543d3afc614390e498bb84664fd0251c3cfb4e99b4885a12
+size 1299002
diff --git a/projects/voxelization/assets/Sponza/Textures/Background_Roughness.png b/projects/voxelization/assets/Sponza/Textures/Background_Roughness.png
new file mode 100644
index 0000000000000000000000000000000000000000..1f47f6a071463d13bb0f719570e3c53c4a65c079
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Background_Roughness.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:15339be54f1f86593a48875dfde6947adf4ba49b3b945619ce3b4fbea64afbd1
+size 791544
diff --git a/projects/voxelization/assets/Sponza/Textures/Bricks_A_Diff.jpg b/projects/voxelization/assets/Sponza/Textures/Bricks_A_Diff.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cff7cf6ceb272d99378ce4b7facd09ade9f7c61a
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Bricks_A_Diff.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9330cf1d51c8f6924b6c9ce09f2dedb21a82e1c7406c37d49ce95beca40bb3be
+size 564801
diff --git a/projects/voxelization/assets/Sponza/Textures/Bricks_A_Norm.jpg b/projects/voxelization/assets/Sponza/Textures/Bricks_A_Norm.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f83f46eb02cbf045886b8867b2dac93321800bf5
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Bricks_A_Norm.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0821ec8dc8226fd5ac70f068e134d014a9952276a7100cc7f0660853471b39f6
+size 339486
diff --git a/projects/voxelization/assets/Sponza/Textures/Bricks_A_Spec.jpg b/projects/voxelization/assets/Sponza/Textures/Bricks_A_Spec.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..da82e3992c1cdac7b90f294809193f4227da08cf
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Bricks_A_Spec.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bee42826d56d3ff719c4908c11ef65584fbe19e08273862fde7ec1408e44ba25
+size 530384
diff --git a/projects/voxelization/assets/Sponza/Textures/Ceiling_Diff.jpg b/projects/voxelization/assets/Sponza/Textures/Ceiling_Diff.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..20f8871aa9bb13d4c2ccc7a0aa9ddddcd226080b
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Ceiling_Diff.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5bbd0cbf2da7416d5620514ebb0c4837b0ccc841b649cf4070281aa333ff8520
+size 520162
diff --git a/projects/voxelization/assets/Sponza/Textures/Ceiling_Norm.jpg b/projects/voxelization/assets/Sponza/Textures/Ceiling_Norm.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8a8fba3de7e4b68e5d5a095cb30442fb30c53be6
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Ceiling_Norm.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e309f1e6310dc0bb85df0a5f3b139207876f067378796a7aa666b0bb169587ae
+size 919134
diff --git a/projects/voxelization/assets/Sponza/Textures/Ceiling_Spec.jpg b/projects/voxelization/assets/Sponza/Textures/Ceiling_Spec.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..59ec4fabc2df618dce40d73a1e71c23e780b0422
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Ceiling_Spec.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:81b21b9352f1fb9ccfeaf237f8547b985ddbf0cfd7ade7a4a8670b73a72b2cb5
+size 608687
diff --git a/projects/voxelization/assets/Sponza/Textures/Chain_Diff.png b/projects/voxelization/assets/Sponza/Textures/Chain_Diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..9629f2f279c8b2d338724b96538f2858a9f3f4ff
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Chain_Diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d6df943a0dcf535d9dfb009c2833e0c7dc21939cfe3a2c10316c552b3dc3441b
+size 1077432
diff --git a/projects/voxelization/assets/Sponza/Textures/Chain_Norm.jpg b/projects/voxelization/assets/Sponza/Textures/Chain_Norm.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..127484f4ff3aa31597262c8e675c9d1f4dc979f1
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Chain_Norm.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:763740af8d22f0bfc6a43bd46050ef24bb661833bcace92f1f3326b9d5d7dc16
+size 137384
diff --git a/projects/voxelization/assets/Sponza/Textures/Cloth1_Norm.jpg b/projects/voxelization/assets/Sponza/Textures/Cloth1_Norm.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cd117615246364139418024db5474e3e29ccc40b
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Cloth1_Norm.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3af8848a1a952ad8cc83199bbda0c095f9ed136be839659aa668f72afac91508
+size 1034060
diff --git a/projects/voxelization/assets/Sponza/Textures/Cloth1_Spec.jpg b/projects/voxelization/assets/Sponza/Textures/Cloth1_Spec.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e505337ce76a797c4b385c89c25f9486eccab9a8
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Cloth1_Spec.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:21bb124f5b5d4d5e9c8d9773cd31dedfc41b30eb302af36e7257cf930152908e
+size 486634
diff --git a/projects/voxelization/assets/Sponza/Textures/Cloth2_Norm.jpg b/projects/voxelization/assets/Sponza/Textures/Cloth2_Norm.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..35885a182fc5cf119afbebbeb2cc060850970731
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Cloth2_Norm.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:231d919288db7c977fda3091549195c1bd5197e9e63c039e972da8764f5435c1
+size 844768
diff --git a/projects/voxelization/assets/Sponza/Textures/Cloth2_Spec.jpg b/projects/voxelization/assets/Sponza/Textures/Cloth2_Spec.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..982536602c4633893fa19c27c0b0e08a8ea0aec8
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Cloth2_Spec.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8ad0e8a9ca6de602225a527361bc718881de8741bc48927e27259a8803e7fed7
+size 623144
diff --git a/projects/voxelization/assets/Sponza/Textures/ClothBlue1_Diff.jpg b/projects/voxelization/assets/Sponza/Textures/ClothBlue1_Diff.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ae58b3bb674bec1f07a3f7de7eb52c8b4c23f4be
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/ClothBlue1_Diff.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:005b774a968c1f6af00e4bf97afd668a6a045126a94f524df722ba19edc77d61
+size 1151448
diff --git a/projects/voxelization/assets/Sponza/Textures/ClothBlue2_Diff.jpg b/projects/voxelization/assets/Sponza/Textures/ClothBlue2_Diff.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e8597404fcae24e14967eb2220faa1d5cd4d8170
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/ClothBlue2_Diff.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:751606c94a5fe5bda3bf6709e7f6a74363bd7902bf2353d54e55ef3b50007487
+size 785678
diff --git a/projects/voxelization/assets/Sponza/Textures/ClothBlue2_Diff_jpg.jpg b/projects/voxelization/assets/Sponza/Textures/ClothBlue2_Diff_jpg.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ae58b3bb674bec1f07a3f7de7eb52c8b4c23f4be
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/ClothBlue2_Diff_jpg.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:005b774a968c1f6af00e4bf97afd668a6a045126a94f524df722ba19edc77d61
+size 1151448
diff --git a/projects/voxelization/assets/Sponza/Textures/ClothGreen1_Diff.jpg b/projects/voxelization/assets/Sponza/Textures/ClothGreen1_Diff.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..833de50475dc9d4db7c36e0447bd8e17a5de41f5
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/ClothGreen1_Diff.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7f55814430f289e89c7d675c3db925eee904fbc566ff83f4b49904de53138e98
+size 1044330
diff --git a/projects/voxelization/assets/Sponza/Textures/ClothGreen2_Diff.jpg b/projects/voxelization/assets/Sponza/Textures/ClothGreen2_Diff.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..63ca00ba7ca17e80054fdf35b87aad4227edadef
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/ClothGreen2_Diff.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9d5fd79619a52db0232edb2287f5f05c0124c304355e3b035a4e48c0871090bf
+size 764529
diff --git a/projects/voxelization/assets/Sponza/Textures/ClothRed1_Diff.jpg b/projects/voxelization/assets/Sponza/Textures/ClothRed1_Diff.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..406d620bf21db33765c00f87f3750f73c8df2de0
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/ClothRed1_Diff.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:23c32277c33d8eac091cd73daf81561ebe3640eede49b037546ed5c244a01347
+size 1079152
diff --git a/projects/voxelization/assets/Sponza/Textures/ClothRed2_Diff.jpg b/projects/voxelization/assets/Sponza/Textures/ClothRed2_Diff.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f5b06c8dcee807f739981bfc061e4cd3d8f0a291
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/ClothRed2_Diff.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:88861c8c7f10517c1d7942f316aae3b74dc5b8ae2faa2021d9dfe9d2c09fb24f
+size 780166
diff --git a/projects/voxelization/assets/Sponza/Textures/Column_B_Diff.jpg b/projects/voxelization/assets/Sponza/Textures/Column_B_Diff.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ba6158fe4718103e6f37ee0f1e482e4e5dfc25d8
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Column_B_Diff.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:170f6c637ebd71a101244eb4d80c3dcf7063caf89e42053d3114d8aeca755cdf
+size 643517
diff --git a/projects/voxelization/assets/Sponza/Textures/Column_B_Norm.jpg b/projects/voxelization/assets/Sponza/Textures/Column_B_Norm.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..af99619ce8ff2b71246c00a79d94999c89f40e2e
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Column_B_Norm.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:46ffc9123ee3ad94b2595843b4446fd67dbf5a2f79dc1e3b4e637f5ea4578630
+size 652476
diff --git a/projects/voxelization/assets/Sponza/Textures/Column_B_Spec.jpg b/projects/voxelization/assets/Sponza/Textures/Column_B_Spec.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..52c8e41dbc885800512ccc8527932be0f6964c63
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Column_B_Spec.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:93d495e33d754c5723c910e322ac94108fc753b65af082a19be5252905aa741b
+size 388462
diff --git a/projects/voxelization/assets/Sponza/Textures/Column_C_Diff.jpg b/projects/voxelization/assets/Sponza/Textures/Column_C_Diff.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..53c725db4dece66f1458bc93dfb95ab20f8927cb
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Column_C_Diff.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9edc7c1c73661658cd8ed7733728002d45fecd17cb37a962fb654ff1d8c1933d
+size 665796
diff --git a/projects/voxelization/assets/Sponza/Textures/Column_C_Norm.jpg b/projects/voxelization/assets/Sponza/Textures/Column_C_Norm.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9d2c5f3fe9792c825c50743e80969cb3bca660a9
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Column_C_Norm.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:58942b73c8f209ab9cc6ea174375adfde9c1875cbfe093f993086a0ab6570724
+size 698410
diff --git a/projects/voxelization/assets/Sponza/Textures/Column_C_Spec.jpg b/projects/voxelization/assets/Sponza/Textures/Column_C_Spec.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cf4a888d7023a9d23248a68d6217e5e1317c9b46
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Column_C_Spec.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:06c6480e207ce64f9e7c08df3f7f21638da7da43f77682657f4c823acd8c81a5
+size 193077
diff --git a/projects/voxelization/assets/Sponza/Textures/Column_Diff.jpg b/projects/voxelization/assets/Sponza/Textures/Column_Diff.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f6e1fc5dfdbc52d8bf80bd736b6c7881f260b10d
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Column_Diff.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c39c9f304c2605be791b4c0a58cd0f8eb20de2dd6a4a4af893c4eb112d158353
+size 518310
diff --git a/projects/voxelization/assets/Sponza/Textures/Column_Norm.jpg b/projects/voxelization/assets/Sponza/Textures/Column_Norm.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..549b3a7e290e79a4275124ce339c2d7a6b308cb2
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Column_Norm.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d54c520da7e60e2a62630e9cf946faf139648a7ab831c4531272238e4ade8fa8
+size 557865
diff --git a/projects/voxelization/assets/Sponza/Textures/Column_Spec.jpg b/projects/voxelization/assets/Sponza/Textures/Column_Spec.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5c3770a5faebbe1e98df4b39367a87246b0161cc
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Column_Spec.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4f06c93f8e9f90be44a2e78847e57acec82bd793f58f7be7f86b088150730b88
+size 260119
diff --git a/projects/voxelization/assets/Sponza/Textures/Detail_Diff.jpg b/projects/voxelization/assets/Sponza/Textures/Detail_Diff.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8ede3e8fff423456faf51cb1419261a33ab708d0
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Detail_Diff.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:98e4c4a091fea684cd693f3ae7ebe83e491969b501b4cd6e1fdd6a3cc1fa5cab
+size 321375
diff --git a/projects/voxelization/assets/Sponza/Textures/Detail_norm.jpg b/projects/voxelization/assets/Sponza/Textures/Detail_norm.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6dad5879bba40f4273658e28f6d7fa3e3d8a663b
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Detail_norm.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9552937c9fa0481a07dc66768958b4be382544add46d1d66a687af54c51ab6da
+size 547553
diff --git a/projects/voxelization/assets/Sponza/Textures/Detail_spec.jpg b/projects/voxelization/assets/Sponza/Textures/Detail_spec.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d3918da21cdc2bcb27db235749791c45b1fb88f4
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Detail_spec.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:967005ccfbf4d38f674df41aebb3e58ac1fdcd91f554ead7acfcc132372f8ec6
+size 551432
diff --git a/projects/voxelization/assets/Sponza/Textures/Fill_Spec.jpg b/projects/voxelization/assets/Sponza/Textures/Fill_Spec.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b7dfccb5c93c5d215f3466b9352b6e77f4034555
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Fill_Spec.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5562f0e76ca213d72295e48b8a14a59ee1971f58a47f95e5c022a4e78371c5aa
+size 12575
diff --git a/projects/voxelization/assets/Sponza/Textures/Flagpole_Diff.jpg b/projects/voxelization/assets/Sponza/Textures/Flagpole_Diff.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..43e93c048438d4d57501c198f45d5888d5a478fd
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Flagpole_Diff.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:fbdb7bcced57a005b84b4c3bbf744bae6309804cca51dabd9237e03a72fbc505
+size 399027
diff --git a/projects/voxelization/assets/Sponza/Textures/Flagpole_Norm.jpg b/projects/voxelization/assets/Sponza/Textures/Flagpole_Norm.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8a8c2891f2c44d175806df7d1451dea97649154a
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Flagpole_Norm.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:62a52f200082c02a0fbfa25c0bc40ea7d105a21f10d5aa0ec9408c4f45523794
+size 772201
diff --git a/projects/voxelization/assets/Sponza/Textures/Flagpole_Spec.jpg b/projects/voxelization/assets/Sponza/Textures/Flagpole_Spec.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..145dc9d42327723970227151f945048b34471287
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Flagpole_Spec.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d939d2e8bf11c2cf375154129f861b982c786b83db79cb9fde1a2b94b46caf62
+size 682743
diff --git a/projects/voxelization/assets/Sponza/Textures/Floor_A_Diff.jpg b/projects/voxelization/assets/Sponza/Textures/Floor_A_Diff.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d1c351ca22197a2556ff0acc08c73912f63fdac5
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Floor_A_Diff.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:34b464edddbd2295b0ee23fa7a7a440a19456888c586acdc47032f841b302abd
+size 573157
diff --git a/projects/voxelization/assets/Sponza/Textures/Floor_A_Norm.jpg b/projects/voxelization/assets/Sponza/Textures/Floor_A_Norm.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0304c61dbae476fb226b151d44612fca91892e44
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Floor_A_Norm.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3a2c2aeb87a490a9bd37a8aa912a00e5f5c13974879fcb4f4f0ef39b17c52a7a
+size 718904
diff --git a/projects/voxelization/assets/Sponza/Textures/Floor_A_Spec.jpg b/projects/voxelization/assets/Sponza/Textures/Floor_A_Spec.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..90621f970579c5c8e268be5d415cab1162cf5910
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Floor_A_Spec.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9158388312a76ba25a40d3b57978e1ace224882e53198a766494d98f9118aeba
+size 313460
diff --git a/projects/voxelization/assets/Sponza/Textures/Flower_Diff.png b/projects/voxelization/assets/Sponza/Textures/Flower_Diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..0f297bbb9581dddb4bc61f8e29c14537a3415736
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Flower_Diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:75328d1aac87f8539be2456e6a5392cef7c6d08476d75585eafcd334dbeee0c5
+size 1225157
diff --git a/projects/voxelization/assets/Sponza/Textures/Flower_Norm.jpg b/projects/voxelization/assets/Sponza/Textures/Flower_Norm.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..23b8b1d0bc30d90e0dbf2964b7591e3a4c90c215
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Flower_Norm.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:81ede4ce51045540817c1efb83dbb65a9a9701f1abd78fa130def51d227162fc
+size 421442
diff --git a/projects/voxelization/assets/Sponza/Textures/Flower_Spec.jpg b/projects/voxelization/assets/Sponza/Textures/Flower_Spec.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fc60f6df8ee0a01601294062d585377ec14f28f3
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Flower_Spec.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d81efcb42b3a044595ca984cb46d941b159064bddae9a43ed8d9582db68d6b47
+size 280745
diff --git a/projects/voxelization/assets/Sponza/Textures/Lion_Diff.jpg b/projects/voxelization/assets/Sponza/Textures/Lion_Diff.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..51286b49a1eb9d1e52ad599f1d1a4abccc53d7f7
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Lion_Diff.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1ea4f9bc86354d363ea8153df9b7593c3bd3da153c53b9624c1fd8a960cb3ebc
+size 599959
diff --git a/projects/voxelization/assets/Sponza/Textures/Lion_Norm.jpg b/projects/voxelization/assets/Sponza/Textures/Lion_Norm.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..49132519e6e14b0a0964575ad92c86f805500421
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Lion_Norm.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f296e43c0d4c92953d4899ea0c38e8af992ba471acc87feb10f08e29a94aaef6
+size 512035
diff --git a/projects/voxelization/assets/Sponza/Textures/Lion_Spec.jpg b/projects/voxelization/assets/Sponza/Textures/Lion_Spec.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3058c29c325e5768bc3189a8c61548d44d88db55
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Lion_Spec.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8c26ae9523cfae7a062fd074f74e0ddbb61630772f9a2130489e1d6e4724c38b
+size 281980
diff --git a/projects/voxelization/assets/Sponza/Textures/Roof_Diff.jpg b/projects/voxelization/assets/Sponza/Textures/Roof_Diff.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1e9b6f6f9a00a99b4563229f76997abf402bcc7f
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Roof_Diff.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:dfa17f2dee2cd2d850585673d19d5cda477a63e21659b346d8730e80edd3b347
+size 822373
diff --git a/projects/voxelization/assets/Sponza/Textures/Roof_Norm.jpg b/projects/voxelization/assets/Sponza/Textures/Roof_Norm.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..88ee02405bb090eed4ea2b658d0adcb93b1a8ed6
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Roof_Norm.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e5166a5fba339e44a9d197dedc168683971f8acb2156034248a80674cbba0dd5
+size 1607263
diff --git a/projects/voxelization/assets/Sponza/Textures/Roof_Spec.jpg b/projects/voxelization/assets/Sponza/Textures/Roof_Spec.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..dccdb78a2b439240d6cb3d79ce1694e53f83217f
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Roof_Spec.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8e90e94cadf80528a3df3496035d3c77900eba24d88b1c1d31c9dce77990fc44
+size 872255
diff --git a/projects/voxelization/assets/Sponza/Textures/Shield_Norm.jpg b/projects/voxelization/assets/Sponza/Textures/Shield_Norm.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..da701b850fd9bb49714263dba7c0d2681808ad5a
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Shield_Norm.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:70d85bff3bc017177124942ae187db117ba2588e490746f440f2cef085852956
+size 357112
diff --git a/projects/voxelization/assets/Sponza/Textures/Shield_Spec.jpg b/projects/voxelization/assets/Sponza/Textures/Shield_Spec.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c918da16e61180632dae222a622bd8d479ff9031
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Shield_Spec.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9cbd61fcb8047f1c21450eff05207b29d71824edd75483267b5fbc2f7b21378c
+size 329124
diff --git a/projects/voxelization/assets/Sponza/Textures/Shield_diff.jpg b/projects/voxelization/assets/Sponza/Textures/Shield_diff.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..90aa71962d62e0370f1d3550fc35781c54a466bf
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Shield_diff.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:73a94a12fb0a9ab4c75e40996c9a2d49ac45ac18a8c8ae81d5ceb1c8ae52d6dc
+size 362437
diff --git a/projects/voxelization/assets/Sponza/Textures/Thorn_Diff.png b/projects/voxelization/assets/Sponza/Textures/Thorn_Diff.png
new file mode 100644
index 0000000000000000000000000000000000000000..cd52793a05b4f0827299d86c7446315ce67c1c68
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Thorn_Diff.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6fbdbf67d3501831ad3a4e2adbf3d27e4c3d13ba1d655e5861c3f39b5f899f65
+size 2427921
diff --git a/projects/voxelization/assets/Sponza/Textures/Thorn_Norm.jpg b/projects/voxelization/assets/Sponza/Textures/Thorn_Norm.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d85686c7aa40b03a94d00fa86dcd1f41681f1e1f
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Thorn_Norm.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:dc5bf5f3091b548f1dfe40410b1aa1e2e55d82e2d77ba57dbd1f92955a0d8ff8
+size 413837
diff --git a/projects/voxelization/assets/Sponza/Textures/Thorn_Spec.jpg b/projects/voxelization/assets/Sponza/Textures/Thorn_Spec.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..dc414f0ea13433f7fe9832f4a64d0514ae9a2239
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Thorn_Spec.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:da2a0b308b4d6d57ffbcf4bb7509d4afe91156d5d86d0396e83d7cac883a4857
+size 668962
diff --git a/projects/voxelization/assets/Sponza/Textures/VaseRound_Diff.jpg b/projects/voxelization/assets/Sponza/Textures/VaseRound_Diff.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..74e6d243f6da2015398ef9a08de663c59f32c329
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/VaseRound_Diff.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:430758549ba2189d94f06abeaae092bfc0a1d8c22afc56f5caf3f99c5ee7407c
+size 572245
diff --git a/projects/voxelization/assets/Sponza/Textures/VaseRound_Norm.jpg b/projects/voxelization/assets/Sponza/Textures/VaseRound_Norm.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..154b1fa17729990b54b368febb55a259b7a47292
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/VaseRound_Norm.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8af8fb0810bf3493242491da44f7ae14f7c0bfa6cbacea4be01f6f4261db4559
+size 618837
diff --git a/projects/voxelization/assets/Sponza/Textures/VaseRound_Spec.jpg b/projects/voxelization/assets/Sponza/Textures/VaseRound_Spec.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..97c19f41117e9a3aaec02885b236349c8ba9c6ca
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/VaseRound_Spec.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:12e1f2ea3c2714a6ec455fa8e623a702e33ef95765ebd09f7874a68fa55fd104
+size 399963
diff --git a/projects/voxelization/assets/Sponza/Textures/Vase_Diff.jpg b/projects/voxelization/assets/Sponza/Textures/Vase_Diff.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4d53ec1a796184e24893aff99f53652d08bd4f47
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Vase_Diff.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1da7664b7b88ae58386a6e6b5eca2829537818061d704f002356d8fc483c1a28
+size 531156
diff --git a/projects/voxelization/assets/Sponza/Textures/Vase_Hanging_Diff.jpg b/projects/voxelization/assets/Sponza/Textures/Vase_Hanging_Diff.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..149a0f1c47e15ebfc2a9de474e8dda46ed3ab2de
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Vase_Hanging_Diff.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9f849f0510ca0df66ba1cba2380a6a1b481444d9e64b68da6b2ce202b54d9cec
+size 302363
diff --git a/projects/voxelization/assets/Sponza/Textures/Vase_Hanging_Norm.jpg b/projects/voxelization/assets/Sponza/Textures/Vase_Hanging_Norm.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..bcdd65e4b718c0dc7e74f03aa50cac22f10febea
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Vase_Hanging_Norm.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b93291121303b5a24a488a3e29e0645a24bb471413cfd25c0917d306dff1d038
+size 208352
diff --git a/projects/voxelization/assets/Sponza/Textures/Vase_Hanging_Spec.jpg b/projects/voxelization/assets/Sponza/Textures/Vase_Hanging_Spec.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8976a203dfe463d121d48e743b3ecb662ac52d09
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Vase_Hanging_Spec.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c288a37ed8373d1132644873624b096a72ba81d03cda601e050a5de24ba13c39
+size 104626
diff --git a/projects/voxelization/assets/Sponza/Textures/Vase_Norm.jpg b/projects/voxelization/assets/Sponza/Textures/Vase_Norm.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..de5758090ab8ab0aaa374110aa08df6c7f53d599
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Vase_Norm.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:728dd0fe3b570103e51dbfcc1eac64ad09916a7a31603c62a4090a2f2afaa6f8
+size 876227
diff --git a/projects/voxelization/assets/Sponza/Textures/Vase_spec.jpg b/projects/voxelization/assets/Sponza/Textures/Vase_spec.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0cce3ebf638a3ca932bca8b28c31b8474f123349
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/Vase_spec.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cb09708202859d08764601acc9c9ea3b5254b2c4fd39dcc6b6055768c77aee9b
+size 370061
diff --git a/projects/voxelization/assets/Sponza/Textures/white.png b/projects/voxelization/assets/Sponza/Textures/white.png
new file mode 100644
index 0000000000000000000000000000000000000000..98e867371da9926f451d5754604bc97b3186296a
--- /dev/null
+++ b/projects/voxelization/assets/Sponza/Textures/white.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e2ab2939dda535ad28779e41c20c98368f630f29c5824a0a5a430f47b3b6da12
+size 951
diff --git a/projects/voxelization/assets/lensDirt.jpg b/projects/voxelization/assets/lensDirt.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f941567527fe92f23aa6955e15ba95dc46881dad
--- /dev/null
+++ b/projects/voxelization/assets/lensDirt.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:95982351ecf3d4d129d17612b40a8092944a285c0bffdd74d4886dd8489695ca
+size 107603
diff --git a/projects/voxelization/assets/shaders/brdf.inc b/projects/voxelization/assets/shaders/brdf.inc
new file mode 100644
index 0000000000000000000000000000000000000000..4cf334eaceedd18815ab928aed38d5f8d3f51c1e
--- /dev/null
+++ b/projects/voxelization/assets/shaders/brdf.inc
@@ -0,0 +1,31 @@
+#ifndef BRDF_INC
+#define BRDF_INC
+
+const float pi = 3.1415; 
+
+vec3 lambertBRDF(vec3 albedo){
+    return albedo / pi;
+}
+
+vec3 fresnelSchlick(float cosTheta, vec3 f0){
+    return f0 + (vec3(1) - f0) * pow(1 - cosTheta, 5);
+}
+
+float GGXDistribution(float r, float NoH){
+    float r2    = r * r;
+    float denom = pi * pow(NoH * NoH * (r2 - 1) + 1, 2);
+    return r2 / max(denom, 0.00001);
+}
+
+float GGXSmithShadowingPart(float r, float cosTheta){
+    float nom   = cosTheta * 2;
+    float r2    = r * r;
+    float denom = cosTheta + sqrt(r2 + (1 - r2) * cosTheta * cosTheta);
+    return nom / max(denom, 0.00001);
+}
+
+float GGXSmithShadowing(float r, float NoV, float NoL){
+    return GGXSmithShadowingPart(r, NoV) * GGXSmithShadowingPart(r, NoL);
+}
+
+#endif // #ifndef BRDF_INC
\ No newline at end of file
diff --git a/projects/voxelization/assets/shaders/depthPrepass.frag b/projects/voxelization/assets/shaders/depthPrepass.frag
new file mode 100644
index 0000000000000000000000000000000000000000..5e2f7a092ca300af40cc039608a44d080c28730f
--- /dev/null
+++ b/projects/voxelization/assets/shaders/depthPrepass.frag
@@ -0,0 +1,20 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+#include "perMeshResources.inc"
+
+layout(location = 0) in vec2 passUV;
+
+layout(location = 0) out vec4 outColor; // only used for alpha to coverage, not actually written to
+
+// coverage to alpha techniques explained in: https://bgolus.medium.com/anti-aliased-alpha-test-the-esoteric-alpha-to-coverage-8b177335ae4f
+void main()	{
+    float alpha         = texture(sampler2D(albedoTexture, textureSampler), passUV).a;
+    float alphaCutoff   = 0.5;
+    
+    // scale alpha to one pixel width
+    alpha               = (alpha - alphaCutoff) / max(fwidth(alpha), 0.0001) + 0.5;
+    
+    outColor.a          = alpha;
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/shadow.vert b/projects/voxelization/assets/shaders/depthPrepass.vert
similarity index 61%
rename from projects/voxelization/resources/shaders/shadow.vert
rename to projects/voxelization/assets/shaders/depthPrepass.vert
index e0f41d42d575fa64fedbfa04adf89ac0f4aeebe8..4bb3500eb59214e30fce84862e181fd7e24b7340 100644
--- a/projects/voxelization/resources/shaders/shadow.vert
+++ b/projects/voxelization/assets/shaders/depthPrepass.vert
@@ -1,7 +1,12 @@
 #version 450
 #extension GL_ARB_separate_shader_objects : enable
 
+#extension GL_GOOGLE_include_directive : enable
+
 layout(location = 0) in vec3 inPosition;
+layout(location = 2) in vec2 inUV;
+
+layout(location = 0) out vec2 passUV;
 
 layout( push_constant ) uniform constants{
     mat4 mvp;
@@ -9,4 +14,5 @@ layout( push_constant ) uniform constants{
 
 void main()	{
 	gl_Position = mvp * vec4(inPosition, 1.0);
+    passUV = inUV;
 }
\ No newline at end of file
diff --git a/projects/voxelization/assets/shaders/depthToMoments.comp b/projects/voxelization/assets/shaders/depthToMoments.comp
new file mode 100644
index 0000000000000000000000000000000000000000..79e47cdef02143ed97d53e533f47e822db8c0f6f
--- /dev/null
+++ b/projects/voxelization/assets/shaders/depthToMoments.comp
@@ -0,0 +1,38 @@
+#version 450
+#extension GL_GOOGLE_include_directive : enable
+#extension GL_ARB_texture_multisample : enable
+
+#include "shadowMapping.inc"
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+layout(set=0, binding=0)                    uniform texture2DMS srcTexture;
+layout(set=0, binding=1)                    uniform sampler     depthSampler;                
+layout(set=0, binding=2, rgba16)            uniform image2D     outImage;
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+layout( push_constant ) uniform constants{
+    int msaaCount;
+};
+
+void main(){
+
+    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(outImage)))){
+        return;
+    }
+    ivec2 uv = ivec2(gl_GlobalInvocationID.xy);
+
+    float z = 0;
+    for(int i = 0; i < msaaCount; i++){
+        z += texelFetch(sampler2DMS(srcTexture, depthSampler), uv, i).r;
+    }
+    z /= msaaCount;
+    z = 2 * z - 1;	// algorithm expects depth in range [-1:1]
+	
+    float   z2                  = z*z;   
+    vec4    moments             = vec4(z, z2, z2*z, z2*z2);
+    vec4    momentsQuantized    = quantizeMoments(moments);
+	
+    imageStore(outImage, uv, momentsQuantized);
+}
\ No newline at end of file
diff --git a/projects/voxelization/assets/shaders/lightInfo.inc b/projects/voxelization/assets/shaders/lightInfo.inc
new file mode 100644
index 0000000000000000000000000000000000000000..a87f9ce7bebc1db1688dd20dd80608e99925755a
--- /dev/null
+++ b/projects/voxelization/assets/shaders/lightInfo.inc
@@ -0,0 +1,12 @@
+#ifndef LIGHT_INFO_INC
+#define LIGHT_INFO_INC
+
+struct LightInfo{
+    vec3    L;             
+    float   padding;
+    vec3    sunColor;      
+    float   sunStrength;
+    mat4    lightMatrix;
+};
+
+#endif // #ifndef LIGHT_INFO_INC
\ No newline at end of file
diff --git a/projects/voxelization/assets/shaders/luma.inc b/projects/voxelization/assets/shaders/luma.inc
new file mode 100644
index 0000000000000000000000000000000000000000..17b3b282830ab155ce62e9b1394c0985ceccecd9
--- /dev/null
+++ b/projects/voxelization/assets/shaders/luma.inc
@@ -0,0 +1,8 @@
+#ifndef LUMA_INC
+#define LUMA_INC
+
+float computeLuma(vec3 c){
+    return dot(c, vec3(0.21, 0.72, 0.07));
+}
+
+#endif // #ifndef LUMA_INC
\ No newline at end of file
diff --git a/projects/voxelization/assets/shaders/msaa4XResolve.comp b/projects/voxelization/assets/shaders/msaa4XResolve.comp
new file mode 100644
index 0000000000000000000000000000000000000000..8bb1a946e3ba43f4e80f21f6bd730e020276f2d8
--- /dev/null
+++ b/projects/voxelization/assets/shaders/msaa4XResolve.comp
@@ -0,0 +1,83 @@
+#version 450
+#extension GL_ARB_texture_multisample : enable
+#extension GL_GOOGLE_include_directive : enable
+
+layout(set=0, binding=0)                    uniform texture2DMS     srcTexture;
+layout(set=0, binding=1)                    uniform sampler         MSAASampler;                
+layout(set=0, binding=2, r11f_g11f_b10f)    uniform image2D         outImage;
+
+#include "luma.inc"
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+vec3 tonemap(vec3 c){
+    return c / (1 + computeLuma(c));
+}
+
+vec3 tonemapReverse(vec3 c){
+    return c / (1 - computeLuma(c));
+}
+
+float reconstructionFilter(float d){
+    // gauß filter, tuned so that distance of one has weight around 20%
+    float a = 1.6; 
+    return exp(-a * d*d);
+}
+
+void main(){
+
+    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(outImage)))){
+        return;
+    }
+    ivec2 uv            = ivec2(gl_GlobalInvocationID.xy);
+    
+    vec2 samplePositions[4] = {
+     vec2(0.375, 0.125),
+     vec2(0.875, 0.375),
+     vec2(0.125, 0.625),
+     vec2(0.625, 0.875)};
+    
+    vec3    color   = vec3(0);
+    float   wTotal  = 0;
+    
+    // four samples from main pixel
+    for(int i = 0; i < 4; i++){
+        vec3 msaaSample = texelFetch(sampler2DMS(srcTexture, MSAASampler), uv, i).rgb;
+        float d         = distance(vec2(0.5), samplePositions[i]);
+        float w         = reconstructionFilter(d);
+        color           += tonemap(msaaSample) * w;
+        wTotal          += w;
+    }
+    
+    ivec2 neighbourOffsets[4] = {
+        ivec2( 1,  0),   // right
+        ivec2(-1,  0),   // left
+        ivec2( 0,  1),   // top
+        ivec2( 0, -1)    // bot
+    };
+    
+    int neighbourSampleIndices[8] = {
+        0, 2, // left  samples of right neighbour
+        1, 3, // right samples of left  neighbour
+        2, 3, // bot   samples of top   neighbour
+        0, 1  // top   samples of bot   neighbour
+    };
+    
+    // two additional samples from each neighbour
+    for(int neighbour = 0; neighbour < 4; neighbour++){
+        for(int i = 0; i < 2; i++){
+            int     sampleIndex = neighbourSampleIndices[neighbour * 2 + i];
+            ivec2   pixelOffset = neighbourOffsets[neighbour];
+            ivec2   pixelUV     = uv + pixelOffset;
+            vec3    msaaSample  = texelFetch(sampler2DMS(srcTexture, MSAASampler), pixelUV, sampleIndex).rgb;
+            float   d           = distance(vec2(0.5), samplePositions[sampleIndex] + pixelOffset);
+            float   w           = reconstructionFilter(d);
+            color               += tonemap(msaaSample) * w;
+            wTotal              += w;
+        }
+    }    
+    color /= wTotal;
+    color = tonemapReverse(color);
+    
+    imageStore(outImage, uv, vec4(color, 0.f));
+}
\ No newline at end of file
diff --git a/projects/voxelization/assets/shaders/perMeshResources.inc b/projects/voxelization/assets/shaders/perMeshResources.inc
new file mode 100644
index 0000000000000000000000000000000000000000..b1523713cf2040f672f74be3f47f9bf43996c614
--- /dev/null
+++ b/projects/voxelization/assets/shaders/perMeshResources.inc
@@ -0,0 +1,4 @@
+layout(set=1, binding=0) uniform texture2D  albedoTexture;
+layout(set=1, binding=1) uniform sampler    textureSampler;
+layout(set=1, binding=2) uniform texture2D  normalTexture;
+layout(set=1, binding=3) uniform texture2D  specularTexture;
\ No newline at end of file
diff --git a/projects/voxelization/assets/shaders/postEffects.comp b/projects/voxelization/assets/shaders/postEffects.comp
new file mode 100644
index 0000000000000000000000000000000000000000..c0f9fe1a764bcdabac5501e2f82692c6f476e9e6
--- /dev/null
+++ b/projects/voxelization/assets/shaders/postEffects.comp
@@ -0,0 +1,149 @@
+#version 440
+#extension GL_GOOGLE_include_directive : enable
+
+#include "luma.inc"
+
+layout(set=0, binding=0)        uniform texture2D   inTexture;
+layout(set=0, binding=1)        uniform sampler     textureSampler;
+layout(set=0, binding=2, rgba8) uniform image2D     outImage;
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+layout( push_constant ) uniform constants{
+    float time;
+};
+
+// from: https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/
+vec3 ACESFilm(vec3 x)
+{
+    float a = 2.51f;
+    float b = 0.03f;
+    float c = 2.43f;
+    float d = 0.59f;
+    float e = 0.14f;
+    return clamp((x*(a*x+b))/(x*(c*x+d)+e), 0, 1);
+}
+
+// From Dave Hoskins: https://www.shadertoy.com/view/4djSRW.
+float hash(vec3 p3){
+    p3 = fract(p3 * 0.1031);
+    p3 += dot(p3,p3.yzx + 19.19);
+    return fract((p3.x + p3.y) * p3.z);
+}
+
+// From iq: https://www.shadertoy.com/view/4sfGzS.
+float noise(vec3 x){
+    vec3 i = floor(x);
+    vec3 f = fract(x);
+    f = f*f*(3.0-2.0*f);
+    return mix(mix(mix(hash(i+vec3(0, 0, 0)),
+    hash(i+vec3(1, 0, 0)),f.x),
+    mix(hash(i+vec3(0, 1, 0)),
+    hash(i+vec3(1, 1, 0)),f.x),f.y),
+    mix(mix(hash(i+vec3(0, 0, 1)),
+    hash(i+vec3(1, 0, 1)),f.x),
+    mix(hash(i+vec3(0, 1, 1)),
+    hash(i+vec3(1, 1, 1)),f.x),f.y),f.z);
+}
+
+// From: https://www.shadertoy.com/view/3sGSWVF
+// Slightly high-passed continuous value-noise.
+float grainSource(vec3 x, float strength, float pitch){
+    float center = noise(x);
+    float v1 = center - noise(vec3( 1, 0, 0)/pitch + x) + 0.5;
+    float v2 = center - noise(vec3( 0, 1, 0)/pitch + x) + 0.5;
+    float v3 = center - noise(vec3(-1, 0, 0)/pitch + x) + 0.5;
+    float v4 = center - noise(vec3( 0,-1, 0)/pitch + x) + 0.5;
+
+    float total = (v1 + v2 + v3 + v4) / 4.0;
+    return mix(1, 0.5 + total, strength);
+}
+
+vec3 applyGrain(ivec2 uv, vec3 c){
+    float grainLift     = 0.6;
+    float grainStrength = 0.4;
+    float grainTimeFactor = 0.1;
+
+    float timeColorOffset = 1.2;
+    vec3 grain = vec3(
+    grainSource(vec3(uv, floor(grainTimeFactor*time)),                   grainStrength, grainLift),
+    grainSource(vec3(uv, floor(grainTimeFactor*time + timeColorOffset)), grainStrength, grainLift),
+    grainSource(vec3(uv, floor(grainTimeFactor*time - timeColorOffset)), grainStrength, grainLift));
+
+    return c * grain;
+}
+
+vec2 computeDistortedUV(vec2 uv, float aspectRatio){
+    uv          = uv * 2 - 1;
+    float   r2  = dot(uv, uv);
+    float   k1  = 0.02f;
+
+    float maxR2     = dot(vec2(1), vec2(1));
+    float maxFactor = maxR2 * k1;
+
+    // correction only needed for pincushion distortion
+    maxFactor       = min(maxFactor, 0);
+
+    uv /= 1 + r2*k1;
+
+    // correction to avoid going out of [-1, 1] range when using barrel distortion
+    uv *= 1 + maxFactor;
+
+    return uv * 0.5 + 0.5;
+}
+
+float computeLocalContrast(vec2 uv){
+    float lumaMin = 100;
+    float lumaMax = 0;
+
+    vec2 pixelSize = vec2(1) / textureSize(sampler2D(inTexture, textureSampler), 0);
+
+    for(int x = -1; x <= 1; x++){
+        for(int y = -1; y <= 1; y++){
+            vec3 c = texture(sampler2D(inTexture, textureSampler), uv + vec2(x, y) * pixelSize).rgb;
+            float luma  = computeLuma(c);
+            lumaMin     = min(lumaMin, luma);
+            lumaMax     = max(lumaMax, luma);
+        }
+    }
+
+    return lumaMax - lumaMin;
+}
+
+vec3 computeChromaticAberrationScale(vec2 uv){
+    float   localContrast   = computeLocalContrast(uv);
+    vec3    colorScales     = vec3(-1, 0, 1);
+    float   aberrationScale = 0.004;
+    vec3    maxScaleFactors = colorScales * aberrationScale;
+    float   factor          = clamp(localContrast, 0, 1);
+    return mix(vec3(0), maxScaleFactors, factor);
+}
+
+vec3 sampleColorChromaticAberration(vec2 uv){
+    vec2 toCenter       = (vec2(0.5) - uv);
+
+    vec3 scaleFactors = computeChromaticAberrationScale(uv);
+
+    float r = texture(sampler2D(inTexture, textureSampler), uv + toCenter * scaleFactors.r).r;
+    float g = texture(sampler2D(inTexture, textureSampler), uv + toCenter * scaleFactors.g).g;
+    float b = texture(sampler2D(inTexture, textureSampler), uv + toCenter * scaleFactors.b).b;
+    return vec3(r, g, b);
+}
+
+void main(){
+
+    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(outImage)))){
+        return;
+    }
+    ivec2   textureRes  = textureSize(sampler2D(inTexture, textureSampler), 0);
+    ivec2   coord       = ivec2(gl_GlobalInvocationID.xy);
+    vec2    uv          = vec2(coord) / textureRes;
+    float   aspectRatio = float(textureRes.x) / textureRes.y;
+    uv                  = computeDistortedUV(uv, aspectRatio);
+
+    vec3 tonemapped    = sampleColorChromaticAberration(uv);
+    tonemapped          = applyGrain(coord, tonemapped);
+
+    vec3 gammaCorrected = pow(tonemapped, vec3(1.f / 2.2f));
+    imageStore(outImage, coord, vec4(gammaCorrected, 0.f));
+}
\ No newline at end of file
diff --git a/projects/voxelization/assets/shaders/shader.frag b/projects/voxelization/assets/shaders/shader.frag
new file mode 100644
index 0000000000000000000000000000000000000000..25ec69acb77bace1134920bbcee56deb40bb936b
--- /dev/null
+++ b/projects/voxelization/assets/shaders/shader.frag
@@ -0,0 +1,170 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
+
+#include "perMeshResources.inc"
+#include "lightInfo.inc"
+#include "shadowMapping.inc"
+#include "brdf.inc"
+#include "voxel.inc"
+
+layout(location = 0) in vec3 passNormal;
+layout(location = 1) in vec2 passUV;
+layout(location = 2) in vec3 passPos;
+layout(location = 3) in vec4 passTangent;
+
+layout(location = 0) out vec3 outColor;
+
+layout(set=0, binding=0) uniform sunBuffer {
+    LightInfo lightInfo;
+};
+layout(set=0, binding=1) uniform texture2D  shadowMap;
+layout(set=0, binding=2) uniform sampler    shadowMapSampler;
+
+layout(set=0, binding=3) uniform cameraBuffer {
+    vec3 cameraPos;
+};
+
+layout(set=0, binding=4) uniform texture3D  voxelTexture;
+layout(set=0, binding=5) uniform sampler    voxelSampler;
+
+layout(set=0, binding=6) uniform VoxelInfoBuffer{
+    VoxelInfo voxelInfo;
+};
+
+layout(set=0, binding=7) uniform VolumetricSettings {
+    vec3    scatteringCoefficient;
+    float   volumetricAmbientLight;
+    vec3    absorptionCoefficient;
+};
+
+
+vec3 cookTorrance(vec3 f0, float r, vec3 N, vec3 V, vec3 L){
+    
+    vec3 H  = normalize(L + V);
+    
+    float NoH = clamp(dot(N, H), 0, 1);
+    float NoL = clamp(dot(N, L), 0, 1);
+    float NoV = clamp(abs(dot(N, V)), 0, 1);    // abs to account for wrong visibility caused by normal mapping
+    
+    vec3    F = fresnelSchlick(NoH, f0);
+    float   D = GGXDistribution(r, NoH);
+    float   G = GGXSmithShadowing(r, NoV, NoL);
+    
+    return (F * D * G) / max(4 * NoV * NoL, 0.00001);
+}
+
+float roughnessToConeAngleDegree(float r){
+    return mix(degreeToRadian(3), degreeToRadian(60), r);
+}
+
+// from: "Next Generation Post Processing in Call Of Duty Advanced Warfare" slide page 123
+float interleavedGradientNoise(vec2 uv){
+    vec3 magic = vec3(0.06711056, 0.00583715, 62.9829189);
+    return fract(magic.z * fract(dot(uv, magic.xy)));
+}
+
+// from: https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile
+vec3 EnvBRDFApprox(vec3 SpecularColor, float Roughness, float NoV )
+{
+	const vec4 c0 = { -1, -0.0275, -0.572, 0.022 };
+	const vec4 c1 = { 1, 0.0425, 1.04, -0.04 };
+	vec4 r = Roughness * c0 + c1;
+	float a004 = min( r.x * r.x, exp2( -9.28 * NoV ) ) * r.x + r.y;
+	vec2 AB = vec2( -1.04, 1.04 ) * a004 + r.zw;
+	return SpecularColor * AB.x + AB.y;
+}
+
+float isotropicPhase(){
+    return 1 / (4 * pi);
+}
+
+vec3 volumetricLighting(vec3 colorIn, vec3 V, vec3 pos, float d){
+    vec3 color      = colorIn;
+    
+    int sampleCount = 20;
+    float stepSize  = d / sampleCount;
+    
+    vec3 extinctionCoefficient = scatteringCoefficient + absorptionCoefficient;
+    
+    float   noise           = 2 * pi * interleavedGradientNoise(gl_FragCoord.xy);
+    vec2    shadowOffset    = 3.f * vec2(sin(noise), cos(noise)) / textureSize(sampler2D(shadowMap, shadowMapSampler), 0);
+    
+    float noiseScale    = 0.1f;
+    pos                 += V * noiseScale * interleavedGradientNoise(gl_FragCoord.xy);
+    
+    for(int i = 0; i < sampleCount; i++){
+        vec3    samplePoint = pos + V * i * stepSize;
+        float   phase       = isotropicPhase();
+        vec3    light       = lightInfo.sunColor * lightInfo.sunStrength;
+        float   shadow      = shadowTest(samplePoint, lightInfo, shadowMap, shadowMapSampler, shadowOffset);
+        light               *= shadow;
+        light               += volumetricAmbientLight;
+        
+        color               += phase * light * scatteringCoefficient * stepSize;
+        color               *= exp(-stepSize * extinctionCoefficient);
+    }
+    return color;
+}
+
+void main()	{
+
+    vec3 albedoTexel    = texture(sampler2D(albedoTexture, textureSampler), passUV).rgb;
+    vec3 normalTexel    = texture(sampler2D(normalTexture, textureSampler), passUV).rgb;
+    vec3 specularTexel  = texture(sampler2D(specularTexture, textureSampler), passUV).rgb;
+    
+    float r             = specularTexel.g;
+    
+    float metal         = specularTexel.b;
+    vec3 albedo         = mix(albedoTexel, vec3(0), metal);
+    vec3 f0_dielectric  = vec3(0.04f);
+    vec3 f0             = mix(f0_dielectric, albedoTexel, metal);
+    
+    vec3 T      = normalize(passTangent.xyz);
+    vec3 N_geo  = normalize(passNormal);
+    vec3 B      = cross(N_geo, T) * passTangent.w;
+    mat3 TBN    = mat3(T, B, N_geo);
+    normalTexel = normalTexel * 2 - 1;
+
+    vec3 N  = normalize(TBN * normalTexel);
+    vec3 L  = lightInfo.L;
+    vec3 V  = normalize(cameraPos - passPos);
+    
+    float NoL = clamp(dot(N, L), 0, 1);    
+    float NoV = clamp(abs(dot(N, V)), 0, 1);
+    
+    vec3 sunSpecular    = cookTorrance(f0, r, N, V, L);
+    vec3 sun            = lightInfo.sunStrength * lightInfo.sunColor * NoL;
+    
+    float   noise           = 2 * pi * interleavedGradientNoise(gl_FragCoord.xy);
+    vec2    shadowOffset    = 0.05f * vec2(sin(noise), cos(noise)) / textureSize(sampler2D(shadowMap, shadowMapSampler), 0);
+    float   shadow          = shadowTest(passPos, lightInfo, shadowMap, shadowMapSampler, shadowOffset);
+    sun                     *= shadow;
+    
+    vec3 F_in       = fresnelSchlick(NoL, f0);
+    vec3 F_out      = fresnelSchlick(NoV, f0);
+    vec3 diffuse    = lambertBRDF(albedo) * (1 - F_in) * (1 - F_out);
+    
+    vec3 up         = abs(N_geo.y) >= 0.99 ? vec3(1, 0, 0) : vec3(0, 1, 0);
+    vec3 right      = normalize(cross(up, N));
+    up              = cross(N, right); 
+    mat3 toSurface  = mat3(right, up, N);
+    
+    vec3 diffuseTrace = diffuseVoxelTraceHemisphere(toSurface, passPos, voxelTexture, voxelSampler, voxelInfo);
+    
+    vec3 R                      = reflect(-V, N);
+    float reflectionConeAngle   = roughnessToConeAngleDegree(r);
+    vec3 offsetTraceStart       = passPos + N_geo * 0.1f;
+    offsetTraceStart            += R * interleavedGradientNoise(gl_FragCoord.xy) * 0.5;
+    vec3 specularTrace          = voxelConeTrace(R, offsetTraceStart, reflectionConeAngle, voxelTexture, voxelSampler, voxelInfo);
+    specularTrace               *= clamp(dot(N, R), 0, 1);
+    vec3 reflectionBRDF         = EnvBRDFApprox(f0, r, NoV); 
+    
+	outColor = 
+        (diffuse + sunSpecular) * sun + 
+        lambertBRDF(albedo) * diffuseTrace + 
+        reflectionBRDF * specularTrace;
+        
+    float d     = distance(cameraPos, passPos);
+    outColor    = volumetricLighting(outColor, V, passPos, d);
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/shader.vert b/projects/voxelization/assets/shaders/shader.vert
similarity index 80%
rename from projects/voxelization/resources/shaders/shader.vert
rename to projects/voxelization/assets/shaders/shader.vert
index 926f86af2860cb57c44d2d5ee78712b6ae155e5c..e3873f98a308347592725e794d6b7102cbbe3e5c 100644
--- a/projects/voxelization/resources/shaders/shader.vert
+++ b/projects/voxelization/assets/shaders/shader.vert
@@ -4,10 +4,12 @@
 layout(location = 0) in vec3 inPosition;
 layout(location = 1) in vec3 inNormal;
 layout(location = 2) in vec2 inUV;
+layout(location = 3) in vec4 inTangent;
 
 layout(location = 0) out vec3 passNormal;
 layout(location = 1) out vec2 passUV;
 layout(location = 2) out vec3 passPos;
+layout(location = 3) out vec4 passTangent;
 
 layout( push_constant ) uniform constants{
     mat4 mvp;
@@ -19,4 +21,5 @@ void main()	{
 	passNormal  = mat3(model) * inNormal;    // assuming no weird stuff like shearing or non-uniform scaling
     passUV      = inUV;
     passPos     = (model * vec4(inPosition, 1)).xyz;
+    passTangent = vec4(mat3(model) * inTangent.xyz, inTangent.w);
 }
\ No newline at end of file
diff --git a/projects/bloom/resources/shaders/shadow.frag b/projects/voxelization/assets/shaders/shadow.frag
similarity index 62%
rename from projects/bloom/resources/shaders/shadow.frag
rename to projects/voxelization/assets/shaders/shadow.frag
index 848f853f556660b4900b5db7fb6fc98d57c1cd5b..65592d2cfe161b8522de1a0c3e68fa1d6afa80be 100644
--- a/projects/bloom/resources/shaders/shadow.frag
+++ b/projects/voxelization/assets/shaders/shadow.frag
@@ -1,5 +1,6 @@
 #version 450
 #extension GL_ARB_separate_shader_objects : enable
+#extension GL_GOOGLE_include_directive : enable
 
 void main()	{
 
diff --git a/projects/bloom/resources/shaders/shadow.vert b/projects/voxelization/assets/shaders/shadow.vert
similarity index 82%
rename from projects/bloom/resources/shaders/shadow.vert
rename to projects/voxelization/assets/shaders/shadow.vert
index e0f41d42d575fa64fedbfa04adf89ac0f4aeebe8..d800c547368c4f2126c880534276a3be3cf336f5 100644
--- a/projects/bloom/resources/shaders/shadow.vert
+++ b/projects/voxelization/assets/shaders/shadow.vert
@@ -1,6 +1,8 @@
 #version 450
 #extension GL_ARB_separate_shader_objects : enable
 
+#extension GL_GOOGLE_include_directive : enable
+
 layout(location = 0) in vec3 inPosition;
 
 layout( push_constant ) uniform constants{
diff --git a/projects/voxelization/assets/shaders/shadowBlur.inc b/projects/voxelization/assets/shaders/shadowBlur.inc
new file mode 100644
index 0000000000000000000000000000000000000000..ed4994ed1ace34afdafff15920d18a2433a3c0a4
--- /dev/null
+++ b/projects/voxelization/assets/shaders/shadowBlur.inc
@@ -0,0 +1,27 @@
+#ifndef SHADOW_BLUR_INC
+#define SHADOW_BLUR_INC
+
+vec4 blurMomentShadowMap1D(ivec2 coord, ivec2 blurDirection, texture2D srcTexture, sampler depthSampler){
+    
+    int blurRadius  = 7;
+    int minOffset   = -(blurRadius-1) / 2;
+    int maxOffset   = -minOffset;
+    
+    vec2 pixelSize = vec2(1) / textureSize(sampler2D(srcTexture, depthSampler), 0);
+    
+    float wTotal = 0;
+    vec4 moments = vec4(0);
+    
+    float weights1D[4] = { 0.5, 0.25, 0.125, 0.0625 };    // gaussian
+    
+    for(int i = minOffset; i <= maxOffset; i++){
+        vec2 uv = (coord + i * blurDirection) * pixelSize;
+        uv      += 0.5 * pixelSize * blurDirection * sign(i); // half pixel shift to take advantage of bilinear filtering
+        float w = weights1D[abs(i)];
+        moments += w * texture(sampler2D(srcTexture, depthSampler), uv);
+        wTotal  += w;
+    }
+    return moments / wTotal;
+}
+
+#endif // #ifndef SHADOW_BLUR_INC
\ No newline at end of file
diff --git a/projects/voxelization/assets/shaders/shadowBlurX.comp b/projects/voxelization/assets/shaders/shadowBlurX.comp
new file mode 100644
index 0000000000000000000000000000000000000000..41d127fdf5ce46dec883d49af4f284b5787d5d38
--- /dev/null
+++ b/projects/voxelization/assets/shaders/shadowBlurX.comp
@@ -0,0 +1,23 @@
+#version 450
+#extension GL_GOOGLE_include_directive : enable
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+#include "shadowBlur.inc"
+
+layout(set=0, binding=0)            uniform texture2D   srcTexture;
+layout(set=0, binding=1)            uniform sampler     depthSampler;                
+layout(set=0, binding=2, rgba16)    uniform image2D     outImage;
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+void main(){
+
+    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(outImage)))){
+        return;
+    }
+    ivec2 coord = ivec2(gl_GlobalInvocationID.xy);    
+    vec4 moments = blurMomentShadowMap1D(coord, ivec2(1, 0), srcTexture, depthSampler);
+    // moments = texelFetch(sampler2D(srcTexture, depthSampler), coord, 0);
+    imageStore(outImage, coord, moments);
+}
\ No newline at end of file
diff --git a/projects/voxelization/assets/shaders/shadowBlurY.comp b/projects/voxelization/assets/shaders/shadowBlurY.comp
new file mode 100644
index 0000000000000000000000000000000000000000..c1710d7d6c75ef0093fecfe708272f56f9541eaf
--- /dev/null
+++ b/projects/voxelization/assets/shaders/shadowBlurY.comp
@@ -0,0 +1,23 @@
+#version 450
+#extension GL_GOOGLE_include_directive : enable
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+#include "shadowBlur.inc"
+
+layout(set=0, binding=0)            uniform texture2D   srcTexture;
+layout(set=0, binding=1)            uniform sampler     depthSampler;                
+layout(set=0, binding=2, rgba16)    uniform image2D     outImage;
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+void main(){
+
+    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(outImage)))){
+        return;
+    }
+    ivec2 coord = ivec2(gl_GlobalInvocationID.xy);    
+    vec4 moments = blurMomentShadowMap1D(coord, ivec2(0, 1), srcTexture, depthSampler);
+    // moments = texelFetch(sampler2D(srcTexture, depthSampler), coord, 0);
+    imageStore(outImage, coord, moments);
+}
\ No newline at end of file
diff --git a/projects/voxelization/assets/shaders/shadowMapping.inc b/projects/voxelization/assets/shaders/shadowMapping.inc
new file mode 100644
index 0000000000000000000000000000000000000000..9124a05c310c2cc16e6b02802f5adb36bde42804
--- /dev/null
+++ b/projects/voxelization/assets/shaders/shadowMapping.inc
@@ -0,0 +1,97 @@
+#ifndef SHADOW_MAPPING_INC
+#define SHADOW_MAPPING_INC
+
+#include "lightInfo.inc"
+
+// nice math blob from the moment shadow mapping presentation
+float ComputeMSMShadowIntensity(vec4 _4Moments, float FragmentDepth, float DepthBias, float MomentBias)
+{
+    vec4 b=mix(_4Moments, vec4(0, 0.63, 0, 0.63),MomentBias);
+	
+    vec3 z;
+    z[0]=FragmentDepth-DepthBias;
+    float L32D22=fma(-b[0], b[1], b[2]);
+    float D22=fma(-b[0], b[0], b[1]);
+    float SquaredDepthVariance=fma(-b[1], b[1], b[3]);
+    float D33D22=dot(vec2(SquaredDepthVariance,-L32D22),
+                     vec2(D22,                  L32D22));
+                     
+    float InvD22=1.0/D22;
+    float L32=L32D22*InvD22;
+    vec3 c=vec3(1.0,z[0],z[0]*z[0]);
+    c[1]-=b.x;
+    c[2]-=b.y+L32*c[1];
+    c[1]*=InvD22;
+    c[2]*=D22/D33D22;
+    c[1]-=L32*c[2];
+    c[0]-=dot(c.yz,b.xy);
+    float p=c[1]/c[2];
+    float q=c[0]/c[2];
+    float r=sqrt((p*p*0.25)-q);
+    z[1]=-p*0.5-r;
+    z[2]=-p*0.5+r;
+    vec4 Switch=
+    	(z[2]<z[0])?vec4(z[1],z[0],1.0,1.0):(
+    	(z[1]<z[0])?vec4(z[0],z[1],0.0,1.0):
+    	vec4(0.0));
+    float Quotient=(Switch[0]*z[2]-b[0]*(Switch[0]+z[2])+b[1])
+                  /((z[2]-Switch[1])*(z[0]-z[1]));
+    return 1-clamp(Switch[2]+Switch[3]*Quotient, 0, 1);
+}
+
+vec4 quantizeMoments(vec4 moments){
+    vec4 quantized;
+	quantized.r = 1.5 * moments.r - 2 * moments.b + 0.5;
+	quantized.g = 4   * moments.g - 4 * moments.a;
+	quantized.b = sqrt(3)/2 * moments.r - sqrt(12)/9 * moments.b + 0.5;
+	quantized.a = 0.5 * moments.g + 0.5 * moments.a;
+	
+	return quantized;
+}
+
+vec4 unquantizeMoments(vec4 moments){
+    moments -= vec4(0.5, 0, 0.5, 0);
+	vec4 unquantized;
+	unquantized.r = -1.f / 3 * moments.r + sqrt(3) * moments.b;
+	unquantized.g = 0.125 * moments.g + moments.a;
+	unquantized.b = -0.75 * moments.r + 0.75 * sqrt(3) * moments.b;
+	unquantized.a = -0.125 * moments.g + moments.a;
+	return unquantized / 0.98;	// division reduces light bleeding
+}
+
+float rescaleRange(float a, float b, float v)
+{
+    return clamp((v - a) / (b - a), 0, 1);
+}
+
+float reduceLightBleeding(float shadow, float amount)
+{
+   return rescaleRange(amount, 1.0f, shadow);
+}
+
+float shadowTest(vec3 worldPos, LightInfo lightInfo, texture2D shadowMap, sampler shadowMapSampler, vec2 offset){
+    vec4 lightPos   = lightInfo.lightMatrix * vec4(worldPos, 1);
+    lightPos        /= lightPos.w;
+    lightPos.xy     = lightPos.xy * 0.5 + 0.5;
+    lightPos.xy     += offset;
+    
+    if(any(lessThan(lightPos.xy, vec2(0))) || any(greaterThan(lightPos.xy, vec2(1)))){
+        return 1;
+    }
+	
+    lightPos.z = clamp(lightPos.z, 0, 1);
+	lightPos.z = 2 * lightPos.z - 1;	// algorithm expects depth in range [-1:1]
+
+    vec4 shadowMapSample = texture(sampler2D(shadowMap, shadowMapSampler), lightPos.xy);
+    
+    shadowMapSample = unquantizeMoments(shadowMapSample);
+    
+    float depthBias     = 0.f;
+    float momentBias    = 0.0006;
+    
+    float shadow = ComputeMSMShadowIntensity(shadowMapSample, lightPos.z, depthBias, momentBias);
+	return clamp(shadow, 0, 1);
+    // return reduceLightBleeding(shadow, 0.1f);
+}
+
+#endif // #ifndef SHADOW_MAPPING_INC
\ No newline at end of file
diff --git a/projects/voxelization/assets/shaders/sky.frag b/projects/voxelization/assets/shaders/sky.frag
new file mode 100644
index 0000000000000000000000000000000000000000..2a3b2ad03e1936641a565b2f3fbd1f19f186ff7a
--- /dev/null
+++ b/projects/voxelization/assets/shaders/sky.frag
@@ -0,0 +1,13 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) out vec3 outColor;
+
+layout( push_constant ) uniform constants{
+    vec3 skyColor;
+    float skyStrength;
+};
+
+void main()	{
+    outColor = skyColor * skyStrength;
+}
\ No newline at end of file
diff --git a/projects/voxelization/assets/shaders/sky.vert b/projects/voxelization/assets/shaders/sky.vert
new file mode 100644
index 0000000000000000000000000000000000000000..686e6f352e9bb1054656f58340a9cfc9b55fcff4
--- /dev/null
+++ b/projects/voxelization/assets/shaders/sky.vert
@@ -0,0 +1,12 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+const vec2 positions[3] = {
+    vec2(-1, -1),
+    vec2(-1, 4),
+    vec2(4, -1)
+};
+
+void main()	{
+	gl_Position = vec4(positions[gl_VertexIndex], 1, 1);
+}
\ No newline at end of file
diff --git a/projects/voxelization/assets/shaders/tonemapping.comp b/projects/voxelization/assets/shaders/tonemapping.comp
new file mode 100644
index 0000000000000000000000000000000000000000..ffadc9a71e207f97fec9a8815aa1c61bc709c369
--- /dev/null
+++ b/projects/voxelization/assets/shaders/tonemapping.comp
@@ -0,0 +1,34 @@
+#version 440
+#extension GL_GOOGLE_include_directive : enable
+
+layout(set=0, binding=0)        uniform texture2D   inTexture;
+layout(set=0, binding=1)        uniform sampler     textureSampler;
+layout(set=0, binding=2, rgba8) uniform image2D     outImage;
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+// from: https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/
+vec3 ACESFilm(vec3 x)
+{
+    float a = 2.51f;
+    float b = 0.03f;
+    float c = 2.43f;
+    float d = 0.59f;
+    float e = 0.14f;
+    return clamp((x*(a*x+b))/(x*(c*x+d)+e), 0, 1);
+}
+
+void main(){
+
+    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(outImage)))){
+        return;
+    }
+    ivec2   textureRes  = textureSize(sampler2D(inTexture, textureSampler), 0);
+    ivec2   coord       = ivec2(gl_GlobalInvocationID.xy);
+    vec2    uv          = vec2(coord) / textureRes;
+
+    vec3 linearColor    = texture(sampler2D(inTexture, textureSampler), uv).rgb;
+    vec3 tonemapped     = ACESFilm(linearColor);
+
+    imageStore(outImage, coord, vec4(tonemapped, 0.f));
+}
\ No newline at end of file
diff --git a/projects/voxelization/assets/shaders/voxel.inc b/projects/voxelization/assets/shaders/voxel.inc
new file mode 100644
index 0000000000000000000000000000000000000000..6133ca7cfc52ca77cb70fb8c2cc0e83ef6da4016
--- /dev/null
+++ b/projects/voxelization/assets/shaders/voxel.inc
@@ -0,0 +1,179 @@
+#include "brdf.inc"
+
+struct VoxelInfo{
+    vec3 offset;
+    float extent;
+};
+
+struct PackedVoxelData{
+    uint color;
+    uint normal;
+    uint albedo;
+};
+
+uint flattenVoxelUVToIndex(ivec3 UV, ivec3 voxelImageSize){
+    return UV.x + UV.y * voxelImageSize.x + UV.z *  voxelImageSize.x*  voxelImageSize.y;
+}
+
+vec3 worldToVoxelCoordinates(vec3 world, VoxelInfo info){
+    return (world - info.offset) / info.extent + 0.5f;
+}
+
+ivec3 voxelCoordinatesToUV(vec3 voxelCoordinates, ivec3 voxelImageResolution){
+    return ivec3(voxelCoordinates * voxelImageResolution);
+}
+
+vec3 voxelCoordinatesToWorldPosition(ivec3 coord, int voxelResolution, VoxelInfo voxelInfo, float voxelHalfSize){
+    return (vec3(coord) / voxelResolution - 0.5) * voxelInfo.extent + voxelHalfSize + voxelInfo.offset;
+}
+
+// packed voxel data: 
+// 1 bit opacity
+// 7 bit exposure
+// 8 bit blue
+// 8 bit green
+// 8 bit red
+float maxExposure = 16.f;
+
+uint packVoxelColor(vec3 color){
+    
+    color               = clamp(color, vec3(0), vec3(maxExposure));
+    float maxComponent  = max(max(max(color.r, color.g), color.b), 1.f);
+    color               /= maxComponent;
+    
+    uint opaqueBit      = 1 << 31;
+    uint exposureBits   = (0x0000007F & uint(maxComponent / maxExposure * 127)) << 24;
+    uint redBits        = (0x000000FF & uint(color.r * 255)) << 0;
+    uint greenBits      = (0x000000FF & uint(color.g * 255)) << 8;
+    uint blueBits       = (0x000000FF & uint(color.b * 255)) << 16;
+    return opaqueBit | exposureBits | blueBits | greenBits | redBits;
+}
+
+vec4 unpackVoxelColor(uint packed){
+    vec4 rgba;
+    rgba.r = (packed >> 0  & 0x000000FF) / 255.f;
+    rgba.g = (packed >> 8  & 0x000000FF) / 255.f;
+    rgba.b = (packed >> 16 & 0x000000FF) / 255.f;
+    rgba.a =  packed  >> 31; 
+    
+    rgba.rgb *= (packed >> 24 & 0x0000007F) / 127.f * maxExposure; 
+    
+    return rgba;
+}
+
+uint packSNormInto9Bits(float x){
+    uint lengthBits = 0x000000FF & uint(abs(x) * 255.f);
+    uint signBits   = (x < 0 ? 1 : 0)  << 8;
+    return lengthBits | signBits;
+}
+
+float unpack9LowBitsIntoSNorm(uint bits){
+    bits = (0x000001FF & bits);
+    float length    = bits / 255.f;
+    float sign      = (bits >> 8) == 0 ? 1 : -1;
+    return sign * length;
+}
+
+// normals are packed with 9 bits each, 8 for length and 1 for sign
+uint packVoxelNormal(vec3 N){
+    N           = clamp(N, vec3(0), vec3(1));
+    uint xBits  = packSNormInto9Bits(N.x) << 0;
+    uint yBits  = packSNormInto9Bits(N.y) << 9;
+    uint zBits  = packSNormInto9Bits(N.z) << 18;
+    return zBits | yBits | xBits;
+}
+
+vec3 unpackVoxelNormal(uint packed){
+    vec3 N;
+    N.x  = unpack9LowBitsIntoSNorm(packed >> 0);
+    N.y  = unpack9LowBitsIntoSNorm(packed >> 9);
+    N.z  = unpack9LowBitsIntoSNorm(packed >> 18);
+    return normalize(N);
+}
+
+uint packUNormInto8Bits(float x){
+    return 0x000000FF & uint(abs(x) * 255.f);
+}
+
+float unpack8LowBitsIntoUNorm(uint bits){
+    bits = (0x000000FF & bits);
+    return bits / 255.f;
+}
+
+// albedo is packed with 8 bits each
+uint packVoxelAlbedo(vec3 albedo){
+    albedo      = clamp(albedo, vec3(0), vec3(1));
+    uint rBits  = packUNormInto8Bits(albedo.r) << 0;
+    uint gBits  = packUNormInto8Bits(albedo.g) << 8;
+    uint bBits  = packUNormInto8Bits(albedo.b) << 16;
+    return bBits | gBits | rBits;
+}
+
+vec3 unpackVoxelAlbedo(uint packed){
+    vec3 albedo;
+    albedo.r  = unpack8LowBitsIntoUNorm(packed >> 0);
+    albedo.g  = unpack8LowBitsIntoUNorm(packed >> 8);
+    albedo.b  = unpack8LowBitsIntoUNorm(packed >> 16);
+    return albedo;
+}
+
+vec3 voxelConeTrace(vec3 direction, vec3 startPosition, float coneAngleRadian, texture3D voxelTexture, sampler voxelSampler, VoxelInfo voxelInfo){
+
+    int voxelResolution =  textureSize(sampler3D(voxelTexture, voxelSampler), 0).x;
+    float voxelSize     = voxelInfo.extent / voxelResolution;
+    float maxMip        = float(log2(voxelResolution));
+    float maxStableMip  = 4;    // must be the same as in Voxelization::voxelizeMeshes
+    maxMip              = min(maxMip, maxStableMip);
+    float d             = 2 * sqrt(3 * pow(voxelSize, 2));
+    vec3 color          = vec3(0);
+    float a             = 0;
+    
+    float coneAngleHalf = coneAngleRadian * 0.5f;
+    
+    int maxSamples = 16;
+    for(int i = 0; i < maxSamples; i++){
+        
+        vec3 samplePos      = startPosition + d * direction;
+        vec3 sampleUV       = worldToVoxelCoordinates(samplePos, voxelInfo);
+        
+        if(a >= 0.95 || any(lessThan(sampleUV, vec3(0))) || any(greaterThan(sampleUV, vec3(1)))){
+            break;
+        }
+        
+        float coneDiameter  = 2 * tan(coneAngleHalf) * d;
+        float mip           = log2(coneDiameter / voxelSize);
+        mip                 = min(mip, maxMip);
+    
+        vec4 voxelSample    = textureLod(sampler3D(voxelTexture, voxelSampler), sampleUV , mip);
+        
+        color               += (1 - a) * voxelSample.rgb;
+        a                   += (1 - a) * voxelSample.a;
+        
+        float minStepSize   = 1.f;
+        d                   += max(coneDiameter, minStepSize);
+    }
+    return color;
+}
+
+float degreeToRadian(float d){
+    return d / 180.f * pi;
+}
+
+vec3 diffuseVoxelTraceHemisphere(mat3 toSurface, vec3 position, texture3D voxelTexture, sampler voxelSampler, VoxelInfo voxelInfo){
+    float coneAngle = degreeToRadian(60.f);
+    vec3 diffuseTrace = vec3(0);
+    {
+        vec3 sampleDirection    = toSurface * vec3(0, 0, 1);
+        float weight            = pi / 4.f;
+        diffuseTrace            += weight * voxelConeTrace(sampleDirection, position, coneAngle, voxelTexture, voxelSampler, voxelInfo);
+    }
+    for(int i = 0; i < 6;i++){
+        float theta             = 2 * pi / i;
+        float phi               = pi / 3;   // 60 degrees
+        vec3 sampleDirection    = toSurface * vec3(cos(theta) * sin(phi), sin(theta) * sin(phi), cos(phi));
+        float weight            = pi * (3.f / 4.f) / 6;
+        vec3 trace              = voxelConeTrace(sampleDirection, position, coneAngle, voxelTexture, voxelSampler, voxelInfo);
+        diffuseTrace            += weight * trace;
+    }
+    return diffuseTrace;
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelBufferToImage.comp b/projects/voxelization/assets/shaders/voxelBufferToImage.comp
similarity index 50%
rename from projects/voxelization/resources/shaders/voxelBufferToImage.comp
rename to projects/voxelization/assets/shaders/voxelBufferToImage.comp
index 5e8298886cb2bacbc81f981e8e90310cdc876d5d..2c2cffe856c8b0fc7db07202572aaa35e8445603 100644
--- a/projects/voxelization/resources/shaders/voxelBufferToImage.comp
+++ b/projects/voxelization/assets/shaders/voxelBufferToImage.comp
@@ -3,7 +3,7 @@
 #include "voxel.inc"
 
 layout(set=0, binding=0, std430) buffer voxelBuffer{
-    uint packedVoxelData[];
+    PackedVoxelData packedVoxelData[];
 };
 
 layout(set=0, binding=1, rgba16f) uniform image3D voxelImage;
@@ -19,6 +19,15 @@ void main(){
     ivec3 UV = ivec3(gl_GlobalInvocationID);
     uint flatIndex = flattenVoxelUVToIndex(UV, voxelImageSize);
     
-    vec4 color = unpackVoxelInfo(packedVoxelData[flatIndex]);
+    vec4 color = unpackVoxelColor(packedVoxelData[flatIndex].color);
+    
+    // for proper visualisation voxel secondary bounce should be disabled, otherwise it adds color
+    
+    // for debugging: write normal into image, so voxel visualisation draws normal
+    // color = vec4(unpackVoxelNormal(packedVoxelData[flatIndex].normal), color.a); 
+    
+    // for debugging: write albedo into image, so voxel visualisation draws albedo
+    // color = vec4(unpackVoxelAlbedo(packedVoxelData[flatIndex].albedo), color.a); 
+    
     imageStore(voxelImage, UV, vec4(color));
 }
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelReset.comp b/projects/voxelization/assets/shaders/voxelReset.comp
similarity index 50%
rename from projects/voxelization/resources/shaders/voxelReset.comp
rename to projects/voxelization/assets/shaders/voxelReset.comp
index 14b78d6584d703be68594e3cb03ebcd47c94b6e0..79eda9ec95e703d39af57bc3b29044f0ad6c1bf9 100644
--- a/projects/voxelization/resources/shaders/voxelReset.comp
+++ b/projects/voxelization/assets/shaders/voxelReset.comp
@@ -1,7 +1,9 @@
 #version 450
+#extension GL_GOOGLE_include_directive : enable
+#include "voxel.inc"
 
 layout(set=0, binding=0) buffer voxelizationBuffer{
-    uint isFilled[];
+    PackedVoxelData packedVoxelData[];
 };
 
 layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
@@ -15,5 +17,7 @@ void main(){
     if(gl_GlobalInvocationID.x> voxelCount){
         return;
     }  
-    isFilled[gl_GlobalInvocationID.x] = 0;
+    packedVoxelData[gl_GlobalInvocationID.x].color     = 0;
+    packedVoxelData[gl_GlobalInvocationID.x].normal    = 0;
+    packedVoxelData[gl_GlobalInvocationID.x].albedo    = 0;
 }
\ No newline at end of file
diff --git a/projects/voxelization/assets/shaders/voxelSecondaryBounce.comp b/projects/voxelization/assets/shaders/voxelSecondaryBounce.comp
new file mode 100644
index 0000000000000000000000000000000000000000..29026e7052861ab190200b23d9f860bc609d1550
--- /dev/null
+++ b/projects/voxelization/assets/shaders/voxelSecondaryBounce.comp
@@ -0,0 +1,46 @@
+#version 450
+#extension GL_GOOGLE_include_directive : enable
+#include "voxel.inc"
+#include "brdf.inc"
+
+layout(set=0, binding=0, std430) buffer voxelBuffer{
+    PackedVoxelData packedVoxelData[];
+};
+layout(set=0, binding=1) uniform texture3D          voxelImageIn;
+layout(set=0, binding=2) uniform sampler            voxelSampler;
+layout(set=0, binding=3, rgba16f) uniform image3D   voxelImageOut;
+layout(set=0, binding=4) uniform voxelizationInfo{
+    VoxelInfo voxelInfo;
+};
+
+layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
+
+void main(){
+
+    ivec3 voxelImageSize = imageSize(voxelImageOut);
+    if(any(greaterThanEqual(gl_GlobalInvocationID, voxelImageSize))){
+        return;
+    }
+    ivec3 UV = ivec3(gl_GlobalInvocationID);
+    
+    vec4 color = texelFetch(sampler3D(voxelImageIn, voxelSampler), UV, 0);
+    
+    if(color.a > 0){
+        uint flatIndex  = flattenVoxelUVToIndex(UV, voxelImageSize);
+        vec3 N          = unpackVoxelNormal(packedVoxelData[flatIndex].normal);
+        
+        float halfVoxelSize = voxelInfo.extent / float(voxelImageSize.x) * 0.5f;
+        vec3 pos            = voxelCoordinatesToWorldPosition(UV, voxelImageSize.x, voxelInfo, halfVoxelSize);
+        
+        vec3 up         = abs(N.y) >= 0.99 ? vec3(1, 0, 0) : vec3(0, 1, 0);
+        vec3 right      = normalize(cross(up, N));
+        up              = cross(N, right); 
+        mat3 toSurface  = mat3(right, up, N);
+    
+        vec3 secondaryBounce    = diffuseVoxelTraceHemisphere(toSurface, pos, voxelImageIn, voxelSampler, voxelInfo);
+        vec3 albedo             = unpackVoxelAlbedo(packedVoxelData[flatIndex].albedo);
+        color.rgb               += lambertBRDF(albedo) * secondaryBounce;
+    }
+    
+    imageStore(voxelImageOut, UV, color);
+}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelVisualisation.frag b/projects/voxelization/assets/shaders/voxelVisualisation.frag
similarity index 100%
rename from projects/voxelization/resources/shaders/voxelVisualisation.frag
rename to projects/voxelization/assets/shaders/voxelVisualisation.frag
diff --git a/projects/voxelization/resources/shaders/voxelVisualisation.geom b/projects/voxelization/assets/shaders/voxelVisualisation.geom
similarity index 100%
rename from projects/voxelization/resources/shaders/voxelVisualisation.geom
rename to projects/voxelization/assets/shaders/voxelVisualisation.geom
diff --git a/projects/voxelization/resources/shaders/voxelVisualisation.vert b/projects/voxelization/assets/shaders/voxelVisualisation.vert
similarity index 90%
rename from projects/voxelization/resources/shaders/voxelVisualisation.vert
rename to projects/voxelization/assets/shaders/voxelVisualisation.vert
index 8377143f4f4bbf351d3251df9724d37e1747a4dc..e26e2209ffb9bd3e62103fa9e7eeccce13d7d602 100644
--- a/projects/voxelization/resources/shaders/voxelVisualisation.vert
+++ b/projects/voxelization/assets/shaders/voxelVisualisation.vert
@@ -25,7 +25,7 @@ void main()	{
     int index2D         = gl_VertexIndex % slicePixelCount;
     int y               = index2D / voxelResolution;
     int x               = index2D % voxelResolution;
-    vec3 position       = (vec3(x, y, z) / voxelResolution - 0.5) * voxelInfo.extent + passCubeHalf + voxelInfo.offset;
+    vec3 position       = voxelCoordinatesToWorldPosition(ivec3(x, y, z), voxelResolution, voxelInfo, passCubeHalf);
 	gl_Position         = vec4(position, 1.0);
     
     vec4 voxelColor = imageLoad(voxelImage, ivec3(x,y,z));
diff --git a/projects/voxelization/resources/shaders/voxelization.frag b/projects/voxelization/assets/shaders/voxelization.frag
similarity index 75%
rename from projects/voxelization/resources/shaders/voxelization.frag
rename to projects/voxelization/assets/shaders/voxelization.frag
index a49b13185ec26b069661141cfdbbfbbe45d14fd3..0bbd26bff249db1390399b26f2f4b5a139195fef 100644
--- a/projects/voxelization/resources/shaders/voxelization.frag
+++ b/projects/voxelization/assets/shaders/voxelization.frag
@@ -6,13 +6,14 @@
 #include "perMeshResources.inc"
 #include "lightInfo.inc"
 #include "shadowMapping.inc"
+#include "brdf.inc"
 
 layout(location = 0) in vec3 passPos;
 layout(location = 1) in vec2 passUV;
 layout(location = 2) in vec3 passN;
 
 layout(set=0, binding=0, std430) buffer voxelizationBuffer{
-    uint packedVoxelData[];
+    PackedVoxelData packedVoxelData[];
 };
 
 layout(set=0, binding=1) uniform voxelizationInfo{
@@ -28,14 +29,6 @@ layout(set=0, binding=3) uniform sunBuffer {
 layout(set=0, binding=4) uniform texture2D  shadowMap;
 layout(set=0, binding=5) uniform sampler    shadowMapSampler;
 
-vec3 worldToVoxelCoordinates(vec3 world, VoxelInfo info){
-    return (world - info.offset) / info.extent + 0.5f;
-}
-
-ivec3 voxelCoordinatesToUV(vec3 voxelCoordinates, ivec3 voxelImageResolution){
-    return ivec3(voxelCoordinates * voxelImageResolution);
-}
-
 void main()	{
     vec3 voxelCoordinates = worldToVoxelCoordinates(passPos, voxelInfo);
     ivec3 voxelImageSize = imageSize(voxelImage);
@@ -49,9 +42,11 @@ void main()	{
     
     vec3 N      = normalize(passN);
     float NoL   = clamp(dot(N, lightInfo.L), 0, 1);
-    vec3 sun    = lightInfo.sunStrength * lightInfo.sunColor * NoL * shadowTest(passPos, lightInfo, shadowMap, shadowMapSampler);
+    vec3 sun    = lightInfo.sunStrength * lightInfo.sunColor * NoL * shadowTest(passPos, lightInfo, shadowMap, shadowMapSampler, vec2(0));
     vec3 color  = albedo * sun;
-    color = albedo * sun;
+    color       = lambertBRDF(albedo) * sun;
     
-    atomicMax(packedVoxelData[flatIndex], packVoxelInfo(color));
+    atomicMax(packedVoxelData[flatIndex].color, packVoxelColor(color));
+    atomicMax(packedVoxelData[flatIndex].normal, packVoxelNormal(N));
+    atomicMax(packedVoxelData[flatIndex].albedo, packVoxelAlbedo(albedo));
 }
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxelization.geom b/projects/voxelization/assets/shaders/voxelization.geom
similarity index 100%
rename from projects/voxelization/resources/shaders/voxelization.geom
rename to projects/voxelization/assets/shaders/voxelization.geom
diff --git a/projects/voxelization/resources/shaders/voxelization.vert b/projects/voxelization/assets/shaders/voxelization.vert
similarity index 92%
rename from projects/voxelization/resources/shaders/voxelization.vert
rename to projects/voxelization/assets/shaders/voxelization.vert
index 1302a42441b5b9c8ea7d24f97d29b684e4d64993..221d0f6d189cfe1d6fb8e9e8e2fc9c04884c40c1 100644
--- a/projects/voxelization/resources/shaders/voxelization.vert
+++ b/projects/voxelization/assets/shaders/voxelization.vert
@@ -18,5 +18,5 @@ void main()	{
 	gl_Position = mvp * vec4(inPosition, 1.0);
     passPos     = (model * vec4(inPosition, 1)).xyz;
     passUV      = inUV;
-    passN       = inNormal;
+    passN       = mat3(model) * inNormal;
 }
\ No newline at end of file
diff --git a/projects/voxelization/resources/Sponza/Sponza.bin b/projects/voxelization/resources/Sponza/Sponza.bin
deleted file mode 100644
index cfedd26ca5a67b6d0a47d44d13a75e14a141717a..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/Sponza.bin
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:4b809f7a17687dc99e6f41ca1ea32c06eded8779bf34d16f1f565d750b0ffd68
-size 6347696
diff --git a/projects/voxelization/resources/Sponza/Sponza.gltf b/projects/voxelization/resources/Sponza/Sponza.gltf
deleted file mode 100644
index 172ea07e21c94465211c860cd805355704cef230..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/Sponza.gltf
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:5cc0ecad5c4694088ff820e663619c370421afc1323ac487406e8e9b4735d787
-size 713962
diff --git a/projects/voxelization/resources/Sponza/background.png b/projects/voxelization/resources/Sponza/background.png
deleted file mode 100644
index b64def129da38f4e23d89e21b4af1039008a4327..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/background.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:f5b5f900ff8ed83a31750ec8e428b5b91273794ddcbfc4e4b8a6a7e781f8c686
-size 1417666
diff --git a/projects/voxelization/resources/Sponza/chain_texture.png b/projects/voxelization/resources/Sponza/chain_texture.png
deleted file mode 100644
index c1e1768cff78e0614ad707eca8602a4c4edab5e5..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/chain_texture.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:d8362cfd472880daeaea37439326a4651d1338680ae69bb2513fc6b17c8de7d4
-size 490895
diff --git a/projects/voxelization/resources/Sponza/lion.png b/projects/voxelization/resources/Sponza/lion.png
deleted file mode 100644
index c49c7f0ed31e762e19284d0d3624fbc47664e56b..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/lion.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:9f882f746c3a9cd51a9c6eedc1189b97668721d91a3fe49232036e789912c652
-size 2088728
diff --git a/projects/voxelization/resources/Sponza/spnza_bricks_a_diff.png b/projects/voxelization/resources/Sponza/spnza_bricks_a_diff.png
deleted file mode 100644
index cde4c7a6511e9a5f03c63ad996437fcdba3ce2df..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/spnza_bricks_a_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:b94219c2f5f943f3f4715c74e7d1038bf0ab3b3b3216a758eaee67f875df0851
-size 1928829
diff --git a/projects/voxelization/resources/Sponza/sponza_arch_diff.png b/projects/voxelization/resources/Sponza/sponza_arch_diff.png
deleted file mode 100644
index bcd9bda2918d226039f9e2d03902d377b706fab6..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/sponza_arch_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:c0df2c8a01b2843b1c792b494f7173cdbc4f834840fc2177af3e5d690fceda57
-size 1596151
diff --git a/projects/voxelization/resources/Sponza/sponza_ceiling_a_diff.png b/projects/voxelization/resources/Sponza/sponza_ceiling_a_diff.png
deleted file mode 100644
index 59de631ffac4414cabf69b2dc794c46fc187d6cb..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/sponza_ceiling_a_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:ab6c187a81aa68f4eba30119e17fce2e4882a9ec320f70c90482dbe9da82b1c6
-size 1872074
diff --git a/projects/voxelization/resources/Sponza/sponza_column_a_diff.png b/projects/voxelization/resources/Sponza/sponza_column_a_diff.png
deleted file mode 100644
index 01a82432d3f9939bbefe850bdb900f1ff9a3f6db..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/sponza_column_a_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:2c291507e2808bb83e160ab4b020689817df273baad3713a9ad19ac15fac6826
-size 1840992
diff --git a/projects/voxelization/resources/Sponza/sponza_column_b_diff.png b/projects/voxelization/resources/Sponza/sponza_column_b_diff.png
deleted file mode 100644
index 10a660cce2a5a9b8997772c746058ce23e7d45d7..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/sponza_column_b_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:2820b0267c4289c6cedbb42721792a57ef244ec2d0935941011c2a7d3fe88a9b
-size 2170433
diff --git a/projects/voxelization/resources/Sponza/sponza_column_c_diff.png b/projects/voxelization/resources/Sponza/sponza_column_c_diff.png
deleted file mode 100644
index bc46fd979044a938d3adca7601689e71504e48bf..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/sponza_column_c_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:a0bc993ff59865468ef4530798930c7dfefb07482d71db45bc2a520986b27735
-size 2066950
diff --git a/projects/voxelization/resources/Sponza/sponza_curtain_blue_diff.png b/projects/voxelization/resources/Sponza/sponza_curtain_blue_diff.png
deleted file mode 100644
index 384c8c2c051160d530eb3ac8b05c9c60752a2d2b..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/sponza_curtain_blue_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:b85c6bb3cd5105f48d3812ec8e7a1068521ce69e917300d79e136e19d45422fb
-size 9510905
diff --git a/projects/voxelization/resources/Sponza/sponza_curtain_diff.png b/projects/voxelization/resources/Sponza/sponza_curtain_diff.png
deleted file mode 100644
index af842e9f5fe18c1f609875e00899a6770fa4488b..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/sponza_curtain_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:563c56bdbbee395a6ef7f0c51c8ac9223c162e517b4cdba0d4654e8de27c98d8
-size 9189263
diff --git a/projects/voxelization/resources/Sponza/sponza_curtain_green_diff.png b/projects/voxelization/resources/Sponza/sponza_curtain_green_diff.png
deleted file mode 100644
index 6c9b6391a199407637fa71033d79fb58b8b4f0d7..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/sponza_curtain_green_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:238fe1c7f481388d1c1d578c2da8d411b99e8f0030ab62060a306db333124476
-size 8785458
diff --git a/projects/voxelization/resources/Sponza/sponza_details_diff.png b/projects/voxelization/resources/Sponza/sponza_details_diff.png
deleted file mode 100644
index 12656686362c3e0a297e060491f33bd7351551f9..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/sponza_details_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:cb1223b3bb82f8757e7df25a6891f1239cdd7ec59990340e952fb2d6b7ea570c
-size 1522643
diff --git a/projects/voxelization/resources/Sponza/sponza_fabric_blue_diff.png b/projects/voxelization/resources/Sponza/sponza_fabric_blue_diff.png
deleted file mode 100644
index 879d16ef84722a4fc13e83a771778de326e4bc54..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/sponza_fabric_blue_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:467d290bf5d4b2a017da140ba9e244ed8a8a9be5418a9ac9bcb4ad572ae2d7ab
-size 2229440
diff --git a/projects/voxelization/resources/Sponza/sponza_fabric_diff.png b/projects/voxelization/resources/Sponza/sponza_fabric_diff.png
deleted file mode 100644
index 3311287a219d2148620b87fe428fea071688d051..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/sponza_fabric_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:1594f59cc2848db26add47361f4e665e3d8afa147760ed915d839fea42b20287
-size 2267382
diff --git a/projects/voxelization/resources/Sponza/sponza_fabric_green_diff.png b/projects/voxelization/resources/Sponza/sponza_fabric_green_diff.png
deleted file mode 100644
index de110f369004388dae4cd5067c63428db3a07834..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/sponza_fabric_green_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:902b87faab221173bf370cea7c74cb9060b4d870ac6316b190dafded1cb12993
-size 2258220
diff --git a/projects/voxelization/resources/Sponza/sponza_flagpole_diff.png b/projects/voxelization/resources/Sponza/sponza_flagpole_diff.png
deleted file mode 100644
index 5f6e0812a0df80346318baa3cb50a6888afc58f8..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/sponza_flagpole_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:bfffb62e770959c725d0f3db6dc7dbdd46a380ec55ef884dab94d44ca017b438
-size 1425673
diff --git a/projects/voxelization/resources/Sponza/sponza_floor_a_diff.png b/projects/voxelization/resources/Sponza/sponza_floor_a_diff.png
deleted file mode 100644
index 788ed764f79ba724f04a2d603076a5b85013e188..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/sponza_floor_a_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:a16f9230fa91f9f31dfca6216ce205f1ef132d44f3b012fbf6efc0fba69770ab
-size 1996838
diff --git a/projects/voxelization/resources/Sponza/sponza_roof_diff.png b/projects/voxelization/resources/Sponza/sponza_roof_diff.png
deleted file mode 100644
index c5b84261fdd1cc776a94b3ce398c7806b895f9a3..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/sponza_roof_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:7fc412138c20da19f8173e53545e771f4652558dff624d4dc67143e40efe562b
-size 2320533
diff --git a/projects/voxelization/resources/Sponza/sponza_thorn_diff.png b/projects/voxelization/resources/Sponza/sponza_thorn_diff.png
deleted file mode 100644
index 7a9142674a7d4a6f94a48c5152cf0300743b597a..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/sponza_thorn_diff.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:a73a17c883cd0d0d67cfda2dc4118400a916366c05b9a5ac465f0c8b30fd9c8e
-size 635001
diff --git a/projects/voxelization/resources/Sponza/vase_dif.png b/projects/voxelization/resources/Sponza/vase_dif.png
deleted file mode 100644
index 61236a81cb324af8797b05099cd264cefe189e56..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/vase_dif.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:53d06f52bf9e59df4cf00237707cca76c4f692bda61a62b06a30d321311d6dd9
-size 1842101
diff --git a/projects/voxelization/resources/Sponza/vase_hanging.png b/projects/voxelization/resources/Sponza/vase_hanging.png
deleted file mode 100644
index 36a3cee71d8213225090c74f8c0dce33b9d44378..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/vase_hanging.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:a9d10b4f27a3c9a78d5bac882fdd4b6a6987c262f48fa490670fe5e235951e31
-size 1432804
diff --git a/projects/voxelization/resources/Sponza/vase_plant.png b/projects/voxelization/resources/Sponza/vase_plant.png
deleted file mode 100644
index 7ad95e702e229f1ebd803e5203a266d15f2c07b9..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/vase_plant.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:d2087371ff02212fb7014b6daefa191cf5676d2227193fff261a5d02f554cb8e
-size 998089
diff --git a/projects/voxelization/resources/Sponza/vase_round.png b/projects/voxelization/resources/Sponza/vase_round.png
deleted file mode 100644
index c17953abc000c44b8991e23c136c2b67348f3d1b..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/Sponza/vase_round.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:aa23d48d492d5d4ada2ddb27d1ef22952b214e6eb3b301c65f9d88442723d20a
-size 1871399
diff --git a/projects/voxelization/resources/cube/boards2_vcyc_jpg.jpg b/projects/voxelization/resources/cube/boards2_vcyc_jpg.jpg
deleted file mode 100644
index 2636039e272289c0fba3fa2d88a060b857501248..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/cube/boards2_vcyc_jpg.jpg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:cca33a6e58ddd1b37a6e6853a9aa0e7b15ca678937119194752393dd2a0a0564
-size 1192476
diff --git a/projects/voxelization/resources/cube/cube.bin b/projects/voxelization/resources/cube/cube.bin
deleted file mode 100644
index 3303cd8635848bee18e10ab8754d5e4e7218db92..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/cube/cube.bin
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:9bb9b6b8bbe50a0aaa517057f245ee844f80afa7426dacb2aed4128f71629ce4
-size 840
diff --git a/projects/voxelization/resources/cube/cube.blend b/projects/voxelization/resources/cube/cube.blend
deleted file mode 100644
index 62ccb2c742094bcfb5ed194ab905bffae86bfd65..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/cube/cube.blend
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:a6c1e245f259c610528c9485db6688928faac0ab2addee9e3c2dde7740e4dd09
-size 774920
diff --git a/projects/voxelization/resources/cube/cube.blend1 b/projects/voxelization/resources/cube/cube.blend1
deleted file mode 100644
index 13f21dcca218d7bc7a07a8a9682b5e1d9e607736..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/cube/cube.blend1
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:f4496f423569b8ca81f3b3a55fad00f925557e0193fb9dbe6cdce7e71fb48f7b
-size 774920
diff --git a/projects/voxelization/resources/cube/cube.glb b/projects/voxelization/resources/cube/cube.glb
deleted file mode 100644
index 66a42c65e71dcf375e04cc378256024dd3c7834d..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/cube/cube.glb
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:198568b715f397d78f7c358c0f709a419e7fd677e54cdec7c19f71b5ed264897
-size 1194508
diff --git a/projects/voxelization/resources/cube/cube.gltf b/projects/voxelization/resources/cube/cube.gltf
deleted file mode 100644
index 428176144843dd06c78fe1d11a6392a0ea02b22d..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/cube/cube.gltf
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:f82f455647a84ca6242882ae26a79a499d3ce594f8de317ab89488c5b79721ac
-size 2823
diff --git a/projects/voxelization/resources/shaders/lightInfo.inc b/projects/voxelization/resources/shaders/lightInfo.inc
deleted file mode 100644
index 4345d4f1504d27df7392b34bcaf17efdcfecef33..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/shaders/lightInfo.inc
+++ /dev/null
@@ -1,6 +0,0 @@
-struct LightInfo{
-    vec3 L;             float padding;
-    vec3 sunColor;      
-    float sunStrength;
-    mat4 lightMatrix;
-};
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/perMeshResources.inc b/projects/voxelization/resources/shaders/perMeshResources.inc
deleted file mode 100644
index 95e4fb7c27009965659d14a9c72acfec950c37e3..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/shaders/perMeshResources.inc
+++ /dev/null
@@ -1,2 +0,0 @@
-layout(set=1, binding=0) uniform texture2D  albedoTexture;
-layout(set=1, binding=1) uniform sampler    textureSampler;
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/shader.frag b/projects/voxelization/resources/shaders/shader.frag
deleted file mode 100644
index 8653ae5958ce3b42eac6b1eaa6813f85b6ed589c..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/shaders/shader.frag
+++ /dev/null
@@ -1,28 +0,0 @@
-#version 450
-#extension GL_ARB_separate_shader_objects : enable
-#extension GL_GOOGLE_include_directive : enable
-
-#include "perMeshResources.inc"
-#include "lightInfo.inc"
-#include "shadowMapping.inc"
-
-layout(location = 0) in vec3 passNormal;
-layout(location = 1) in vec2 passUV;
-layout(location = 2) in vec3 passPos;
-
-layout(location = 0) out vec3 outColor;
-
-layout(set=0, binding=0) uniform sunBuffer {
-    LightInfo lightInfo;
-};
-layout(set=0, binding=1) uniform texture2D  shadowMap;
-layout(set=0, binding=2) uniform sampler    shadowMapSampler;
-
-void main()	{
-    vec3 N          = normalize(passNormal);
-    vec3 sun        = lightInfo.sunStrength * lightInfo.sunColor * clamp(dot(N, lightInfo.L), 0, 1);
-    sun             *= shadowTest(passPos, lightInfo, shadowMap, shadowMapSampler);
-    vec3 ambient    = vec3(0.05);
-    vec3 albedo     = texture(sampler2D(albedoTexture, textureSampler), passUV).rgb;
-	outColor        = albedo * (sun + ambient);
-}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/shadowMapping.inc b/projects/voxelization/resources/shaders/shadowMapping.inc
deleted file mode 100644
index 1fa34a388c35b96a3316e972ca562d35e2c3cf90..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/shaders/shadowMapping.inc
+++ /dev/null
@@ -1,16 +0,0 @@
-float shadowTest(vec3 worldPos, LightInfo lightInfo, texture2D shadowMap, sampler shadowMapSampler){
-    vec4 lightPos = lightInfo.lightMatrix * vec4(worldPos, 1);
-    lightPos /= lightPos.w;
-    lightPos.xy = lightPos.xy * 0.5 + 0.5;
-    
-    if(any(lessThan(lightPos.xy, vec2(0))) || any(greaterThan(lightPos.xy, vec2(1)))){
-        return 1;
-    }
-    
-    lightPos.z = clamp(lightPos.z, 0, 1);
-    
-    float shadowMapSample = texture(sampler2D(shadowMap, shadowMapSampler), lightPos.xy).r;
-    float bias = 0.01f;
-    shadowMapSample += bias;
-    return shadowMapSample < lightPos.z ? 0 : 1;
-}
\ No newline at end of file
diff --git a/projects/voxelization/resources/shaders/voxel.inc b/projects/voxelization/resources/shaders/voxel.inc
deleted file mode 100644
index 25c0a82bbc887913a4d69ccdeee2b0d8934828c8..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/shaders/voxel.inc
+++ /dev/null
@@ -1,42 +0,0 @@
-struct VoxelInfo{
-    vec3 offset;
-    float extent;
-};
-
-uint flattenVoxelUVToIndex(ivec3 UV, ivec3 voxelImageSize){
-    return UV.x + UV.y * voxelImageSize.x + UV.z *  voxelImageSize.x*  voxelImageSize.y;
-}
-
-// packed voxel data: 
-// 1 bit opacity
-// 7 bit exposure
-// 8 bit blue
-// 8 bit green
-// 8 bit red
-float maxExposure = 16.f;
-
-uint packVoxelInfo(vec3 color){
-    
-    color               = clamp(color, vec3(0), vec3(maxExposure));
-    float maxComponent  = max(max(max(color.r, color.g), color.b), 1.f);
-    color               /= maxComponent;
-    
-    uint opaqueBit      = 1 << 31;
-    uint exposureBits   = (0x0000007F & uint(maxComponent / maxExposure * 127)) << 24;
-    uint redBits        = (0x000000FF & uint(color.r * 255)) << 0;
-    uint greenBits      = (0x000000FF & uint(color.g * 255)) << 8;
-    uint blueBits       = (0x000000FF & uint(color.b * 255)) << 16;
-    return opaqueBit | exposureBits | blueBits | greenBits | redBits;
-}
-
-vec4 unpackVoxelInfo(uint packed){
-    vec4 rgba;
-    rgba.r = (packed >> 0  & 0x000000FF) / 255.f;
-    rgba.g = (packed >> 8  & 0x000000FF) / 255.f;
-    rgba.b = (packed >> 16 & 0x000000FF) / 255.f;
-    rgba.a =  packed  >> 31; 
-    
-    rgba.rgb *= (packed >> 24 & 0x0000007F) / 127.f * maxExposure; 
-    
-    return rgba;
-}
\ No newline at end of file
diff --git a/projects/voxelization/resources/triangle/Triangle.bin b/projects/voxelization/resources/triangle/Triangle.bin
deleted file mode 100644
index 57f26ad96592b64377e6aa93823d96a94e6c5022..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/triangle/Triangle.bin
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:412ebd5f7242c266b4957e7e26be13aa331dbcb7bbb854ab334a2437ae8ed959
-size 104
diff --git a/projects/voxelization/resources/triangle/Triangle.blend b/projects/voxelization/resources/triangle/Triangle.blend
deleted file mode 100644
index 2421dc5e1bb029d73a9ec09cc4530c5196851fd7..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/triangle/Triangle.blend
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:387e544df173219fbf292a64a6656d1d782bbf71a5a9e9fdef0a308f47b05477
-size 758144
diff --git a/projects/voxelization/resources/triangle/Triangle.glb b/projects/voxelization/resources/triangle/Triangle.glb
deleted file mode 100644
index 4148620cd6af0dadbc791aa1c52bb5431a40884b..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/triangle/Triangle.glb
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:f4be087a605212d139416b5352a018283b26b99260cbcddb7013a1beeb331227
-size 980
diff --git a/projects/voxelization/resources/triangle/Triangle.gltf b/projects/voxelization/resources/triangle/Triangle.gltf
deleted file mode 100644
index a188e6ee16a5e8486cf307c7bda8cfd99bdbeea6..0000000000000000000000000000000000000000
--- a/projects/voxelization/resources/triangle/Triangle.gltf
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:d5fc354e040f79cff329e919677b194c75e3a522c6406f75c1108ad9575f12ec
-size 2202
diff --git a/projects/voxelization/src/ShadowMapping.cpp b/projects/voxelization/src/ShadowMapping.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..73f98cd3ea35d7f2bdf99d29f803df0abbc1adac
--- /dev/null
+++ b/projects/voxelization/src/ShadowMapping.cpp
@@ -0,0 +1,338 @@
+#include "ShadowMapping.hpp"
+
+#include <vkcv/Pass.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+
+const vk::Format            shadowMapFormat         = vk::Format::eR16G16B16A16Unorm;
+const vk::Format            shadowMapDepthFormat    = vk::Format::eD32Sfloat;
+const uint32_t              shadowMapResolution     = 1024;
+const vkcv::Multisampling   msaa                    = vkcv::Multisampling::MSAA8X;
+
+vkcv::ShaderProgram loadShadowShader() {
+	vkcv::ShaderProgram shader;
+	vkcv::shader::GLSLCompiler compiler;
+	compiler.compile(vkcv::ShaderStage::VERTEX, "assets/shaders/shadow.vert",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, "assets/shaders/shadow.frag",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	return shader;
+}
+
+vkcv::ShaderProgram loadDepthToMomentsShader() {
+	vkcv::ShaderProgram shader;
+	vkcv::shader::GLSLCompiler compiler;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "assets/shaders/depthToMoments.comp",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	return shader;
+}
+
+vkcv::ShaderProgram loadShadowBlurXShader() {
+	vkcv::ShaderProgram shader;
+	vkcv::shader::GLSLCompiler compiler;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "assets/shaders/shadowBlurX.comp",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	return shader;
+}
+
+vkcv::ShaderProgram loadShadowBlurYShader() {
+	vkcv::ShaderProgram shader;
+	vkcv::shader::GLSLCompiler compiler;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "assets/shaders/shadowBlurY.comp",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	return shader;
+}
+
+glm::mat4 computeShadowViewProjectionMatrix(
+	const glm::vec3&            lightDirection, 
+	const vkcv::camera::Camera& camera, 
+	float                       maxShadowDistance,
+	const glm::vec3&            voxelVolumeOffset,
+	float                       voxelVolumeExtent) {
+
+	const glm::vec3 cameraPos   = camera.getPosition();
+	const glm::vec3 forward     = glm::normalize(camera.getFront());
+	glm::vec3 up                = glm::normalize(camera.getUp());
+	const glm::vec3 right       = glm::normalize(glm::cross(forward, up));
+	up = glm::cross(right, forward);
+
+	const float fov         = camera.getFov();
+	const float aspectRatio = camera.getRatio();
+
+	float near;
+	float far;
+	camera.getNearFar(near, far);
+	far = std::min(maxShadowDistance, far);
+
+	const glm::vec3 nearCenter  = cameraPos + forward * near;
+	const float nearUp          = near * tan(fov * 0.5);
+	const float nearRight       = nearUp * aspectRatio;
+	
+	const glm::vec3 farCenter   = cameraPos + forward * far;
+	const float farUp           = far * tan(fov * 0.5);
+	const float farRight        = farUp * aspectRatio;
+
+	std::array<glm::vec3, 8> viewFrustumCorners = {
+		nearCenter + right * nearRight + nearUp * up,
+		nearCenter + right * nearRight - nearUp * up,
+		nearCenter - right * nearRight + nearUp * up,
+		nearCenter - right * nearRight - nearUp * up,
+
+		farCenter + right * farRight + farUp * up,
+		farCenter + right * farRight - farUp * up,
+		farCenter - right * farRight + farUp * up,
+		farCenter - right * farRight - farUp * up
+	};
+
+	std::array<glm::vec3, 8> voxelVolumeCorners = {
+		voxelVolumeOffset + voxelVolumeExtent * glm::vec3(1, 1, 1),
+		voxelVolumeOffset + voxelVolumeExtent * glm::vec3(1, 1, -1),
+		voxelVolumeOffset + voxelVolumeExtent * glm::vec3(1, -1, 1),
+		voxelVolumeOffset + voxelVolumeExtent * glm::vec3(1, -1, -1),
+
+		voxelVolumeOffset + voxelVolumeExtent * glm::vec3(-1, 1, 1),
+		voxelVolumeOffset + voxelVolumeExtent * glm::vec3(-1, 1, -1),
+		voxelVolumeOffset + voxelVolumeExtent * glm::vec3(-1, -1, 1),
+		voxelVolumeOffset + voxelVolumeExtent * glm::vec3(-1, -1, -1),
+	};
+
+	glm::vec3 minView(std::numeric_limits<float>::max());
+	glm::vec3 maxView(std::numeric_limits<float>::lowest());
+
+	const glm::mat4 view = glm::lookAt(glm::vec3(0), -lightDirection, glm::vec3(0, -1, 0));
+
+	auto getMinMaxView = [&](std::array<glm::vec3, 8> points) {
+		for (const glm::vec3& p : points) {
+			const auto& pView = glm::vec3(view * glm::vec4(p, 1));
+			minView = glm::min(minView, pView);
+			maxView = glm::max(maxView, pView);
+		}
+	};
+
+	getMinMaxView(viewFrustumCorners);
+	getMinMaxView(voxelVolumeCorners);
+
+	// rotationaly invariant to avoid shadow  swimming when moving camera
+	// could potentially be wasteful, but guarantees stability, regardless of camera and voxel volume
+	 glm::vec3 scale = glm::vec3(1.f / glm::max(far, voxelVolumeExtent));
+
+	glm::vec3 offset = -0.5f * (maxView + minView) * scale;
+
+	// snap to texel to avoid shadow swimming when moving
+	glm::vec2 offset2D = glm::vec2(offset);
+	glm::vec2 frustumExtent2D = glm::vec2(1) / glm::vec2(scale);
+	glm::vec2 texelSize = glm::vec2(frustumExtent2D / static_cast<float>(shadowMapResolution));
+	offset2D = glm::ceil(offset2D / texelSize) * texelSize;
+	offset.x = offset2D.x;
+	offset.y = offset2D.y;
+
+	glm::mat4 crop(1);
+	crop[0][0] = scale.x;
+	crop[1][1] = scale.y;
+	crop[2][2] = scale.z;
+
+	crop[3][0] = offset.x;
+	crop[3][1] = offset.y;
+	crop[3][2] = offset.z;
+
+	glm::mat4 vulkanCorrectionMatrix(1.f);
+	vulkanCorrectionMatrix[2][2] = 0.5;
+	vulkanCorrectionMatrix[3][2] = 0.5;
+
+	return vulkanCorrectionMatrix * crop * view;
+}
+
+ShadowMapping::ShadowMapping(vkcv::Core* corePtr, const vkcv::VertexLayout& vertexLayout) : 
+	m_corePtr(corePtr),
+	m_shadowMap(vkcv::image(*corePtr, shadowMapFormat, shadowMapResolution, shadowMapResolution, 1, true, true)),
+	m_shadowMapIntermediate(vkcv::image(*corePtr, shadowMapFormat, shadowMapResolution, shadowMapResolution, 1, false, true)),
+	m_shadowMapDepth(vkcv::image(*corePtr, shadowMapDepthFormat, shadowMapResolution, shadowMapResolution, 1, false, false, false, msaa)),
+	m_lightInfoBuffer(buffer<LightInfo>(*corePtr, vkcv::BufferType::UNIFORM, 1)){
+
+	vkcv::ShaderProgram shadowShader = loadShadowShader();
+
+	// pass
+	m_shadowMapPass = vkcv::passFormat(*corePtr, shadowMapDepthFormat, true, msaa);
+
+	// pipeline
+	vkcv::GraphicsPipelineConfig shadowPipeConfig (
+		shadowShader,
+		m_shadowMapPass,
+		vertexLayout,
+		{}
+	);
+	
+	shadowPipeConfig.setResolution(shadowMapResolution, shadowMapResolution);
+	shadowPipeConfig.setDepthClampingEnabled(true);
+	shadowPipeConfig.setCulling(vkcv::CullMode::Front);
+	m_shadowMapPipe = corePtr->createGraphicsPipeline(shadowPipeConfig);
+
+	m_shadowSampler = corePtr->createSampler(
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerFilterType::LINEAR,
+		vkcv::SamplerMipmapMode::LINEAR,
+		vkcv::SamplerAddressMode::CLAMP_TO_EDGE
+	);
+
+	// depth to moments
+	vkcv::ShaderProgram depthToMomentsShader    = loadDepthToMomentsShader();
+
+	m_depthToMomentsDescriptorSetLayout         = corePtr->createDescriptorSetLayout(depthToMomentsShader.getReflectedDescriptors().at(0));
+	m_depthToMomentsDescriptorSet               = corePtr->createDescriptorSet(m_depthToMomentsDescriptorSetLayout);
+    m_depthToMomentsPipe = corePtr->createComputePipeline({ depthToMomentsShader, { m_depthToMomentsDescriptorSetLayout }});
+
+	vkcv::DescriptorWrites depthToMomentDescriptorWrites;
+	depthToMomentDescriptorWrites.writeSampledImage(0, m_shadowMapDepth.getHandle());
+	depthToMomentDescriptorWrites.writeSampler(1, m_shadowSampler);
+	depthToMomentDescriptorWrites.writeStorageImage(2, m_shadowMap.getHandle());
+	corePtr->writeDescriptorSet(m_depthToMomentsDescriptorSet, depthToMomentDescriptorWrites);
+
+	// shadow blur X
+	vkcv::ShaderProgram shadowBlurXShader   = loadShadowBlurXShader();
+	m_shadowBlurXDescriptorSetLayout        = corePtr->createDescriptorSetLayout(shadowBlurXShader.getReflectedDescriptors().at(0));
+	m_shadowBlurXDescriptorSet              = corePtr->createDescriptorSet(m_shadowBlurXDescriptorSetLayout);
+	m_shadowBlurXPipe                       = corePtr->createComputePipeline({ shadowBlurXShader, { m_shadowBlurXDescriptorSetLayout }});
+
+	vkcv::DescriptorWrites shadowBlurXDescriptorWrites;
+	shadowBlurXDescriptorWrites.writeSampledImage(0, m_shadowMap.getHandle());
+	shadowBlurXDescriptorWrites.writeSampler(1, m_shadowSampler);
+	shadowBlurXDescriptorWrites.writeStorageImage(2, m_shadowMapIntermediate.getHandle());
+	corePtr->writeDescriptorSet(m_shadowBlurXDescriptorSet, shadowBlurXDescriptorWrites);
+
+	// shadow blur Y
+	vkcv::ShaderProgram shadowBlurYShader   = loadShadowBlurYShader();
+	m_shadowBlurYDescriptorSetLayout        = corePtr->createDescriptorSetLayout(shadowBlurYShader.getReflectedDescriptors().at(0));
+	m_shadowBlurYDescriptorSet              = corePtr->createDescriptorSet(m_shadowBlurYDescriptorSetLayout);
+    m_shadowBlurYPipe                       = corePtr->createComputePipeline({ shadowBlurYShader, { m_shadowBlurYDescriptorSetLayout }});
+
+    vkcv::DescriptorWrites shadowBlurYDescriptorWrites;
+	shadowBlurYDescriptorWrites.writeSampledImage(0, m_shadowMapIntermediate.getHandle());
+	shadowBlurYDescriptorWrites.writeSampler(1, m_shadowSampler);
+	shadowBlurYDescriptorWrites.writeStorageImage(2, m_shadowMap.getHandle());
+	corePtr->writeDescriptorSet(m_shadowBlurYDescriptorSet, shadowBlurYDescriptorWrites);
+}
+
+void ShadowMapping::recordShadowMapRendering(
+	const vkcv::CommandStreamHandle&     cmdStream,
+	const glm::vec2&                     lightAngleRadian,
+	const glm::vec3&                     lightColor,
+	float                                lightStrength,
+	float                                maxShadowDistance,
+	const std::vector<vkcv::VertexData>& meshes,
+	const std::vector<glm::mat4>&        modelMatrices,
+	const vkcv::camera::Camera&          camera,
+	const glm::vec3&                     voxelVolumeOffset,
+	float                                voxelVolumeExtent,
+	const vkcv::WindowHandle&            windowHandle,
+	vkcv::Downsampler&					 downsampler) {
+
+	LightInfo lightInfo;
+	lightInfo.sunColor = lightColor;
+	lightInfo.sunStrength = lightStrength;
+	lightInfo.direction = glm::normalize(glm::vec3(
+		std::cos(lightAngleRadian.x) * std::cos(lightAngleRadian.y),
+		std::sin(lightAngleRadian.x),
+		std::cos(lightAngleRadian.x) * std::sin(lightAngleRadian.y)));
+
+	lightInfo.lightMatrix = computeShadowViewProjectionMatrix(
+		lightInfo.direction,
+		camera,
+		maxShadowDistance,
+		voxelVolumeOffset,
+		voxelVolumeExtent);
+	m_lightInfoBuffer.fill({ lightInfo });
+	
+	vkcv::PushConstants shadowPushConstants = vkcv::pushConstants<glm::mat4>();
+	
+	for (const auto& m : modelMatrices) {
+		shadowPushConstants.appendDrawcall(lightInfo.lightMatrix * m);
+	}
+	
+	std::vector<vkcv::InstanceDrawcall> drawcalls;
+	for (const auto& mesh : meshes) {
+		drawcalls.push_back(vkcv::InstanceDrawcall(mesh));
+	}
+
+	m_corePtr->recordBeginDebugLabel(cmdStream, "Shadow map depth", {1, 1, 1, 1});
+	m_corePtr->recordDrawcallsToCmdStream(
+		cmdStream,
+		m_shadowMapPipe,
+		shadowPushConstants,
+		drawcalls,
+		{ m_shadowMapDepth.getHandle() },
+		windowHandle
+	);
+	m_corePtr->prepareImageForSampling(cmdStream, m_shadowMapDepth.getHandle());
+	m_corePtr->recordEndDebugLabel(cmdStream);
+
+	// depth to moments
+	const auto dispatchCount = vkcv::dispatchInvocations(
+			vkcv::DispatchSize(shadowMapResolution, shadowMapResolution),
+			vkcv::DispatchSize(8, 8)
+	);
+
+	const uint32_t msaaSampleCount = vkcv::msaaToSampleCount(msaa);
+	
+	vkcv::PushConstants msaaPushConstants = vkcv::pushConstants<uint32_t>();
+	msaaPushConstants.appendDrawcall(msaaSampleCount);
+
+	m_corePtr->recordBeginDebugLabel(cmdStream, "Depth to moments", { 1, 1, 1, 1 });
+
+	m_corePtr->prepareImageForStorage(cmdStream, m_shadowMap.getHandle());
+	m_corePtr->recordComputeDispatchToCmdStream(
+		cmdStream,
+		m_depthToMomentsPipe,
+		dispatchCount,
+		{ vkcv::useDescriptorSet(0, m_depthToMomentsDescriptorSet) },
+		msaaPushConstants
+	);
+	m_corePtr->prepareImageForSampling(cmdStream, m_shadowMap.getHandle());
+	m_corePtr->recordEndDebugLabel(cmdStream);
+
+	m_corePtr->recordBeginDebugLabel(cmdStream, "Moment shadow map blur", { 1, 1, 1, 1 });
+
+	// blur X
+	m_corePtr->prepareImageForStorage(cmdStream, m_shadowMapIntermediate.getHandle());
+	m_corePtr->recordComputeDispatchToCmdStream(
+		cmdStream,
+		m_shadowBlurXPipe,
+		dispatchCount,
+		{ vkcv::useDescriptorSet(0, m_shadowBlurXDescriptorSet) },
+		vkcv::PushConstants(0)
+	);
+	m_corePtr->prepareImageForSampling(cmdStream, m_shadowMapIntermediate.getHandle());
+
+	// blur Y
+	m_corePtr->prepareImageForStorage(cmdStream, m_shadowMap.getHandle());
+	m_corePtr->recordComputeDispatchToCmdStream(
+		cmdStream,
+		m_shadowBlurYPipe,
+		dispatchCount,
+		{ vkcv::useDescriptorSet(0, m_shadowBlurYDescriptorSet) },
+		vkcv::PushConstants(0)
+	);
+	m_shadowMap.recordMipChainGeneration(cmdStream, downsampler);
+
+	m_corePtr->recordEndDebugLabel(cmdStream);
+}
+
+vkcv::ImageHandle ShadowMapping::getShadowMap() {
+	return m_shadowMap.getHandle();
+}
+
+vkcv::SamplerHandle ShadowMapping::getShadowSampler() {
+	return m_shadowSampler;
+}
+
+vkcv::BufferHandle ShadowMapping::getLightInfoBuffer() {
+	return m_lightInfoBuffer.getHandle();
+}
\ No newline at end of file
diff --git a/projects/voxelization/src/ShadowMapping.hpp b/projects/voxelization/src/ShadowMapping.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a843a7de9c9e4c731b16ae0c8d5bdfd2ac263711
--- /dev/null
+++ b/projects/voxelization/src/ShadowMapping.hpp
@@ -0,0 +1,66 @@
+#pragma once
+
+#include <vkcv/Buffer.hpp>
+#include <vkcv/Core.hpp>
+#include <vkcv/camera/Camera.hpp>
+#include <vkcv/Downsampler.hpp>
+#include <vkcv/Image.hpp>
+
+#include <glm/glm.hpp>
+#define GLM_ENABLE_EXPERIMENTAL // use this before inclusion, else error!
+#include <glm/gtx/transform.hpp>
+
+struct LightInfo {
+	glm::vec3   direction;
+	float       padding;
+	glm::vec3   sunColor;
+	float       sunStrength;
+	glm::mat4   lightMatrix;
+};
+
+class ShadowMapping {
+public:
+	ShadowMapping(vkcv::Core* corePtr, const vkcv::VertexLayout& vertexLayout);
+
+	void recordShadowMapRendering(
+		const vkcv::CommandStreamHandle&     cmdStream,
+		const glm::vec2&                     lightAngleRadian,
+		const glm::vec3&                     lightColor,
+		float                                lightStrength,
+		float                                maxShadowDistance,
+		const std::vector<vkcv::VertexData>& meshes,
+		const std::vector<glm::mat4>&        modelMatrices,
+		const vkcv::camera::Camera&          camera,
+		const glm::vec3&                     voxelVolumeOffset,
+		float                                voxelVolumeExtent,
+		const vkcv::WindowHandle&            windowHandle,
+		vkcv::Downsampler&					 downsampler);
+
+	vkcv::ImageHandle   getShadowMap();
+	vkcv::SamplerHandle getShadowSampler();
+	vkcv::BufferHandle  getLightInfoBuffer();
+
+private:
+	vkcv::Core* m_corePtr;
+
+	vkcv::Image                         m_shadowMap;
+	vkcv::Image                         m_shadowMapIntermediate;
+	vkcv::Image                         m_shadowMapDepth;
+	vkcv::SamplerHandle                 m_shadowSampler;
+	vkcv::Buffer<LightInfo>             m_lightInfoBuffer;
+
+	vkcv::PassHandle                    m_shadowMapPass;
+	vkcv::GraphicsPipelineHandle        m_shadowMapPipe;
+
+	vkcv::ComputePipelineHandle         m_depthToMomentsPipe;
+	vkcv::DescriptorSetLayoutHandle     m_depthToMomentsDescriptorSetLayout;
+	vkcv::DescriptorSetHandle           m_depthToMomentsDescriptorSet;
+
+	vkcv::ComputePipelineHandle         m_shadowBlurXPipe;
+	vkcv::DescriptorSetLayoutHandle     m_shadowBlurXDescriptorSetLayout;
+	vkcv::DescriptorSetHandle           m_shadowBlurXDescriptorSet;
+
+	vkcv::ComputePipelineHandle         m_shadowBlurYPipe;
+	vkcv::DescriptorSetLayoutHandle     m_shadowBlurYDescriptorSetLayout;
+	vkcv::DescriptorSetHandle           m_shadowBlurYDescriptorSet;
+};
\ No newline at end of file
diff --git a/projects/voxelization/src/Voxelization.cpp b/projects/voxelization/src/Voxelization.cpp
index a04131cedfdc508e14a3dbd97da642e1af48f8da..5c70b9dd0b3fe21118c4eddc32fca53e6e21c329 100644
--- a/projects/voxelization/src/Voxelization.cpp
+++ b/projects/voxelization/src/Voxelization.cpp
@@ -1,4 +1,6 @@
 #include "Voxelization.hpp"
+
+#include <vkcv/Pass.hpp>
 #include <vkcv/shader/GLSLCompiler.hpp>
 #include <glm/gtc/matrix_transform.hpp>
 #include <algorithm>
@@ -6,15 +8,15 @@
 vkcv::ShaderProgram loadVoxelizationShader() {
 	vkcv::shader::GLSLCompiler compiler;
 	vkcv::ShaderProgram shader;
-	compiler.compile(vkcv::ShaderStage::VERTEX, "resources/shaders/voxelization.vert",
+	compiler.compile(vkcv::ShaderStage::VERTEX, "assets/shaders/voxelization.vert",
 		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
 		shader.addShader(shaderStage, path);
 	});
-	compiler.compile(vkcv::ShaderStage::GEOMETRY, "resources/shaders/voxelization.geom",
+	compiler.compile(vkcv::ShaderStage::GEOMETRY, "assets/shaders/voxelization.geom",
 		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
 		shader.addShader(shaderStage, path);
 	});
-	compiler.compile(vkcv::ShaderStage::FRAGMENT, "resources/shaders/voxelization.frag",
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, "assets/shaders/voxelization.frag",
 		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
 		shader.addShader(shaderStage, path);
 	});
@@ -24,15 +26,15 @@ vkcv::ShaderProgram loadVoxelizationShader() {
 vkcv::ShaderProgram loadVoxelVisualisationShader() {
 	vkcv::shader::GLSLCompiler compiler;
 	vkcv::ShaderProgram shader;
-	compiler.compile(vkcv::ShaderStage::VERTEX, "resources/shaders/voxelVisualisation.vert",
+	compiler.compile(vkcv::ShaderStage::VERTEX, "assets/shaders/voxelVisualisation.vert",
 		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
 		shader.addShader(shaderStage, path);
 	});
-	compiler.compile(vkcv::ShaderStage::GEOMETRY, "resources/shaders/voxelVisualisation.geom",
+	compiler.compile(vkcv::ShaderStage::GEOMETRY, "assets/shaders/voxelVisualisation.geom",
 		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
 		shader.addShader(shaderStage, path);
 	});
-	compiler.compile(vkcv::ShaderStage::FRAGMENT, "resources/shaders/voxelVisualisation.frag",
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, "assets/shaders/voxelVisualisation.frag",
 		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
 		shader.addShader(shaderStage, path);
 	});
@@ -42,7 +44,7 @@ vkcv::ShaderProgram loadVoxelVisualisationShader() {
 vkcv::ShaderProgram loadVoxelResetShader() {
 	vkcv::shader::GLSLCompiler compiler;
 	vkcv::ShaderProgram shader;
-	compiler.compile(vkcv::ShaderStage::COMPUTE, "resources/shaders/voxelReset.comp",
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "assets/shaders/voxelReset.comp",
 		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
 		shader.addShader(shaderStage, path);
 	});
@@ -52,152 +54,173 @@ vkcv::ShaderProgram loadVoxelResetShader() {
 vkcv::ShaderProgram loadVoxelBufferToImageShader() {
 	vkcv::shader::GLSLCompiler compiler;
 	vkcv::ShaderProgram shader;
-	compiler.compile(vkcv::ShaderStage::COMPUTE, "resources/shaders/voxelBufferToImage.comp",
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "assets/shaders/voxelBufferToImage.comp",
 		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
 		shader.addShader(shaderStage, path);
 	});
 	return shader;
 }
 
-const uint32_t voxelResolution = 128;
-uint32_t voxelCount = voxelResolution * voxelResolution * voxelResolution;
-const vk::Format voxelizationDummyFormat = vk::Format::eR8Unorm;
+vkcv::ShaderProgram loadSecondaryBounceShader() {
+	vkcv::shader::GLSLCompiler compiler;
+	vkcv::ShaderProgram shader;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "assets/shaders/voxelSecondaryBounce.comp",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		shader.addShader(shaderStage, path);
+	});
+	return shader;
+}
+
+const uint32_t      voxelResolution = 128;
+uint32_t            voxelCount = voxelResolution * voxelResolution * voxelResolution;
+const vk::Format    voxelizationDummyFormat = vk::Format::eR8Unorm;
+const int           maxStableMip = 4;	// must be the same as in voxelConeTrace shader function
 
 Voxelization::Voxelization(
 	vkcv::Core* corePtr,
 	const Dependencies& dependencies,
 	vkcv::BufferHandle  lightInfoBuffer,
 	vkcv::ImageHandle   shadowMap,
-	vkcv::SamplerHandle shadowSampler)
+	vkcv::SamplerHandle shadowSampler,
+	vkcv::SamplerHandle voxelSampler,
+	vkcv::Multisampling msaa)
 	:
-	m_corePtr(corePtr), 
-	m_voxelImage(m_corePtr->createImage(vk::Format::eR16G16B16A16Sfloat, voxelResolution, voxelResolution, voxelResolution, true, true)),
-	m_dummyRenderTarget(m_corePtr->createImage(voxelizationDummyFormat, voxelResolution, voxelResolution, 1, false, false, true)),
-	m_voxelInfoBuffer(m_corePtr->createBuffer<VoxelizationInfo>(vkcv::BufferType::UNIFORM, 1)),
-	m_voxelBuffer(m_corePtr->createBuffer<VoxelBufferContent>(vkcv::BufferType::STORAGE, voxelCount)){
+	m_corePtr(corePtr),
+	m_voxelImageIntermediate(vkcv::image(*m_corePtr, vk::Format::eR16G16B16A16Sfloat, voxelResolution, voxelResolution, voxelResolution, true, true)),
+	m_voxelImage(vkcv::image(*m_corePtr, vk::Format::eR16G16B16A16Sfloat, voxelResolution, voxelResolution, voxelResolution, true, true)),
+	m_voxelBuffer(buffer<VoxelBufferContent>(*m_corePtr, vkcv::BufferType::STORAGE, voxelCount)),
+	m_dummyRenderTarget(vkcv::image(*m_corePtr, voxelizationDummyFormat, voxelResolution, voxelResolution, 1, false, false, true)),
+	m_voxelInfoBuffer(buffer<VoxelizationInfo>(*m_corePtr, vkcv::BufferType::UNIFORM, 1)) {
 
 	const vkcv::ShaderProgram voxelizationShader = loadVoxelizationShader();
 
-	const vkcv::PassConfig voxelizationPassConfig({vkcv::AttachmentDescription(
-		vkcv::AttachmentOperation::DONT_CARE, 
-		vkcv::AttachmentOperation::DONT_CARE, 
-		voxelizationDummyFormat) });
+	const vkcv::PassConfig voxelizationPassConfig {{
+		  vkcv::AttachmentDescription(
+				  voxelizationDummyFormat,
+				  vkcv::AttachmentOperation::DONT_CARE,
+				  vkcv::AttachmentOperation::DONT_CARE
+		  )
+	}, vkcv::Multisampling::None };
 	m_voxelizationPass = m_corePtr->createPass(voxelizationPassConfig);
 
-	std::vector<vkcv::DescriptorBinding> voxelizationDescriptorBindings = 
-	{ voxelizationShader.getReflectedDescriptors()[0] };
-	m_voxelizationDescriptorSet = m_corePtr->createDescriptorSet(voxelizationDescriptorBindings);
+	m_voxelizationDescriptorSetLayout = m_corePtr->createDescriptorSetLayout(voxelizationShader.getReflectedDescriptors().at(0));
+	m_voxelizationDescriptorSet = m_corePtr->createDescriptorSet(m_voxelizationDescriptorSetLayout);
 
-	vkcv::DescriptorSetHandle dummyPerMeshDescriptorSet =
-		m_corePtr->createDescriptorSet({ voxelizationShader.getReflectedDescriptors()[1] });
+	vkcv::DescriptorSetLayoutHandle dummyPerMeshDescriptorSetLayout = m_corePtr->createDescriptorSetLayout(voxelizationShader.getReflectedDescriptors().at(1));
+	vkcv::DescriptorSetHandle dummyPerMeshDescriptorSet = m_corePtr->createDescriptorSet(dummyPerMeshDescriptorSetLayout);
 
-	const vkcv::PipelineConfig voxelizationPipeConfig{
+	vkcv::GraphicsPipelineConfig voxelizationPipeConfig (
 		voxelizationShader,
-		voxelResolution,
-		voxelResolution,
 		m_voxelizationPass,
 		dependencies.vertexLayout,
-		{ 
-			m_corePtr->getDescriptorSet(m_voxelizationDescriptorSet).layout,
-			m_corePtr->getDescriptorSet(dummyPerMeshDescriptorSet).layout},
-		false,
-		true };
+		{ m_voxelizationDescriptorSetLayout, dummyPerMeshDescriptorSetLayout }
+	);
+	
+	voxelizationPipeConfig.setResolution(voxelResolution, voxelResolution);
+	voxelizationPipeConfig.setUsingConservativeRasterization(true);
+	
 	m_voxelizationPipe = m_corePtr->createGraphicsPipeline(voxelizationPipeConfig);
 
 	vkcv::DescriptorWrites voxelizationDescriptorWrites;
-	voxelizationDescriptorWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, m_voxelBuffer.getHandle()) };
-	voxelizationDescriptorWrites.uniformBufferWrites = { 
-		vkcv::UniformBufferDescriptorWrite(1, m_voxelInfoBuffer.getHandle()),
-		vkcv::UniformBufferDescriptorWrite(3, lightInfoBuffer)
-	};
-	voxelizationDescriptorWrites.sampledImageWrites = { vkcv::SampledImageDescriptorWrite(4, shadowMap) };
-	voxelizationDescriptorWrites.samplerWrites      = { vkcv::SamplerDescriptorWrite(5, shadowSampler) };
-	voxelizationDescriptorWrites.storageImageWrites = { vkcv::StorageImageDescriptorWrite(2, m_voxelImage.getHandle()) };
+	voxelizationDescriptorWrites.writeStorageBuffer(0, m_voxelBuffer.getHandle());
+	voxelizationDescriptorWrites.writeUniformBuffer(
+			1, m_voxelInfoBuffer.getHandle()
+	).writeUniformBuffer(
+			3, lightInfoBuffer
+	);
+	
+	voxelizationDescriptorWrites.writeSampledImage(4, shadowMap);
+	voxelizationDescriptorWrites.writeSampler(5, shadowSampler);
+	voxelizationDescriptorWrites.writeStorageImage(2, m_voxelImageIntermediate.getHandle());
 	m_corePtr->writeDescriptorSet(m_voxelizationDescriptorSet, voxelizationDescriptorWrites);
 
 	vkcv::ShaderProgram voxelVisualisationShader = loadVoxelVisualisationShader();
 
-	const std::vector<vkcv::DescriptorBinding> voxelVisualisationDescriptorBindings = 
-		{ voxelVisualisationShader.getReflectedDescriptors()[0] };
-	m_visualisationDescriptorSet = m_corePtr->createDescriptorSet(voxelVisualisationDescriptorBindings);
-
-	const vkcv::AttachmentDescription voxelVisualisationColorAttachments(
-		vkcv::AttachmentOperation::STORE,
-		vkcv::AttachmentOperation::LOAD,
-		dependencies.colorBufferFormat
+	m_visualisationDescriptorSetLayout = m_corePtr->createDescriptorSetLayout(voxelVisualisationShader.getReflectedDescriptors().at(0));
+	m_visualisationDescriptorSet = m_corePtr->createDescriptorSet(m_visualisationDescriptorSetLayout);
+	
+	m_visualisationPass = vkcv::passFormats(
+			*m_corePtr,
+			{ dependencies.colorBufferFormat, dependencies.depthBufferFormat },
+			false,
+			msaa
 	);
 
-	const vkcv::AttachmentDescription voxelVisualisationDepthAttachments(
-		vkcv::AttachmentOperation::STORE,
-		vkcv::AttachmentOperation::LOAD,
-		dependencies.depthBufferFormat
-	);
-
-	vkcv::PassConfig voxelVisualisationPassDefinition(
-		{ voxelVisualisationColorAttachments, voxelVisualisationDepthAttachments });
-	m_visualisationPass = m_corePtr->createPass(voxelVisualisationPassDefinition);
-
-	const vkcv::PipelineConfig voxelVisualisationPipeConfig{
+	vkcv::GraphicsPipelineConfig voxelVisualisationPipeConfig (
 		voxelVisualisationShader,
-		0,
-		0,
 		m_visualisationPass,
 		{},
-		{ m_corePtr->getDescriptorSet(m_visualisationDescriptorSet).layout },
-		true,
-		false,
-		vkcv::PrimitiveTopology::PointList };	// points are extended to cubes in the geometry shader
+		{ m_visualisationDescriptorSetLayout }
+	);	// points are extended to cubes in the geometry shader
+	
+	voxelVisualisationPipeConfig.setPrimitiveTopology(vkcv::PrimitiveTopology::PointList);
 	m_visualisationPipe = m_corePtr->createGraphicsPipeline(voxelVisualisationPipeConfig);
 
 	std::vector<uint16_t> voxelIndexData;
-	for (int i = 0; i < voxelCount; i++) {
-		voxelIndexData.push_back(i);
+	for (uint32_t i = 0; i < voxelCount; i++) {
+		voxelIndexData.push_back(static_cast<uint16_t>(i));
 	}
 
-	const vkcv::DescriptorSetUsage voxelizationDescriptorUsage(0, m_corePtr->getDescriptorSet(m_visualisationDescriptorSet).vulkanHandle);
+	const auto voxelizationDescriptorUsage = vkcv::useDescriptorSet(0, m_visualisationDescriptorSet);
 
 	vkcv::ShaderProgram resetVoxelShader = loadVoxelResetShader();
 
-	m_voxelResetDescriptorSet = m_corePtr->createDescriptorSet(resetVoxelShader.getReflectedDescriptors()[0]);
-	m_voxelResetPipe = m_corePtr->createComputePipeline(
+	m_voxelResetDescriptorSetLayout = m_corePtr->createDescriptorSetLayout(resetVoxelShader.getReflectedDescriptors().at(0));
+	m_voxelResetDescriptorSet = m_corePtr->createDescriptorSet(m_voxelResetDescriptorSetLayout);
+	m_voxelResetPipe = m_corePtr->createComputePipeline({
 		resetVoxelShader,
-		{ m_corePtr->getDescriptorSet(m_voxelResetDescriptorSet).layout });
+		{ m_voxelResetDescriptorSetLayout }
+	});
 
 	vkcv::DescriptorWrites resetVoxelWrites;
-	resetVoxelWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, m_voxelBuffer.getHandle()) };
+	resetVoxelWrites.writeStorageBuffer(0, m_voxelBuffer.getHandle());
 	m_corePtr->writeDescriptorSet(m_voxelResetDescriptorSet, resetVoxelWrites);
 
-
+	// buffer to image
 	vkcv::ShaderProgram bufferToImageShader = loadVoxelBufferToImageShader();
 
-	m_bufferToImageDescriptorSet = m_corePtr->createDescriptorSet(bufferToImageShader.getReflectedDescriptors()[0]);
-	m_bufferToImagePipe = m_corePtr->createComputePipeline(
+	m_bufferToImageDescriptorSetLayout = m_corePtr->createDescriptorSetLayout(bufferToImageShader.getReflectedDescriptors().at(0));
+	m_bufferToImageDescriptorSet = m_corePtr->createDescriptorSet(m_bufferToImageDescriptorSetLayout);
+	m_bufferToImagePipe = m_corePtr->createComputePipeline({
 		bufferToImageShader,
-		{ m_corePtr->getDescriptorSet(m_bufferToImageDescriptorSet).layout });
+		{ m_bufferToImageDescriptorSetLayout }
+	});
 
 	vkcv::DescriptorWrites bufferToImageDescriptorWrites;
-	bufferToImageDescriptorWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, m_voxelBuffer.getHandle()) };
-	bufferToImageDescriptorWrites.storageImageWrites = { vkcv::StorageImageDescriptorWrite(1, m_voxelImage.getHandle()) };
+	bufferToImageDescriptorWrites.writeStorageBuffer(0, m_voxelBuffer.getHandle());
+	bufferToImageDescriptorWrites.writeStorageImage(1, m_voxelImageIntermediate.getHandle());
 	m_corePtr->writeDescriptorSet(m_bufferToImageDescriptorSet, bufferToImageDescriptorWrites);
+
+	// secondary bounce
+	vkcv::ShaderProgram secondaryBounceShader = loadSecondaryBounceShader();
+
+	m_secondaryBounceDescriptorSetLayout = m_corePtr->createDescriptorSetLayout(secondaryBounceShader.getReflectedDescriptors().at(0));
+	m_secondaryBounceDescriptorSet = m_corePtr->createDescriptorSet(m_secondaryBounceDescriptorSetLayout);
+	m_secondaryBouncePipe = m_corePtr->createComputePipeline({
+		secondaryBounceShader,
+		{ m_secondaryBounceDescriptorSetLayout }
+	});
+
+	vkcv::DescriptorWrites secondaryBounceDescriptorWrites;
+	secondaryBounceDescriptorWrites.writeStorageBuffer(0, m_voxelBuffer.getHandle());
+	secondaryBounceDescriptorWrites.writeSampledImage(1, m_voxelImageIntermediate.getHandle());
+	secondaryBounceDescriptorWrites.writeSampler(2, voxelSampler);
+	secondaryBounceDescriptorWrites.writeStorageImage(3, m_voxelImage.getHandle());
+	secondaryBounceDescriptorWrites.writeUniformBuffer(4, m_voxelInfoBuffer.getHandle());
+	m_corePtr->writeDescriptorSet(m_secondaryBounceDescriptorSet, secondaryBounceDescriptorWrites);
 }
 
 void Voxelization::voxelizeMeshes(
-	vkcv::CommandStreamHandle                       cmdStream, 
-	const glm::vec3&                                cameraPosition, 
-	const std::vector<vkcv::Mesh>&                  meshes,
+	vkcv::CommandStreamHandle                       cmdStream,
+	const std::vector<vkcv::VertexData>&            meshes,
 	const std::vector<glm::mat4>&                   modelMatrices,
-	const std::vector<vkcv::DescriptorSetHandle>&   perMeshDescriptorSets) {
+	const std::vector<vkcv::DescriptorSetHandle>&   perMeshDescriptorSets,
+	const vkcv::WindowHandle&                       windowHandle,
+	vkcv::Downsampler&								downsampler) {
 
-	VoxelizationInfo voxelizationInfo;
-	voxelizationInfo.extent = m_voxelExtent;
+	m_voxelInfoBuffer.fill({ m_voxelInfo });
 
-	// move voxel offset with camera in voxel sized steps
-	const float voxelSize = m_voxelExtent / voxelResolution;
-	voxelizationInfo.offset = glm::floor(cameraPosition / voxelSize) * voxelSize;
-
-	m_voxelInfoBuffer.fill({ voxelizationInfo });
-
-	const float voxelizationHalfExtent = 0.5f * m_voxelExtent;
+	const float voxelizationHalfExtent = 0.5f * m_voxelInfo.extent;
 	const glm::mat4 voxelizationProjection = glm::ortho(
 		-voxelizationHalfExtent,
 		voxelizationHalfExtent,
@@ -206,103 +229,160 @@ void Voxelization::voxelizeMeshes(
 		-voxelizationHalfExtent,
 		voxelizationHalfExtent);
 
-	const glm::mat4 voxelizationView = glm::translate(glm::mat4(1.f), -voxelizationInfo.offset);
+	const glm::mat4 voxelizationView = glm::translate(glm::mat4(1.f), -m_voxelInfo.offset);
 	const glm::mat4 voxelizationViewProjection = voxelizationProjection * voxelizationView;
-
-	std::vector<std::array<glm::mat4, 2>> voxelizationMatrices;
+	
+	vkcv::PushConstants voxelizationPushConstants (2 * sizeof(glm::mat4));
+	
 	for (const auto& m : modelMatrices) {
-		voxelizationMatrices.push_back({ voxelizationViewProjection * m, m });
+		voxelizationPushConstants.appendDrawcall(std::array<glm::mat4, 2>{ voxelizationViewProjection * m, m });
 	}
 
-	const vkcv::PushConstantData voxelizationPushConstantData((void*)voxelizationMatrices.data(), 2 * sizeof(glm::mat4));
-
 	// reset voxels
 	const uint32_t resetVoxelGroupSize = 64;
-	uint32_t resetVoxelDispatchCount[3];
-	resetVoxelDispatchCount[0] = glm::ceil(voxelCount / float(resetVoxelGroupSize));
-	resetVoxelDispatchCount[1] = 1;
-	resetVoxelDispatchCount[2] = 1;
+	
+	vkcv::PushConstants voxelCountPushConstants = vkcv::pushConstants<uint32_t>();
+	voxelCountPushConstants.appendDrawcall(voxelCount);
 
-	m_corePtr->prepareImageForStorage(cmdStream, m_voxelImage.getHandle());
+	m_corePtr->recordBeginDebugLabel(cmdStream, "Voxel reset", { 1, 1, 1, 1 });
 	m_corePtr->recordComputeDispatchToCmdStream(
 		cmdStream,
 		m_voxelResetPipe,
-		resetVoxelDispatchCount,
-		{ vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_voxelResetDescriptorSet).vulkanHandle) },
-		vkcv::PushConstantData(&voxelCount, sizeof(voxelCount)));
+		vkcv::dispatchInvocations(voxelCount, resetVoxelGroupSize),
+		{ vkcv::useDescriptorSet(0, m_voxelResetDescriptorSet) },
+		voxelCountPushConstants
+	);
 	m_corePtr->recordBufferMemoryBarrier(cmdStream, m_voxelBuffer.getHandle());
+	m_corePtr->recordEndDebugLabel(cmdStream);
 
 	// voxelization
-	std::vector<vkcv::DrawcallInfo> drawcalls;
-	for (int i = 0; i < meshes.size(); i++) {
-		drawcalls.push_back(vkcv::DrawcallInfo(
-			meshes[i], 
-			{ 
-				vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_voxelizationDescriptorSet).vulkanHandle),
-				vkcv::DescriptorSetUsage(1, m_corePtr->getDescriptorSet(perMeshDescriptorSets[i]).vulkanHandle) 
-			}));
+	std::vector<vkcv::InstanceDrawcall> drawcalls;
+	for (size_t i = 0; i < meshes.size(); i++) {
+		vkcv::InstanceDrawcall drawcall (meshes[i]);
+		drawcall.useDescriptorSet(0, m_voxelizationDescriptorSet);
+		drawcall.useDescriptorSet(1, perMeshDescriptorSets[i]);
+		drawcalls.push_back(drawcall);
 	}
 
+	m_corePtr->recordBeginDebugLabel(cmdStream, "Voxelization", { 1, 1, 1, 1 });
+	m_corePtr->prepareImageForStorage(cmdStream, m_voxelImageIntermediate.getHandle());
 	m_corePtr->recordDrawcallsToCmdStream(
 		cmdStream,
-		m_voxelizationPass,
 		m_voxelizationPipe,
-		voxelizationPushConstantData,
+		voxelizationPushConstants,
 		drawcalls,
-		{ m_dummyRenderTarget.getHandle() });
+		{ m_dummyRenderTarget.getHandle() },
+		windowHandle
+	);
+	m_corePtr->recordEndDebugLabel(cmdStream);
 
 	// buffer to image
-	const uint32_t bufferToImageGroupSize[3] = { 4, 4, 4 };
-	uint32_t bufferToImageDispatchCount[3];
-	for (int i = 0; i < 3; i++) {
-		bufferToImageDispatchCount[i] = glm::ceil(voxelResolution / float(bufferToImageGroupSize[i]));
-	}
-
+	const auto bufferToImageDispatchCount = vkcv::dispatchInvocations(
+			vkcv::DispatchSize(voxelResolution, voxelResolution, voxelResolution),
+			vkcv::DispatchSize(4, 4, 4)
+	);
+	
+	m_corePtr->recordBeginDebugLabel(cmdStream, "Voxel buffer to image", { 1, 1, 1, 1 });
 	m_corePtr->recordComputeDispatchToCmdStream(
 		cmdStream,
 		m_bufferToImagePipe,
 		bufferToImageDispatchCount,
-		{ vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_bufferToImageDescriptorSet).vulkanHandle) },
-		vkcv::PushConstantData(nullptr, 0));
+		{ vkcv::useDescriptorSet(0, m_bufferToImageDescriptorSet) },
+		vkcv::PushConstants(0)
+	);
 
-	m_corePtr->recordImageMemoryBarrier(cmdStream, m_voxelImage.getHandle());
+	m_corePtr->recordImageMemoryBarrier(cmdStream, m_voxelImageIntermediate.getHandle());
+	m_corePtr->recordEndDebugLabel(cmdStream);
 
-	m_voxelImage.recordMipChainGeneration(cmdStream);
+	// intermediate image mipchain
+	m_corePtr->recordBeginDebugLabel(cmdStream, "Intermediate Voxel mipmap generation", { 1, 1, 1, 1 });
+	m_voxelImageIntermediate.recordMipChainGeneration(cmdStream, downsampler);
+	m_corePtr->recordEndDebugLabel(cmdStream);
+
+	// secondary bounce
+	m_corePtr->recordBeginDebugLabel(cmdStream, "Voxel secondary bounce", { 1, 1, 1, 1 });
+	m_corePtr->prepareImageForStorage(cmdStream, m_voxelImage.getHandle());
+	m_corePtr->recordComputeDispatchToCmdStream(
+		cmdStream,
+		m_secondaryBouncePipe,
+		bufferToImageDispatchCount,
+		{ vkcv::useDescriptorSet(0, m_secondaryBounceDescriptorSet) },
+		vkcv::PushConstants(0));
+	m_voxelImage.recordMipChainGeneration(cmdStream, downsampler);
+	m_corePtr->recordEndDebugLabel(cmdStream);
+
+	// final image mipchain
+	m_corePtr->recordBeginDebugLabel(cmdStream, "Voxel mipmap generation", { 1, 1, 1, 1 });
+	m_voxelImage.recordMipChainGeneration(cmdStream, downsampler);
+	m_corePtr->recordEndDebugLabel(cmdStream);
 }
 
 void Voxelization::renderVoxelVisualisation(
 	vkcv::CommandStreamHandle               cmdStream, 
 	const glm::mat4&                        viewProjectin,
 	const std::vector<vkcv::ImageHandle>&   renderTargets,
-	uint32_t                                mipLevel) {
+	uint32_t                                mipLevel,
+	const vkcv::WindowHandle&               windowHandle) {
 
-	const vkcv::PushConstantData voxelVisualisationPushConstantData((void*)&viewProjectin, sizeof(glm::mat4));
+	vkcv::PushConstants voxelVisualisationPushConstants = vkcv::pushConstants<glm::mat4>();
+	voxelVisualisationPushConstants.appendDrawcall(viewProjectin);
 
-	mipLevel = std::clamp(mipLevel, (uint32_t)0, m_voxelImage.getMipCount()-1);
+	mipLevel = std::clamp(mipLevel, (uint32_t) 0, m_voxelImage.getMipLevels() - 1);
 
 	// write descriptor set
 	vkcv::DescriptorWrites voxelVisualisationDescriptorWrite;
-	voxelVisualisationDescriptorWrite.storageImageWrites =
-	{ vkcv::StorageImageDescriptorWrite(0, m_voxelImage.getHandle(), mipLevel) };
-	voxelVisualisationDescriptorWrite.uniformBufferWrites =
-	{ vkcv::UniformBufferDescriptorWrite(1, m_voxelInfoBuffer.getHandle()) };
+	voxelVisualisationDescriptorWrite.writeStorageImage(0, m_voxelImage.getHandle(), mipLevel);
+	voxelVisualisationDescriptorWrite.writeUniformBuffer(1, m_voxelInfoBuffer.getHandle());
 	m_corePtr->writeDescriptorSet(m_visualisationDescriptorSet, voxelVisualisationDescriptorWrite);
 
 	uint32_t drawVoxelCount = voxelCount / exp2(mipLevel);
-
-	const auto drawcall = vkcv::DrawcallInfo(
-		vkcv::Mesh({}, nullptr, drawVoxelCount),
-		{ vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_visualisationDescriptorSet).vulkanHandle) });
-
+	
+	vkcv::VertexData voxelData;
+	voxelData.setCount(drawVoxelCount);
+	
+	vkcv::InstanceDrawcall drawcall (voxelData);
+	drawcall.useDescriptorSet(0, m_visualisationDescriptorSet);
+
+	m_corePtr->recordBeginDebugLabel(cmdStream, "Voxel visualisation", { 1, 1, 1, 1 });
+	m_corePtr->prepareImageForStorage(cmdStream, m_voxelImage.getHandle());
 	m_corePtr->recordDrawcallsToCmdStream(
 		cmdStream,
-		m_visualisationPass,
 		m_visualisationPipe,
-		voxelVisualisationPushConstantData,
+		voxelVisualisationPushConstants,
 		{ drawcall },
-		renderTargets);
+		renderTargets,
+		windowHandle
+	);
+	m_corePtr->recordEndDebugLabel(cmdStream);
+}
+
+void Voxelization::updateVoxelOffset(const vkcv::camera::Camera& camera) {
+
+	// move voxel offset with camera in voxel sized steps
+	const float voxelSize   = m_voxelInfo.extent / voxelResolution;
+	const float snapSize    = voxelSize * exp2(maxStableMip);
+
+	glm::vec3 voxelVolumeCenter = camera.getPosition() + (1.f / 3.f) * m_voxelInfo.extent * glm::normalize(camera.getFront());
+	voxelVolumeCenter.y         = camera.getPosition().y;
+	m_voxelInfo.offset          = glm::floor(voxelVolumeCenter / snapSize) * snapSize;
 }
 
 void Voxelization::setVoxelExtent(float extent) {
-	m_voxelExtent = extent;
-}
\ No newline at end of file
+	m_voxelInfo.extent = extent;
+}
+
+vkcv::ImageHandle Voxelization::getVoxelImageHandle() const {
+	return m_voxelImage.getHandle();
+}
+
+vkcv::BufferHandle Voxelization::getVoxelInfoBufferHandle() const {
+	return m_voxelInfoBuffer.getHandle();
+}
+
+glm::vec3 Voxelization::getVoxelOffset() const{
+	return m_voxelInfo.offset;
+}
+
+float Voxelization::getVoxelExtent() const {
+	return m_voxelInfo.extent;
+}
diff --git a/projects/voxelization/src/Voxelization.hpp b/projects/voxelization/src/Voxelization.hpp
index 25830b171edb9154e37b2d597c2bbbf2daea6b2e..f624d03503b67cc2de0632367d90742fb975473e 100644
--- a/projects/voxelization/src/Voxelization.hpp
+++ b/projects/voxelization/src/Voxelization.hpp
@@ -1,6 +1,11 @@
 #pragma once
+
+#include <vkcv/Buffer.hpp>
 #include <vkcv/Core.hpp>
+#include <vkcv/Image.hpp>
 #include <glm/glm.hpp>
+#include <vkcv/camera/Camera.hpp>
+#include <vkcv/Downsampler.hpp>
 
 class Voxelization{
 public:
@@ -14,48 +19,70 @@ public:
 		const Dependencies& dependencies, 
 		vkcv::BufferHandle  lightInfoBuffer,
 		vkcv::ImageHandle   shadowMap,
-		vkcv::SamplerHandle shadowSampler);
+		vkcv::SamplerHandle shadowSampler,
+		vkcv::SamplerHandle voxelSampler,
+		vkcv::Multisampling msaa);
 
 	void voxelizeMeshes(
-		vkcv::CommandStreamHandle                       cmdStream, 
-		const glm::vec3&                                cameraPosition, 
-		const std::vector<vkcv::Mesh>&                  meshes,
+		vkcv::CommandStreamHandle                       cmdStream,
+		const std::vector<vkcv::VertexData>&            meshes,
 		const std::vector<glm::mat4>&                   modelMatrices,
-		const std::vector<vkcv::DescriptorSetHandle>&   perMeshDescriptorSets);
+		const std::vector<vkcv::DescriptorSetHandle>&   perMeshDescriptorSets,
+		const vkcv::WindowHandle&                       windowHandle,
+		vkcv::Downsampler&								downsampler);
 
 	void renderVoxelVisualisation(
 		vkcv::CommandStreamHandle               cmdStream,
 		const glm::mat4&                        viewProjectin,
 		const std::vector<vkcv::ImageHandle>&   renderTargets,
-		uint32_t                                mipLevel);
+		uint32_t                                mipLevel,
+		const vkcv::WindowHandle&               windowHandle);
 
+	void updateVoxelOffset(const vkcv::camera::Camera& camera);
 	void setVoxelExtent(float extent);
 
+	vkcv::ImageHandle   getVoxelImageHandle() const;
+	vkcv::BufferHandle  getVoxelInfoBufferHandle() const;
+
+	glm::vec3   getVoxelOffset() const;
+	float       getVoxelExtent() const;
+
 private:
 	vkcv::Core* m_corePtr;
 
 	struct VoxelBufferContent{
-		uint32_t isFilled;
+		uint32_t lightEncoded;
+		uint32_t normalEncoded;
+		uint32_t albedoEncoded;
 	};
 
+	vkcv::Image                         m_voxelImageIntermediate;
 	vkcv::Image                         m_voxelImage;
-    vkcv::Buffer<VoxelBufferContent>    m_voxelBuffer;
+	vkcv::Buffer<VoxelBufferContent>    m_voxelBuffer;
+
+	vkcv::Image                         m_dummyRenderTarget;
+	vkcv::PassHandle                    m_voxelizationPass;
+	vkcv::GraphicsPipelineHandle        m_voxelizationPipe;
+	vkcv::DescriptorSetLayoutHandle     m_voxelizationDescriptorSetLayout;
+	vkcv::DescriptorSetHandle           m_voxelizationDescriptorSet;
 
-	vkcv::Image                 m_dummyRenderTarget;
-	vkcv::PassHandle            m_voxelizationPass;
-	vkcv::PipelineHandle        m_voxelizationPipe;
-	vkcv::DescriptorSetHandle   m_voxelizationDescriptorSet;
+	vkcv::ComputePipelineHandle         m_voxelResetPipe;
+	vkcv::DescriptorSetLayoutHandle     m_voxelResetDescriptorSetLayout;
+	vkcv::DescriptorSetHandle           m_voxelResetDescriptorSet;
 
-	vkcv::PipelineHandle        m_voxelResetPipe;
-	vkcv::DescriptorSetHandle   m_voxelResetDescriptorSet;
+	vkcv::ComputePipelineHandle         m_bufferToImagePipe;
+	vkcv::DescriptorSetLayoutHandle     m_bufferToImageDescriptorSetLayout;
+	vkcv::DescriptorSetHandle           m_bufferToImageDescriptorSet;
 
-	vkcv::PipelineHandle        m_bufferToImagePipe;
-	vkcv::DescriptorSetHandle   m_bufferToImageDescriptorSet;
+	vkcv::PassHandle                    m_visualisationPass;
+	vkcv::GraphicsPipelineHandle        m_visualisationPipe;
 
-	vkcv::PassHandle            m_visualisationPass;
-	vkcv::PipelineHandle        m_visualisationPipe;
+	vkcv::ComputePipelineHandle         m_secondaryBouncePipe;
+	vkcv::DescriptorSetLayoutHandle     m_secondaryBounceDescriptorSetLayout;
+	vkcv::DescriptorSetHandle           m_secondaryBounceDescriptorSet;
 
-	vkcv::DescriptorSetHandle   m_visualisationDescriptorSet;
+	vkcv::DescriptorSetLayoutHandle     m_visualisationDescriptorSetLayout;
+	vkcv::DescriptorSetHandle           m_visualisationDescriptorSet;
 
 	struct VoxelizationInfo {
 		glm::vec3 offset;
@@ -63,5 +90,5 @@ private:
 	};
 	vkcv::Buffer<VoxelizationInfo> m_voxelInfoBuffer;
 
-	float m_voxelExtent = 20.f;
+	VoxelizationInfo m_voxelInfo;
 };
\ No newline at end of file
diff --git a/projects/voxelization/src/main.cpp b/projects/voxelization/src/main.cpp
index aabed2180f598c9792a76ddcdcf4a2f400d16334..9b9bf39b205cd65b8a0f5813b7f4376ee68152a9 100644
--- a/projects/voxelization/src/main.cpp
+++ b/projects/voxelization/src/main.cpp
@@ -1,50 +1,133 @@
 #include <iostream>
 #include <vkcv/Core.hpp>
+#include <vkcv/Pass.hpp>
+#include <vkcv/Sampler.hpp>
 #include <GLFW/glfw3.h>
 #include <vkcv/camera/CameraManager.hpp>
 #include <chrono>
 #include <vkcv/asset/asset_loader.hpp>
 #include <vkcv/shader/GLSLCompiler.hpp>
-#include <vkcv/Logger.hpp>
 #include "Voxelization.hpp"
-#include <glm/glm.hpp>
 #include "vkcv/gui/GUI.hpp"
+#include "ShadowMapping.hpp"
+#include <vkcv/upscaling/FSRUpscaling.hpp>
+#include <vkcv/upscaling/BilinearUpscaling.hpp>
+#include <vkcv/upscaling/NISUpscaling.hpp>
+#include <vkcv/effects/BloomAndFlaresEffect.hpp>
+#include <vkcv/algorithm/SinglePassDownsampler.hpp>
 
 int main(int argc, const char** argv) {
-	const char* applicationName = "Voxelization";
+	const std::string applicationName = "Voxelization";
 
-	uint32_t windowWidth = 1280;
-	uint32_t windowHeight = 720;
+	const vkcv::Multisampling   msaa        = vkcv::Multisampling::MSAA4X;
+	const bool                  usingMsaa   = msaa != vkcv::Multisampling::None;
+
+	vkcv::Features features;
+	features.requireExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
+	
+	features.requireFeature([](vk::PhysicalDeviceFeatures& features) {
+		features.setGeometryShader(true);
+		features.setDepthClamp(true);
+		features.setShaderInt16(true);
+	});
 	
-	vkcv::Window window = vkcv::Window::create(
-		applicationName,
-		windowWidth,
-		windowHeight,
-		true
+	features.requireExtensionFeature<vk::PhysicalDeviceDescriptorIndexingFeatures>(
+			VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME,
+			[](vk::PhysicalDeviceDescriptorIndexingFeatures& features) {
+				features.setDescriptorBindingPartiallyBound(true);
+			}
 	);
-
-    vkcv::camera::CameraManager cameraManager(window);
-    uint32_t camIndex = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
-    uint32_t camIndex2 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
-    
-    cameraManager.getCamera(camIndex).setPosition(glm::vec3(0.f, 0.f, 3.f));
-    cameraManager.getCamera(camIndex).setNearFar(0.1f, 30.0f);
-	cameraManager.getCamera(camIndex).setYaw(180.0f);
 	
-	cameraManager.getCamera(camIndex2).setNearFar(0.1f, 30.0f);
+	features.tryExtensionFeature<vk::PhysicalDeviceShaderSubgroupExtendedTypesFeatures>(
+		VK_KHR_SHADER_SUBGROUP_EXTENDED_TYPES_EXTENSION_NAME,
+		[](vk::PhysicalDeviceShaderSubgroupExtendedTypesFeatures& features) {
+			features.setShaderSubgroupExtendedTypes(true);
+		}
+	);
+	
+	features.tryExtensionFeature<vk::PhysicalDevice16BitStorageFeatures>(
+		VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME,
+		[](vk::PhysicalDevice16BitStorageFeatures& features) {
+			features.setStorageBuffer16BitAccess(true);
+		}
+	);
+
+	features.tryExtensionFeature<vk::PhysicalDeviceShaderFloat16Int8Features>(
+		VK_KHR_16BIT_STORAGE_EXTENSION_NAME,
+		[](vk::PhysicalDeviceShaderFloat16Int8Features& features) {
+			features.setShaderFloat16(true);
+		}
+	);
+
+	const uint32_t windowWidth = 1280;
+	const uint32_t windowHeight = 720;
 
 	vkcv::Core core = vkcv::Core::create(
-		window,
-		applicationName,
-		VK_MAKE_VERSION(0, 0, 1),
-		{ vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },
-		{},
-		{ "VK_KHR_swapchain" }
+			applicationName,
+			VK_MAKE_VERSION(0, 0, 1),
+			{ vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },
+			features
 	);
+	
+	vkcv::WindowHandle windowHandle = core.createWindow(applicationName, windowWidth, windowHeight, true);
+	vkcv::Window& window = core.getWindow(windowHandle);
+
+	bool     isFullscreen            = false;
+	uint32_t windowedWidthBackup     = window.getWidth();
+	uint32_t windowedHeightBackup    = window.getHeight();
+	int      windowedPosXBackup;
+	int      windowedPosYBackup;
+    glfwGetWindowPos(window.getWindow(), &windowedPosXBackup, &windowedPosYBackup);
+
+	window.e_key.add([&](int key, int scancode, int action, int mods) {
+		if (key == GLFW_KEY_F11 && action == GLFW_PRESS) {
+			if (isFullscreen) {
+				glfwSetWindowMonitor(
+					window.getWindow(),
+					nullptr,
+					windowedPosXBackup,
+					windowedPosYBackup,
+					windowedWidthBackup,
+					windowedHeightBackup,
+					GLFW_DONT_CARE);
+			}
+			else {
+				windowedWidthBackup     = window.getWidth();
+				windowedHeightBackup    = window.getHeight();
+
+				glfwGetWindowPos(window.getWindow(), &windowedPosXBackup, &windowedPosYBackup);
+
+				GLFWmonitor*        monitor     = glfwGetPrimaryMonitor();
+				const GLFWvidmode*  videoMode   = glfwGetVideoMode(monitor);
+
+				glfwSetWindowMonitor(
+					window.getWindow(),
+					glfwGetPrimaryMonitor(),
+					0,
+					0,
+					videoMode->width,
+					videoMode->height,
+					videoMode->refreshRate);
+			}
+			isFullscreen = !isFullscreen;
+		}
+	});
+
+	vkcv::camera::CameraManager cameraManager(window);
+	auto camHandle0  = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+	auto camHandle1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+
+	cameraManager.getCamera(camHandle0).setPosition(glm::vec3(0.f, 0.f, 3.f));
+	cameraManager.getCamera(camHandle0).setNearFar(0.1f, 30.0f);
+	cameraManager.getCamera(camHandle0).setYaw(180.0f);
+	cameraManager.getCamera(camHandle0).setFov(glm::radians(37.8));	// fov of a 35mm lens
+	
+	cameraManager.getCamera(camHandle1).setPosition(glm::vec3(0.f, 0.f, 3.f));
+	cameraManager.getCamera(camHandle1).setNearFar(0.1f, 30.0f);
 
 	vkcv::asset::Scene mesh;
 
-	const char* path = argc > 1 ? argv[1] : "resources/Sponza/Sponza.gltf";
+	const char* path = argc > 1 ? argv[1] : "assets/Sponza/Sponza.gltf";
 	vkcv::asset::Scene scene;
 	int result = vkcv::asset::loadScene(path, scene);
 
@@ -61,25 +144,17 @@ int main(int argc, const char** argv) {
 	std::vector<std::vector<uint8_t>> vBuffers;
 	std::vector<std::vector<uint8_t>> iBuffers;
 
-	std::vector<vkcv::VertexBufferBinding> vBufferBindings;
 	std::vector<std::vector<vkcv::VertexBufferBinding>> vertexBufferBindings;
-	std::vector<vkcv::asset::VertexAttribute> vAttributes;
-
-	for (int i = 0; i < scene.vertexGroups.size(); i++) {
 
+	for (size_t i = 0; i < scene.vertexGroups.size(); i++) {
 		vBuffers.push_back(scene.vertexGroups[i].vertexBuffer.data);
 		iBuffers.push_back(scene.vertexGroups[i].indexBuffer.data);
-
-		auto& attributes = scene.vertexGroups[i].vertexBuffer.attributes;
-
-		std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) {
-			return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type);
-		});
 	}
 
 	std::vector<vkcv::Buffer<uint8_t>> vertexBuffers;
 	for (const vkcv::asset::VertexGroup& group : scene.vertexGroups) {
-		vertexBuffers.push_back(core.createBuffer<uint8_t>(
+		vertexBuffers.push_back(buffer<uint8_t>(
+			core,
 			vkcv::BufferType::VERTEX,
 			group.vertexBuffer.data.size()));
 		vertexBuffers.back().fill(group.vertexBuffer.data);
@@ -87,48 +162,52 @@ int main(int argc, const char** argv) {
 
 	std::vector<vkcv::Buffer<uint8_t>> indexBuffers;
 	for (const auto& dataBuffer : iBuffers) {
-		indexBuffers.push_back(core.createBuffer<uint8_t>(
+		indexBuffers.push_back(buffer<uint8_t>(
+			core,
 			vkcv::BufferType::INDEX,
 			dataBuffer.size()));
 		indexBuffers.back().fill(dataBuffer);
 	}
 
-	int vertexBufferIndex = 0;
-	for (const auto& vertexGroup : scene.vertexGroups) {
-		for (const auto& attribute : vertexGroup.vertexBuffer.attributes) {
-			vAttributes.push_back(attribute);
-			vBufferBindings.push_back(vkcv::VertexBufferBinding(attribute.offset, vertexBuffers[vertexBufferIndex].getVulkanHandle()));
-		}
-		vertexBufferBindings.push_back(vBufferBindings);
-		vBufferBindings.clear();
-		vertexBufferIndex++;
+	for (size_t i = 0; i < scene.vertexGroups.size(); i++) {
+		vertexBufferBindings.push_back(vkcv::asset::loadVertexBufferBindings(
+				scene.vertexGroups[i].vertexBuffer.attributes,
+				vertexBuffers[i].getHandle(),
+				{
+						vkcv::asset::PrimitiveType::POSITION,
+						vkcv::asset::PrimitiveType::NORMAL,
+						vkcv::asset::PrimitiveType::TEXCOORD_0,
+						vkcv::asset::PrimitiveType::TANGENT
+				}
+		));
 	}
 
 	const vk::Format colorBufferFormat = vk::Format::eB10G11R11UfloatPack32;
-	const vkcv::AttachmentDescription color_attachment(
-		vkcv::AttachmentOperation::STORE,
-		vkcv::AttachmentOperation::CLEAR,
-		colorBufferFormat
+	const vkcv::AttachmentDescription color_attachment (
+			colorBufferFormat,
+			vkcv::AttachmentOperation::CLEAR,
+			vkcv::AttachmentOperation::STORE
 	);
 	
 	const vk::Format depthBufferFormat = vk::Format::eD32Sfloat;
-	const vkcv::AttachmentDescription depth_attachment(
-		vkcv::AttachmentOperation::STORE,
-		vkcv::AttachmentOperation::CLEAR,
-		depthBufferFormat
+	const vkcv::AttachmentDescription depth_attachment (
+			depthBufferFormat,
+			vkcv::AttachmentOperation::LOAD,
+			vkcv::AttachmentOperation::STORE
 	);
-
-	vkcv::PassConfig forwardPassDefinition({ color_attachment, depth_attachment });
+	
+	// forward shading config
+	vkcv::PassConfig forwardPassDefinition({ color_attachment, depth_attachment }, msaa);
 	vkcv::PassHandle forwardPass = core.createPass(forwardPassDefinition);
 
 	vkcv::shader::GLSLCompiler compiler;
 
 	vkcv::ShaderProgram forwardProgram;
-	compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/shader.vert"), 
+	compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("assets/shaders/shader.vert"), 
 		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
 		forwardProgram.addShader(shaderStage, path);
 	});
-	compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/shader.frag"),
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("assets/shaders/shader.frag"),
 		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
 		forwardProgram.addShader(shaderStage, path);
 	});
@@ -137,134 +216,250 @@ int main(int argc, const char** argv) {
 
 	std::vector<vkcv::VertexBinding> vertexBindings;
 	for (size_t i = 0; i < vertexAttachments.size(); i++) {
-		vertexBindings.push_back(vkcv::VertexBinding(i, { vertexAttachments[i] }));
+		vertexBindings.push_back(vkcv::createVertexBinding(i, { vertexAttachments[i] }));
 	}
-	const vkcv::VertexLayout vertexLayout (vertexBindings);
-
-	// shadow map
-	vkcv::SamplerHandle shadowSampler = core.createSampler(
-		vkcv::SamplerFilterType::NEAREST,
-		vkcv::SamplerFilterType::NEAREST,
-		vkcv::SamplerMipmapMode::NEAREST,
-		vkcv::SamplerAddressMode::CLAMP_TO_EDGE
-	);
-	const vk::Format shadowMapFormat = vk::Format::eD16Unorm;
-	const uint32_t shadowMapResolution = 1024;
-	const vkcv::Image shadowMap = core.createImage(shadowMapFormat, shadowMapResolution, shadowMapResolution);
-
-	// light info buffer
-	struct LightInfo {
-		glm::vec3   direction;
-		float       padding;
-		glm::vec3   sunColor    = glm::vec3(1.f);
-		float       sunStrength = 8.f;
-		glm::mat4   lightMatrix;
-	};
-	LightInfo lightInfo;
-	vkcv::Buffer lightBuffer = core.createBuffer<LightInfo>(vkcv::BufferType::UNIFORM, sizeof(glm::vec3));
+	const vkcv::VertexLayout vertexLayout { vertexBindings };
 
-	vkcv::DescriptorSetHandle forwardShadingDescriptorSet = 
-		core.createDescriptorSet({ forwardProgram.getReflectedDescriptors()[0] });
+	vkcv::DescriptorSetLayoutHandle forwardShadingDescriptorSetLayout = core.createDescriptorSetLayout(forwardProgram.getReflectedDescriptors().at(0));
+	vkcv::DescriptorSetHandle forwardShadingDescriptorSet = core.createDescriptorSet(forwardShadingDescriptorSetLayout);
 
-	vkcv::DescriptorWrites forwardDescriptorWrites;
-	forwardDescriptorWrites.uniformBufferWrites = { vkcv::UniformBufferDescriptorWrite(0, lightBuffer.getHandle()) };
-	forwardDescriptorWrites.sampledImageWrites  = { vkcv::SampledImageDescriptorWrite(1, shadowMap.getHandle()) };
-	forwardDescriptorWrites.samplerWrites       = { vkcv::SamplerDescriptorWrite(2, shadowSampler) };
-	core.writeDescriptorSet(forwardShadingDescriptorSet, forwardDescriptorWrites);
+	// depth prepass config
+	vkcv::ShaderProgram depthPrepassShader;
+	compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("assets/shaders/depthPrepass.vert"),
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		depthPrepassShader.addShader(shaderStage, path);
+	});
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("assets/shaders/depthPrepass.frag"),
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		depthPrepassShader.addShader(shaderStage, path);
+	});
 
-	vkcv::SamplerHandle colorSampler = core.createSampler(
-		vkcv::SamplerFilterType::LINEAR,
-		vkcv::SamplerFilterType::LINEAR,
-		vkcv::SamplerMipmapMode::LINEAR,
-		vkcv::SamplerAddressMode::REPEAT
-	);
+	const std::vector<vkcv::VertexAttachment> prepassVertexAttachments = depthPrepassShader.getVertexAttachments();
+
+	std::vector<vkcv::VertexBinding> prepassVertexBindings;
+	for (size_t i = 0; i < prepassVertexAttachments.size(); i++) {
+		prepassVertexBindings.push_back(vkcv::createVertexBinding(i, { prepassVertexAttachments[i] }));
+	}
+	const vkcv::VertexLayout prepassVertexLayout { prepassVertexBindings };
+	
+	vkcv::PassHandle prepassPass = vkcv::passFormat(core, depthBufferFormat, true, msaa);
 
 	// create descriptor sets
+	vkcv::SamplerHandle colorSampler = vkcv::samplerLinear(core);
+
+	std::vector<vkcv::DescriptorSetLayoutHandle> materialDescriptorSetLayouts;
 	std::vector<vkcv::DescriptorSetHandle> materialDescriptorSets;
 	std::vector<vkcv::Image> sceneImages;
+	
+	vkcv::algorithm::SinglePassDownsampler spdDownsampler (core, colorSampler);
+	
+	auto mipStream = core.createCommandStream(vkcv::QueueType::Graphics);
 
 	for (const auto& material : scene.materials) {
-		int baseColorIndex = material.baseColor;
-		if (baseColorIndex < 0) {
-			vkcv_log(vkcv::LogLevel::WARNING, "Material lacks base color");
-			baseColorIndex = 0;
+		int albedoIndex     = material.baseColor;
+		int normalIndex     = material.normal;
+		int specularIndex   = material.metalRough;
+
+		if (albedoIndex < 0) {
+			vkcv_log(vkcv::LogLevel::WARNING, "Material lacks albedo");
+			albedoIndex = 0;
+		}
+		if (normalIndex < 0) {
+			vkcv_log(vkcv::LogLevel::WARNING, "Material lacks normal");
+			normalIndex = 0;
 		}
+		if (specularIndex < 0) {
+			vkcv_log(vkcv::LogLevel::WARNING, "Material lacks specular");
+			specularIndex = 0;
+		}
+
+		materialDescriptorSetLayouts.push_back(core.createDescriptorSetLayout(forwardProgram.getReflectedDescriptors().at(1)));
+		materialDescriptorSets.push_back(core.createDescriptorSet(materialDescriptorSetLayouts.back()));
+
+		vkcv::asset::Texture& albedoTexture     = scene.textures[albedoIndex];
+		vkcv::asset::Texture& normalTexture     = scene.textures[normalIndex];
+		vkcv::asset::Texture& specularTexture   = scene.textures[specularIndex];
 
-		materialDescriptorSets.push_back(core.createDescriptorSet(forwardProgram.getReflectedDescriptors()[1]));
+		// albedo texture
+		sceneImages.push_back(vkcv::image(core, vk::Format::eR8G8B8A8Srgb, albedoTexture.w, albedoTexture.h, 1, true));
+		sceneImages.back().fill(albedoTexture.data.data());
+		sceneImages.back().recordMipChainGeneration(mipStream, spdDownsampler);
+		const vkcv::ImageHandle albedoHandle = sceneImages.back().getHandle();
 
-		vkcv::asset::Texture& sceneTexture = scene.textures[baseColorIndex];
+		// normal texture
+		sceneImages.push_back(vkcv::image(core, vk::Format::eR8G8B8A8Unorm, normalTexture.w, normalTexture.h, 1, true, true));
+		sceneImages.back().fill(normalTexture.data.data());
+		sceneImages.back().recordMipChainGeneration(mipStream, spdDownsampler);
+		const vkcv::ImageHandle normalHandle = sceneImages.back().getHandle();
 
-		sceneImages.push_back(core.createImage(vk::Format::eR8G8B8A8Srgb, sceneTexture.w, sceneTexture.h, 1, true));
-		sceneImages.back().fill(sceneTexture.data.data());
-		sceneImages.back().generateMipChainImmediate();
-		sceneImages.back().switchLayout(vk::ImageLayout::eShaderReadOnlyOptimal);
+		// specular texture
+		sceneImages.push_back(vkcv::image(core, vk::Format::eR8G8B8A8Unorm, specularTexture.w, specularTexture.h, 1, true, true));
+		sceneImages.back().fill(specularTexture.data.data());
+		sceneImages.back().recordMipChainGeneration(mipStream, spdDownsampler);
+		const vkcv::ImageHandle specularHandle = sceneImages.back().getHandle();
 
 		vkcv::DescriptorWrites setWrites;
-		setWrites.sampledImageWrites = {
-			vkcv::SampledImageDescriptorWrite(0, sceneImages.back().getHandle())
-		};
-		setWrites.samplerWrites = {
-			vkcv::SamplerDescriptorWrite(1, colorSampler),
-		};
+		setWrites.writeSampledImage(
+				0, albedoHandle
+		).writeSampledImage(
+				2, normalHandle
+		).writeSampledImage(
+				3, specularHandle
+		);
+		
+		setWrites.writeSampler(1, colorSampler);
 		core.writeDescriptorSet(materialDescriptorSets.back(), setWrites);
 	}
+	
+	core.submitCommandStream(mipStream, false);
 
+	std::vector<vkcv::DescriptorSetLayoutHandle> perMeshDescriptorSetLayouts;
 	std::vector<vkcv::DescriptorSetHandle> perMeshDescriptorSets;
 	for (const auto& vertexGroup : scene.vertexGroups) {
+	    perMeshDescriptorSetLayouts.push_back(materialDescriptorSetLayouts[vertexGroup.materialIndex]);
 		perMeshDescriptorSets.push_back(materialDescriptorSets[vertexGroup.materialIndex]);
 	}
 
-	const vkcv::PipelineConfig forwardPipelineConfig {
+	// prepass pipeline
+	vkcv::DescriptorSetLayoutHandle prepassDescriptorSetLayout = core.createDescriptorSetLayout({});
+	vkcv::DescriptorSetHandle prepassDescriptorSet = core.createDescriptorSet(prepassDescriptorSetLayout);
+
+	auto swapchainExtent = core.getSwapchainExtent(window.getSwapchain());
+	
+	vkcv::GraphicsPipelineConfig prepassPipelineConfig (
+		depthPrepassShader,
+		prepassPass,
+		vertexLayout,
+		{ prepassDescriptorSetLayout, perMeshDescriptorSetLayouts[0] }
+	);
+	
+	prepassPipelineConfig.setCulling(vkcv::CullMode::Back);
+	prepassPipelineConfig.setDepthTest(vkcv::DepthTest::LessEqual);
+	prepassPipelineConfig.setWritingAlphaToCoverage(true);
+
+	vkcv::GraphicsPipelineHandle prepassPipeline = core.createGraphicsPipeline(prepassPipelineConfig);
+
+	// forward pipeline
+	vkcv::GraphicsPipelineConfig forwardPipelineConfig (
 		forwardProgram,
-		windowWidth,
-		windowHeight,
 		forwardPass,
 		vertexLayout,
-		{	core.getDescriptorSet(forwardShadingDescriptorSet).layout, 
-			core.getDescriptorSet(perMeshDescriptorSets[0]).layout },
-		true
-	};
+		{ forwardShadingDescriptorSetLayout, perMeshDescriptorSetLayouts[0] }
+	);
+	
+	forwardPipelineConfig.setCulling(vkcv::CullMode::Back);
+	forwardPipelineConfig.setDepthTest(vkcv::DepthTest::Equal);
+	forwardPipelineConfig.setWritingDepth(false);
 	
-	vkcv::PipelineHandle forwardPipeline = core.createGraphicsPipeline(forwardPipelineConfig);
+	vkcv::GraphicsPipelineHandle forwardPipeline = core.createGraphicsPipeline(forwardPipelineConfig);
 	
 	if (!forwardPipeline) {
 		std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl;
 		return EXIT_FAILURE;
 	}
 
-	vkcv::ImageHandle depthBuffer = core.createImage(depthBufferFormat, windowWidth, windowHeight).getHandle();
-	vkcv::ImageHandle colorBuffer = core.createImage(colorBufferFormat, windowWidth, windowHeight, 1, false, true, true).getHandle();
-
-	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+	// sky
+	struct SkySettings {
+		glm::vec3   color;
+		float       strength;
+	};
+	SkySettings skySettings;
+	skySettings.color       = glm::vec3(0.15, 0.65, 1);
+	skySettings.strength    = 5;
+	
+	vkcv::PassHandle skyPass = vkcv::passFormats(
+			core,
+			{ colorBufferFormat, depthBufferFormat },
+			false,
+			msaa
+	);
 
-	vkcv::ShaderProgram shadowShader;
-	compiler.compile(vkcv::ShaderStage::VERTEX, "resources/shaders/shadow.vert",
+	vkcv::ShaderProgram skyShader;
+	compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("assets/shaders/sky.vert"),
 		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
-		shadowShader.addShader(shaderStage, path);
+		skyShader.addShader(shaderStage, path);
 	});
-	compiler.compile(vkcv::ShaderStage::FRAGMENT, "resources/shaders/shadow.frag",
+	compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("assets/shaders/sky.frag"),
 		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
-		shadowShader.addShader(shaderStage, path);
+		skyShader.addShader(shaderStage, path);
 	});
 
-	const std::vector<vkcv::AttachmentDescription> shadowAttachments = {
-		vkcv::AttachmentDescription(vkcv::AttachmentOperation::STORE, vkcv::AttachmentOperation::CLEAR, shadowMapFormat)
-	};
-	const vkcv::PassConfig shadowPassConfig(shadowAttachments);
-	const vkcv::PassHandle shadowPass = core.createPass(shadowPassConfig);
-	const vkcv::PipelineConfig shadowPipeConfig{
-		shadowShader,
-		shadowMapResolution,
-		shadowMapResolution,
-		shadowPass,
-		vertexLayout,
-		{},
-		false
-	};
-	const vkcv::PipelineHandle shadowPipe = core.createGraphicsPipeline(shadowPipeConfig);
+	vkcv::GraphicsPipelineConfig skyPipeConfig (
+			skyShader,
+			skyPass,
+			{},
+			{}
+	);
+	
+	skyPipeConfig.setWritingDepth(false);
+
+	vkcv::GraphicsPipelineHandle skyPipe = core.createGraphicsPipeline(skyPipeConfig);
+	
+	vkcv::ImageConfig depthBufferConfig (
+			swapchainExtent.width,
+			swapchainExtent.height
+	);
+	
+	depthBufferConfig.setMultisampling(msaa);
+
+	// render targets
+	vkcv::ImageHandle depthBuffer = core.createImage(
+			depthBufferFormat,
+			depthBufferConfig
+	);
+	
+	const bool colorBufferRequiresStorage = !usingMsaa;
+	
+	vkcv::ImageConfig colorBufferConfig (
+			swapchainExtent.width,
+			swapchainExtent.height
+	);
+	
+	colorBufferConfig.setSupportingStorage(colorBufferRequiresStorage);
+	colorBufferConfig.setSupportingColorAttachment(true);
+	colorBufferConfig.setMultisampling(msaa);
+
+	vkcv::ImageHandle colorBuffer = core.createImage(
+			colorBufferFormat,
+			colorBufferConfig
+	);
+	
+	vkcv::ImageConfig resolveBufferConfig (
+			swapchainExtent.width,
+			swapchainExtent.height
+	);
+	
+	resolveBufferConfig.setSupportingStorage(true);
+	resolveBufferConfig.setSupportingColorAttachment(true);
+
+	vkcv::ImageHandle resolvedColorBuffer;
+	if (usingMsaa) {
+		resolvedColorBuffer = core.createImage(
+				colorBufferFormat,
+				resolveBufferConfig
+		);
+	} else {
+		resolvedColorBuffer = colorBuffer;
+	}
+	
+	vkcv::ImageConfig swapBufferConfig (
+			swapchainExtent.width,
+			swapchainExtent.height
+	);
+	
+	swapBufferConfig.setSupportingStorage(true);
+	
+	vkcv::ImageHandle swapBuffer = core.createImage(
+			colorBufferFormat,
+			swapBufferConfig
+	);
+	
+	vkcv::ImageHandle swapBuffer2 = core.createImage(
+			colorBufferFormat,
+			swapBufferConfig
+	);
 
-	std::vector<std::array<glm::mat4, 2>> mainPassMatrices;
-	std::vector<glm::mat4> mvpLight;
+	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
 
 	bool renderVoxelVis = false;
 	window.e_key.add([&renderVoxelVis](int key ,int scancode, int action, int mods) {
@@ -273,15 +468,59 @@ int main(int argc, const char** argv) {
 		}
 	});
 
+	bool renderUI = true;
+	window.e_key.add([&renderUI](int key, int scancode, int action, int mods) {
+		if (key == GLFW_KEY_I && action == GLFW_PRESS) {
+			renderUI = !renderUI;
+		}
+	});
+
 	// tonemapping compute shader
 	vkcv::ShaderProgram tonemappingProgram;
-	compiler.compile(vkcv::ShaderStage::COMPUTE, "resources/shaders/tonemapping.comp", 
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "assets/shaders/tonemapping.comp", 
 		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
 		tonemappingProgram.addShader(shaderStage, path);
 	});
-	vkcv::DescriptorSetHandle tonemappingDescriptorSet = core.createDescriptorSet(tonemappingProgram.getReflectedDescriptors()[0]);
-	vkcv::PipelineHandle tonemappingPipeline = core.createComputePipeline(tonemappingProgram,
-		{ core.getDescriptorSet(tonemappingDescriptorSet).layout });
+
+	vkcv::DescriptorSetLayoutHandle tonemappingDescriptorSetLayout = core.createDescriptorSetLayout(
+	        tonemappingProgram.getReflectedDescriptors().at(0));
+	vkcv::DescriptorSetHandle tonemappingDescriptorSet = core.createDescriptorSet(tonemappingDescriptorSetLayout);
+	vkcv::ComputePipelineHandle tonemappingPipeline = core.createComputePipeline({
+		tonemappingProgram,
+		{ tonemappingDescriptorSetLayout }
+	});
+	
+	// tonemapping compute shader
+	vkcv::ShaderProgram postEffectsProgram;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "assets/shaders/postEffects.comp",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		postEffectsProgram.addShader(shaderStage, path);
+	});
+
+	vkcv::DescriptorSetLayoutHandle postEffectsDescriptorSetLayout = core.createDescriptorSetLayout(
+	        postEffectsProgram.getReflectedDescriptors().at(0));
+	vkcv::DescriptorSetHandle postEffectsDescriptorSet = core.createDescriptorSet(postEffectsDescriptorSetLayout);
+	vkcv::ComputePipelineHandle postEffectsPipeline = core.createComputePipeline({
+			postEffectsProgram,
+			{ postEffectsDescriptorSetLayout }
+	});
+
+	// resolve compute shader
+	vkcv::ShaderProgram resolveProgram;
+	compiler.compile(vkcv::ShaderStage::COMPUTE, "assets/shaders/msaa4XResolve.comp",
+		[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+		resolveProgram.addShader(shaderStage, path);
+	});
+
+	vkcv::DescriptorSetLayoutHandle resolveDescriptorSetLayout = core.createDescriptorSetLayout(
+		resolveProgram.getReflectedDescriptors().at(0));
+	vkcv::DescriptorSetHandle resolveDescriptorSet = core.createDescriptorSet(resolveDescriptorSetLayout);
+	vkcv::ComputePipelineHandle resolvePipeline = core.createComputePipeline({
+		resolveProgram,
+		{ resolveDescriptorSetLayout }
+	});
+
+	vkcv::SamplerHandle resolveSampler = vkcv::samplerNearest(core, true);
 
 	// model matrices per mesh
 	std::vector<glm::mat4> modelMatrices;
@@ -293,26 +532,34 @@ int main(int argc, const char** argv) {
 		}
 	}
 
-	// prepare drawcalls
-	std::vector<vkcv::Mesh> meshes;
-	for (int i = 0; i < scene.vertexGroups.size(); i++) {
-		vkcv::Mesh mesh(
-			vertexBufferBindings[i], 
-			indexBuffers[i].getVulkanHandle(), 
-			scene.vertexGroups[i].numIndices);
+	// prepare meshes
+	std::vector<vkcv::VertexData> meshes;
+	for (size_t i = 0; i < scene.vertexGroups.size(); i++) {
+		vkcv::VertexData mesh (vertexBufferBindings[i]);
+		mesh.setIndexBuffer(indexBuffers[i].getHandle());
+		mesh.setCount(scene.vertexGroups[i].numIndices);
 		meshes.push_back(mesh);
 	}
 
-	std::vector<vkcv::DrawcallInfo> drawcalls;
-	std::vector<vkcv::DrawcallInfo> shadowDrawcalls;
-	for (int i = 0; i < meshes.size(); i++) {
-
-		drawcalls.push_back(vkcv::DrawcallInfo(meshes[i], { 
-			vkcv::DescriptorSetUsage(0, core.getDescriptorSet(forwardShadingDescriptorSet).vulkanHandle),
-			vkcv::DescriptorSetUsage(1, core.getDescriptorSet(perMeshDescriptorSets[i]).vulkanHandle) }));
-		shadowDrawcalls.push_back(vkcv::DrawcallInfo(meshes[i], {}));
+	std::vector<vkcv::InstanceDrawcall> drawcalls;
+	std::vector<vkcv::InstanceDrawcall> prepassDrawcalls;
+	for (size_t i = 0; i < meshes.size(); i++) {
+		vkcv::InstanceDrawcall drawcall (meshes[i]);
+		drawcall.useDescriptorSet(0, forwardShadingDescriptorSet);
+		drawcall.useDescriptorSet(1, perMeshDescriptorSets[i]);
+		
+		vkcv::InstanceDrawcall prepassDrawcall (meshes[i]);
+		prepassDrawcall.useDescriptorSet(0, prepassDescriptorSet);
+		prepassDrawcall.useDescriptorSet(1, perMeshDescriptorSets[i]);
+		
+		drawcalls.push_back(drawcall);
+		prepassDrawcalls.push_back(prepassDrawcall);
 	}
 
+	vkcv::SamplerHandle voxelSampler = vkcv::samplerLinear(core, true);
+
+	ShadowMapping shadowMapping(&core, vertexLayout);
+
 	Voxelization::Dependencies voxelDependencies;
 	voxelDependencies.colorBufferFormat = colorBufferFormat;
 	voxelDependencies.depthBufferFormat = depthBufferFormat;
@@ -320,156 +567,472 @@ int main(int argc, const char** argv) {
 	Voxelization voxelization(
 		&core,
 		voxelDependencies,
-		lightBuffer.getHandle(),
-		shadowMap.getHandle(),
-		shadowSampler);
+		shadowMapping.getLightInfoBuffer(),
+		shadowMapping.getShadowMap(),
+		shadowMapping.getShadowSampler(),
+		voxelSampler,
+		msaa);
+
+	vkcv::effects::BloomAndFlaresEffect bloomFlares (core, true);
+	vkcv::Buffer<glm::vec3> cameraPosBuffer = buffer<glm::vec3>(core, vkcv::BufferType::UNIFORM, 1);
+
+	struct VolumetricSettings {
+		glm::vec3   scatteringCoefficient;
+		float       ambientLight;
+		glm::vec3   absorptionCoefficient;
+	};
+	vkcv::Buffer<VolumetricSettings> volumetricSettingsBuffer
+		= buffer<VolumetricSettings>(core, vkcv::BufferType::UNIFORM ,1);
 
-	vkcv::gui::GUI gui(core, window);
+	// write forward pass descriptor set
+	vkcv::DescriptorWrites forwardDescriptorWrites;
+	forwardDescriptorWrites.writeUniformBuffer(
+			0, shadowMapping.getLightInfoBuffer()
+	).writeUniformBuffer(
+			3, cameraPosBuffer.getHandle()
+	).writeUniformBuffer(
+			6, voxelization.getVoxelInfoBufferHandle()
+	).writeUniformBuffer(
+			7, volumetricSettingsBuffer.getHandle()
+	);
+	
+	forwardDescriptorWrites.writeSampledImage(
+			1, shadowMapping.getShadowMap()
+	).writeSampledImage(
+			4, voxelization.getVoxelImageHandle()
+	);
+	
+	forwardDescriptorWrites.writeSampler(
+			2, shadowMapping.getShadowSampler()
+	).writeSampler(
+			5, voxelSampler
+	);
+	
+	core.writeDescriptorSet(forwardShadingDescriptorSet, forwardDescriptorWrites);
 
-	glm::vec2 lightAngles(90.f, 0.f);
-	int voxelVisualisationMip = 0;
-	float voxelizationExtent = 20.f;
+	vkcv::upscaling::FSRUpscaling upscaling (core);
+	uint32_t fsrWidth = swapchainExtent.width, fsrHeight = swapchainExtent.height;
+	
+	vkcv::upscaling::FSRQualityMode fsrMode = vkcv::upscaling::FSRQualityMode::NONE;
+	int fsrModeIndex = static_cast<int>(fsrMode);
+	
+	const std::vector<const char*> fsrModeNames = {
+			"None",
+			"Ultra Quality",
+			"Quality",
+			"Balanced",
+			"Performance"
+	};
+	
+	bool fsrMipLoadBiasFlag = true;
+	bool fsrMipLoadBiasFlagBackup = fsrMipLoadBiasFlag;
+	
+	vkcv::upscaling::BilinearUpscaling upscaling1 (core);
+	vkcv::upscaling::NISUpscaling upscaling2 (core);
+	
+	const std::vector<const char*> modeNames = {
+			"Bilinear Upscaling",
+			"FSR Upscaling",
+			"NIS Upscaling"
+	};
+	
+	int upscalingMode = 0;
+	
+	vkcv::gui::GUI gui(core, windowHandle);
 
-	auto start = std::chrono::system_clock::now();
-	const auto appStartTime = start;
-	while (window.isWindowOpen()) {
-		vkcv::Window::pollEvents();
+	glm::vec2   lightAnglesDegree               = glm::vec2(90.f, 0.f);
+	glm::vec3   lightColor                      = glm::vec3(1);
+	float       lightStrength                   = 25.f;
+	float       maxShadowDistance               = 30.f;
 
-		uint32_t swapchainWidth, swapchainHeight;
-		if (!core.beginFrame(swapchainWidth, swapchainHeight)) {
-			continue;
-		}
+	int     voxelVisualisationMip   = 0;
+	float   voxelizationExtent      = 35.f;
 
-		if ((swapchainWidth != windowWidth) || ((swapchainHeight != windowHeight))) {
-			depthBuffer = core.createImage(depthBufferFormat, swapchainWidth, swapchainHeight).getHandle();
-			colorBuffer = core.createImage(colorBufferFormat, swapchainWidth, swapchainHeight, 1, false, true, true).getHandle();
+	bool msaaCustomResolve = true;
 
-			windowWidth = swapchainWidth;
-			windowHeight = swapchainHeight;
+	glm::vec3   scatteringColor     = glm::vec3(1);
+	float       scatteringDensity   = 0.005;
+	glm::vec3   absorptionColor     = glm::vec3(1);
+	float       absorptionDensity   = 0.005;
+	float       volumetricAmbient   = 0.2;
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
+		uint32_t width, height;
+		vkcv::upscaling::getFSRResolution(
+				fsrMode,
+				swapchainWidth, swapchainHeight,
+				width, height
+		);
+
+		if ((width != fsrWidth) || ((height != fsrHeight)) ||
+			(fsrMipLoadBiasFlagBackup != fsrMipLoadBiasFlag)) {
+			fsrWidth = width;
+			fsrHeight = height;
+			fsrMipLoadBiasFlagBackup = fsrMipLoadBiasFlag;
+			
+			colorSampler = core.createSampler(
+					vkcv::SamplerFilterType::LINEAR,
+					vkcv::SamplerFilterType::LINEAR,
+					vkcv::SamplerMipmapMode::LINEAR,
+					vkcv::SamplerAddressMode::REPEAT,
+					fsrMipLoadBiasFlag? vkcv::upscaling::getFSRLodBias(fsrMode) : 0.0f
+			);
+			
+			for (size_t i = 0; i < scene.materials.size(); i++) {
+				vkcv::DescriptorWrites setWrites;
+				setWrites.writeSampler(1, colorSampler);
+				core.writeDescriptorSet(materialDescriptorSets[i], setWrites);
+			}
+			
+			depthBufferConfig.setWidth(fsrWidth);
+			depthBufferConfig.setHeight(fsrHeight);
+			
+			depthBuffer = core.createImage(
+					depthBufferFormat,
+					depthBufferConfig
+			);
+			
+			colorBufferConfig.setWidth(fsrWidth);
+			colorBufferConfig.setHeight(fsrHeight);
+			
+			colorBuffer = core.createImage(
+					colorBufferFormat,
+					colorBufferConfig
+			);
+
+			if (usingMsaa) {
+				resolveBufferConfig.setWidth(fsrWidth);
+				resolveBufferConfig.setHeight(fsrHeight);
+				
+				resolvedColorBuffer = core.createImage(
+						colorBufferFormat,
+						resolveBufferConfig
+				);
+			} else {
+				resolvedColorBuffer = colorBuffer;
+			}
+			
+			swapBufferConfig.setWidth(fsrWidth);
+			swapBufferConfig.setHeight(fsrHeight);
+			
+			swapBuffer = core.createImage(
+					colorBufferFormat,
+					swapBufferConfig
+			);
+			
+			swapBufferConfig.setWidth(swapchainWidth);
+			swapBufferConfig.setHeight(swapchainHeight);
+			
+			swapBuffer2 = core.createImage(
+					colorBufferFormat,
+					swapBufferConfig
+			);
 		}
 
-		auto end = std::chrono::system_clock::now();
-		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
-
 		// update descriptor sets which use swapchain image
 		vkcv::DescriptorWrites tonemappingDescriptorWrites;
-		tonemappingDescriptorWrites.storageImageWrites = {
-			vkcv::StorageImageDescriptorWrite(0, colorBuffer),
-			vkcv::StorageImageDescriptorWrite(1, swapchainInput) };
-		core.writeDescriptorSet(tonemappingDescriptorSet, tonemappingDescriptorWrites);
-
-		start = end;
-		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
-
-		glm::vec2 lightAngleRadian = glm::radians(lightAngles);
-		lightInfo.direction = glm::normalize(glm::vec3(
-			std::cos(lightAngleRadian.x) * std::cos(lightAngleRadian.y),
-			std::sin(lightAngleRadian.x),
-			std::cos(lightAngleRadian.x) * std::sin(lightAngleRadian.y)));
+		tonemappingDescriptorWrites.writeSampledImage(0, resolvedColorBuffer);
+		tonemappingDescriptorWrites.writeSampler(1, colorSampler);
+		tonemappingDescriptorWrites.writeStorageImage(2, swapBuffer);
 
-		const float shadowProjectionSize = 20.f;
-		glm::mat4 projectionLight = glm::ortho(
-			-shadowProjectionSize,
-			shadowProjectionSize,
-			-shadowProjectionSize,
-			shadowProjectionSize,
-			-shadowProjectionSize,
-			shadowProjectionSize);
+		core.writeDescriptorSet(tonemappingDescriptorSet, tonemappingDescriptorWrites);
+		
+		// update descriptor sets which use swapchain image
+		vkcv::DescriptorWrites postEffectsDescriptorWrites;
+		postEffectsDescriptorWrites.writeSampledImage(0, swapBuffer2);
+		postEffectsDescriptorWrites.writeSampler(1, colorSampler);
+		postEffectsDescriptorWrites.writeStorageImage(2, swapchainInput);
+		
+		core.writeDescriptorSet(postEffectsDescriptorSet, postEffectsDescriptorWrites);
+
+		// update resolve descriptor, color images could be changed
+		vkcv::DescriptorWrites resolveDescriptorWrites;
+		resolveDescriptorWrites.writeSampledImage(0, colorBuffer);
+		resolveDescriptorWrites.writeSampler(1, resolveSampler);
+		resolveDescriptorWrites.writeStorageImage(2, resolvedColorBuffer);
+		core.writeDescriptorSet(resolveDescriptorSet, resolveDescriptorWrites);
+
+		cameraManager.update(dt);
+		cameraPosBuffer.fill({ cameraManager.getActiveCamera().getPosition() });
 
-		glm::mat4 vulkanCorrectionMatrix(1.f);
-		vulkanCorrectionMatrix[2][2] = 0.5;
-		vulkanCorrectionMatrix[3][2] = 0.5;
-		projectionLight = vulkanCorrectionMatrix * projectionLight;
+		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
 
-		const glm::mat4 viewLight = glm::lookAt(glm::vec3(0), -lightInfo.direction, glm::vec3(0, -1, 0));
+		voxelization.updateVoxelOffset(cameraManager.getActiveCamera());
 
-		lightInfo.lightMatrix = projectionLight * viewLight;
-		lightBuffer.fill({ lightInfo });
+		// shadow map
+		glm::vec2 lightAngleRadian = glm::radians(lightAnglesDegree);
+		shadowMapping.recordShadowMapRendering(
+			cmdStream,
+			lightAngleRadian,
+			lightColor,
+			lightStrength,
+			maxShadowDistance,
+			meshes,
+			modelMatrices,
+			cameraManager.getActiveCamera(),
+			voxelization.getVoxelOffset(),
+			voxelization.getVoxelExtent(),
+			windowHandle,
+			spdDownsampler
+		);
+
+		// voxelization
+		voxelization.setVoxelExtent(voxelizationExtent);
+		voxelization.voxelizeMeshes(
+			cmdStream,
+			meshes, 
+			modelMatrices,
+			perMeshDescriptorSets,
+			windowHandle,
+			spdDownsampler
+		);
 
+		// depth prepass
 		const glm::mat4 viewProjectionCamera = cameraManager.getActiveCamera().getMVP();
-
-		mainPassMatrices.clear();
-		mvpLight.clear();
+		
+		vkcv::PushConstants prepassPushConstants = vkcv::pushConstants<glm::mat4>();
+		
+		std::vector<glm::mat4> prepassMatrices;
 		for (const auto& m : modelMatrices) {
-			mainPassMatrices.push_back({ viewProjectionCamera * m, m });
-			mvpLight.push_back(lightInfo.lightMatrix * m);
+			prepassPushConstants.appendDrawcall(viewProjectionCamera * m);
 		}
+		
+		const std::vector<vkcv::ImageHandle>    prepassRenderTargets = { depthBuffer };
 
-		vkcv::PushConstantData pushConstantData((void*)mainPassMatrices.data(), 2 * sizeof(glm::mat4));
-		const std::vector<vkcv::ImageHandle> renderTargets = { colorBuffer, depthBuffer };
-
-		const vkcv::PushConstantData shadowPushConstantData((void*)mvpLight.data(), sizeof(glm::mat4));
-
-		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
-
-		// shadow map
+		core.recordBeginDebugLabel(cmdStream, "Depth prepass", { 1, 1, 1, 1 });
 		core.recordDrawcallsToCmdStream(
 			cmdStream,
-			shadowPass,
-			shadowPipe,
-			shadowPushConstantData,
-			shadowDrawcalls,
-			{ shadowMap.getHandle() });
-		core.prepareImageForSampling(cmdStream, shadowMap.getHandle());
+			prepassPipeline,
+			prepassPushConstants,
+			prepassDrawcalls,
+			prepassRenderTargets,
+			windowHandle
+		);
+
+		core.recordImageMemoryBarrier(cmdStream, depthBuffer);
+		core.recordEndDebugLabel(cmdStream);
+		
+		vkcv::PushConstants pushConstants (2 * sizeof(glm::mat4));
+		
+		// main pass
+		for (const auto& m : modelMatrices) {
+			pushConstants.appendDrawcall(std::array<glm::mat4, 2>{ viewProjectionCamera * m, m });
+		}
 
-		voxelization.setVoxelExtent(voxelizationExtent);
-		voxelization.voxelizeMeshes(
-			cmdStream, 
-			cameraManager.getActiveCamera().getPosition(), 
-			meshes, 
-			modelMatrices,
-			perMeshDescriptorSets);
+		VolumetricSettings volumeSettings;
+		volumeSettings.scatteringCoefficient    = scatteringColor * scatteringDensity;
+		volumeSettings.absorptionCoefficient    = absorptionColor * absorptionDensity;
+		volumeSettings.ambientLight             = volumetricAmbient;
+		volumetricSettingsBuffer.fill({ volumeSettings });
+		
+		const std::vector<vkcv::ImageHandle>    renderTargets = { colorBuffer, depthBuffer };
 
-		// main pass
+		core.recordBeginDebugLabel(cmdStream, "Forward rendering", { 1, 1, 1, 1 });
 		core.recordDrawcallsToCmdStream(
 			cmdStream,
-            forwardPass,
-            forwardPipeline,
-			pushConstantData,
+			forwardPipeline,
+			pushConstants,
 			drawcalls,
-			renderTargets);
+			renderTargets,
+			windowHandle
+		);
+		core.recordEndDebugLabel(cmdStream);
 
 		if (renderVoxelVis) {
-			voxelization.renderVoxelVisualisation(cmdStream, viewProjectionCamera, renderTargets, voxelVisualisationMip);
+			voxelization.renderVoxelVisualisation(cmdStream, viewProjectionCamera, renderTargets, voxelVisualisationMip, windowHandle);
 		}
-
-		const uint32_t tonemappingLocalGroupSize = 8;
-		const uint32_t tonemappingDispatchCount[3] = {
-			static_cast<uint32_t>(glm::ceil(windowWidth / static_cast<float>(tonemappingLocalGroupSize))),
-			static_cast<uint32_t>(glm::ceil(windowHeight / static_cast<float>(tonemappingLocalGroupSize))),
-			1
-		};
-
+		
+		vkcv::PushConstants skySettingsPushConstants = vkcv::pushConstants<SkySettings>();
+		skySettingsPushConstants.appendDrawcall(skySettings);
+		
+		vkcv::VertexData skyData;
+		skyData.setCount(3);
+
+		// sky
+		core.recordBeginDebugLabel(cmdStream, "Sky", { 1, 1, 1, 1 });
+		core.recordDrawcallsToCmdStream(
+			cmdStream,
+			skyPipe,
+			skySettingsPushConstants,
+			{ vkcv::InstanceDrawcall(skyData) },
+			renderTargets,
+			windowHandle
+		);
+		core.recordEndDebugLabel(cmdStream);
+		
+		const uint32_t fullscreenLocalGroupSize = 8;
+		auto fullscreenDispatchCount = vkcv::dispatchInvocations(
+				vkcv::DispatchSize(fsrWidth, fsrHeight),
+				vkcv::DispatchSize(fullscreenLocalGroupSize, fullscreenLocalGroupSize)
+		);
+
+		if (usingMsaa) {
+			core.recordBeginDebugLabel(cmdStream, "MSAA resolve", { 1, 1, 1, 1 });
+			if (msaaCustomResolve) {
+				core.prepareImageForSampling(cmdStream, colorBuffer);
+				core.prepareImageForStorage(cmdStream, resolvedColorBuffer);
+
+				assert(msaa == vkcv::Multisampling::MSAA4X);	// shaders is written for msaa 4x
+				core.recordComputeDispatchToCmdStream(
+						cmdStream,
+						resolvePipeline,
+						fullscreenDispatchCount,
+						{ vkcv::useDescriptorSet(0, resolveDescriptorSet) },
+						vkcv::PushConstants(0)
+				);
+
+				core.recordImageMemoryBarrier(cmdStream, resolvedColorBuffer);
+			}
+			else {
+				core.resolveMSAAImage(cmdStream, colorBuffer, resolvedColorBuffer);
+			}
+			core.recordEndDebugLabel(cmdStream);
+		}
+		
+		bloomFlares.updateCameraDirection(cameraManager.getActiveCamera());
+		bloomFlares.recordEffect(cmdStream, resolvedColorBuffer, resolvedColorBuffer);
+
+		core.prepareImageForStorage(cmdStream, swapBuffer);
+		core.prepareImageForSampling(cmdStream, resolvedColorBuffer);
+		
+		core.recordBeginDebugLabel(cmdStream, "Tonemapping", { 1, 1, 1, 1 });
+		core.recordComputeDispatchToCmdStream(
+				cmdStream,
+				tonemappingPipeline,
+				fullscreenDispatchCount,
+				{ vkcv::useDescriptorSet(0, tonemappingDescriptorSet) },
+				vkcv::PushConstants(0)
+		);
+		
+		core.prepareImageForStorage(cmdStream, swapBuffer2);
+		core.prepareImageForSampling(cmdStream, swapBuffer);
+		core.recordEndDebugLabel(cmdStream);
+		
+		switch (upscalingMode) {
+			case 0:
+				upscaling1.recordUpscaling(cmdStream, swapBuffer, swapBuffer2);
+				break;
+			case 1:
+				upscaling.recordUpscaling(cmdStream, swapBuffer, swapBuffer2);
+				break;
+			case 2:
+				upscaling2.recordUpscaling(cmdStream, swapBuffer, swapBuffer2);
+				break;
+			default:
+				break;
+		}
+		
 		core.prepareImageForStorage(cmdStream, swapchainInput);
-		core.prepareImageForStorage(cmdStream, colorBuffer);
-
+		core.prepareImageForSampling(cmdStream, swapBuffer2);
+		
+		vkcv::PushConstants timePushConstants = vkcv::pushConstants<float>();
+		timePushConstants.appendDrawcall(static_cast<float>(t * 100000.0));
+		
+		fullscreenDispatchCount = vkcv::dispatchInvocations(
+				vkcv::DispatchSize(swapchainWidth, swapchainHeight),
+				vkcv::DispatchSize(fullscreenLocalGroupSize, fullscreenLocalGroupSize)
+		);
+		
+		core.recordBeginDebugLabel(cmdStream, "Post Processing", { 1, 1, 1, 1 });
 		core.recordComputeDispatchToCmdStream(
-			cmdStream, 
-			tonemappingPipeline, 
-			tonemappingDispatchCount,
-			{ vkcv::DescriptorSetUsage(0, core.getDescriptorSet(tonemappingDescriptorSet).vulkanHandle) },
-			vkcv::PushConstantData(nullptr, 0));
+				cmdStream,
+				postEffectsPipeline,
+				fullscreenDispatchCount,
+				{ vkcv::useDescriptorSet(0, postEffectsDescriptorSet) },
+				timePushConstants
+		);
+		core.recordEndDebugLabel(cmdStream);
 
 		// present and end
 		core.prepareSwapchainImageForPresent(cmdStream);
 		core.submitCommandStream(cmdStream);
 
+		// draw UI
 		gui.beginGUI();
 
-		ImGui::Begin("Settings");
-		ImGui::DragFloat2("Light angles",   &lightAngles.x);
-		ImGui::ColorEdit3("Sun color",      &lightInfo.sunColor.x);
-		ImGui::DragFloat("Sun strength",    &lightInfo.sunStrength);
-		ImGui::Checkbox("Draw voxel visualisation", &renderVoxelVis);
-		ImGui::SliderInt("Visualisation mip",       &voxelVisualisationMip, 0, 7);
-		ImGui::DragFloat("Voxelization extent",     &voxelizationExtent, 1.f, 0.f);
-		voxelVisualisationMip = std::max(voxelVisualisationMip, 0);
-		ImGui::End();
+		if (renderUI) {
+			ImGui::Begin("Settings");
+
+			ImGui::Checkbox("MSAA custom resolve", &msaaCustomResolve);
+
+			ImGui::DragFloat2("Light angles", &lightAnglesDegree.x);
+			ImGui::ColorEdit3("Sun color", &lightColor.x);
+			ImGui::DragFloat("Sun strength", &lightStrength);
+			ImGui::DragFloat("Max shadow distance", &maxShadowDistance);
+			maxShadowDistance = std::max(maxShadowDistance, 1.f);
+
+			ImGui::ColorEdit3("Sky color", &skySettings.color.x);
+			ImGui::DragFloat("Sky strength", &skySettings.strength, 0.1);
+
+			ImGui::Checkbox("Draw voxel visualisation", &renderVoxelVis);
+			ImGui::SliderInt("Visualisation mip", &voxelVisualisationMip, 0, 7);
+			ImGui::DragFloat("Voxelization extent", &voxelizationExtent, 1.f, 0.f);
+			voxelizationExtent = std::max(voxelizationExtent, 1.f);
+			voxelVisualisationMip = std::max(voxelVisualisationMip, 0);
+			
+			ImGui::ColorEdit3("Scattering color", &scatteringColor.x);
+			ImGui::DragFloat("Scattering density", &scatteringDensity, 0.0001);
+			ImGui::ColorEdit3("Absorption color", &absorptionColor.x);
+			ImGui::DragFloat("Absorption density", &absorptionDensity, 0.0001);
+			ImGui::DragFloat("Volumetric ambient", &volumetricAmbient, 0.002);
+			
+			float sharpness = upscaling.getSharpness();
+			
+			ImGui::Combo("FSR Quality Mode", &fsrModeIndex, fsrModeNames.data(), fsrModeNames.size());
+			ImGui::DragFloat("FSR Sharpness", &sharpness, 0.001, 0.0f, 1.0f);
+			ImGui::Checkbox("FSR Mip Lod Bias", &fsrMipLoadBiasFlag);
+			ImGui::Combo("Upscaling Mode", &upscalingMode, modeNames.data(), modeNames.size());
+			
+			if ((fsrModeIndex >= 0) && (fsrModeIndex <= 4)) {
+				fsrMode = static_cast<vkcv::upscaling::FSRQualityMode>(fsrModeIndex);
+			}
+			
+			upscaling.setSharpness(sharpness);
+			upscaling2.setSharpness(sharpness);
+
+			if (ImGui::Button("Reload forward pass")) {
+				vkcv::ShaderProgram newForwardProgram;
+				
+				compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("assets/shaders/shader.vert"),
+					[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+					newForwardProgram.addShader(shaderStage, path);
+				});
+				compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("assets/shaders/shader.frag"),
+					[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+					newForwardProgram.addShader(shaderStage, path);
+				});
+				
+				forwardPipelineConfig.setShaderProgram(newForwardProgram);
+				vkcv::GraphicsPipelineHandle newPipeline = core.createGraphicsPipeline(forwardPipelineConfig);
+
+				if (newPipeline) {
+					forwardPipeline = newPipeline;
+				}
+			}
+			
+			if (ImGui::Button("Reload tonemapping")) {
+				vkcv::ShaderProgram newProgram;
+				compiler.compile(vkcv::ShaderStage::COMPUTE, std::filesystem::path("assets/shaders/tonemapping.comp"),
+					[&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+					newProgram.addShader(shaderStage, path);
+				});
+
+				vkcv::ComputePipelineHandle newPipeline = core.createComputePipeline({
+					newProgram,
+					{ tonemappingDescriptorSetLayout }
+				});
+
+				if (newPipeline) {
+					tonemappingPipeline = newPipeline;
+				}
+			}
+			
+			ImGui::End();
+		}
 
 		gui.endGUI();
-
-		core.endFrame();
-	}
+	});
 	
 	return 0;
 }
diff --git a/resources/extensions.txt b/resources/extensions.txt
new file mode 100644
index 0000000000000000000000000000000000000000..db1c0ef745c97e11b18118da1874a6854f2b09fd
--- /dev/null
+++ b/resources/extensions.txt
@@ -0,0 +1,6 @@
+GitLab.gitlab-workflow
+llvm-vs-code-extensions.vscode-clangd
+ms-vscode.cmake-tools
+slevesque.vscode-3dviewer
+twxs.cmake
+webfreak.debug
diff --git a/screenshots/bindless_textures.png b/screenshots/bindless_textures.png
new file mode 100644
index 0000000000000000000000000000000000000000..e25ed64cdb1386d966e4fd693efb7c8c1e47ae86
--- /dev/null
+++ b/screenshots/bindless_textures.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ffc3bb769bd6ed17520ed0ee5e055246b2f8769451ef79bc9bd2d08aa064f50a
+size 181594
diff --git a/screenshots/fire_works.png b/screenshots/fire_works.png
new file mode 100644
index 0000000000000000000000000000000000000000..79dcf38616c0e5817c73efbd7fe931167764e546
--- /dev/null
+++ b/screenshots/fire_works.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:31aeb780a731731c8c680e10c8d3336921881b0ae6f3cff575c71af834b8bd5b
+size 282503
diff --git a/screenshots/first_mesh.png b/screenshots/first_mesh.png
new file mode 100644
index 0000000000000000000000000000000000000000..980baafbbacfa1a7d29c6eada5d4396ab0cf2425
--- /dev/null
+++ b/screenshots/first_mesh.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f585b69b4f5c517d5f44c5eefa5cf2324d2cd58ee0e215135b824926377bfd61
+size 190960
diff --git a/screenshots/first_scene.png b/screenshots/first_scene.png
new file mode 100644
index 0000000000000000000000000000000000000000..3ac8147bbef9a11deb64153ebd1dd0e3503b1b9c
--- /dev/null
+++ b/screenshots/first_scene.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8bce66f549f154b5d5ad20d6fcb633e62d0e9a1d62889d148bf6e73d46287edc
+size 1185543
diff --git a/screenshots/first_triangle.png b/screenshots/first_triangle.png
new file mode 100644
index 0000000000000000000000000000000000000000..7eda745af4f4e8103910d1d618b6ed3c26cbb850
--- /dev/null
+++ b/screenshots/first_triangle.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:82577075fbbcc774b08cccdcace4cc1ab94e10e135f176091faadcb089047c3f
+size 20840
diff --git a/screenshots/head_demo.png b/screenshots/head_demo.png
new file mode 100644
index 0000000000000000000000000000000000000000..4fb51ce5d96354f647bc69b1c7b53fd43085f07b
--- /dev/null
+++ b/screenshots/head_demo.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:fd4beee1709a1f4b85537b7d951b06b1cdf27ea1c00c73cea5a8b8b261bd11b6
+size 438783
diff --git a/screenshots/indirect_dispatch.png b/screenshots/indirect_dispatch.png
new file mode 100644
index 0000000000000000000000000000000000000000..196a28a8fe34350ca0bccae5af02f9ba3755598a
--- /dev/null
+++ b/screenshots/indirect_dispatch.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b6b64f1ca2fe9ab5324be8917c1a36fdf2efb83d46a83dee5c1200ca51479096
+size 795319
diff --git a/screenshots/indirect_draw.png b/screenshots/indirect_draw.png
new file mode 100644
index 0000000000000000000000000000000000000000..13f0aaf3c020ddf117b3d6c29b65c31899e0743c
--- /dev/null
+++ b/screenshots/indirect_draw.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f37958116905dd90f57326f78e086a4b2739424e850c49439421d0986dd4454f
+size 2353039
diff --git a/screenshots/mesh_shader.png b/screenshots/mesh_shader.png
new file mode 100644
index 0000000000000000000000000000000000000000..bf9fe85f01518c4b9d31d46fe6f1424e214a9ce5
--- /dev/null
+++ b/screenshots/mesh_shader.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:37049cc8301eea437be01d6f485553b96e8961b2bd63d1ade7d0b2be3c17d5d4
+size 39343
diff --git a/screenshots/mpm.png b/screenshots/mpm.png
new file mode 100644
index 0000000000000000000000000000000000000000..bf8e172251cc31bcad91108848f32a7bcca86e9e
--- /dev/null
+++ b/screenshots/mpm.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d3443fe0eee59cc56f0b675704b1f4c7dc94eb1ede1ce6af7866bcd85c52b816
+size 36964
diff --git a/screenshots/particle_simulation_gravity.png b/screenshots/particle_simulation_gravity.png
new file mode 100644
index 0000000000000000000000000000000000000000..88e39c1191d8d4a118d00e1912e46ee7114e6416
--- /dev/null
+++ b/screenshots/particle_simulation_gravity.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:47e8b6d364a696fb011a9fabc888b29031a2c54f0a0e4418b09ba6c2b40557e2
+size 721671
diff --git a/screenshots/particle_simulation_space.png b/screenshots/particle_simulation_space.png
new file mode 100644
index 0000000000000000000000000000000000000000..81ba7839b99810cfdbb55c4ebfb9ac24c5d25064
--- /dev/null
+++ b/screenshots/particle_simulation_space.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6bd48bab2359e3f819d702012a4adde9699a3a093d96f4980edd9d7eed84379d
+size 541571
diff --git a/screenshots/particle_simulation_water.png b/screenshots/particle_simulation_water.png
new file mode 100644
index 0000000000000000000000000000000000000000..46bc5b6a51735f237b09ded014a271c6ad02c811
--- /dev/null
+++ b/screenshots/particle_simulation_water.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7c8d3262556252815f922e37eae809c727eed3870c05a2aaedf0b651dc61b7f9
+size 587136
diff --git a/screenshots/path_tracer.png b/screenshots/path_tracer.png
new file mode 100644
index 0000000000000000000000000000000000000000..154e7b1a7d1c84d0f5e72187ed5f5312edd75969
--- /dev/null
+++ b/screenshots/path_tracer.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e331aabbd5947b7b62d6762e3087ee2c2be18e2b53005f2eca77be9d80c183e9
+size 1547130
diff --git a/screenshots/ray_tracer.png b/screenshots/ray_tracer.png
new file mode 100644
index 0000000000000000000000000000000000000000..71041d9474fba0f7534e367af679dc1e76e2ecde
--- /dev/null
+++ b/screenshots/ray_tracer.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c39817e4eec1dd7661d76a539670699df9caaf64c077bff6b1430b57ac99bc54
+size 62317
diff --git a/screenshots/rtx_ambient_occlusion.png b/screenshots/rtx_ambient_occlusion.png
new file mode 100644
index 0000000000000000000000000000000000000000..87c2471d1a55016949184e7cbfbdcf0d0702908f
--- /dev/null
+++ b/screenshots/rtx_ambient_occlusion.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0df2a044b9ccc69ad221a0421ae3c79ff94b72eda7773625a26a000855592545
+size 50286
diff --git a/screenshots/sph.png b/screenshots/sph.png
new file mode 100644
index 0000000000000000000000000000000000000000..3a4fd8bf426474bc9db222af44bf16e992ab23e7
--- /dev/null
+++ b/screenshots/sph.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d238d48ffcfe1e28b73811d4d2bfea8ca3d2c23cf12e1b3ac9d095a8f7930384
+size 312081
diff --git a/screenshots/voxelization.png b/screenshots/voxelization.png
new file mode 100644
index 0000000000000000000000000000000000000000..880601199622c1bba68fa822af16d86c1dd12074
--- /dev/null
+++ b/screenshots/voxelization.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:946ae03143eaeb5d202dafbaa30e560427a3e887e8c3fddb6852e5c8c5c031ac
+size 3060521
diff --git a/scripts/build.sh b/scripts/build.sh
new file mode 100755
index 0000000000000000000000000000000000000000..7e41ca8e8ad62ce6667ab4d9c549f2ea613d28aa
--- /dev/null
+++ b/scripts/build.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+# Check if release or debug build
+CMAKE_BUILD_DIR="build"
+CMAKE_FLAGS=""
+if [ "$1" = "--debug" ]; then
+	CMAKE_BUILD_DIR="cmake-build-debug"
+	CMAKE_FLAGS="-DCMAKE_BUILD_TYPE=Debug"
+elif [ "$1" = "--release" ]; then
+	CMAKE_BUILD_DIR="cmake-build-release"
+	CMAKE_FLAGS="-DCMAKE_BUILD_TYPE=Release"
+fi
+
+# Navigate to the main directory of the cloned repository
+cd "$(dirname "$0")" || exit
+cd ..
+
+# Setup git lfs and the submodules
+git lfs install
+git submodule init
+git submodule update
+
+# Setup build directory
+mkdir $CMAKE_BUILD_DIR
+cd $CMAKE_BUILD_DIR || exit
+BUILD_THREADS=$(($(nproc --all) - 1))
+
+if [ $BUILD_THREADS -lt 1 ]; then
+	BUILD_THREADS=1
+fi
+
+# Build process
+cmake $CMAKE_FLAGS ..
+make -j $BUILD_THREADS "$@"
\ No newline at end of file
diff --git a/scripts/generate.sh b/scripts/generate.sh
new file mode 100755
index 0000000000000000000000000000000000000000..23e5b0ee54589ebfacbb08942ec9a5018d8704fa
--- /dev/null
+++ b/scripts/generate.sh
@@ -0,0 +1,85 @@
+#!/bin/sh
+if [ $# -gt 0 ]; then
+  CMAKE_PROJECT_DIR="$(realpath "$1")"
+  mkdir -p $CMAKE_PROJECT_DIR
+else
+  CMAKE_PROJECT_DIR="$(pwd)"
+fi
+
+CMAKE_PROJECT_NAME="$(basename "$CMAKE_PROJECT_DIR")"
+
+# Navigate to the main directory of the cloned repository
+cd "$(dirname "$0")" || exit
+cd ..
+
+CMAKE_FRAMEWORK_DIR="$(realpath -s --relative-to="$CMAKE_PROJECT_DIR" "$(pwd)")"
+
+if [ "$CMAKE_FRAMEWORK_DIR" == "../.." ]; then
+  IS_INTERNAL_PROJECT=true
+else
+  IS_INTERNAL_PROJECT=false
+fi
+
+# Navigate back to the project directory
+cd "$CMAKE_PROJECT_DIR" || exit
+
+test -f "CMakeLists.txt" && echo "WARNING: CMakeLists.txt exists already! Project generation stops!" && exit
+test -f "src/main.cpp" && echo "WARNING: src/main.cpp exists already! Project generation stops!" && exit
+
+generate_cmake_lists() {
+  echo "cmake_minimum_required(VERSION 3.16)"
+  echo "project($CMAKE_PROJECT_NAME)"
+  echo
+  echo "set(CMAKE_CXX_STANDARD 20)"
+  echo "set(CMAKE_CXX_STANDARD_REQUIRED ON)"
+  echo
+
+  if test "$IS_INTERNAL_PROJECT" = false; then
+    echo "set(BUILD_MODULES ON CACHE INTERNAL \"\")"
+    echo "set(BUILD_PROJECTS OFF CACHE INTERNAL \"\")"
+    echo "set(BUILD_DOXYGEN_DOCS OFF CACHE INTERNAL \"\")"
+    echo "set(BUILD_SHARED OFF CACHE INTERNAL \"\")"
+    echo "add_subdirectory($CMAKE_FRAMEWORK_DIR)"
+    echo
+  fi
+  
+  echo "add_executable($CMAKE_PROJECT_NAME src/main.cpp)"
+  echo
+
+  if test "$IS_INTERNAL_PROJECT" = true; then
+    echo "target_include_directories($CMAKE_PROJECT_NAME SYSTEM BEFORE PRIVATE \${vkcv_include} \${vkcv_includes})"
+    echo "target_link_libraries($CMAKE_PROJECT_NAME vkcv \${vkcv_libraries})"
+  else
+    echo "target_include_directories($CMAKE_PROJECT_NAME SYSTEM BEFORE PRIVATE \${vkcv_includes})"
+    echo "target_link_libraries($CMAKE_PROJECT_NAME \${vkcv_libraries})"
+  fi
+}
+
+generate_main_cpp() {
+  echo "#include <vkcv/Core.hpp>"
+  echo
+  echo "int main(int argc, const char** argv) {"
+  echo "  vkcv::Core core = vkcv::Core::create("
+  echo "    \"$CMAKE_PROJECT_NAME\","
+  echo "    VK_MAKE_VERSION(0, 0, 1),"
+  echo "    { vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute },"
+  echo "    { VK_KHR_SWAPCHAIN_EXTENSION_NAME }"
+  echo "  );"
+  echo "  "
+  echo "  vkcv::WindowHandle windowHandle = core.createWindow("
+  echo "    \"$CMAKE_PROJECT_NAME\","
+  echo "    800,"
+  echo "    600,"
+  echo "    true"
+  echo "  );"
+  echo "  "
+  echo "  while (vkcv::Window::hasOpenWindow()) {"
+  echo "    vkcv::Window::pollEvents();"
+  echo "  }"
+  echo "}"
+}
+
+generate_cmake_lists > "CMakeLists.txt"
+
+mkdir -p "src"
+generate_main_cpp > "src/main.cpp"
diff --git a/scripts/release.sh b/scripts/release.sh
new file mode 100755
index 0000000000000000000000000000000000000000..15468cc23703a68e235a9b22644662d6f79b4673
--- /dev/null
+++ b/scripts/release.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+VKCV_BRANCH="$(git status | awk 'NR == 1 { print $3 }')"
+
+if [ "$VKCV_BRANCH" != "develop" ]; then
+	echo "WARNING: Please switch to origin/develop branch for preparing the next release!"
+	exit
+fi
+
+VKCV_VERSION="$(grep -oEi "#define.+VKCV_FRAMEWORK_VERSION.+\(VK_MAKE_VERSION\(([0-9]+,.*[0-9]+,.*[0-9]+)\)\)" include/vkcv/Core.hpp | awk 'match($0, /.*\(([0-9]+),.*([0-9]+),.*([0-9]+)\)/, a) {print a[1]"."a[2]"."a[3]}')"
+
+if [ $(git tag | grep "$VKCV_VERSION" | wc -l) -gt 0 ]; then
+	echo "WARNING: Please adjust the version of the framework before uploading a release! (Duplicate version: $VKCV_VERSION)"
+	exit
+fi
+
+vim CHANGELOG.md
+git add CHANGELOG.md
+
+git commit -S -s -m "==========  VkCV-$VKCV_VERSION  =========="
+git push
+
+git checkout master
+git merge develop
+
+git tag -a "$VKCV_VERSION" -m "VkCV-$VKCV_VERSION release"
+git push
+git push --tags
diff --git a/scripts/run.sh b/scripts/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..2f5a32131003b8c684adb4a4d0d9219bdc8fc3d8
--- /dev/null
+++ b/scripts/run.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+# Navigate to the scripts directory
+cd "$(dirname "$0")" || exit
+
+# Check if there is a project name as argument
+if [ $# -lt 1 ]; then
+	echo "You need to specify a project name to run!">&2
+	exit
+fi
+
+RUN_WITH_HUD="no"
+BUILD_FLAGS=""
+while [ $# -gt 1 ]; do
+  case "$1" in
+    "--"*)
+      if [ "$1" = "--hud" ]; then
+        RUN_WITH_HUD="yes"
+      else
+        BUILD_FLAGS="$BUILD_FLAGS$1 "
+      fi
+      shift 1;;
+    *) break;;
+  esac
+done
+
+PROJECT="$1"
+PROJECT_DIR="../projects/$PROJECT"
+shift 1
+
+# Check if the project name is valid
+if [ ! -d "$PROJECT_DIR" ]; then
+	echo "There is no project with the name '$PROJECT'!">&2
+	exit
+fi
+
+./build.sh $FLAGS "$PROJECT"
+cd "$PROJECT_DIR" || exit
+
+if [ ! -f "$PROJECT" ]; then
+	echo "Building the project '$PROJECT' failed!">&2
+	exit
+fi
+
+if [ "$RUN_WITH_HUD" = "yes" ]; then
+  MANGOHUD=1 ./"$PROJECT" "$@"
+else
+  ./"$PROJECT" "$@"
+fi
\ No newline at end of file
diff --git a/scripts/setup-codium.sh b/scripts/setup-codium.sh
new file mode 100755
index 0000000000000000000000000000000000000000..66a217d81b545cee4b6cdb2995e54350de6cdcb3
--- /dev/null
+++ b/scripts/setup-codium.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+CODIUM=codium
+
+if [ ! -f /usr/bin/codium ]; then
+	if [ -f /usr/bin/code ]; then
+		CODIUM=code
+	else
+		echo "Please install VSCodium on your system!"
+		echo "More information here: https://github.com/VSCodium/vscodium"
+		exit
+	fi
+fi
+
+cat "resources/extensions.txt" | while read EXTENSION || [[ -n $EXTENSION ]];
+do
+  $CODIUM --install-extension $EXTENSION --force
+done
+
diff --git a/scripts/upload_docs.sh b/scripts/upload_docs.sh
new file mode 100755
index 0000000000000000000000000000000000000000..656354f6d4996976b632fd42afed89827f956569
--- /dev/null
+++ b/scripts/upload_docs.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+VKCV_VERSION="$(grep -oEi "#define.+VKCV_FRAMEWORK_VERSION.+\(VK_MAKE_VERSION\(([0-9]+,.*[0-9]+,.*[0-9]+)\)\)" include/vkcv/Core.hpp | awk 'match($0, /.*\(([0-9]+),.*([0-9]+),.*([0-9]+)\)/, a) {print a[1]"."a[2]"."a[3]}')"
+
+DOXYFILE_TMP=$(mktemp)
+sed -r "s/(PROJECT_NUMBER.*=.*)([0-9]+\.[0-9]+\.[0-9]+)/\1$VKCV_VERSION/" Doxyfile > $DOXYFILE_TMP
+mv $DOXYFILE_TMP Doxyfile
+
+doxygen Doxyfile
+rsync -r doc/html/ vkcv@penguin2.uni-koblenz.de:/home/vkcv/public_html/doc/
diff --git a/src/vkcv/BlitDownsampler.cpp b/src/vkcv/BlitDownsampler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..956c3079e683f3326b6085ca314d5c7081a74e0d
--- /dev/null
+++ b/src/vkcv/BlitDownsampler.cpp
@@ -0,0 +1,18 @@
+
+#include "vkcv/BlitDownsampler.hpp"
+
+#include "ImageManager.hpp"
+#include "vkcv/Core.hpp"
+
+namespace vkcv {
+
+	BlitDownsampler::BlitDownsampler(Core &core, ImageManager &imageManager) :
+		Downsampler(core), m_imageManager(imageManager) {}
+
+	void BlitDownsampler::recordDownsampling(const CommandStreamHandle &cmdStream,
+											 const ImageHandle &image) {
+		m_imageManager.recordImageMipChainGenerationToCmdStream(cmdStream, image);
+		m_core.prepareImageForSampling(cmdStream, image);
+	}
+
+} // namespace vkcv
diff --git a/src/vkcv/BufferManager.cpp b/src/vkcv/BufferManager.cpp
index aec96411c5d9e07f200b24fbdcf9fa69e2af53d5..b443f8caeb9b8ca591cba6bbc7147001aa419154 100644
--- a/src/vkcv/BufferManager.cpp
+++ b/src/vkcv/BufferManager.cpp
@@ -3,139 +3,234 @@
  * @file vkcv/BufferManager.cpp
  */
 
-#include "vkcv/BufferManager.hpp"
+#include "BufferManager.hpp"
 #include "vkcv/Core.hpp"
 #include <vkcv/Logger.hpp>
 
+#include <limits>
+
 namespace vkcv {
-	
-	BufferManager::BufferManager() noexcept :
-		m_core(nullptr), m_buffers(), m_stagingBuffer(BufferHandle())
-	{
-	}
-	
-	void BufferManager::init() {
-		if (!m_core) {
-			return;
+
+	bool BufferManager::init(Core &core) {
+		if (!HandleManager<BufferEntry, BufferHandle>::init(core)) {
+			return false;
 		}
 		
-		m_stagingBuffer = createBuffer(BufferType::STAGING, 1024 * 1024, BufferMemoryType::HOST_VISIBLE);
-	}
-	
-	BufferManager::~BufferManager() noexcept {
-		for (uint64_t id = 0; id < m_buffers.size(); id++) {
-			destroyBufferById(id);
+		const vma::Allocator &allocator = getCore().getContext().getAllocator();
+		const auto& memoryProperties = allocator.getMemoryProperties();
+		const auto& heaps = memoryProperties->memoryHeaps;
+		
+		std::vector<vk::MemoryPropertyFlags> heapMemoryFlags;
+		heapMemoryFlags.resize(heaps.size());
+		
+		for (const auto& type : memoryProperties->memoryTypes) {
+			if (type.heapIndex >= heaps.size()) {
+				continue;
+			}
+			
+			heapMemoryFlags[type.heapIndex] |= type.propertyFlags;
 		}
+		
+		vk::DeviceSize maxDeviceHeapSize = 0;
+		uint32_t deviceHeapIndex = 0;
+		
+		for (uint32_t i = 0; i < heaps.size(); i++) {
+			if (!(heaps[i].flags & vk::MemoryHeapFlagBits::eDeviceLocal)) {
+				continue;
+			}
+			
+			if (!(heapMemoryFlags[i] & vk::MemoryPropertyFlagBits::eDeviceLocal)) {
+				continue;
+			}
+			
+			if (heaps[i].size < maxDeviceHeapSize) {
+				continue;
+			}
+			
+			maxDeviceHeapSize = heaps[i].size;
+			deviceHeapIndex = i;
+		}
+		
+		if (heapMemoryFlags[deviceHeapIndex] & vk::MemoryPropertyFlagBits::eHostVisible) {
+			m_resizableBar = true;
+		} else {
+			m_resizableBar = false;
+		}
+
+		m_stagingBuffer = createBuffer(
+				TypeGuard(1),
+				BufferType::STAGING,
+				BufferMemoryType::HOST_VISIBLE,
+				1024 * 1024,
+				false
+		);
+
+		return true;
 	}
-	
-	/**
-	 * @brief searches memory type index for buffer allocation, combines requirements of buffer and application
-	 * @param physicalMemoryProperties Memory Properties of physical device
-	 * @param typeBits Bit field for suitable memory types
-	 * @param requirements Property flags that are required
-	 * @return memory type index for Buffer
-	 */
-	uint32_t searchBufferMemoryType(const vk::PhysicalDeviceMemoryProperties& physicalMemoryProperties, uint32_t typeBits, vk::MemoryPropertyFlags requirements) {
-		const uint32_t memoryCount = physicalMemoryProperties.memoryTypeCount;
-		for (uint32_t memoryIndex = 0; memoryIndex < memoryCount; ++memoryIndex) {
-			const uint32_t memoryTypeBits = (1 << memoryIndex);
-			const bool isRequiredMemoryType = typeBits & memoryTypeBits;
-
-			const vk::MemoryPropertyFlags properties =
-				physicalMemoryProperties.memoryTypes[memoryIndex].propertyFlags;
-			const bool hasRequiredProperties =
-				(properties & requirements) == requirements;
-
-			if (isRequiredMemoryType && hasRequiredProperties)
-				return static_cast<int32_t>(memoryIndex);
+
+	uint64_t BufferManager::getIdFrom(const BufferHandle &handle) const {
+		return handle.getId();
+	}
+
+	BufferHandle BufferManager::createById(uint64_t id, const HandleDestroyFunction &destroy) {
+		return BufferHandle(id, destroy);
+	}
+
+	void BufferManager::destroyById(uint64_t id) {
+		auto &buffer = getById(id);
+
+		const vma::Allocator &allocator = getCore().getContext().getAllocator();
+
+		if (buffer.m_handle) {
+			if (buffer.m_mapping) {
+				allocator.unmapMemory(buffer.m_allocation);
+			}
+			
+			allocator.destroyBuffer(buffer.m_handle, buffer.m_allocation);
+
+			buffer.m_handle = nullptr;
+			buffer.m_allocation = nullptr;
 		}
+	}
 
-		// failed to find memory type
-		return -1;
+	BufferManager::BufferManager() noexcept :
+		HandleManager<BufferEntry, BufferHandle>(),
+		m_resizableBar(false),
+		m_stagingBuffer(BufferHandle()) {}
+
+	BufferManager::~BufferManager() noexcept {
+		clear();
 	}
-	
-	BufferHandle BufferManager::createBuffer(BufferType type, size_t size, BufferMemoryType memoryType) {
+
+	BufferHandle BufferManager::createBuffer(const TypeGuard &typeGuard, BufferType type,
+											 BufferMemoryType memoryType, size_t size,
+											 bool readable) {
 		vk::BufferCreateFlags createFlags;
 		vk::BufferUsageFlags usageFlags;
-		
+
 		switch (type) {
-			case BufferType::VERTEX:
-				usageFlags = vk::BufferUsageFlagBits::eVertexBuffer;
-				break;
-			case BufferType::UNIFORM:
-				usageFlags = vk::BufferUsageFlagBits::eUniformBuffer;
-				break;
-			case BufferType::STORAGE:
-				usageFlags = vk::BufferUsageFlagBits::eStorageBuffer;
-				break;
-			case BufferType::STAGING:
-				usageFlags = vk::BufferUsageFlagBits::eTransferSrc;
-				break;
-			case BufferType::INDEX:
-				usageFlags = vk::BufferUsageFlagBits::eIndexBuffer;
-				break;
-			default:
-				// TODO: maybe an issue
-				break;
+		case BufferType::VERTEX:
+			usageFlags = vk::BufferUsageFlagBits::eVertexBuffer;
+			break;
+		case BufferType::UNIFORM:
+			usageFlags = vk::BufferUsageFlagBits::eUniformBuffer;
+			break;
+		case BufferType::STORAGE:
+			usageFlags = vk::BufferUsageFlagBits::eStorageBuffer;
+			break;
+		case BufferType::STAGING:
+			usageFlags = vk::BufferUsageFlagBits::eTransferSrc
+						| vk::BufferUsageFlagBits::eTransferDst;
+			break;
+		case BufferType::INDEX:
+			usageFlags = vk::BufferUsageFlagBits::eIndexBuffer;
+			break;
+		case BufferType::INDIRECT:
+			usageFlags = vk::BufferUsageFlagBits::eStorageBuffer
+						| vk::BufferUsageFlagBits::eIndirectBuffer;
+			break;
+		default:
+			vkcv_log(LogLevel::WARNING, "Unknown buffer type");
+			break;
 		}
-		
+
 		if (memoryType == BufferMemoryType::DEVICE_LOCAL) {
 			usageFlags |= vk::BufferUsageFlagBits::eTransferDst;
 		}
-		
-		const vk::Device& device = m_core->getContext().getDevice();
-		
-		vk::Buffer buffer = device.createBuffer(
-				vk::BufferCreateInfo(createFlags, size, usageFlags)
-		);
-		
-		const vk::MemoryRequirements requirements = device.getBufferMemoryRequirements(buffer);
-		const vk::PhysicalDevice& physicalDevice = m_core->getContext().getPhysicalDevice();
-		
-		vk::MemoryPropertyFlags memoryTypeFlags;
+
+		if (readable) {
+			usageFlags |= vk::BufferUsageFlagBits::eTransferSrc;
+		}
+
+		const vma::Allocator &allocator = getCore().getContext().getAllocator();
+
+		vma::MemoryUsage memoryUsage;
 		bool mappable = false;
-		
+
 		switch (memoryType) {
-			case BufferMemoryType::DEVICE_LOCAL:
-				memoryTypeFlags = vk::MemoryPropertyFlagBits::eDeviceLocal;
-				break;
-			case BufferMemoryType::HOST_VISIBLE:
-				memoryTypeFlags = vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent;
-				mappable = true;
-				break;
-			default:
-				// TODO: maybe an issue
-				break;
+		case BufferMemoryType::DEVICE_LOCAL:
+			memoryUsage = vma::MemoryUsage::eAutoPreferDevice;
+			mappable = false;
+			break;
+		case BufferMemoryType::HOST_VISIBLE:
+			memoryUsage = vma::MemoryUsage::eAutoPreferHost;
+			mappable = true;
+			break;
+		default:
+			vkcv_log(LogLevel::WARNING, "Unknown buffer memory type");
+			memoryUsage = vma::MemoryUsage::eUnknown;
+			mappable = false;
+			break;
 		}
 		
-		const uint32_t memoryTypeIndex = searchBufferMemoryType(
-				physicalDevice.getMemoryProperties(),
-				requirements.memoryTypeBits,
-				memoryTypeFlags
+		vma::AllocationCreateFlags allocationCreateFlags;
+		
+		if (mappable) {
+			if (type == vkcv::BufferType::STAGING) {
+				allocationCreateFlags = vma::AllocationCreateFlagBits::eHostAccessSequentialWrite;
+			} else {
+				allocationCreateFlags = vma::AllocationCreateFlagBits::eHostAccessRandom;
+			}
+		} else
+		if ((m_resizableBar) && (memoryType == BufferMemoryType::DEVICE_LOCAL)) {
+			allocationCreateFlags = vma::AllocationCreateFlagBits::eHostAccessAllowTransferInstead
+									| vma::AllocationCreateFlagBits::eHostAccessSequentialWrite;
+		}
+
+		auto bufferAllocation = allocator.createBuffer(
+			vk::BufferCreateInfo(createFlags, size, usageFlags),
+			vma::AllocationCreateInfo(
+					allocationCreateFlags,
+					memoryUsage,
+					vk::MemoryPropertyFlags(),
+					vk::MemoryPropertyFlags(),
+					0,
+					vma::Pool(),
+					nullptr
+			)
 		);
+
+		vk::Buffer buffer = bufferAllocation.first;
+		vma::Allocation allocation = bufferAllocation.second;
 		
-		vk::DeviceMemory memory = device.allocateMemory(vk::MemoryAllocateInfo(requirements.size, memoryTypeIndex));
-		
-		device.bindBufferMemory(buffer, memory, 0);
+		const vk::MemoryPropertyFlags finalMemoryFlags = allocator.getAllocationMemoryProperties(
+				allocation
+		);
 		
-		const uint64_t id = m_buffers.size();
-		m_buffers.push_back({ buffer, memory, size, nullptr, mappable });
-		return BufferHandle(id, [&](uint64_t id) { destroyBufferById(id); });
+		if (vk::MemoryPropertyFlagBits::eHostVisible & finalMemoryFlags) {
+			mappable = true;
+		}
+
+		return add({
+			typeGuard,
+			type,
+			memoryType,
+			size,
+			buffer,
+			allocation,
+			readable,
+			mappable,
+			nullptr,
+			0
+		});
 	}
-	
-	struct StagingStepInfo {
+
+	/**
+	 * @brief Structure to store details required for a write staging process.
+	 */
+	struct StagingWriteInfo {
 		const void* data;
 		size_t size;
 		size_t offset;
-		
+
 		vk::Buffer buffer;
 		vk::Buffer stagingBuffer;
-		vk::DeviceMemory stagingMemory;
-		
+		vma::Allocation stagingAllocation;
+
 		size_t stagingLimit;
 		size_t stagingPosition;
 	};
-	
+
 	/**
 	 * Copies data from CPU to a staging buffer and submits the commands to copy
 	 * each part one after another into the actual target buffer.
@@ -146,223 +241,291 @@ namespace vkcv {
 	 * @param core Core instance
 	 * @param info Staging-info structure
 	 */
-	void copyFromStagingBuffer(Core* core, StagingStepInfo& info) {
+	static void fillFromStagingBuffer(Core &core, StagingWriteInfo &info) {
 		const size_t remaining = info.size - info.stagingPosition;
 		const size_t mapped_size = std::min(remaining, info.stagingLimit);
-		
-		const vk::Device& device = core->getContext().getDevice();
-		
-		void* mapped = device.mapMemory(info.stagingMemory, 0, mapped_size);
-		memcpy(mapped, reinterpret_cast<const char*>(info.data) + info.stagingPosition, mapped_size);
-		device.unmapMemory(info.stagingMemory);
-		
-		SubmitInfo submitInfo;
-		submitInfo.queueType = QueueType::Transfer;
-		
-		core->recordAndSubmitCommandsImmediate(
-				submitInfo,
-				[&info, &mapped_size](const vk::CommandBuffer& commandBuffer) {
-					const vk::BufferCopy region (
-							0,
-							info.offset + info.stagingPosition,
-							mapped_size
-					);
-					
-					commandBuffer.copyBuffer(info.stagingBuffer, info.buffer, 1, &region);
-				},
-				[&core, &info, &mapped_size, &remaining]() {
-					if (mapped_size < remaining) {
-						info.stagingPosition += mapped_size;
-						
-						copyFromStagingBuffer(
-								core,
-								info
-						);
-					}
+
+		const vma::Allocator &allocator = core.getContext().getAllocator();
+
+		void* mapped = allocator.mapMemory(info.stagingAllocation);
+		memcpy(mapped, reinterpret_cast<const char*>(info.data) + info.stagingPosition,
+			   mapped_size);
+		allocator.unmapMemory(info.stagingAllocation);
+
+		auto stream = core.createCommandStream(QueueType::Transfer);
+
+		core.recordCommandsToStream(
+			stream,
+			[&info, &mapped_size](const vk::CommandBuffer &commandBuffer) {
+				const vk::BufferCopy region(0, info.offset + info.stagingPosition, mapped_size);
+
+				commandBuffer.copyBuffer(info.stagingBuffer, info.buffer, 1, &region);
+			},
+			[&core, &info, &mapped_size, &remaining]() {
+				if (mapped_size < remaining) {
+					info.stagingPosition += mapped_size;
+
+					fillFromStagingBuffer(core, info);
 				}
-		);
+			});
+
+		core.submitCommandStream(stream, false);
 	}
-	
-	vk::Buffer BufferManager::getBuffer(const BufferHandle& handle) const {
-		const uint64_t id = handle.getId();
-		
-		if (id >= m_buffers.size()) {
-			return nullptr;
-		}
-		
-		auto& buffer = m_buffers[id];
-		
+
+	/**
+	 * @brief Structure to store details required for a read staging process.
+	 */
+	struct StagingReadInfo {
+		void* data;
+		size_t size;
+		size_t offset;
+
+		vk::Buffer buffer;
+		vk::Buffer stagingBuffer;
+		vma::Allocation stagingAllocation;
+
+		size_t stagingLimit;
+		size_t stagingPosition;
+	};
+
+	/**
+	 * Copies data from a staging buffer to CPU and submits the commands to copy
+	 * each part one after another into the actual target data pointer.
+	 *
+	 * The function can be used fully asynchronously!
+	 * Just be careful to not use the staging buffer in parallel!
+	 *
+	 * @param core Core instance
+	 * @param info Staging-info structure
+	 */
+	static void readToStagingBuffer(Core &core, StagingReadInfo &info) {
+		const size_t remaining = info.size - info.stagingPosition;
+		const size_t mapped_size = std::min(remaining, info.stagingLimit);
+
+		auto stream = core.createCommandStream(QueueType::Transfer);
+
+		core.recordCommandsToStream(
+			stream,
+			[&info, &mapped_size](const vk::CommandBuffer &commandBuffer) {
+				const vk::BufferCopy region(info.offset + info.stagingPosition, 0, mapped_size);
+
+				commandBuffer.copyBuffer(info.buffer, info.stagingBuffer, 1, &region);
+			},
+			[&core, &info, &mapped_size, &remaining]() {
+				const vma::Allocator &allocator = core.getContext().getAllocator();
+
+				const void* mapped = allocator.mapMemory(info.stagingAllocation);
+				memcpy(reinterpret_cast<char*>(info.data) + info.stagingPosition, mapped,
+					   mapped_size);
+				allocator.unmapMemory(info.stagingAllocation);
+
+				if (mapped_size < remaining) {
+					info.stagingPosition += mapped_size;
+
+					readToStagingBuffer(core, info);
+				}
+			});
+
+		core.submitCommandStream(stream, false);
+	}
+
+	vk::Buffer BufferManager::getBuffer(const BufferHandle &handle) const {
+		auto &buffer = (*this) [handle];
+
 		return buffer.m_handle;
 	}
-	
+
+	TypeGuard BufferManager::getTypeGuard(const BufferHandle &handle) const {
+		auto &buffer = (*this) [handle];
+
+		return buffer.m_typeGuard;
+	}
+
+	BufferType BufferManager::getBufferType(const BufferHandle &handle) const {
+		auto &buffer = (*this) [handle];
+
+		return buffer.m_type;
+	}
+
+	BufferMemoryType BufferManager::getBufferMemoryType(const BufferHandle &handle) const {
+		auto &buffer = (*this) [handle];
+
+		return buffer.m_memoryType;
+	}
+
 	size_t BufferManager::getBufferSize(const BufferHandle &handle) const {
-		const uint64_t id = handle.getId();
-		
-		if (id >= m_buffers.size()) {
-			return 0;
-		}
-		
-		auto& buffer = m_buffers[id];
-		
+		auto &buffer = (*this) [handle];
+
 		return buffer.m_size;
 	}
-	
-	vk::DeviceMemory BufferManager::getDeviceMemory(const BufferHandle& handle) const {
-		const uint64_t id = handle.getId();
-		
-		if (id >= m_buffers.size()) {
-			return nullptr;
-		}
-		
-		auto& buffer = m_buffers[id];
-		
-		return buffer.m_memory;
+
+	vk::DeviceMemory BufferManager::getDeviceMemory(const BufferHandle &handle) const {
+		auto &buffer = (*this) [handle];
+
+		const vma::Allocator &allocator = getCore().getContext().getAllocator();
+
+		auto info = allocator.getAllocationInfo(buffer.m_allocation);
+
+		return info.deviceMemory;
 	}
-	
-	void BufferManager::fillBuffer(const BufferHandle& handle, const void *data, size_t size, size_t offset) {
-		const uint64_t id = handle.getId();
-		
+
+	void BufferManager::fillBuffer(const BufferHandle &handle, const void* data, size_t size,
+								   size_t offset) {
+		auto &buffer = (*this) [handle];
+
 		if (size == 0) {
-			size = SIZE_MAX;
+			size = std::numeric_limits<size_t>::max();
 		}
-		
-		if (id >= m_buffers.size()) {
+
+		const vma::Allocator &allocator = getCore().getContext().getAllocator();
+
+		if (offset > buffer.m_size) {
 			return;
 		}
-		
-		auto& buffer = m_buffers[id];
-		
-		if (buffer.m_mapped) {
-			return;
+
+		const size_t max_size = std::min(size, buffer.m_size - offset);
+
+		if (buffer.m_mappable) {
+			void* mapped = allocator.mapMemory(buffer.m_allocation);
+			memcpy(reinterpret_cast<char*>(mapped) + offset, data, max_size);
+			allocator.unmapMemory(buffer.m_allocation);
+		} else {
+			auto &stagingBuffer = (*this) [m_stagingBuffer];
+
+			StagingWriteInfo info;
+			info.data = data;
+			info.size = max_size;
+			info.offset = offset;
+
+			info.buffer = buffer.m_handle;
+			info.stagingBuffer = stagingBuffer.m_handle;
+			info.stagingAllocation = stagingBuffer.m_allocation;
+
+			info.stagingLimit = stagingBuffer.m_size;
+			info.stagingPosition = 0;
+
+			fillFromStagingBuffer(getCore(), info);
 		}
-		
-		const vk::Device& device = m_core->getContext().getDevice();
-		
+	}
+
+	void BufferManager::readBuffer(const BufferHandle &handle, void* data, size_t size,
+								   size_t offset) {
+		auto &buffer = (*this) [handle];
+
+		if (size == 0) {
+			size = std::numeric_limits<size_t>::max();
+		}
+
+		const vma::Allocator &allocator = getCore().getContext().getAllocator();
+
 		if (offset > buffer.m_size) {
 			return;
 		}
-		
+
 		const size_t max_size = std::min(size, buffer.m_size - offset);
-		
+
 		if (buffer.m_mappable) {
-			void* mapped = device.mapMemory(buffer.m_memory, offset, max_size);
-			memcpy(mapped, data, max_size);
-			device.unmapMemory(buffer.m_memory);
+			const void* mapped = allocator.mapMemory(buffer.m_allocation);
+			memcpy(data, reinterpret_cast<const char*>(mapped) + offset, max_size);
+			allocator.unmapMemory(buffer.m_allocation);
 		} else {
-			auto& stagingBuffer = m_buffers[ m_stagingBuffer.getId() ];
-			
-			StagingStepInfo info;
+			auto &stagingBuffer = (*this) [m_stagingBuffer];
+
+			StagingReadInfo info;
 			info.data = data;
-			info.size = std::min(size, max_size - offset);
+			info.size = max_size;
 			info.offset = offset;
-			
+
 			info.buffer = buffer.m_handle;
 			info.stagingBuffer = stagingBuffer.m_handle;
-			info.stagingMemory = stagingBuffer.m_memory;
-			
-			const vk::MemoryRequirements stagingRequirements = device.getBufferMemoryRequirements(stagingBuffer.m_handle);
-			
-			info.stagingLimit = stagingRequirements.size;
+			info.stagingAllocation = stagingBuffer.m_allocation;
+
+			info.stagingLimit = stagingBuffer.m_size;
 			info.stagingPosition = 0;
-			
-			copyFromStagingBuffer(m_core, info);
+
+			readToStagingBuffer(getCore(), info);
 		}
 	}
-	
-	void* BufferManager::mapBuffer(const BufferHandle& handle, size_t offset, size_t size) {
-		const uint64_t id = handle.getId();
-		
+
+	void* BufferManager::mapBuffer(const BufferHandle &handle, size_t offset, size_t size) {
+		auto &buffer = (*this) [handle];
+
 		if (size == 0) {
-			size = SIZE_MAX;
+			size = std::numeric_limits<size_t>::max();
 		}
-		
-		if (id >= m_buffers.size()) {
+
+		if (offset > buffer.m_size) {
 			return nullptr;
 		}
 		
-		auto& buffer = m_buffers[id];
-		
-		if (buffer.m_mapped) {
-			return nullptr;
+		if (buffer.m_mapping) {
+			++buffer.m_mapCounter;
+			
+			vkcv_log(LogLevel::WARNING,
+					 "Mapping a buffer multiple times (%lu) is not recommended",
+					 buffer.m_mapCounter);
+			
+			return buffer.m_mapping + offset;
 		}
 		
-		const vk::Device& device = m_core->getContext().getDevice();
-		
-		if (offset > buffer.m_size) {
-			return nullptr;
+		if (buffer.m_mappable) {
+			const vma::Allocator &allocator = getCore().getContext().getAllocator();
+			
+			buffer.m_mapping = reinterpret_cast<char*>(allocator.mapMemory(buffer.m_allocation));
+		} else {
+			buffer.m_mapping = m_allocator.allocate(buffer.m_size);
+			
+			if (buffer.m_readable) {
+				readBuffer(handle, buffer.m_mapping, buffer.m_size, 0);
+			}
 		}
 		
-		const size_t max_size = std::min(size, buffer.m_size - offset);
-		buffer.m_mapped = device.mapMemory(buffer.m_memory, offset, max_size);
-		return buffer.m_mapped;
+		buffer.m_mapCounter = 1;
+		return buffer.m_mapping + offset;
 	}
-	
-	void BufferManager::unmapBuffer(const BufferHandle& handle) {
-		const uint64_t id = handle.getId();
+
+	void BufferManager::unmapBuffer(const BufferHandle &handle) {
+		auto &buffer = (*this) [handle];
 		
-		if (id >= m_buffers.size()) {
+		if (buffer.m_mapCounter > 1) {
+			--buffer.m_mapCounter;
 			return;
 		}
 		
-		auto& buffer = m_buffers[id];
-		
-		if (buffer.m_mapped == nullptr) {
-			return;
+		if (buffer.m_mapCounter == 0) {
+			vkcv_log(LogLevel::WARNING,
+					 "It seems like the buffer is not mapped to memory");
 		}
 		
-		const vk::Device& device = m_core->getContext().getDevice();
-		
-		device.unmapMemory(buffer.m_memory);
-		buffer.m_mapped = nullptr;
-	}
-	
-	void BufferManager::destroyBufferById(uint64_t id) {
-		if (id >= m_buffers.size()) {
-			return;
+		if (!buffer.m_mapping) {
+			vkcv_log(LogLevel::ERROR,
+					 "Buffer is not mapped to memory");
 		}
 		
-		auto& buffer = m_buffers[id];
-		
-		const vk::Device& device = m_core->getContext().getDevice();
-		
-		if (buffer.m_memory) {
-			device.freeMemory(buffer.m_memory);
-			buffer.m_memory = nullptr;
+		if (buffer.m_mappable) {
+			const vma::Allocator &allocator = getCore().getContext().getAllocator();
+			
+			allocator.unmapMemory(buffer.m_allocation);
+		} else {
+			fillBuffer(handle, buffer.m_mapping, buffer.m_size, 0);
+			m_allocator.deallocate(buffer.m_mapping, buffer.m_size);
 		}
 		
-		if (buffer.m_handle) {
-			device.destroyBuffer(buffer.m_handle);
-			buffer.m_handle = nullptr;
-		}
+		buffer.m_mapping = nullptr;
+		buffer.m_mapCounter = 0;
 	}
 
-	void BufferManager ::recordBufferMemoryBarrier(const BufferHandle& handle, vk::CommandBuffer cmdBuffer) {
+	void BufferManager ::recordBufferMemoryBarrier(const BufferHandle &handle,
+												   vk::CommandBuffer cmdBuffer) {
+		auto &buffer = (*this) [handle];
 
-		const uint64_t id = handle.getId();
+		vk::BufferMemoryBarrier memoryBarrier(vk::AccessFlagBits::eMemoryWrite,
+											  vk::AccessFlagBits::eMemoryRead, 0, 0,
+											  buffer.m_handle, 0, buffer.m_size);
 
-		if (id >= m_buffers.size()) {
-			vkcv_log(vkcv::LogLevel::ERROR, "Invalid buffer handle");
-			return;
-		}
-
-		auto& buffer = m_buffers[id];
-		
-		vk::BufferMemoryBarrier memoryBarrier(
-			vk::AccessFlagBits::eMemoryWrite, 
-			vk::AccessFlagBits::eMemoryRead,
-			0,
-			0,
-			buffer.m_handle,
-			0,
-			buffer.m_size);
-
-		cmdBuffer.pipelineBarrier(
-			vk::PipelineStageFlagBits::eTopOfPipe,
-			vk::PipelineStageFlagBits::eBottomOfPipe,
-			{},
-			nullptr,
-			memoryBarrier,
-			nullptr);
+		cmdBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
+								  vk::PipelineStageFlagBits::eAllCommands, {}, nullptr,
+								  memoryBarrier, nullptr);
 	}
 
-}
+} // namespace vkcv
diff --git a/src/vkcv/BufferManager.hpp b/src/vkcv/BufferManager.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f28269725bd2025fe8d5fced57c47a87358bd45b
--- /dev/null
+++ b/src/vkcv/BufferManager.hpp
@@ -0,0 +1,191 @@
+#pragma once
+/**
+ * @authors Tobias Frisch, Alexander Gauggel, Artur Wasmut, Lars Hoerttrich, Sebastian Gaida
+ * @file vkcv/BufferManager.hpp
+ * @brief Manager to handle buffer operations.
+ */
+
+#include <memory>
+#include <vector>
+
+#include <vk_mem_alloc.hpp>
+#include <vulkan/vulkan.hpp>
+
+#include "vkcv/BufferTypes.hpp"
+#include "vkcv/TypeGuard.hpp"
+
+#include "HandleManager.hpp"
+
+namespace vkcv {
+
+	struct BufferEntry {
+		TypeGuard m_typeGuard;
+
+		BufferType m_type;
+		BufferMemoryType m_memoryType;
+		size_t m_size;
+
+		vk::Buffer m_handle;
+		vma::Allocation m_allocation;
+
+		bool m_readable;
+		bool m_mappable;
+		char *m_mapping;
+		size_t m_mapCounter;
+	};
+
+	/**
+	 * @brief Class to manage the creation, destruction, allocation
+	 * and filling of buffers.
+	 */
+	class BufferManager : public HandleManager<BufferEntry, BufferHandle> {
+		friend class Core;
+
+	private:
+		std::allocator<char> m_allocator;
+		
+		bool m_resizableBar;
+		BufferHandle m_stagingBuffer;
+
+		bool init(Core &core) override;
+
+		[[nodiscard]] uint64_t getIdFrom(const BufferHandle &handle) const override;
+
+		[[nodiscard]] BufferHandle createById(uint64_t id,
+											  const HandleDestroyFunction &destroy) override;
+
+		/**
+		 * Destroys and deallocates buffer represented by a given
+		 * buffer handle id.
+		 *
+		 * @param id Buffer handle id
+		 */
+		void destroyById(uint64_t id) override;
+
+	public:
+		BufferManager() noexcept;
+
+		~BufferManager() noexcept override;
+
+		/**
+		 * @brief Creates and allocates a new buffer and returns its
+		 * unique buffer handle.
+		 *
+		 * @param[in] typeGuard Type guard
+		 * @param[in] type Type of buffer
+		 * @param[in] memoryType Type of buffers memory
+		 * @param[in] size Size of buffer in bytes
+		 * @param[in] supportIndirect Support of indirect usage
+		 * @param[in] readable Support read functionality
+		 * @return New buffer handle
+		 */
+		[[nodiscard]] BufferHandle createBuffer(const TypeGuard &typeGuard, BufferType type,
+												BufferMemoryType memoryType, size_t size,
+												bool readable);
+
+		/**
+		 * @brief Returns the Vulkan buffer handle of a buffer
+		 * represented by a given buffer handle.
+		 *
+		 * @param[in] handle Buffer handle
+		 * @return Vulkan buffer handle
+		 */
+		[[nodiscard]] vk::Buffer getBuffer(const BufferHandle &handle) const;
+
+		/**
+		 * @brief Returns the type guard of a buffer represented
+		 * by a given buffer handle.
+		 *
+		 * @param[in] handle Buffer handle
+		 * @return Type guard
+		 */
+		[[nodiscard]] TypeGuard getTypeGuard(const BufferHandle &handle) const;
+
+		/**
+		 * @brief Returns the buffer type of a buffer represented
+		 * by a given buffer handle.
+		 *
+		 * @param[in] handle Buffer handle
+		 * @return Buffer type
+		 */
+		[[nodiscard]] BufferType getBufferType(const BufferHandle &handle) const;
+
+		/**
+		 * @brief Returns the buffer memory type of a buffer
+		 * represented by a given buffer handle.
+		 *
+		 * @param[in] handle Buffer handle
+		 * @return Buffer memory type
+		 */
+		[[nodiscard]] BufferMemoryType getBufferMemoryType(const BufferHandle &handle) const;
+
+		/**
+		 * @brief Returns the size of a buffer represented
+		 * by a given buffer handle.
+		 *
+		 * @param[in] handle Buffer handle
+		 * @return Size of the buffer
+		 */
+		[[nodiscard]] size_t getBufferSize(const BufferHandle &handle) const;
+
+		/**
+		 * @brief Returns the Vulkan device memory handle of a buffer
+		 * represented by a given buffer handle id.
+		 *
+		 * @param[in] handle Buffer handle
+		 * @return Vulkan device memory handle
+		 */
+		[[nodiscard]] vk::DeviceMemory getDeviceMemory(const BufferHandle &handle) const;
+
+		/**
+		 * @brief Fills a buffer represented by a given buffer
+		 * handle with custom data.
+		 *
+		 * @param[in] handle Buffer handle
+		 * @param[in] data Pointer to data
+		 * @param[in] size Size of data in bytes
+		 * @param[in] offset Offset to fill in data in bytes
+		 */
+		void fillBuffer(const BufferHandle &handle, const void* data, size_t size, size_t offset);
+
+		/**
+		 * @brief Reads from a buffer represented by a given
+		 * buffer handle to some data pointer.
+		 *
+		 * @param[in] handle Buffer handle
+		 * @param[in] data Pointer to data
+		 * @param[in] size Size of data to read in bytes
+		 * @param[in] offset Offset to read from buffer in bytes
+		 */
+		void readBuffer(const BufferHandle &handle, void* data, size_t size, size_t offset);
+
+		/**
+		 * @brief Maps memory to a buffer represented by a given
+		 * buffer handle and returns it.
+		 *
+		 * @param[in] handle Buffer handle
+		 * @param[in] offset Offset of mapping in bytes
+		 * @param[in] size Size of mapping in bytes
+		 * @return Pointer to mapped memory
+		 */
+		void* mapBuffer(const BufferHandle &handle, size_t offset, size_t size);
+
+		/**
+		 * @brief Unmaps memory from a buffer represented by a given
+		 * buffer handle.
+		 *
+		 * @param[in] handle Buffer handle
+		 */
+		void unmapBuffer(const BufferHandle &handle);
+
+		/**
+		 * @brief Records a memory barrier for a buffer,
+		 * synchronizing subsequent accesses to buffer data
+		 *
+		 * @param[in] handle BufferHandle of the buffer
+		 * @param[in] cmdBuffer Vulkan command buffer to record the barrier into
+		 */
+		void recordBufferMemoryBarrier(const BufferHandle &handle, vk::CommandBuffer cmdBuffer);
+	};
+
+} // namespace vkcv
diff --git a/src/vkcv/CommandResources.cpp b/src/vkcv/CommandResources.cpp
deleted file mode 100644
index a31e6967d85bd099fe5cbbc865b0e062212ca16e..0000000000000000000000000000000000000000
--- a/src/vkcv/CommandResources.cpp
+++ /dev/null
@@ -1,87 +0,0 @@
-#include "vkcv/CommandResources.hpp"
-#include <iostream>
-
-#include "vkcv/Logger.hpp"
-
-namespace vkcv {
-
-	std::unordered_set<int> generateQueueFamilyIndexSet(const QueueManager &queueManager) {
-		std::unordered_set<int> indexSet;
-		for (const auto& queue : queueManager.getGraphicsQueues()) {
-			indexSet.insert(queue.familyIndex);
-		}
-		for (const auto& queue : queueManager.getComputeQueues()) {
-			indexSet.insert(queue.familyIndex);
-		}
-		for (const auto& queue : queueManager.getTransferQueues()) {
-			indexSet.insert(queue.familyIndex);
-		}
-		indexSet.insert(queueManager.getPresentQueue().familyIndex);
-		return indexSet;
-	}
-
-	CommandResources createCommandResources(const vk::Device& device, const std::unordered_set<int>& familyIndexSet) {
-		CommandResources resources;
-		const size_t queueFamiliesCount = familyIndexSet.size();
-		resources.cmdPoolPerQueueFamily.resize(queueFamiliesCount);
-
-		const vk::CommandPoolCreateFlags poolFlags = vk::CommandPoolCreateFlagBits::eTransient;
-		for (const int familyIndex : familyIndexSet) {
-			const vk::CommandPoolCreateInfo poolCreateInfo(poolFlags, familyIndex);
-			resources.cmdPoolPerQueueFamily[familyIndex] = device.createCommandPool(poolCreateInfo, nullptr, {});
-		}
-
-		return resources;
-	}
-
-	void destroyCommandResources(const vk::Device& device, const CommandResources& resources) {
-		for (const vk::CommandPool &pool : resources.cmdPoolPerQueueFamily) {
-			device.destroyCommandPool(pool);
-		}
-	}
-
-	vk::CommandBuffer allocateCommandBuffer(const vk::Device& device, const vk::CommandPool cmdPool) {
-		const vk::CommandBufferAllocateInfo info(cmdPool, vk::CommandBufferLevel::ePrimary, 1);
-		return device.allocateCommandBuffers(info).front();
-	}
-
-	vk::CommandPool chooseCmdPool(const Queue& queue, const CommandResources& cmdResources) {
-		return cmdResources.cmdPoolPerQueueFamily[queue.familyIndex];
-	}
-
-	Queue getQueueForSubmit(const QueueType type, const QueueManager& queueManager) {
-		if (type == QueueType::Graphics) {
-			return queueManager.getGraphicsQueues().front();
-		}
-		else if (type == QueueType::Compute) {
-			return queueManager.getComputeQueues().front();
-		}
-		else if (type == QueueType::Transfer) {
-			return queueManager.getTransferQueues().front();
-		}
-		else if (type == QueueType::Present) {
-			return queueManager.getPresentQueue();
-		}
-		else {
-			vkcv_log(LogLevel::ERROR, "Unknown queue type");
-			return queueManager.getGraphicsQueues().front();	// graphics is the most general queue
-		}
-	}
-
-	void beginCommandBuffer(const vk::CommandBuffer cmdBuffer, const vk::CommandBufferUsageFlags flags) {
-		const vk::CommandBufferBeginInfo beginInfo(flags);
-		cmdBuffer.begin(beginInfo);
-	}
-
-	void submitCommandBufferToQueue(
-		const vk::Queue						queue,
-		const vk::CommandBuffer				cmdBuffer,
-		const vk::Fence						fence,
-		const std::vector<vk::Semaphore>&	waitSemaphores,
-		const std::vector<vk::Semaphore>&	signalSemaphores) {
-
-		const std::vector<vk::PipelineStageFlags> waitDstStageMasks(waitSemaphores.size(), vk::PipelineStageFlagBits::eAllCommands);
-		const vk::SubmitInfo queueSubmitInfo(waitSemaphores, waitDstStageMasks, cmdBuffer, signalSemaphores);
-		queue.submit(queueSubmitInfo, fence);
-	}
-}
\ No newline at end of file
diff --git a/src/vkcv/CommandStreamManager.cpp b/src/vkcv/CommandStreamManager.cpp
index 5a5b359b912d9cef36e0b03379d7f0f6f0951381..b93cf0a812933ef3f1ea3f62689a5c03062e6e80 100644
--- a/src/vkcv/CommandStreamManager.cpp
+++ b/src/vkcv/CommandStreamManager.cpp
@@ -1,121 +1,112 @@
-#include "vkcv/CommandStreamManager.hpp"
+#include "CommandStreamManager.hpp"
 #include "vkcv/Core.hpp"
 
 #include "vkcv/Logger.hpp"
 
+#include <limits>
+
 namespace vkcv {
-	CommandStreamManager::CommandStreamManager() noexcept : m_core(nullptr){}
 
-	CommandStreamManager::~CommandStreamManager() noexcept {
-		for (const auto& stream : m_commandStreams) {
-			if (stream.cmdBuffer && stream.cmdBuffer) {
-				m_core->getContext().getDevice().freeCommandBuffers(stream.cmdPool, stream.cmdBuffer);
-			}
-		}
+	uint64_t CommandStreamManager::getIdFrom(const CommandStreamHandle &handle) const {
+		return handle.getId();
 	}
 
-	void CommandStreamManager::init(Core* core) {
-		if (!core) {
-			vkcv_log(LogLevel::ERROR, "Requires valid core pointer");
+	CommandStreamHandle CommandStreamManager::createById(uint64_t id,
+														 const HandleDestroyFunction &destroy) {
+		return CommandStreamHandle(id, destroy);
+	}
+
+	void CommandStreamManager::destroyById(uint64_t id) {
+		auto &stream = getById(id);
+
+		if (stream.cmdBuffer) {
+			getCore().getContext().getDevice().freeCommandBuffers(stream.cmdPool, stream.cmdBuffer);
+			stream.cmdBuffer = nullptr;
+			stream.callbacks.clear();
 		}
-		m_core = core;
 	}
 
-	CommandStreamHandle CommandStreamManager::createCommandStream(
-		const vk::Queue queue, 
-		vk::CommandPool cmdPool) {
+	CommandStreamManager::CommandStreamManager() noexcept :
+		HandleManager<CommandStreamEntry, CommandStreamHandle>() {}
 
-		const vk::CommandBuffer cmdBuffer = allocateCommandBuffer(m_core->getContext().getDevice(), cmdPool);
+	CommandStreamManager::~CommandStreamManager() noexcept {
+		clear();
+	}
 
-		CommandStream stream(cmdBuffer, queue, cmdPool);
-		beginCommandBuffer(stream.cmdBuffer, vk::CommandBufferUsageFlagBits::eOneTimeSubmit);
+	CommandStreamHandle CommandStreamManager::createCommandStream(const vk::Queue &queue,
+																  vk::CommandPool cmdPool) {
+		const vk::CommandBufferAllocateInfo info(cmdPool, vk::CommandBufferLevel::ePrimary, 1);
+		auto &device = getCore().getContext().getDevice();
 
-		// find unused stream
-		int unusedStreamIndex = -1;
-		for (int i = 0; i < m_commandStreams.size(); i++) {
-			if (m_commandStreams[i].cmdBuffer) {
-				// still in use
-			}
-			else {
-				unusedStreamIndex = i;
-				break;
+		const vk::CommandBuffer cmdBuffer = device.allocateCommandBuffers(info).front();
+
+		const vk::CommandBufferBeginInfo beginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit);
+		cmdBuffer.begin(beginInfo);
+
+		for (uint64_t id = 0; id < getCount(); id++) {
+			auto &stream = getById(id);
+
+			if (!(stream.cmdBuffer)) {
+				stream.cmdBuffer = cmdBuffer;
+				stream.cmdPool = cmdPool;
+				stream.queue = queue;
+
+				return createById(id, [&](uint64_t id) {
+					destroyById(id);
+				});
 			}
 		}
 
-        const bool foundUnusedStream = unusedStreamIndex >= 0;
-        if (foundUnusedStream) {
-            m_commandStreams[unusedStreamIndex] = stream;
-            return CommandStreamHandle(unusedStreamIndex);
-        }
-
-        CommandStreamHandle handle(m_commandStreams.size());
-        m_commandStreams.push_back(stream);
-        return handle;
-    }
-
-	void CommandStreamManager::recordCommandsToStream(
-		const CommandStreamHandle   handle, 
-		const RecordCommandFunction record) {
-
-		const size_t id = handle.getId();
-		if (id >= m_commandStreams.size()) {
-			vkcv_log(LogLevel::ERROR, "Requires valid handle");
-			return;
-		}
+		return add({ cmdBuffer, cmdPool, queue, {} });
+	}
 
-		CommandStream& stream = m_commandStreams[id];
+	void CommandStreamManager::recordCommandsToStream(const CommandStreamHandle &handle,
+													  const RecordCommandFunction &record) {
+		auto &stream = (*this) [handle];
 		record(stream.cmdBuffer);
 	}
 
-	void CommandStreamManager::addFinishCallbackToStream(
-		const CommandStreamHandle   handle, 
-		const FinishCommandFunction finish) {
-
-		const size_t id = handle.getId();
-		if (id >= m_commandStreams.size()) {
-			vkcv_log(LogLevel::ERROR, "Requires valid handle");
-			return;
-		}
-
-		CommandStream& stream = m_commandStreams[id];
+	void CommandStreamManager::addFinishCallbackToStream(const CommandStreamHandle &handle,
+														 const FinishCommandFunction &finish) {
+		auto &stream = (*this) [handle];
 		stream.callbacks.push_back(finish);
 	}
 
 	void CommandStreamManager::submitCommandStreamSynchronous(
-		const CommandStreamHandle   handle,
-		std::vector<vk::Semaphore>  &waitSemaphores,
-		std::vector<vk::Semaphore>  &signalSemaphores) {
-
-		const size_t id = handle.getId();
-		if (id >= m_commandStreams.size()) {
-			vkcv_log(LogLevel::ERROR, "Requires valid handle");
-			return;
-		}
-		CommandStream& stream = m_commandStreams[id];
+		const CommandStreamHandle &handle, std::vector<vk::Semaphore> &waitSemaphores,
+		std::vector<vk::Semaphore> &signalSemaphores) {
+		auto &stream = (*this) [handle];
 		stream.cmdBuffer.end();
 
-		const auto device = m_core->getContext().getDevice();
-		const vk::Fence waitFence = createFence(device);
-		submitCommandBufferToQueue(stream.queue, stream.cmdBuffer, waitFence, waitSemaphores, signalSemaphores);
-		waitForFence(device, waitFence);
-		device.destroyFence(waitFence);
+		const auto device = getCore().getContext().getDevice();
+		const vk::Fence waitFence = device.createFence({});
 
-		device.freeCommandBuffers(stream.cmdPool, stream.cmdBuffer);
-		stream.cmdBuffer    = nullptr;
-		stream.cmdPool      = nullptr;
-		stream.queue        = nullptr;
+		const std::vector<vk::PipelineStageFlags> waitDstStageMasks(
+			waitSemaphores.size(), vk::PipelineStageFlagBits::eAllCommands);
 
-		for (const auto& finishCallback : stream.callbacks) {
+		const vk::SubmitInfo queueSubmitInfo(waitSemaphores, waitDstStageMasks, stream.cmdBuffer,
+											 signalSemaphores);
+
+		stream.queue.submit(queueSubmitInfo, waitFence);
+		
+		const auto result = device.waitForFences(waitFence, true, std::numeric_limits<uint64_t>::max());
+		
+		if (result == vk::Result::eTimeout) {
+			device.waitIdle();
+		}
+
+		device.destroyFence(waitFence);
+		stream.queue = nullptr;
+
+		for (const auto &finishCallback : stream.callbacks) {
 			finishCallback();
 		}
 	}
 
-	vk::CommandBuffer CommandStreamManager::getStreamCommandBuffer(const CommandStreamHandle handle) {
-		const size_t id = handle.getId();
-		if (id >= m_commandStreams.size()) {
-			vkcv_log(LogLevel::ERROR, "Requires valid handle");
-			return nullptr;
-		}
-		return m_commandStreams[id].cmdBuffer;
+	vk::CommandBuffer
+	CommandStreamManager::getStreamCommandBuffer(const CommandStreamHandle &handle) {
+		auto &stream = (*this) [handle];
+		return stream.cmdBuffer;
 	}
-}
\ No newline at end of file
+
+} // namespace vkcv
\ No newline at end of file
diff --git a/src/vkcv/CommandStreamManager.hpp b/src/vkcv/CommandStreamManager.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..898f170c08494b941f58632443261afee6134f75
--- /dev/null
+++ b/src/vkcv/CommandStreamManager.hpp
@@ -0,0 +1,95 @@
+#pragma once
+
+#include <vector>
+#include <vulkan/vulkan.hpp>
+
+#include "vkcv/Event.hpp"
+#include "vkcv/EventFunctionTypes.hpp"
+
+#include "HandleManager.hpp"
+
+namespace vkcv {
+
+	/**
+	 * @brief Represents one command stream, into which commands can be recorded into.
+	 * Consists of a command buffer, the command buffer's command pool and a queue, as well as some
+	 * callbacks.
+	 */
+	struct CommandStreamEntry {
+		vk::CommandBuffer cmdBuffer;
+		vk::CommandPool cmdPool;
+		vk::Queue queue;
+		std::vector<FinishCommandFunction> callbacks;
+	};
+
+	/**
+	 * @brief Responsible for creation, deletion, callbacks and recording of command streams
+	 */
+	class CommandStreamManager : public HandleManager<CommandStreamEntry, CommandStreamHandle> {
+		friend class Core;
+
+	private:
+		[[nodiscard]] uint64_t getIdFrom(const CommandStreamHandle &handle) const override;
+
+		[[nodiscard]] CommandStreamHandle createById(uint64_t id,
+													 const HandleDestroyFunction &destroy) override;
+
+		void destroyById(uint64_t id) override;
+
+	public:
+		CommandStreamManager() noexcept;
+
+		~CommandStreamManager() noexcept override;
+
+		/**
+		 * @brief Creates a new command stream
+		 *
+		 * @param queue Queue the command buffer will be submitted to
+		 * @param cmdPool Command pool the command buffer will be allocated from
+		 * @return Handle that represents the #CommandStream
+		 */
+		CommandStreamHandle createCommandStream(const vk::Queue &queue, vk::CommandPool cmdPool);
+
+		/**
+		 * @brief Record vulkan commands to a #CommandStream, using a record function
+		 *
+		 * @param handle Command stream handle
+		 * @param record Function that records the vulkan commands
+		 */
+		void recordCommandsToStream(const CommandStreamHandle &handle,
+									const RecordCommandFunction &record);
+
+		/**
+		 * @brief Add a callback to a #CommandStream that is called
+		 * every time the command stream is submitted and finished
+		 *
+		 * @param handle Command stream handle
+		 * @param finish Callback that is called when a command stream submission is finished
+		 */
+		void addFinishCallbackToStream(const CommandStreamHandle &handle,
+									   const FinishCommandFunction &finish);
+
+		/**
+		 * @brief Submits a #CommandStream to it's queue and returns after execution is finished
+		 *
+		 * @param handle Command stream handle
+		 * @param waitSemaphores Semaphores that are waited upon before executing the recorded
+		 * commands
+		 * @param signalSemaphores Semaphores that are signaled when execution of the recorded
+		 * commands is finished
+		 */
+		void submitCommandStreamSynchronous(const CommandStreamHandle &handle,
+											std::vector<vk::Semaphore> &waitSemaphores,
+											std::vector<vk::Semaphore> &signalSemaphores);
+
+		/**
+		 * @brief Returns the underlying vulkan handle of a #CommandStream to be used for manual
+		 * command recording
+		 *
+		 * @param handle Command stream handle
+		 * @return Vulkan handle of the #CommandStream
+		 */
+		vk::CommandBuffer getStreamCommandBuffer(const CommandStreamHandle &handle);
+	};
+
+} // namespace vkcv
\ No newline at end of file
diff --git a/src/vkcv/ComputePipelineManager.cpp b/src/vkcv/ComputePipelineManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..453dddd1baf93337280fe381b7f77e88bd2e3517
--- /dev/null
+++ b/src/vkcv/ComputePipelineManager.cpp
@@ -0,0 +1,102 @@
+#include "ComputePipelineManager.hpp"
+
+#include "vkcv/Core.hpp"
+
+namespace vkcv {
+
+	uint64_t ComputePipelineManager::getIdFrom(const ComputePipelineHandle &handle) const {
+		return handle.getId();
+	}
+
+	ComputePipelineHandle ComputePipelineManager::createById(uint64_t id,
+															 const HandleDestroyFunction &destroy) {
+		return ComputePipelineHandle(id, destroy);
+	}
+
+	void ComputePipelineManager::destroyById(uint64_t id) {
+		auto &pipeline = getById(id);
+
+		if (pipeline.m_handle) {
+			getCore().getContext().getDevice().destroy(pipeline.m_handle);
+			pipeline.m_handle = nullptr;
+		}
+
+		if (pipeline.m_layout) {
+			getCore().getContext().getDevice().destroy(pipeline.m_layout);
+			pipeline.m_layout = nullptr;
+		}
+	}
+
+	ComputePipelineManager::ComputePipelineManager() noexcept :
+		HandleManager<ComputePipelineEntry, ComputePipelineHandle>() {}
+
+	vk::Result ComputePipelineManager::createShaderModule(vk::ShaderModule &module,
+														  const ShaderProgram &shaderProgram,
+														  const ShaderStage stage) {
+		std::vector<uint32_t> code = shaderProgram.getShaderBinary(stage);
+		vk::ShaderModuleCreateInfo moduleInfo({}, code.size() * sizeof(uint32_t), code.data());
+		return getCore().getContext().getDevice().createShaderModule(&moduleInfo, nullptr, &module);
+	}
+
+	ComputePipelineManager::~ComputePipelineManager() noexcept {
+		clear();
+	}
+
+	vk::Pipeline ComputePipelineManager::getVkPipeline(const ComputePipelineHandle &handle) const {
+		auto &pipeline = (*this) [handle];
+		return pipeline.m_handle;
+	}
+
+	vk::PipelineLayout
+	ComputePipelineManager::getVkPipelineLayout(const ComputePipelineHandle &handle) const {
+		auto &pipeline = (*this) [handle];
+		return pipeline.m_layout;
+	}
+
+	ComputePipelineHandle ComputePipelineManager::createComputePipeline(
+		const ShaderProgram &shaderProgram,
+		const std::vector<vk::DescriptorSetLayout> &descriptorSetLayouts) {
+		// Temporally handing over the Shader Program instead of a pipeline config
+		vk::ShaderModule computeModule {};
+		if (createShaderModule(computeModule, shaderProgram, ShaderStage::COMPUTE)
+			!= vk::Result::eSuccess)
+			return {};
+
+		vk::PipelineShaderStageCreateInfo pipelineComputeShaderStageInfo(
+			{}, vk::ShaderStageFlagBits::eCompute, computeModule, "main", nullptr);
+
+		vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo({}, descriptorSetLayouts);
+
+		const size_t pushConstantsSize = shaderProgram.getPushConstantsSize();
+		vk::PushConstantRange pushConstantRange(vk::ShaderStageFlagBits::eCompute, 0,
+												pushConstantsSize);
+		if (pushConstantsSize > 0) {
+			pipelineLayoutCreateInfo.setPushConstantRangeCount(1);
+			pipelineLayoutCreateInfo.setPPushConstantRanges(&pushConstantRange);
+		}
+
+		vk::PipelineLayout vkPipelineLayout {};
+		if (getCore().getContext().getDevice().createPipelineLayout(&pipelineLayoutCreateInfo,
+																	nullptr, &vkPipelineLayout)
+			!= vk::Result::eSuccess) {
+			getCore().getContext().getDevice().destroy(computeModule);
+			return {};
+		}
+
+		vk::ComputePipelineCreateInfo computePipelineCreateInfo {};
+		computePipelineCreateInfo.stage = pipelineComputeShaderStageInfo;
+		computePipelineCreateInfo.layout = vkPipelineLayout;
+
+		vk::Pipeline vkPipeline;
+		if (getCore().getContext().getDevice().createComputePipelines(
+				nullptr, 1, &computePipelineCreateInfo, nullptr, &vkPipeline)
+			!= vk::Result::eSuccess) {
+			getCore().getContext().getDevice().destroy(computeModule);
+			return ComputePipelineHandle();
+		}
+
+		getCore().getContext().getDevice().destroy(computeModule);
+		return add({ vkPipeline, vkPipelineLayout });
+	}
+
+} // namespace vkcv
\ No newline at end of file
diff --git a/src/vkcv/ComputePipelineManager.hpp b/src/vkcv/ComputePipelineManager.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..32fdc57943bff8d6b8194b9167a067de9a61f8b8
--- /dev/null
+++ b/src/vkcv/ComputePipelineManager.hpp
@@ -0,0 +1,80 @@
+#pragma once
+
+/**
+ * @authors Mark Mints, Tobias Frisch
+ * @file src/vkcv/ComputePipelineManager.hpp
+ * @brief Creation and handling of Compute Pipelines
+ */
+
+#include <vector>
+#include <vulkan/vulkan.hpp>
+
+#include "HandleManager.hpp"
+
+#include "vkcv/ComputePipelineConfig.hpp"
+#include "vkcv/ShaderProgram.hpp"
+
+namespace vkcv {
+
+	struct ComputePipelineEntry {
+		vk::Pipeline m_handle;
+		vk::PipelineLayout m_layout;
+	};
+
+	/**
+	 * @brief Class to manage compute pipelines.
+	 */
+	class ComputePipelineManager :
+		public HandleManager<ComputePipelineEntry, ComputePipelineHandle> {
+	private:
+		[[nodiscard]] uint64_t getIdFrom(const ComputePipelineHandle &handle) const override;
+
+		[[nodiscard]] ComputePipelineHandle
+		createById(uint64_t id, const HandleDestroyFunction &destroy) override;
+
+		/**
+		 * Destroys and deallocates compute pipeline represented by a given
+		 * compute pipeline handle id.
+		 *
+		 * @param id Compute pipeline handle id
+		 */
+		void destroyById(uint64_t id) override;
+
+		vk::Result createShaderModule(vk::ShaderModule &module, const ShaderProgram &shaderProgram,
+									  ShaderStage stage);
+
+	public:
+		ComputePipelineManager() noexcept;
+
+		~ComputePipelineManager() noexcept override; // dtor
+
+		/**
+		 * Returns a vk::Pipeline object by handle.
+		 * @param handle Directing to the requested pipeline.
+		 * @return vk::Pipeline.
+		 */
+		[[nodiscard]] vk::Pipeline getVkPipeline(const ComputePipelineHandle &handle) const;
+
+		/**
+		 * Returns a vk::PipelineLayout object by handle.
+		 * @param handle Directing to the requested pipeline.
+		 * @return vk::PipelineLayout.
+		 */
+		[[nodiscard]] vk::PipelineLayout
+		getVkPipelineLayout(const ComputePipelineHandle &handle) const;
+
+		/**
+		 * Creates a Compute Pipeline based on the set shader stages in the Config Struct.
+		 * This function is wrapped in /src/vkcv/Core.cpp by Core::createComputePipeline(const
+		 * ComputePipelineConfig &config). On application level it is necessary first to fill a
+		 * ComputePipelineConfig Struct.
+		 * @param shaderProgram Hands over all needed information for pipeline creation.
+		 * @param descriptorSetLayouts Hands over all needed information for pipeline creation.
+		 * @return A Handler to the created Compute Pipeline Object.
+		 */
+		ComputePipelineHandle
+		createComputePipeline(const ShaderProgram &shaderProgram,
+							  const std::vector<vk::DescriptorSetLayout> &descriptorSetLayouts);
+	};
+
+} // namespace vkcv
diff --git a/src/vkcv/Context.cpp b/src/vkcv/Context.cpp
index ac133d1affc81702ee1a19b3f66810e606bec58d..1d92268e244d62e6efc71bc2c9a3a6387bb5e7f0 100644
--- a/src/vkcv/Context.cpp
+++ b/src/vkcv/Context.cpp
@@ -1,147 +1,136 @@
 
-#include <GLFW/glfw3.h>
-
 #include "vkcv/Context.hpp"
+#include "vkcv/Core.hpp"
+#include "vkcv/Window.hpp"
 
-namespace vkcv
-{
-    Context::Context(Context &&other) noexcept:
-            m_Instance(other.m_Instance),
-            m_PhysicalDevice(other.m_PhysicalDevice),
-            m_Device(other.m_Device),
-            m_QueueManager(other.m_QueueManager)
-    {
-        other.m_Instance        = nullptr;
-        other.m_PhysicalDevice  = nullptr;
-        other.m_Device          = nullptr;
-    }
-
-    Context & Context::operator=(Context &&other) noexcept
-    {
-        m_Instance          = other.m_Instance;
-        m_PhysicalDevice    = other.m_PhysicalDevice;
-        m_Device            = other.m_Device;
-        m_QueueManager		= other.m_QueueManager;
-
-        other.m_Instance        = nullptr;
-        other.m_PhysicalDevice  = nullptr;
-        other.m_Device          = nullptr;
-
-        return *this;
-    }
-
-    Context::Context(vk::Instance instance,
-                     vk::PhysicalDevice physicalDevice,
-                     vk::Device device,
-					 QueueManager&& queueManager) noexcept :
-    m_Instance{instance},
-    m_PhysicalDevice{physicalDevice},
-    m_Device{device},
-    m_QueueManager{queueManager}
-    {}
-
-    Context::~Context() noexcept
-    {
-        m_Device.destroy();
-        m_Instance.destroy();
-    }
-
-    const vk::Instance &Context::getInstance() const
-    {
-        return m_Instance;
-    }
-
-    const vk::PhysicalDevice &Context::getPhysicalDevice() const
-    {
-        return m_PhysicalDevice;
-    }
-
-    const vk::Device &Context::getDevice() const
-    {
-        return m_Device;
-    }
-    
-    const QueueManager& Context::getQueueManager() const {
-    	return m_QueueManager;
-    }
+namespace vkcv {
 	
-	/**
-	 * @brief The physical device is evaluated by three categories:
-	 * discrete GPU vs. integrated GPU, amount of queues and its abilities, and VRAM.physicalDevice.
-	 * @param physicalDevice The physical device
-	 * @return Device score as integer
-	*/
-	int deviceScore(const vk::PhysicalDevice& physicalDevice)
-	{
-		int score = 0;
-		vk::PhysicalDeviceProperties properties = physicalDevice.getProperties();
-		std::vector<vk::QueueFamilyProperties> qFamilyProperties = physicalDevice.getQueueFamilyProperties();
-		
-		// for every queue family compute queue flag bits and the amount of queues
-		for (const auto& qFamily : qFamilyProperties) {
-			uint32_t qCount = qFamily.queueCount;
-			uint32_t bitCount = (static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eCompute) != 0)
-								+ (static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eGraphics) != 0)
-								+ (static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eTransfer) != 0)
-								+ (static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eSparseBinding) != 0);
-			score += static_cast<int>(qCount * bitCount);
-		}
-		
-		// compute the VRAM of the physical device
-		vk::PhysicalDeviceMemoryProperties memoryProperties = physicalDevice.getMemoryProperties();
-		auto vram = static_cast<int>(memoryProperties.memoryHeaps[0].size / static_cast<uint32_t>(1E9));
-		score *= vram;
-		
-		if (properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu) {
-			score *= 2;
-		}
-		else if (properties.deviceType != vk::PhysicalDeviceType::eIntegratedGpu) {
-			score = -1;
-		}
-		
-		return score;
+	Context::Context(Context &&other) noexcept :
+		m_Instance(other.m_Instance), m_PhysicalDevice(other.m_PhysicalDevice),
+		m_Device(other.m_Device), m_FeatureManager(std::move(other.m_FeatureManager)),
+		m_QueueManager(std::move(other.m_QueueManager)), m_Allocator(other.m_Allocator) {
+		other.m_Instance = nullptr;
+		other.m_PhysicalDevice = nullptr;
+		other.m_Device = nullptr;
+		other.m_Allocator = nullptr;
 	}
-	
+
+	Context &Context::operator=(Context &&other) noexcept {
+		m_Instance = other.m_Instance;
+		m_PhysicalDevice = other.m_PhysicalDevice;
+		m_Device = other.m_Device;
+		m_FeatureManager = std::move(other.m_FeatureManager);
+		m_QueueManager = std::move(other.m_QueueManager);
+		m_Allocator = other.m_Allocator;
+
+		other.m_Instance = nullptr;
+		other.m_PhysicalDevice = nullptr;
+		other.m_Device = nullptr;
+		other.m_Allocator = nullptr;
+
+		return *this;
+	}
+
+	Context::Context(vk::Instance instance, vk::PhysicalDevice physicalDevice, vk::Device device,
+					 FeatureManager &&featureManager, QueueManager &&queueManager,
+					 vma::Allocator &&allocator) noexcept :
+		m_Instance(instance),
+		m_PhysicalDevice(physicalDevice), m_Device(device),
+		m_FeatureManager(std::move(featureManager)), m_QueueManager(std::move(queueManager)),
+		m_Allocator(allocator) {}
+
+	Context::~Context() noexcept {
+		m_Allocator.destroy();
+		m_Device.destroy();
+		m_Instance.destroy();
+	}
+
+	const vk::Instance &Context::getInstance() const {
+		return m_Instance;
+	}
+
+	const vk::PhysicalDevice &Context::getPhysicalDevice() const {
+		return m_PhysicalDevice;
+	}
+
+	const vk::Device &Context::getDevice() const {
+		return m_Device;
+	}
+
+	const FeatureManager &Context::getFeatureManager() const {
+		return m_FeatureManager;
+	}
+
+	const QueueManager &Context::getQueueManager() const {
+		return m_QueueManager;
+	}
+
+	const vma::Allocator &Context::getAllocator() const {
+		return m_Allocator;
+	}
+
 	/**
-	 * @brief All existing physical devices will be evaluated by deviceScore.
+	 * @brief All existing physical devices will be evaluated.
 	 * @param instance The instance
-	 * @return The optimal physical device
-	 * @see Context.deviceScore
-	*/
-	vk::PhysicalDevice pickPhysicalDevice(vk::Instance& instance)
-	{
-		vk::PhysicalDevice phyDevice;
-		std::vector<vk::PhysicalDevice> devices = instance.enumeratePhysicalDevices();
-		
+	 * @param physicalDevice The optimal physical device
+	 * @return Returns if a suitable GPU is found as physical device
+	 */
+	static bool pickPhysicalDevice(const vk::Instance &instance,
+								   vk::PhysicalDevice &physicalDevice) {
+		const std::vector<vk::PhysicalDevice> &devices = instance.enumeratePhysicalDevices();
+
 		if (devices.empty()) {
-			throw std::runtime_error("failed to find GPUs with Vulkan support!");
+			vkcv_log(LogLevel::ERROR, "Failed to find GPUs with Vulkan support");
+			return false;
 		}
-		
-		int max_score = -1;
-		for (const auto& device : devices) {
-			int score = deviceScore(device);
-			if (score > max_score) {
-				max_score = score;
-				phyDevice = device;
+
+		vk::PhysicalDeviceType type = vk::PhysicalDeviceType::eOther;
+		vk::DeviceSize vramSize = 0;
+
+		for (const auto &device : devices) {
+			const auto &properties = device.getProperties();
+
+			if (vk::PhysicalDeviceType::eOther == type) {
+				type = properties.deviceType;
+				physicalDevice = device;
+				continue;
+			}
+
+			if (vk::PhysicalDeviceType::eDiscreteGpu != properties.deviceType)
+				continue;
+
+			if (vk::PhysicalDeviceType::eDiscreteGpu != type) {
+				type = properties.deviceType;
+				physicalDevice = device;
+				continue;
+			}
+
+			vk::PhysicalDeviceMemoryProperties memoryProperties =
+				physicalDevice.getMemoryProperties();
+			vk::DeviceSize maxSize = 0;
+
+			for (uint32_t i = 0; i < memoryProperties.memoryHeapCount; i++)
+				if (memoryProperties.memoryHeaps [i].size > maxSize)
+					maxSize = memoryProperties.memoryHeaps [i].size;
+
+			if (maxSize > vramSize) {
+				type = properties.deviceType;
+				physicalDevice = device;
+				vramSize = maxSize;
 			}
 		}
-		
-		if (max_score == -1) {
-			throw std::runtime_error("failed to find a suitable GPU!");
-		}
-		
-		return phyDevice;
+
+		return true;
 	}
-	
+
 	/**
-	 * @brief With the help of the reference "supported" all elements in "check" checked,
-	 * if they are supported by the physical device.
-	 * @param supported The reference that can be used to check "check"
-	 * @param check The elements to be checked
-	 * @return True, if all elements in "check" are supported
-	*/
-	bool checkSupport(std::vector<const char*>& supported, std::vector<const char*>& check)
-	{
+	 * @brief Check whether all string occurrences in "check" are contained in "supported"
+	 * @param supported The const vector const char* reference used to compare entries in "check"
+	 * @param check The const vector const char* reference elements to be checked by "supported"
+	 * @return True, if all elements in "check" are supported (contained in supported)
+	 */
+	bool checkSupport(const std::vector<const char*> &supported,
+					  const std::vector<const char*> &check) {
 		for (auto checkElem : check) {
 			bool found = false;
 			for (auto supportedElem : supported) {
@@ -155,143 +144,332 @@ namespace vkcv
 		}
 		return true;
 	}
-	
-	
-	std::vector<const char*> getRequiredExtensions() {
-		uint32_t glfwExtensionCount = 0;
-		const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
-		std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);
 
-#ifndef NDEBUG
-		extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
+	std::vector<std::string> getRequiredExtensions() {
+		std::vector<std::string> extensions = Window::getExtensions();
+
+#ifdef VULKAN_DEBUG_LABELS
+		extensions.emplace_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
 #endif
-		
+
 		return extensions;
 	}
-	
-	Context Context::create(const char *applicationName,
-							uint32_t applicationVersion,
-							std::vector<vk::QueueFlagBits> queueFlags,
-							std::vector<const char *> instanceExtensions,
-							std::vector<const char *> deviceExtensions) {
+
+	/**
+	 * Given the @p physicalDevice and the @p queuePriorities, the @p queueCreateInfos are computed.
+	 * First, the requested queues are sorted by priority depending on the availability of queues in
+	 * the queue families of the given
+	 * @p physicalDevice. Then check, if all requested queues are creatable. If so, the @p
+	 * queueCreateInfos will be computed. Furthermore, lists of index pairs (queueFamilyIndex,
+	 * queueIndex) for later referencing of the separate queues will be computed.
+	 * @param[in] physicalDevice The physical device
+	 * @param[in] queuePriorities The queue priorities used for the computation of @p
+	 * queueCreateInfos
+	 * @param[in] queueFlags The queue flags requesting the queues
+	 * @param[in,out] queueCreateInfos The queue create info structures to be created
+	 * @param[in,out] queuePairsGraphics The list of index pairs (queueFamilyIndex, queueIndex) of
+	 * queues of type vk::QueueFlagBits::eGraphics
+	 * @param[in,out] queuePairsCompute The list of index pairs (queueFamilyIndex, queueIndex) of
+	 * queues of type vk::QueueFlagBits::eCompute
+	 * @param[in,out] queuePairsTransfer The list of index pairs (queueFamilyIndex, queueIndex) of
+	 * queues of type vk::QueueFlagBits::eTransfer
+	 * @throws std::runtime_error If the requested queues from @p queueFlags are not creatable due
+	 * to insufficient availability.
+	 */
+	static void
+	queueCreateInfosQueueHandles(vk::PhysicalDevice &physicalDevice,
+								 const std::vector<float> &queuePriorities,
+								 const std::vector<vk::QueueFlagBits> &queueFlags,
+								 std::vector<vk::DeviceQueueCreateInfo> &queueCreateInfos,
+								 std::vector<std::pair<int, int>> &queuePairsGraphics,
+								 std::vector<std::pair<int, int>> &queuePairsCompute,
+								 std::vector<std::pair<int, int>> &queuePairsTransfer) {
+		queueCreateInfos = {};
+		queuePairsGraphics = {};
+		queuePairsCompute = {};
+		queuePairsTransfer = {};
+		std::vector<vk::QueueFamilyProperties> qFamilyProperties =
+			physicalDevice.getQueueFamilyProperties();
+
+		// check priorities of flags -> the lower prioCount the higher the priority
+		std::vector<int> prios;
+		for (auto flag : queueFlags) {
+			int prioCount = 0;
+			for (size_t i = 0; i < qFamilyProperties.size(); i++) {
+				prioCount += (static_cast<uint32_t>(flag & qFamilyProperties [i].queueFlags) != 0)
+						   * qFamilyProperties [i].queueCount;
+			}
+			prios.push_back(prioCount);
+		}
+		// resort flags with heighest priority before allocating the queues
+		std::vector<vk::QueueFlagBits> newFlags;
+		for (size_t i = 0; i < prios.size(); i++) {
+			auto minElem = std::min_element(prios.begin(), prios.end());
+			int index = minElem - prios.begin();
+			newFlags.push_back(queueFlags [index]);
+			prios [index] = std::numeric_limits<int>::max();
+		}
+
+		// create requested queues and check if more requested queues are supported
+		// herefore: create vector that updates available queues in each queue family
+		// structure: [qFamily_0, ..., qFamily_n] where
+		// - qFamily_i = [GraphicsCount, ComputeCount, TransferCount], 0 <= i <= n
+		std::vector<std::vector<int>> queueFamilyStatus, initialQueueFamilyStatus;
+
+		for (auto qFamily : qFamilyProperties) {
+			auto graphicsCount =
+				static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eGraphics) != 0 ?
+					qFamily.queueCount :
+					0;
+			auto computeCount =
+				static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eCompute) != 0 ?
+					qFamily.queueCount :
+					0;
+			auto transferCount =
+				static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eTransfer) != 0 ?
+					qFamily.queueCount :
+					0;
+			queueFamilyStatus.push_back({ static_cast<int>(graphicsCount),
+										  static_cast<int>(computeCount),
+										  static_cast<int>(transferCount) });
+		}
+
+		initialQueueFamilyStatus = queueFamilyStatus;
+		// check if every queue with the specified queue flag can be created
+		// this automatically checks for queue flag support!
+		for (auto qFlag : newFlags) {
+			bool found;
+			switch (qFlag) {
+			case vk::QueueFlagBits::eGraphics:
+				found = false;
+				for (size_t i = 0; i < queueFamilyStatus.size() && !found; i++) {
+					if (queueFamilyStatus [i][0] > 0) {
+						queuePairsGraphics.emplace_back(std::pair(
+							i, initialQueueFamilyStatus [i][0] - queueFamilyStatus [i][0]));
+						queueFamilyStatus [i][0]--;
+						queueFamilyStatus [i][1]--;
+						queueFamilyStatus [i][2]--;
+						found = true;
+					}
+				}
+				if (!found) {
+					for (size_t i = 0; i < queueFamilyStatus.size() && !found; i++) {
+						if (initialQueueFamilyStatus [i][0] > 0) {
+							queuePairsGraphics.emplace_back(std::pair(i, 0));
+							found = true;
+						}
+					}
+
+					vkcv_log(LogLevel::WARNING, "Not enough %s queues",
+							 vk::to_string(qFlag).c_str());
+				}
+				break;
+			case vk::QueueFlagBits::eCompute:
+				found = false;
+				for (size_t i = 0; i < queueFamilyStatus.size() && !found; i++) {
+					if (queueFamilyStatus [i][1] > 0) {
+						queuePairsCompute.emplace_back(std::pair(
+							i, initialQueueFamilyStatus [i][1] - queueFamilyStatus [i][1]));
+						queueFamilyStatus [i][0]--;
+						queueFamilyStatus [i][1]--;
+						queueFamilyStatus [i][2]--;
+						found = true;
+					}
+				}
+				if (!found) {
+					for (size_t i = 0; i < queueFamilyStatus.size() && !found; i++) {
+						if (initialQueueFamilyStatus [i][1] > 0) {
+							queuePairsCompute.emplace_back(std::pair(i, 0));
+							found = true;
+						}
+					}
+
+					vkcv_log(LogLevel::WARNING, "Not enough %s queues",
+							 vk::to_string(qFlag).c_str());
+				}
+				break;
+			case vk::QueueFlagBits::eTransfer:
+				found = false;
+				for (size_t i = 0; i < queueFamilyStatus.size() && !found; i++) {
+					if (queueFamilyStatus [i][2] > 0) {
+						queuePairsTransfer.emplace_back(std::pair(
+							i, initialQueueFamilyStatus [i][2] - queueFamilyStatus [i][2]));
+						queueFamilyStatus [i][0]--;
+						queueFamilyStatus [i][1]--;
+						queueFamilyStatus [i][2]--;
+						found = true;
+					}
+				}
+				if (!found) {
+					for (size_t i = 0; i < queueFamilyStatus.size() && !found; i++) {
+						if (initialQueueFamilyStatus [i][2] > 0) {
+							queuePairsTransfer.emplace_back(std::pair(i, 0));
+							found = true;
+						}
+					}
+
+					vkcv_log(LogLevel::WARNING, "Not enough %s queues",
+							 vk::to_string(qFlag).c_str());
+				}
+				break;
+			default:
+				vkcv_log(LogLevel::ERROR, "Invalid input for queue flag bits: %s",
+						 vk::to_string(qFlag).c_str());
+				break;
+			}
+		}
+
+		// create all requested queues
+		for (size_t i = 0; i < qFamilyProperties.size(); i++) {
+			uint32_t create = std::abs(initialQueueFamilyStatus [i][0] - queueFamilyStatus [i][0]);
+			if (create > 0) {
+				vk::DeviceQueueCreateInfo qCreateInfo(vk::DeviceQueueCreateFlags(), i, create,
+													  queuePriorities.data());
+				queueCreateInfos.push_back(qCreateInfo);
+			}
+		}
+	}
+
+	Context Context::create(const std::string &applicationName, uint32_t applicationVersion,
+							const std::vector<vk::QueueFlagBits> &queueFlags,
+							const Features &features,
+							const std::vector<const char*> &instanceExtensions) {
 		// check for layer support
-		
-		const std::vector<vk::LayerProperties>& layerProperties = vk::enumerateInstanceLayerProperties();
-		
+
+		const std::vector<vk::LayerProperties> &layerProperties =
+			vk::enumerateInstanceLayerProperties();
+
 		std::vector<const char*> supportedLayers;
 		supportedLayers.reserve(layerProperties.size());
-		
-		for (auto& elem : layerProperties) {
+
+		for (auto &elem : layerProperties) {
 			supportedLayers.push_back(elem.layerName);
 		}
 
 // if in debug mode, check if validation layers are supported. Enable them if supported
-#ifndef NDEBUG
-		std::vector<const char*> validationLayers = {
-				"VK_LAYER_KHRONOS_validation"
-		};
-		
+#ifdef VULKAN_VALIDATION_LAYERS
+		std::vector<const char*> validationLayers = { "VK_LAYER_KHRONOS_validation" };
+
 		if (!checkSupport(supportedLayers, validationLayers)) {
-			throw std::runtime_error("Validation layers requested but not available!");
+			vkcv_log_throw_error("Validation layers requested but not available!");
 		}
 #endif
-		
-		// check for extension support
-		std::vector<vk::ExtensionProperties> instanceExtensionProperties = vk::enumerateInstanceExtensionProperties();
-		
+
+		// check for instance extension support
+		std::vector<vk::ExtensionProperties> instanceExtensionProperties =
+			vk::enumerateInstanceExtensionProperties();
+
 		std::vector<const char*> supportedExtensions;
 		supportedExtensions.reserve(instanceExtensionProperties.size());
-		
-		for (auto& elem : instanceExtensionProperties) {
+
+		for (auto &elem : instanceExtensionProperties) {
 			supportedExtensions.push_back(elem.extensionName);
 		}
-		
-		if (!checkSupport(supportedExtensions, instanceExtensions)) {
-			throw std::runtime_error("The requested instance extensions are not supported!");
-		}
-		
+
 		// for GLFW: get all required extensions
-		std::vector<const char*> requiredExtensions = getRequiredExtensions();
-		instanceExtensions.insert(instanceExtensions.end(), requiredExtensions.begin(), requiredExtensions.end());
-		
-		const vk::ApplicationInfo applicationInfo(
-				applicationName,
-				applicationVersion,
-				"vkCV",
-				VK_MAKE_VERSION(0, 0, 1),
-				VK_HEADER_VERSION_COMPLETE
-		);
-		
+		auto requiredStrings = getRequiredExtensions();
+		std::vector<const char*> requiredExtensions;
+
+		for (const auto &extension : requiredStrings) {
+			requiredExtensions.push_back(extension.c_str());
+		}
+
+		requiredExtensions.insert(requiredExtensions.end(), instanceExtensions.begin(),
+								  instanceExtensions.end());
+
+		if (!checkSupport(supportedExtensions, requiredExtensions)) {
+			vkcv_log_throw_error("The requested instance extensions are not supported!");
+		}
+
+		const vk::ApplicationInfo applicationInfo(applicationName.c_str(), applicationVersion,
+												  VKCV_FRAMEWORK_NAME, VKCV_FRAMEWORK_VERSION,
+												  VK_HEADER_VERSION_COMPLETE);
+
 		vk::InstanceCreateInfo instanceCreateInfo(
-				vk::InstanceCreateFlags(),
-				&applicationInfo,
-				0,
-				nullptr,
-				static_cast<uint32_t>(instanceExtensions.size()),
-				instanceExtensions.data()
-		);
+			vk::InstanceCreateFlags(), &applicationInfo, 0, nullptr,
+			static_cast<uint32_t>(requiredExtensions.size()), requiredExtensions.data());
 
-#ifndef NDEBUG
+#ifdef VULKAN_VALIDATION_LAYERS
 		instanceCreateInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
 		instanceCreateInfo.ppEnabledLayerNames = validationLayers.data();
 #endif
-		
+
 		vk::Instance instance = vk::createInstance(instanceCreateInfo);
-		
+
 		std::vector<vk::PhysicalDevice> physicalDevices = instance.enumeratePhysicalDevices();
-		vk::PhysicalDevice physicalDevice = pickPhysicalDevice(instance);
-		
-		// check for physical device extension support
-		std::vector<vk::ExtensionProperties> deviceExtensionProperties = physicalDevice.enumerateDeviceExtensionProperties();
-		supportedExtensions.clear();
-		for (auto& elem : deviceExtensionProperties) {
-			supportedExtensions.push_back(elem.extensionName);
+		vk::PhysicalDevice physicalDevice;
+
+		if (!pickPhysicalDevice(instance, physicalDevice)) {
+			vkcv_log_throw_error("Picking suitable GPU as physical device failed!");
 		}
-		if (!checkSupport(supportedExtensions, deviceExtensions)) {
-			throw std::runtime_error("The requested device extensions are not supported by the physical device!");
+
+		FeatureManager featureManager(physicalDevice);
+
+#ifdef __APPLE__
+		featureManager.useExtension("VK_KHR_portability_subset", true);
+#endif
+
+		for (const auto &feature : features.getList()) {
+			feature(featureManager);
 		}
-		
+
+		const auto &extensions = featureManager.getActiveExtensions();
+
 		std::vector<vk::DeviceQueueCreateInfo> qCreateInfos;
-		
-		// create required queues
 		std::vector<float> qPriorities;
 		qPriorities.resize(queueFlags.size(), 1.f);
 		std::vector<std::pair<int, int>> queuePairsGraphics, queuePairsCompute, queuePairsTransfer;
-		
-		QueueManager::queueCreateInfosQueueHandles(physicalDevice, qPriorities, queueFlags, qCreateInfos, queuePairsGraphics, queuePairsCompute, queuePairsTransfer);
-		
+
+		queueCreateInfosQueueHandles(physicalDevice, qPriorities, queueFlags, qCreateInfos,
+									 queuePairsGraphics, queuePairsCompute, queuePairsTransfer);
+
 		vk::DeviceCreateInfo deviceCreateInfo(
-				vk::DeviceCreateFlags(),
-				qCreateInfos.size(),
-				qCreateInfos.data(),
-				0,
-				nullptr,
-				deviceExtensions.size(),
-				deviceExtensions.data(),
-				nullptr		// Should our device use some features??? If yes: TODO
-		);
+			vk::DeviceCreateFlags(), qCreateInfos.size(), qCreateInfos.data(), 0, nullptr,
+			extensions.size(), extensions.data(), nullptr, &(featureManager.getFeatures()));
 
-#ifndef NDEBUG
+#ifdef VULKAN_VALIDATION_LAYERS
 		deviceCreateInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
 		deviceCreateInfo.ppEnabledLayerNames = validationLayers.data();
 #endif
 
-		// FIXME: check if device feature is supported
-		vk::PhysicalDeviceFeatures deviceFeatures;
-		deviceFeatures.fragmentStoresAndAtomics = true;
-		deviceFeatures.geometryShader = true;
-		deviceCreateInfo.pEnabledFeatures = &deviceFeatures;
-
-		// Ablauf
-		// qCreateInfos erstellen --> braucht das Device
-		// device erstellen
-		// jetzt koennen wir mit dem device die queues erstellen
-		
 		vk::Device device = physicalDevice.createDevice(deviceCreateInfo);
+
+		QueueManager queueManager = QueueManager::create(
+				device,
+				queuePairsGraphics,
+				queuePairsCompute,
+				queuePairsTransfer
+		);
 		
-		QueueManager queueManager = QueueManager::create(device, queuePairsGraphics, queuePairsCompute, queuePairsTransfer);
+		const bool coherentDeviceMemory = featureManager.checkFeatures<vk::PhysicalDeviceCoherentMemoryFeaturesAMD>(
+				vk::StructureType::ePhysicalDeviceCoherentMemoryFeaturesAMD,
+				[](const vk::PhysicalDeviceCoherentMemoryFeaturesAMD &features) {
+					return features.deviceCoherentMemory;
+				}
+		);
+
+		vma::AllocatorCreateFlags vmaFlags;
+		if (coherentDeviceMemory) {
+			vmaFlags |= vma::AllocatorCreateFlagBits::eAmdDeviceCoherentMemory;
+		}
 		
-		return Context(instance, physicalDevice, device, std::move(queueManager));
+		const vma::AllocatorCreateInfo allocatorCreateInfo(
+				vmaFlags,
+				physicalDevice,
+				device,
+				0,
+				nullptr,
+				nullptr,
+				nullptr,
+				nullptr,
+				instance,
+				VK_HEADER_VERSION_COMPLETE
+		);
+
+		vma::Allocator allocator = vma::createAllocator(allocatorCreateInfo);
+
+		return Context(instance, physicalDevice, device, std::move(featureManager),
+					   std::move(queueManager), std::move(allocator));
 	}
- 
-}
+
+} // namespace vkcv
diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp
index 1492b1afa563543e6a9eef380295bcb71fef58b8..7fde9196d3187f3aafa7134325ee7d166d6cd23c 100644
--- a/src/vkcv/Core.cpp
+++ b/src/vkcv/Core.cpp
@@ -1,546 +1,1303 @@
 /**
- * @authors Artur Wasmut
+ * @authors Artur Wasmut, Alexander Gauggel, Tobias Frisch
  * @file src/vkcv/Core.cpp
  * @brief Handling of global states regarding dependencies
  */
 
 #include <GLFW/glfw3.h>
+#include <cmath>
 
-#include "vkcv/Core.hpp"
+#include "BufferManager.hpp"
+#include "CommandStreamManager.hpp"
+#include "ComputePipelineManager.hpp"
+#include "DescriptorSetLayoutManager.hpp"
+#include "DescriptorSetManager.hpp"
+#include "GraphicsPipelineManager.hpp"
+#include "ImageManager.hpp"
 #include "PassManager.hpp"
-#include "PipelineManager.hpp"
-#include "vkcv/BufferManager.hpp"
 #include "SamplerManager.hpp"
-#include "ImageManager.hpp"
-#include "DescriptorManager.hpp"
-#include "ImageLayoutTransitions.hpp"
-#include "vkcv/CommandStreamManager.hpp"
-#include <cmath>
+#include "WindowManager.hpp"
+#include "vkcv/BlitDownsampler.hpp"
+#include "vkcv/Core.hpp"
+#include "vkcv/Image.hpp"
 #include "vkcv/Logger.hpp"
 
-namespace vkcv
-{
-
-    Core Core::create(Window &window,
-                      const char *applicationName,
-                      uint32_t applicationVersion,
-                      std::vector<vk::QueueFlagBits> queueFlags,
-                      std::vector<const char *> instanceExtensions,
-                      std::vector<const char *> deviceExtensions)
-    {
-        Context context = Context::create(
-        		applicationName, applicationVersion,
-        		queueFlags,
-        		instanceExtensions,
-        		deviceExtensions
-		);
+namespace vkcv {
+
+	/**
+	 * @brief Generates a set of the family indices for all different kinds of
+	 * queues a given queue manager provides.
+	 *
+	 * @param[in] queueManager Queue manager
+	 * @return Set of queue family indices
+	 */
+	static std::unordered_set<int> generateQueueFamilyIndexSet(const QueueManager &queueManager) {
+		std::unordered_set<int> indexSet;
+
+		for (const auto &queue : queueManager.getGraphicsQueues()) {
+			indexSet.insert(queue.familyIndex);
+		}
+
+		for (const auto &queue : queueManager.getComputeQueues()) {
+			indexSet.insert(queue.familyIndex);
+		}
 
-        Swapchain swapChain = Swapchain::create(window, context);
-
-		 std::vector<vk::ImageView> swapchainImageViews = createSwapchainImageViews( context, swapChain);
-
-        const auto& queueManager = context.getQueueManager();
-        
-		const int						graphicQueueFamilyIndex	= queueManager.getGraphicsQueues()[0].familyIndex;
-		const std::unordered_set<int>	queueFamilySet			= generateQueueFamilyIndexSet(queueManager);
-		const auto						commandResources		= createCommandResources(context.getDevice(), queueFamilySet);
-		const auto						defaultSyncResources	= createSyncResources(context.getDevice());
-
-        return Core(std::move(context) , window, swapChain, swapchainImageViews, commandResources, defaultSyncResources);
-    }
-
-    const Context &Core::getContext() const
-    {
-        return m_Context;
-    }
-    
-    const Swapchain& Core::getSwapchain() const {
-    	return m_swapchain;
-    }
-
-    Core::Core(Context &&context, Window &window, const Swapchain& swapChain,  std::vector<vk::ImageView> swapchainImageViews,
-        const CommandResources& commandResources, const SyncResources& syncResources) noexcept :
-            m_Context(std::move(context)),
-            m_window(window),
-            m_swapchain(swapChain),
-            m_PassManager{std::make_unique<PassManager>(m_Context.m_Device)},
-            m_PipelineManager{std::make_unique<PipelineManager>(m_Context.m_Device)},
-            m_DescriptorManager(std::make_unique<DescriptorManager>(m_Context.m_Device)),
-            m_BufferManager{std::unique_ptr<BufferManager>(new BufferManager())},
-            m_SamplerManager(std::unique_ptr<SamplerManager>(new SamplerManager(m_Context.m_Device))),
-            m_ImageManager{std::unique_ptr<ImageManager>(new ImageManager(*m_BufferManager))},
-            m_CommandStreamManager{std::unique_ptr<CommandStreamManager>(new CommandStreamManager)},
-            m_CommandResources(commandResources),
-            m_SyncResources(syncResources)
-	{
-		m_BufferManager->m_core = this;
-		m_BufferManager->init();
-		m_CommandStreamManager->init(this);
-
-		m_ImageManager->m_core = this;
-		
-		e_resizeHandle = m_window.e_resize.add( [&](int width, int height) {
-			m_swapchain.signalSwapchainRecreation();
-		});
-
-		const auto swapchainImages = m_Context.getDevice().getSwapchainImagesKHR(m_swapchain.getSwapchain());
-		m_ImageManager->setSwapchainImages(
-			swapchainImages, 
-			swapchainImageViews, 
-			swapChain.getExtent().width,
-			swapChain.getExtent().height,
-			swapChain.getFormat());
+		for (const auto &queue : queueManager.getTransferQueues()) {
+			indexSet.insert(queue.familyIndex);
+		}
+
+		indexSet.insert(queueManager.getPresentQueue().familyIndex);
+		return indexSet;
+	}
+
+	/**
+	 * @brief Creates and returns a vector of newly allocated command pools
+	 * for each different queue family index in a given set.
+	 *
+	 * @param[in,out] device Vulkan device
+	 * @param[in] familyIndexSet Set of queue family indices
+	 * @return New command pools
+	 */
+	static std::vector<vk::CommandPool>
+	createCommandPools(const vk::Device &device, const std::unordered_set<int> &familyIndexSet) {
+		std::vector<vk::CommandPool> commandPoolsPerQueueFamily;
+		commandPoolsPerQueueFamily.resize(familyIndexSet.size());
+
+		const vk::CommandPoolCreateFlags poolFlags = vk::CommandPoolCreateFlagBits::eTransient;
+		for (const int familyIndex : familyIndexSet) {
+			const vk::CommandPoolCreateInfo poolCreateInfo(poolFlags, familyIndex);
+			commandPoolsPerQueueFamily [familyIndex] =
+				device.createCommandPool(poolCreateInfo, nullptr, {});
+		}
+
+		return commandPoolsPerQueueFamily;
+	}
+
+	Core Core::create(const std::string &applicationName, uint32_t applicationVersion,
+					  const std::vector<vk::QueueFlagBits> &queueFlags, const Features &features,
+					  const std::vector<const char*> &instanceExtensions) {
+		Context context = Context::create(applicationName, applicationVersion, queueFlags, features,
+										  instanceExtensions);
+
+		return Core(std::move(context));
+	}
+
+	const Context &Core::getContext() const {
+		return m_Context;
+	}
+
+	Core::Core(Context &&context) noexcept :
+		m_Context(std::move(context)), m_PassManager(std::make_unique<PassManager>()),
+		m_GraphicsPipelineManager(std::make_unique<GraphicsPipelineManager>()),
+		m_ComputePipelineManager(std::make_unique<ComputePipelineManager>()),
+		m_DescriptorSetLayoutManager(std::make_unique<DescriptorSetLayoutManager>()),
+		m_DescriptorSetManager(std::make_unique<DescriptorSetManager>()),
+		m_BufferManager(std::make_unique<BufferManager>()),
+		m_SamplerManager(std::make_unique<SamplerManager>()),
+		m_ImageManager(std::make_unique<ImageManager>()),
+		m_CommandStreamManager { std::make_unique<CommandStreamManager>() },
+		m_WindowManager(std::make_unique<WindowManager>()),
+		m_SwapchainManager(std::make_unique<SwapchainManager>()), m_CommandPools(),
+		m_RenderFinished(), m_SwapchainImageAcquired(), m_downsampler(nullptr) {
+		m_CommandPools = createCommandPools(
+			m_Context.getDevice(), generateQueueFamilyIndexSet(m_Context.getQueueManager()));
+
+		m_RenderFinished = m_Context.getDevice().createSemaphore({});
+		m_SwapchainImageAcquired = m_Context.getDevice().createSemaphore({});
+
+		m_PassManager->init(*this);
+		m_GraphicsPipelineManager->init(*this);
+		m_ComputePipelineManager->init(*this);
+		m_DescriptorSetLayoutManager->init(*this);
+		m_DescriptorSetManager->init(*this, *m_DescriptorSetLayoutManager);
+		m_BufferManager->init(*this);
+		m_SamplerManager->init(*this);
+		m_ImageManager->init(*this, *m_BufferManager);
+		m_CommandStreamManager->init(*this);
+		m_SwapchainManager->init(*this);
+		m_downsampler = std::unique_ptr<Downsampler>(new BlitDownsampler(*this, *m_ImageManager));
 	}
 
 	Core::~Core() noexcept {
-    	m_window.e_resize.remove(e_resizeHandle);
-    	
 		m_Context.getDevice().waitIdle();
 
-		destroyCommandResources(m_Context.getDevice(), m_CommandResources);
-		destroySyncResources(m_Context.getDevice(), m_SyncResources);
+		for (const vk::CommandPool &pool : m_CommandPools) {
+			m_Context.getDevice().destroyCommandPool(pool);
+		}
+
+		m_Context.getDevice().destroySemaphore(m_RenderFinished);
+		m_Context.getDevice().destroySemaphore(m_SwapchainImageAcquired);
+	}
+
+	GraphicsPipelineHandle Core::createGraphicsPipeline(const GraphicsPipelineConfig &config) {
+		return m_GraphicsPipelineManager->createPipeline(config, *m_PassManager,
+														 *m_DescriptorSetLayoutManager);
+	}
+
+	ComputePipelineHandle Core::createComputePipeline(const ComputePipelineConfig &config) {
+		std::vector<vk::DescriptorSetLayout> layouts;
+		layouts.resize(config.getDescriptorSetLayouts().size());
+
+		for (size_t i = 0; i < layouts.size(); i++) {
+			layouts [i] = m_DescriptorSetLayoutManager
+							  ->getDescriptorSetLayout(config.getDescriptorSetLayouts() [i])
+							  .vulkanHandle;
+		}
 
-		m_Context.m_Device.destroySwapchainKHR(m_swapchain.getSwapchain());
-		m_Context.m_Instance.destroySurfaceKHR(m_swapchain.getSurface());
+		return m_ComputePipelineManager->createComputePipeline(config.getShaderProgram(), layouts);
 	}
 
-    PipelineHandle Core::createGraphicsPipeline(const PipelineConfig &config)
-    {
-        return m_PipelineManager->createPipeline(config, *m_PassManager);
-    }
+	PassHandle Core::createPass(const PassConfig &config) {
+		return m_PassManager->createPass(config);
+	}
 
-    PipelineHandle Core::createComputePipeline(
-        const ShaderProgram &shaderProgram, 
-        const std::vector<vk::DescriptorSetLayout>& descriptorSetLayouts)
-    {
-        return m_PipelineManager->createComputePipeline(shaderProgram, descriptorSetLayouts);
-    }
+	const PassConfig &Core::getPassConfiguration(const vkcv::PassHandle &pass) {
+		return m_PassManager->getPassConfig(pass);
+	}
 
+	BufferHandle Core::createBuffer(BufferType type, const TypeGuard &typeGuard, size_t count,
+									BufferMemoryType memoryType, bool readable) {
+		return m_BufferManager->createBuffer(typeGuard, type, memoryType,
+											 count * typeGuard.typeSize(), readable);
+	}
 
-    PassHandle Core::createPass(const PassConfig &config)
-    {
-        return m_PassManager->createPass(config);
-    }
+	BufferHandle Core::createBuffer(BufferType type, size_t size, BufferMemoryType memoryType,
+									bool readable) {
+		return m_BufferManager->createBuffer(TypeGuard(1), type, memoryType, size, readable);
+	}
+
+	vk::Buffer Core::getBuffer(const BufferHandle &buffer) const {
+		return m_BufferManager->getBuffer(buffer);
+	}
+
+	BufferType Core::getBufferType(const BufferHandle &handle) const {
+		return m_BufferManager->getBufferType(handle);
+	}
+
+	BufferMemoryType Core::getBufferMemoryType(const BufferHandle &handle) const {
+		return m_BufferManager->getBufferMemoryType(handle);
+	}
+
+	size_t Core::getBufferSize(const BufferHandle &handle) const {
+		return m_BufferManager->getBufferSize(handle);
+	}
+
+	void Core::fillBuffer(const BufferHandle &handle, const void* data, size_t size,
+						  size_t offset) {
+		m_BufferManager->fillBuffer(handle, data, size, offset);
+	}
+
+	void Core::readBuffer(const BufferHandle &handle, void* data, size_t size, size_t offset) {
+		m_BufferManager->readBuffer(handle, data, size, offset);
+	}
+
+	void* Core::mapBuffer(const BufferHandle &handle, size_t offset, size_t size) {
+		return m_BufferManager->mapBuffer(handle, offset, size);
+	}
+
+	void Core::unmapBuffer(const BufferHandle &handle) {
+		m_BufferManager->unmapBuffer(handle);
+	}
+
+	Result Core::acquireSwapchainImage(const SwapchainHandle &swapchainHandle) {
+		uint32_t imageIndex;
+		vk::Result result;
 
-	Result Core::acquireSwapchainImage() {
-    	uint32_t imageIndex;
-		
-    	vk::Result result;
-    	
 		try {
 			result = m_Context.getDevice().acquireNextImageKHR(
-					m_swapchain.getSwapchain(),
-					std::numeric_limits<uint64_t>::max(),
-					m_SyncResources.swapchainImageAcquired,
-					nullptr,
-					&imageIndex, {}
-			);
-		} catch (vk::OutOfDateKHRError e) {
+				m_SwapchainManager->getSwapchain(swapchainHandle).m_Swapchain,
+				std::numeric_limits<uint64_t>::max(), m_SwapchainImageAcquired, nullptr,
+				&imageIndex, {});
+		} catch (const vk::OutOfDateKHRError &e) {
 			result = vk::Result::eErrorOutOfDateKHR;
+		} catch (const vk::DeviceLostError &e) {
+			result = vk::Result::eErrorDeviceLost;
 		}
-		
-		if (result != vk::Result::eSuccess) {
+
+		if ((result != vk::Result::eSuccess) && (result != vk::Result::eSuboptimalKHR)) {
 			vkcv_log(LogLevel::ERROR, "%s", vk::to_string(result).c_str());
 			return Result::ERROR;
+		} else if (result == vk::Result::eSuboptimalKHR) {
+			vkcv_log(LogLevel::WARNING, "Acquired image is suboptimal");
+			m_SwapchainManager->signalRecreation(swapchainHandle);
 		}
-		
+
 		m_currentSwapchainImageIndex = imageIndex;
 		return Result::SUCCESS;
 	}
 
-	bool Core::beginFrame(uint32_t& width, uint32_t& height) {
-		if (m_swapchain.shouldUpdateSwapchain()) {
+	bool Core::beginFrame(uint32_t &width, uint32_t &height, const WindowHandle &windowHandle) {
+		const Window &window = m_WindowManager->getWindow(windowHandle);
+		const SwapchainHandle swapchainHandle = window.getSwapchain();
+
+		if (m_SwapchainManager->shouldUpdateSwapchain(swapchainHandle)) {
 			m_Context.getDevice().waitIdle();
 
-			m_swapchain.updateSwapchain(m_Context, m_window);
-			const auto swapchainViews = createSwapchainImageViews(m_Context, m_swapchain);
-			const auto swapchainImages = m_Context.getDevice().getSwapchainImagesKHR(m_swapchain.getSwapchain());
+			m_SwapchainManager->updateSwapchain(swapchainHandle, window);
 
-			m_ImageManager->setSwapchainImages(swapchainImages, swapchainViews, width, height, m_swapchain.getFormat());
+			if (!m_SwapchainManager->getSwapchain(swapchainHandle).m_Swapchain) {
+				return false;
+			}
+
+			setSwapchainImages(swapchainHandle);
 		}
-		
-    	if (acquireSwapchainImage() != Result::SUCCESS) {
-			vkcv_log(LogLevel::ERROR, "Acquire failed");
-    		
-    		m_currentSwapchainImageIndex = std::numeric_limits<uint32_t>::max();
-    	}
-		
-		m_Context.getDevice().waitIdle(); // TODO: this is a sin against graphics programming, but its getting late - Alex
-		
-		const auto& extent = m_swapchain.getExtent();
-		
+
+		const auto &extent = m_SwapchainManager->getExtent(swapchainHandle);
+
 		width = extent.width;
 		height = extent.height;
-		
+
+		if ((width < MIN_SURFACE_SIZE) || (height < MIN_SURFACE_SIZE)) {
+			return false;
+		}
+
+		if (acquireSwapchainImage(swapchainHandle) != Result::SUCCESS) {
+			vkcv_log(LogLevel::ERROR, "Acquire failed");
+
+			m_currentSwapchainImageIndex = std::numeric_limits<uint32_t>::max();
+		}
+
+		m_Context.getDevice().waitIdle(); // TODO: this is a sin against graphics programming, but
+										  // its getting late - Alex
+
 		m_ImageManager->setCurrentSwapchainImageIndex(m_currentSwapchainImageIndex);
 
 		return (m_currentSwapchainImageIndex != std::numeric_limits<uint32_t>::max());
 	}
 
-	void Core::recordDrawcallsToCmdStream(
-		const CommandStreamHandle       cmdStreamHandle,
-		const PassHandle                renderpassHandle, 
-		const PipelineHandle            pipelineHandle, 
-        const PushConstantData          &pushConstantData,
-        const std::vector<DrawcallInfo> &drawcalls,
-		const std::vector<ImageHandle>  &renderTargets) {
+	static std::array<uint32_t, 2>
+	getWidthHeightFromRenderTargets(const std::vector<ImageHandle> &renderTargets,
+									const vk::Extent2D &swapchainExtent,
+									const ImageManager &imageManager) {
 
-		if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) {
-			return;
-		}
+		std::array<uint32_t, 2> widthHeight;
 
-		uint32_t width;
-		uint32_t height;
 		if (renderTargets.size() > 0) {
-			const vkcv::ImageHandle firstImage = renderTargets[0];
+			const vkcv::ImageHandle firstImage = renderTargets [0];
 			if (firstImage.isSwapchainImage()) {
-				const auto& swapchainExtent = m_swapchain.getExtent();
-				width = swapchainExtent.width;
-				height = swapchainExtent.height;
+				widthHeight [0] = swapchainExtent.width;
+				widthHeight [1] = swapchainExtent.height;
+			} else {
+				widthHeight [0] = imageManager.getImageWidth(firstImage);
+				widthHeight [1] = imageManager.getImageHeight(firstImage);
 			}
-			else {
-				width = m_ImageManager->getImageWidth(firstImage);
-				height = m_ImageManager->getImageHeight(firstImage);
+		} else {
+			widthHeight [0] = 1;
+			widthHeight [1] = 1;
+		}
+		// TODO: validate that width/height match for all attachments
+		return widthHeight;
+	}
+
+	static vk::Framebuffer createFramebuffer(const std::vector<ImageHandle> &renderTargets,
+											 const ImageManager &imageManager,
+											 const vk::Extent2D &renderExtent,
+											 const vk::RenderPass &renderpass,
+											 const vk::Device &device) {
+
+		std::vector<vk::ImageView> attachmentsViews;
+		for (const ImageHandle &handle : renderTargets) {
+			attachmentsViews.push_back(imageManager.getVulkanImageView(handle));
+		}
+
+		const vk::FramebufferCreateInfo createInfo(
+			{}, renderpass, static_cast<uint32_t>(attachmentsViews.size()), attachmentsViews.data(),
+			renderExtent.width, renderExtent.height, 1);
+
+		return device.createFramebuffer(createInfo);
+	}
+
+	void transitionRendertargetsToAttachmentLayout(const std::vector<ImageHandle> &renderTargets,
+												   ImageManager &imageManager,
+												   const vk::CommandBuffer cmdBuffer) {
+
+		for (const ImageHandle &handle : renderTargets) {
+			const bool isDepthImage = isDepthFormat(imageManager.getImageFormat(handle));
+			const vk::ImageLayout targetLayout =
+				isDepthImage ? vk::ImageLayout::eDepthStencilAttachmentOptimal :
+							   vk::ImageLayout::eColorAttachmentOptimal;
+			imageManager.recordImageLayoutTransition(handle, 0, 0, targetLayout, cmdBuffer);
+		}
+	}
+
+	std::vector<vk::ClearValue>
+	createAttachmentClearValues(const std::vector<AttachmentDescription> &attachments) {
+		std::vector<vk::ClearValue> clearValues;
+		for (const auto &attachment : attachments) {
+			if (attachment.getLoadOperation() == AttachmentOperation::CLEAR) {
+				clearValues.push_back(attachment.getClearValue());
 			}
 		}
-		else {
-			width = 1;
-			height = 1;
+		return clearValues;
+	}
+
+	void recordDynamicViewport(vk::CommandBuffer cmdBuffer, uint32_t width, uint32_t height) {
+		vk::Viewport dynamicViewport(0.0f, 0.0f, static_cast<float>(width),
+									 static_cast<float>(height), 0.0f, 1.0f);
+
+		vk::Rect2D dynamicScissor({ 0, 0 }, { width, height });
+
+		cmdBuffer.setViewport(0, 1, &dynamicViewport);
+		cmdBuffer.setScissor(0, 1, &dynamicScissor);
+	}
+
+	vk::IndexType getIndexType(IndexBitCount indexByteCount) {
+		switch (indexByteCount) {
+		case IndexBitCount::Bit8:
+			return vk::IndexType::eUint8EXT;
+		case IndexBitCount::Bit16:
+			return vk::IndexType::eUint16;
+		case IndexBitCount::Bit32:
+			return vk::IndexType::eUint32;
+		default:
+			vkcv_log(LogLevel::ERROR, "unknown Enum");
+			return vk::IndexType::eNoneKHR;
+		}
+	}
+
+	static void recordDrawcall(const DescriptorSetManager &descriptorSetManager,
+							   const BufferManager &bufferManager, const InstanceDrawcall &drawcall,
+							   vk::CommandBuffer cmdBuffer, vk::PipelineLayout pipelineLayout,
+							   const PushConstants &pushConstants, size_t drawcallIndex) {
+
+		const auto &vertexData = drawcall.getVertexData();
+
+		for (uint32_t i = 0; i < vertexData.getVertexBufferBindings().size(); i++) {
+			const auto &vertexBinding = vertexData.getVertexBufferBindings() [i];
+
+			cmdBuffer.bindVertexBuffers(i, bufferManager.getBuffer(vertexBinding.buffer),
+										vertexBinding.offset);
+		}
+
+		for (const auto &usage : drawcall.getDescriptorSetUsages()) {
+			cmdBuffer.bindDescriptorSets(
+				vk::PipelineBindPoint::eGraphics, pipelineLayout, usage.location,
+				descriptorSetManager.getDescriptorSet(usage.descriptorSet).vulkanHandle,
+				usage.dynamicOffsets);
 		}
-		// TODO: validate that width/height match for all attachments
 
-		const vk::RenderPass renderpass = m_PassManager->getVkPass(renderpassHandle);
-		const PassConfig passConfig = m_PassManager->getPassConfig(renderpassHandle);
+		if (pushConstants.getSizePerDrawcall() > 0) {
+			cmdBuffer.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eAll, 0,
+									pushConstants.getSizePerDrawcall(),
+									pushConstants.getDrawcallData(drawcallIndex));
+		}
+
+		if (vertexData.getIndexBuffer()) {
+			cmdBuffer.bindIndexBuffer(bufferManager.getBuffer(vertexData.getIndexBuffer()), 0,
+									  getIndexType(vertexData.getIndexBitCount()));
+
+			cmdBuffer.drawIndexed(vertexData.getCount(), drawcall.getInstanceCount(), 0, 0, {});
+		} else {
+			cmdBuffer.draw(vertexData.getCount(), drawcall.getInstanceCount(), 0, 0, {});
+		}
+	}
+
+	static void recordGraphicsPipeline(Core &core, CommandStreamManager &cmdStreamManager,
+									   GraphicsPipelineManager &pipelineManager,
+									   PassManager &passManager, ImageManager &imageManager,
+									   const CommandStreamHandle &cmdStreamHandle,
+									   const GraphicsPipelineHandle &pipelineHandle,
+									   const PushConstants &pushConstants,
+									   const std::vector<ImageHandle> &renderTargets,
+									   const WindowHandle &windowHandle,
+									   const RecordCommandFunction &record) {
+
+		const SwapchainHandle swapchainHandle = core.getWindow(windowHandle).getSwapchain();
+
+		const std::array<uint32_t, 2> extent = getWidthHeightFromRenderTargets(
+			renderTargets, core.getSwapchainExtent(swapchainHandle), imageManager);
+
+		const auto width = extent [0];
+		const auto height = extent [1];
+
+		const PassHandle &passHandle = pipelineManager.getPipelineConfig(pipelineHandle).getPass();
+
+		const vk::RenderPass renderPass = passManager.getVkPass(passHandle);
+		const PassConfig passConfig = passManager.getPassConfig(passHandle);
+
+		const auto &attachments = passConfig.getAttachments();
+		const auto &layouts = passManager.getLayouts(passHandle);
 
-		const vk::Pipeline pipeline		= m_PipelineManager->getVkPipeline(pipelineHandle);
-		const vk::PipelineLayout pipelineLayout = m_PipelineManager->getVkPipelineLayout(pipelineHandle);
+		if (renderTargets.size() != layouts.size()) {
+			vkcv_log(LogLevel::ERROR, "Amount of render targets does not match specified pipeline");
+			return;
+		}
+
+		const vk::Pipeline pipeline = pipelineManager.getVkPipeline(pipelineHandle);
 		const vk::Rect2D renderArea(vk::Offset2D(0, 0), vk::Extent2D(width, height));
 
-		std::vector<vk::ImageView> attachmentsViews;
-		for (const ImageHandle handle : renderTargets) {
-			vk::ImageView targetHandle;
-			const auto cmdBuffer = m_CommandStreamManager->getStreamCommandBuffer(cmdStreamHandle);
-
-			targetHandle = m_ImageManager->getVulkanImageView(handle);
-			const bool isDepthImage = isDepthFormat(m_ImageManager->getImageFormat(handle));
-			const vk::ImageLayout targetLayout = 
-				isDepthImage ? vk::ImageLayout::eDepthStencilAttachmentOptimal : vk::ImageLayout::eColorAttachmentOptimal;
-			m_ImageManager->recordImageLayoutTransition(handle, targetLayout, cmdBuffer);
-			attachmentsViews.push_back(targetHandle);
-		}
-		
-        const vk::FramebufferCreateInfo createInfo(
-            {},
-            renderpass,
-            static_cast<uint32_t>(attachmentsViews.size()),
-            attachmentsViews.data(),
-            width,
-            height,
-            1
-		);
-		
-		vk::Framebuffer framebuffer = m_Context.m_Device.createFramebuffer(createInfo);
-        
-        if (!framebuffer) {
+		vk::CommandBuffer cmdBuffer = cmdStreamManager.getStreamCommandBuffer(cmdStreamHandle);
+		transitionRendertargetsToAttachmentLayout(renderTargets, imageManager, cmdBuffer);
+
+		for (size_t i = 0; i < layouts.size(); i++) {
+			imageManager.recordImageLayoutTransition(renderTargets [i], 0, 0, layouts [i],
+													 cmdBuffer);
+		}
+
+		const vk::Framebuffer framebuffer =
+			createFramebuffer(renderTargets, imageManager, renderArea.extent, renderPass,
+							  core.getContext().getDevice());
+
+		if (!framebuffer) {
 			vkcv_log(LogLevel::ERROR, "Failed to create temporary framebuffer");
-            return;
-        }
+			return;
+		}
 
-        vk::Viewport dynamicViewport(
-        		0.0f, 0.0f,
-            	static_cast<float>(width), static_cast<float>(height),
-            0.0f, 1.0f
-		);
+		auto submitFunction = [&](const vk::CommandBuffer &cmdBuffer) {
+			const std::vector<vk::ClearValue> clearValues =
+				createAttachmentClearValues(attachments);
 
-        vk::Rect2D dynamicScissor({0, 0}, {width, height});
+			const vk::RenderPassBeginInfo beginInfo(renderPass, framebuffer, renderArea,
+													clearValues.size(), clearValues.data());
 
-		auto &bufferManager = m_BufferManager;
+			cmdBuffer.beginRenderPass(beginInfo, {}, {});
+			cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {});
 
-		SubmitInfo submitInfo;
-		submitInfo.queueType = QueueType::Graphics;
-		submitInfo.signalSemaphores = { m_SyncResources.renderFinished };
+			const GraphicsPipelineConfig &pipeConfig =
+				pipelineManager.getPipelineConfig(pipelineHandle);
 
-		auto submitFunction = [&](const vk::CommandBuffer& cmdBuffer) {
-            std::vector<vk::ClearValue> clearValues;
+			if (pipeConfig.isViewportDynamic()) {
+				recordDynamicViewport(cmdBuffer, width, height);
+			}
 
-            for (const auto& attachment : passConfig.attachments) {
-                if (attachment.load_operation == AttachmentOperation::CLEAR) {
-                    float clear = 0.0f;
+			if (record) {
+				record(cmdBuffer);
+			}
 
-                    if (isDepthFormat(attachment.format)) {
-                        clear = 1.0f;
-                    }
+			cmdBuffer.endRenderPass();
+		};
 
-                    clearValues.emplace_back(std::array<float, 4>{
-                            clear,
-                            clear,
-                            clear,
-                            1.f
-                    });
-                }
-            }
+		auto finishFunction = [framebuffer, &core]() {
+			core.getContext().getDevice().destroy(framebuffer);
+		};
 
-            const vk::RenderPassBeginInfo beginInfo(renderpass, framebuffer, renderArea, clearValues.size(), clearValues.data());
-            const vk::SubpassContents subpassContents = {};
-            cmdBuffer.beginRenderPass(beginInfo, subpassContents, {});
+		core.recordCommandsToStream(cmdStreamHandle, submitFunction, finishFunction);
+	}
 
-            cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {});
+	void Core::recordDrawcallsToCmdStream(const CommandStreamHandle &cmdStreamHandle,
+										  const GraphicsPipelineHandle &pipelineHandle,
+										  const PushConstants &pushConstantData,
+										  const std::vector<InstanceDrawcall> &drawcalls,
+										  const std::vector<ImageHandle> &renderTargets,
+										  const WindowHandle &windowHandle) {
 
-            const PipelineConfig &pipeConfig = m_PipelineManager->getPipelineConfig(pipelineHandle);
-            if(pipeConfig.m_UseDynamicViewport)
-            {
-                cmdBuffer.setViewport(0, 1, &dynamicViewport);
-                cmdBuffer.setScissor(0, 1, &dynamicScissor);
-            }
+		if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) {
+			return;
+		}
 
-            for (int i = 0; i < drawcalls.size(); i++) {
-                recordDrawcall(drawcalls[i], cmdBuffer, pipelineLayout, pushConstantData, i);
-            }
+		const vk::PipelineLayout pipelineLayout =
+			m_GraphicsPipelineManager->getVkPipelineLayout(pipelineHandle);
 
-            cmdBuffer.endRenderPass();
-        };
+		auto recordFunction = [&](const vk::CommandBuffer &cmdBuffer) {
+			for (size_t i = 0; i < drawcalls.size(); i++) {
+				recordDrawcall(*m_DescriptorSetManager, *m_BufferManager, drawcalls [i], cmdBuffer,
+							   pipelineLayout, pushConstantData, i);
+			}
+		};
 
-        auto finishFunction = [framebuffer, this]()
-        {
-            m_Context.m_Device.destroy(framebuffer);
-        };
+		recordGraphicsPipeline(*this, *m_CommandStreamManager, *m_GraphicsPipelineManager,
+							   *m_PassManager, *m_ImageManager, cmdStreamHandle, pipelineHandle,
+							   pushConstantData, renderTargets, windowHandle, recordFunction);
+	}
 
-		recordCommandsToStream(cmdStreamHandle, submitFunction, finishFunction);
+	static void
+	recordIndirectDrawcall(const Core &core, const DescriptorSetManager &descriptorSetManager,
+						   const BufferManager &bufferManager, vk::CommandBuffer cmdBuffer,
+						   vk::PipelineLayout pipelineLayout, const PushConstants &pushConstantData,
+						   size_t drawcallIndex, const IndirectDrawcall &drawcall) {
+		for (const auto &usage : drawcall.getDescriptorSetUsages()) {
+			cmdBuffer.bindDescriptorSets(
+				vk::PipelineBindPoint::eGraphics, pipelineLayout, usage.location,
+				descriptorSetManager.getDescriptorSet(usage.descriptorSet).vulkanHandle,
+				usage.dynamicOffsets);
+		}
+
+		const auto &vertexData = drawcall.getVertexData();
+
+		for (uint32_t i = 0; i < vertexData.getVertexBufferBindings().size(); i++) {
+			const auto &vertexBinding = vertexData.getVertexBufferBindings() [i];
+
+			cmdBuffer.bindVertexBuffers(i, bufferManager.getBuffer(vertexBinding.buffer),
+										vertexBinding.offset);
+		}
+
+		if (pushConstantData.getSizePerDrawcall() > 0) {
+			cmdBuffer.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eAll, 0,
+									pushConstantData.getSizePerDrawcall(),
+									pushConstantData.getDrawcallData(0));
+		}
+
+		if (vertexData.getIndexBuffer()) {
+			cmdBuffer.bindIndexBuffer(bufferManager.getBuffer(vertexData.getIndexBuffer()), 0,
+									  getIndexType(vertexData.getIndexBitCount()));
+
+			cmdBuffer.drawIndexedIndirect(bufferManager.getBuffer(drawcall.getIndirectDrawBuffer()),
+										  0, drawcall.getDrawCount(),
+										  sizeof(vk::DrawIndexedIndirectCommand));
+		} else {
+			cmdBuffer.drawIndirect(bufferManager.getBuffer(drawcall.getIndirectDrawBuffer()), 0,
+								   drawcall.getDrawCount(), sizeof(vk::DrawIndirectCommand));
+		}
 	}
 
-	void Core::recordComputeDispatchToCmdStream(
-		CommandStreamHandle cmdStreamHandle,
-		PipelineHandle computePipeline,
-		const uint32_t dispatchCount[3],
-		const std::vector<DescriptorSetUsage>& descriptorSetUsages,
-		const PushConstantData& pushConstantData) {
+	void Core::recordIndirectDrawcallsToCmdStream(
+		const vkcv::CommandStreamHandle cmdStreamHandle,
+		const vkcv::GraphicsPipelineHandle &pipelineHandle,
+		const vkcv::PushConstants &pushConstantData, const std::vector<IndirectDrawcall> &drawcalls,
+		const std::vector<ImageHandle> &renderTargets, const vkcv::WindowHandle &windowHandle) {
+
+		if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) {
+			return;
+		}
+
+		const vk::PipelineLayout pipelineLayout =
+			m_GraphicsPipelineManager->getVkPipelineLayout(pipelineHandle);
+
+		auto recordFunction = [&](const vk::CommandBuffer &cmdBuffer) {
+			for (size_t i = 0; i < drawcalls.size(); i++) {
+				recordIndirectDrawcall(*this, *m_DescriptorSetManager, *m_BufferManager, cmdBuffer,
+									   pipelineLayout, pushConstantData, i, drawcalls [i]);
+			}
+		};
+
+		recordGraphicsPipeline(*this, *m_CommandStreamManager, *m_GraphicsPipelineManager,
+							   *m_PassManager, *m_ImageManager, cmdStreamHandle, pipelineHandle,
+							   pushConstantData, renderTargets, windowHandle, recordFunction);
+	}
+
+	static void recordMeshShaderDrawcall(const Core &core,
+										 const DescriptorSetManager &descriptorSetManager,
+										 vk::CommandBuffer cmdBuffer,
+										 vk::PipelineLayout pipelineLayout,
+										 const PushConstants &pushConstantData,
+										 size_t drawcallIndex, const TaskDrawcall &drawcall) {
+
+		static PFN_vkCmdDrawMeshTasksNV cmdDrawMeshTasks =
+			reinterpret_cast<PFN_vkCmdDrawMeshTasksNV>(
+				core.getContext().getDevice().getProcAddr("vkCmdDrawMeshTasksNV"));
 
-		auto submitFunction = [&](const vk::CommandBuffer& cmdBuffer) {
+		if (!cmdDrawMeshTasks) {
+			vkcv_log(LogLevel::ERROR, "Mesh shader drawcalls are not supported");
+			return;
+		}
+
+		for (const auto &descriptorUsage : drawcall.getDescriptorSetUsages()) {
+			cmdBuffer.bindDescriptorSets(
+				vk::PipelineBindPoint::eGraphics, pipelineLayout, descriptorUsage.location,
+				descriptorSetManager.getDescriptorSet(descriptorUsage.descriptorSet).vulkanHandle,
+				descriptorUsage.dynamicOffsets);
+		}
+
+		if (pushConstantData.getData()) {
+			cmdBuffer.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eAll, 0,
+									pushConstantData.getSizePerDrawcall(),
+									pushConstantData.getDrawcallData(drawcallIndex));
+		}
 
-			const auto pipelineLayout = m_PipelineManager->getVkPipelineLayout(computePipeline);
+		cmdDrawMeshTasks(VkCommandBuffer(cmdBuffer), drawcall.getTaskCount(), 0);
+	}
+
+	void Core::recordMeshShaderDrawcalls(const CommandStreamHandle &cmdStreamHandle,
+										 const GraphicsPipelineHandle &pipelineHandle,
+										 const PushConstants &pushConstantData,
+										 const std::vector<TaskDrawcall> &drawcalls,
+										 const std::vector<ImageHandle> &renderTargets,
+										 const WindowHandle &windowHandle) {
+
+		if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) {
+			return;
+		}
 
-			cmdBuffer.bindPipeline(vk::PipelineBindPoint::eCompute, m_PipelineManager->getVkPipeline(computePipeline));
-			for (const auto& usage : descriptorSetUsages) {
+		const vk::PipelineLayout pipelineLayout =
+			m_GraphicsPipelineManager->getVkPipelineLayout(pipelineHandle);
+
+		auto recordFunction = [&](const vk::CommandBuffer &cmdBuffer) {
+			for (size_t i = 0; i < drawcalls.size(); i++) {
+				recordMeshShaderDrawcall(*this, *m_DescriptorSetManager, cmdBuffer, pipelineLayout,
+										 pushConstantData, i, drawcalls [i]);
+			}
+		};
+
+		recordGraphicsPipeline(*this, *m_CommandStreamManager, *m_GraphicsPipelineManager,
+							   *m_PassManager, *m_ImageManager, cmdStreamHandle, pipelineHandle,
+							   pushConstantData, renderTargets, windowHandle, recordFunction);
+	}
+
+	void Core::recordRayGenerationToCmdStream(
+		CommandStreamHandle cmdStreamHandle, vk::Pipeline rtxPipeline,
+		vk::PipelineLayout rtxPipelineLayout, vk::StridedDeviceAddressRegionKHR rgenRegion,
+		vk::StridedDeviceAddressRegionKHR rmissRegion,
+		vk::StridedDeviceAddressRegionKHR rchitRegion,
+		vk::StridedDeviceAddressRegionKHR rcallRegion,
+		const std::vector<DescriptorSetUsage> &descriptorSetUsages,
+		const PushConstants &pushConstants, const WindowHandle &windowHandle) {
+
+		auto submitFunction = [&](const vk::CommandBuffer &cmdBuffer) {
+			cmdBuffer.bindPipeline(vk::PipelineBindPoint::eRayTracingKHR, rtxPipeline);
+			for (const auto &usage : descriptorSetUsages) {
 				cmdBuffer.bindDescriptorSets(
-					vk::PipelineBindPoint::eCompute,
-					pipelineLayout,
-					usage.setLocation,
-					{ usage.vulkanHandle },
-					{});
+					vk::PipelineBindPoint::eRayTracingKHR, rtxPipelineLayout, usage.location,
+					{ m_DescriptorSetManager->getDescriptorSet(usage.descriptorSet).vulkanHandle },
+					usage.dynamicOffsets);
 			}
-			if (pushConstantData.sizePerDrawcall > 0) {
+
+			if (pushConstants.getSizePerDrawcall() > 0) {
 				cmdBuffer.pushConstants(
-					pipelineLayout,
-					vk::ShaderStageFlagBits::eCompute,
-					0,
-					pushConstantData.sizePerDrawcall,
-					pushConstantData.data);
+					rtxPipelineLayout,
+					(vk::ShaderStageFlagBits::eClosestHitKHR | vk::ShaderStageFlagBits::eMissKHR
+					 | vk::ShaderStageFlagBits::eRaygenKHR), // TODO: add Support for eAnyHitKHR,
+															 // eCallableKHR, eIntersectionKHR
+					0, pushConstants.getSizePerDrawcall(), pushConstants.getData());
+			}
+
+			auto m_rtxDispatcher = vk::DispatchLoaderDynamic(
+				(PFN_vkGetInstanceProcAddr)m_Context.getInstance().getProcAddr(
+					"vkGetInstanceProcAddr"));
+			m_rtxDispatcher.init(m_Context.getInstance());
+
+			cmdBuffer.traceRaysKHR(&rgenRegion, &rmissRegion, &rchitRegion, &rcallRegion,
+								   getWindow(windowHandle).getWidth(),
+								   getWindow(windowHandle).getHeight(), 1, m_rtxDispatcher);
+		};
+		recordCommandsToStream(cmdStreamHandle, submitFunction, nullptr);
+	}
+
+	void Core::recordComputeDispatchToCmdStream(
+		const CommandStreamHandle &cmdStreamHandle, const ComputePipelineHandle &computePipeline,
+		const DispatchSize &dispatchSize,
+		const std::vector<DescriptorSetUsage> &descriptorSetUsages,
+		const PushConstants &pushConstants) {
+		auto submitFunction = [&](const vk::CommandBuffer &cmdBuffer) {
+			const auto pipelineLayout =
+				m_ComputePipelineManager->getVkPipelineLayout(computePipeline);
+
+			cmdBuffer.bindPipeline(vk::PipelineBindPoint::eCompute,
+								   m_ComputePipelineManager->getVkPipeline(computePipeline));
+			for (const auto &usage : descriptorSetUsages) {
+				cmdBuffer.bindDescriptorSets(
+					vk::PipelineBindPoint::eCompute, pipelineLayout, usage.location,
+					{ m_DescriptorSetManager->getDescriptorSet(usage.descriptorSet).vulkanHandle },
+					usage.dynamicOffsets);
+			}
+			if (pushConstants.getSizePerDrawcall() > 0) {
+				cmdBuffer.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eCompute, 0,
+										pushConstants.getSizePerDrawcall(),
+										pushConstants.getData());
 			}
-			cmdBuffer.dispatch(dispatchCount[0], dispatchCount[1], dispatchCount[2]);
+
+			cmdBuffer.dispatch(dispatchSize.x(), dispatchSize.y(), dispatchSize.z());
 		};
 
 		recordCommandsToStream(cmdStreamHandle, submitFunction, nullptr);
 	}
 
-	void Core::endFrame() {
-		if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) {
+	void Core::recordBeginDebugLabel(const CommandStreamHandle &cmdStream, const std::string &label,
+									 const std::array<float, 4> &color) {
+#ifdef VULKAN_DEBUG_LABELS
+		static PFN_vkCmdBeginDebugUtilsLabelEXT beginDebugLabel =
+			reinterpret_cast<PFN_vkCmdBeginDebugUtilsLabelEXT>(
+				m_Context.getDevice().getProcAddr("vkCmdBeginDebugUtilsLabelEXT"));
+
+		if (!beginDebugLabel) {
 			return;
 		}
-  
-		const auto swapchainImages = m_Context.getDevice().getSwapchainImagesKHR(m_swapchain.getSwapchain());
 
-		const auto& queueManager = m_Context.getQueueManager();
-		std::array<vk::Semaphore, 2> waitSemaphores{
-			m_SyncResources.renderFinished,
-			m_SyncResources.swapchainImageAcquired
+		auto submitFunction = [&](const vk::CommandBuffer &cmdBuffer) {
+			const vk::DebugUtilsLabelEXT debug(label.c_str(), color);
+
+			beginDebugLabel(static_cast<VkCommandBuffer>(cmdBuffer),
+							&(static_cast<const VkDebugUtilsLabelEXT &>(debug)));
 		};
 
-		const vk::SwapchainKHR& swapchain = m_swapchain.getSwapchain();
-		const vk::PresentInfoKHR presentInfo(
-			waitSemaphores,
-			swapchain,
-			m_currentSwapchainImageIndex
-		);
-		
+		recordCommandsToStream(cmdStream, submitFunction, nullptr);
+#endif
+	}
+
+	void Core::recordEndDebugLabel(const CommandStreamHandle &cmdStream) {
+#ifdef VULKAN_DEBUG_LABELS
+		static PFN_vkCmdEndDebugUtilsLabelEXT endDebugLabel =
+			reinterpret_cast<PFN_vkCmdEndDebugUtilsLabelEXT>(
+				m_Context.getDevice().getProcAddr("vkCmdEndDebugUtilsLabelEXT"));
+
+		if (!endDebugLabel) {
+			return;
+		}
+
+		auto submitFunction = [&](const vk::CommandBuffer &cmdBuffer) {
+			endDebugLabel(static_cast<VkCommandBuffer>(cmdBuffer));
+		};
+
+		recordCommandsToStream(cmdStream, submitFunction, nullptr);
+#endif
+	}
+
+	void Core::recordComputeIndirectDispatchToCmdStream(
+		const CommandStreamHandle cmdStream, const ComputePipelineHandle computePipeline,
+		const vkcv::BufferHandle buffer, const size_t bufferArgOffset,
+		const std::vector<DescriptorSetUsage> &descriptorSetUsages,
+		const PushConstants &pushConstants) {
+
+		auto submitFunction = [&](const vk::CommandBuffer &cmdBuffer) {
+			const auto pipelineLayout =
+				m_ComputePipelineManager->getVkPipelineLayout(computePipeline);
+
+			cmdBuffer.bindPipeline(vk::PipelineBindPoint::eCompute,
+								   m_ComputePipelineManager->getVkPipeline(computePipeline));
+			for (const auto &usage : descriptorSetUsages) {
+				cmdBuffer.bindDescriptorSets(
+					vk::PipelineBindPoint::eCompute, pipelineLayout, usage.location,
+					{ m_DescriptorSetManager->getDescriptorSet(usage.descriptorSet).vulkanHandle },
+					usage.dynamicOffsets);
+			}
+			if (pushConstants.getSizePerDrawcall() > 0) {
+				cmdBuffer.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eCompute, 0,
+										pushConstants.getSizePerDrawcall(),
+										pushConstants.getData());
+			}
+			cmdBuffer.dispatchIndirect(m_BufferManager->getBuffer(buffer), bufferArgOffset);
+		};
+
+		recordCommandsToStream(cmdStream, submitFunction, nullptr);
+	}
+
+	void Core::endFrame(const WindowHandle &windowHandle) {
+		SwapchainHandle swapchainHandle = m_WindowManager->getWindow(windowHandle).getSwapchain();
+
+		if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) {
+			return;
+		}
+
+		const std::array<vk::Semaphore, 2> waitSemaphores { m_RenderFinished,
+															m_SwapchainImageAcquired };
+
+		const vk::SwapchainKHR &swapchain =
+			m_SwapchainManager->getSwapchain(swapchainHandle).m_Swapchain;
+		const vk::PresentInfoKHR presentInfo(waitSemaphores, swapchain,
+											 m_currentSwapchainImageIndex);
+
 		vk::Result result;
-		
+
 		try {
-			result = queueManager.getPresentQueue().handle.presentKHR(presentInfo);
-		} catch (vk::OutOfDateKHRError e) {
+			result = m_Context.getDevice()
+						 .getQueue(m_SwapchainManager->getPresentQueueIndex(swapchainHandle), 0)
+						 .presentKHR(presentInfo);
+		} catch (const vk::OutOfDateKHRError &e) {
 			result = vk::Result::eErrorOutOfDateKHR;
+		} catch (const vk::DeviceLostError &e) {
+			result = vk::Result::eErrorDeviceLost;
 		}
-		
-		if (result != vk::Result::eSuccess) {
-			vkcv_log(LogLevel::ERROR, "Swapchain present failed (%s)", vk::to_string(result).c_str());
-		}
-	}
-	
-	void Core::recordAndSubmitCommandsImmediate(
-		const SubmitInfo &submitInfo, 
-		const RecordCommandFunction &record, 
-		const FinishCommandFunction &finish)
-	{
-		const vk::Device& device = m_Context.getDevice();
-
-		const vkcv::Queue		queue		= getQueueForSubmit(submitInfo.queueType, m_Context.getQueueManager());
-		const vk::CommandPool	cmdPool		= chooseCmdPool(queue, m_CommandResources);
-		const vk::CommandBuffer	cmdBuffer	= allocateCommandBuffer(device, cmdPool);
-
-		beginCommandBuffer(cmdBuffer, vk::CommandBufferUsageFlagBits::eOneTimeSubmit);
-		record(cmdBuffer);
-		cmdBuffer.end();
-		
-		vk::Fence waitFence = createFence(device);
-
-		submitCommandBufferToQueue(queue.handle, cmdBuffer, waitFence, submitInfo.waitSemaphores, submitInfo.signalSemaphores);
-		waitForFence(device, waitFence);
-		
-		device.destroyFence(waitFence);
-		
-		device.freeCommandBuffers(cmdPool, cmdBuffer);
-		
-		if (finish) {
-			finish();
+
+		if ((result != vk::Result::eSuccess) && (result != vk::Result::eSuboptimalKHR)) {
+			vkcv_log(LogLevel::ERROR, "Swapchain presentation failed (%s)",
+					 vk::to_string(result).c_str());
+		} else if (result == vk::Result::eSuboptimalKHR) {
+			vkcv_log(LogLevel::WARNING, "Swapchain presentation is suboptimal");
+			m_SwapchainManager->signalRecreation(swapchainHandle);
 		}
 	}
 
-	CommandStreamHandle Core::createCommandStream(QueueType queueType) {
+	/**
+	 * @brief Returns a queue of a given type from a queue manager.
+	 *
+	 * @param[in] type Type of queue
+	 * @param[in] queueManager Queue manager
+	 * @return Queue of a given type
+	 */
+	static Queue getQueueForSubmit(QueueType type, const QueueManager &queueManager) {
+		switch (type) {
+		case QueueType::Graphics:
+			return queueManager.getGraphicsQueues().front();
+		case QueueType::Compute:
+			return queueManager.getComputeQueues().front();
+		case QueueType::Transfer:
+			return queueManager.getTransferQueues().front();
+		case QueueType::Present:
+			return queueManager.getPresentQueue();
+		default: {
+			vkcv_log(LogLevel::ERROR, "Unknown queue type");
+			return queueManager.getGraphicsQueues().front(); // graphics is the most general queue
+		}
+		}
+	}
 
-		const vk::Device&       device  = m_Context.getDevice();
-		const vkcv::Queue       queue   = getQueueForSubmit(queueType, m_Context.getQueueManager());
-		const vk::CommandPool   cmdPool = chooseCmdPool(queue, m_CommandResources);
+	CommandStreamHandle Core::createCommandStream(QueueType queueType) {
+		const vkcv::Queue queue = getQueueForSubmit(queueType, m_Context.getQueueManager());
+		const vk::CommandPool cmdPool = m_CommandPools [queue.familyIndex];
 
 		return m_CommandStreamManager->createCommandStream(queue.handle, cmdPool);
 	}
 
-    void Core::recordCommandsToStream(
-		const CommandStreamHandle   cmdStreamHandle,
-		const RecordCommandFunction &record, 
-		const FinishCommandFunction &finish) {
+	void Core::recordCommandsToStream(const CommandStreamHandle &stream,
+									  const RecordCommandFunction &record,
+									  const FinishCommandFunction &finish) {
+		if (record) {
+			m_CommandStreamManager->recordCommandsToStream(stream, record);
+		}
 
-		m_CommandStreamManager->recordCommandsToStream(cmdStreamHandle, record);
 		if (finish) {
-			m_CommandStreamManager->addFinishCallbackToStream(cmdStreamHandle, finish);
+			m_CommandStreamManager->addFinishCallbackToStream(stream, finish);
 		}
 	}
 
-	void Core::submitCommandStream(const CommandStreamHandle handle) {
+	void Core::submitCommandStream(const CommandStreamHandle &stream, bool signalRendering) {
 		std::vector<vk::Semaphore> waitSemaphores;
+
 		// FIXME: add proper user controllable sync
-		std::vector<vk::Semaphore> signalSemaphores = { m_SyncResources.renderFinished };
-		m_CommandStreamManager->submitCommandStreamSynchronous(handle, waitSemaphores, signalSemaphores);
+		std::vector<vk::Semaphore> signalSemaphores;
+		if (signalRendering) {
+			signalSemaphores.push_back(m_RenderFinished);
+		}
+
+		m_CommandStreamManager->submitCommandStreamSynchronous(stream, waitSemaphores,
+															   signalSemaphores);
 	}
 
 	SamplerHandle Core::createSampler(SamplerFilterType magFilter, SamplerFilterType minFilter,
-									  SamplerMipmapMode mipmapMode, SamplerAddressMode addressMode) {
-		return m_SamplerManager->createSampler(magFilter, minFilter, mipmapMode, addressMode);
+									  SamplerMipmapMode mipmapMode, SamplerAddressMode addressMode,
+									  float mipLodBias, SamplerBorderColor borderColor) {
+		return m_SamplerManager->createSampler(magFilter, minFilter, mipmapMode, addressMode,
+											   mipLodBias, borderColor);
 	}
 
-	Image Core::createImage(
-		vk::Format  format,
-		uint32_t    width,
-		uint32_t    height,
-		uint32_t    depth,
-		bool        createMipChain,
-		bool        supportStorage,
-		bool        supportColorAttachment)
-	{
-
+	ImageHandle Core::createImage(vk::Format format,
+								  const ImageConfig& config,
+								  bool createMipChain) {
 		uint32_t mipCount = 1;
 		if (createMipChain) {
-			mipCount = 1 + (uint32_t)std::floor(std::log2(std::max(width, std::max(height, depth))));
+			mipCount = 1 + (uint32_t) std::floor(
+					std::log2(std::max(
+							config.getWidth(),
+							std::max(config.getHeight(), config.getDepth()))
+					)
+			);
 		}
 
-		return Image::create(
-			m_ImageManager.get(), 
-			format,
-			width,
-			height,
-			depth,
-			mipCount,
-			supportStorage,
-			supportColorAttachment);
+		return m_ImageManager->createImage(
+				format,
+				mipCount,
+				config
+		);
+	}
+
+	void Core::fillImage(const ImageHandle &image,
+						 const void* data,
+						 size_t size,
+						 uint32_t firstLayer,
+						 uint32_t layerCount) {
+		m_ImageManager->fillImage(image, data, size, firstLayer, layerCount);
+	}
+
+	void Core::switchImageLayout(const ImageHandle &image, vk::ImageLayout layout) {
+		m_ImageManager->switchImageLayoutImmediate(image, layout);
+	}
+
+	Downsampler &Core::getDownsampler() {
+		return *m_downsampler;
+	}
+
+	WindowHandle Core::createWindow(const std::string &applicationName, uint32_t windowWidth,
+									uint32_t windowHeight, bool resizeable) {
+		WindowHandle windowHandle = m_WindowManager->createWindow(
+			*m_SwapchainManager, applicationName, windowWidth, windowHeight, resizeable);
+
+		SwapchainHandle swapchainHandle = m_WindowManager->getWindow(windowHandle).getSwapchain();
+		setSwapchainImages(swapchainHandle);
+		return windowHandle;
+	}
+
+	Window &Core::getWindow(const WindowHandle &handle) {
+		return m_WindowManager->getWindow(handle);
+	}
+
+	vk::Format Core::getSwapchainFormat(const SwapchainHandle &swapchain) const {
+		return m_SwapchainManager->getFormat(swapchain);
+	}
+
+	uint32_t Core::getSwapchainImageCount(const SwapchainHandle &swapchain) const {
+		return m_SwapchainManager->getImageCount(swapchain);
+	}
+
+	vk::Extent2D Core::getSwapchainExtent(const SwapchainHandle &swapchain) const {
+		return m_SwapchainManager->getExtent(swapchain);
+	}
+
+	uint32_t Core::getImageWidth(const ImageHandle &image) {
+		return m_ImageManager->getImageWidth(image);
+	}
+
+	uint32_t Core::getImageHeight(const ImageHandle &image) {
+		return m_ImageManager->getImageHeight(image);
+	}
+
+	uint32_t Core::getImageDepth(const ImageHandle &image) {
+		return m_ImageManager->getImageDepth(image);
+	}
+
+	vk::Format Core::getImageFormat(const ImageHandle &image) {
+		return m_ImageManager->getImageFormat(image);
+	}
+
+	bool Core::isImageSupportingStorage(const ImageHandle &image) {
+		return m_ImageManager->isImageSupportingStorage(image);
 	}
 
-    DescriptorSetHandle Core::createDescriptorSet(const std::vector<DescriptorBinding>& bindings)
-    {
-        return m_DescriptorManager->createDescriptorSet(bindings);
-    }
+	uint32_t Core::getImageMipLevels(const ImageHandle &image) {
+		return m_ImageManager->getImageMipCount(image);
+	}
+
+	uint32_t Core::getImageArrayLayers(const ImageHandle &image) {
+		return m_ImageManager->getImageArrayLayers(image);
+	}
+
+	DescriptorSetLayoutHandle Core::createDescriptorSetLayout(const DescriptorBindings &bindings) {
+		return m_DescriptorSetLayoutManager->createDescriptorSetLayout(bindings);
+	}
+
+	DescriptorSetHandle Core::createDescriptorSet(const DescriptorSetLayoutHandle &layout) {
+		return m_DescriptorSetManager->createDescriptorSet(layout);
+	}
 
 	void Core::writeDescriptorSet(DescriptorSetHandle handle, const DescriptorWrites &writes) {
-		m_DescriptorManager->writeDescriptorSet(
-			handle,
-			writes, 
-			*m_ImageManager, 
-			*m_BufferManager, 
-			*m_SamplerManager);
-	}
-
-	DescriptorSet Core::getDescriptorSet(const DescriptorSetHandle handle) const {
-		return m_DescriptorManager->getDescriptorSet(handle);
-	}
-
-    std::vector<vk::ImageView> Core::createSwapchainImageViews( Context &context, Swapchain& swapChain){
-        std::vector<vk::ImageView> imageViews;
-        std::vector<vk::Image> swapChainImages = context.getDevice().getSwapchainImagesKHR(swapChain.getSwapchain());
-        imageViews.reserve( swapChainImages.size() );
-        //here can be swizzled with vk::ComponentSwizzle if needed
-        vk::ComponentMapping componentMapping(
-                vk::ComponentSwizzle::eR,
-                vk::ComponentSwizzle::eG,
-                vk::ComponentSwizzle::eB,
-                vk::ComponentSwizzle::eA );
-
-        vk::ImageSubresourceRange subResourceRange( vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 );
-
-        for ( auto image : swapChainImages )
-        {
-            vk::ImageViewCreateInfo imageViewCreateInfo(
-                    vk::ImageViewCreateFlags(),
-                    image,
-                    vk::ImageViewType::e2D,
-                    swapChain.getFormat(),
-                    componentMapping,
-                    subResourceRange);
-
-            imageViews.push_back(context.getDevice().createImageView(imageViewCreateInfo));
-        }
-        return imageViews;
-    }
-
-	void Core::prepareSwapchainImageForPresent(const CommandStreamHandle cmdStream) {
+		m_DescriptorSetManager->writeDescriptorSet(handle, writes, *m_ImageManager,
+												   *m_BufferManager, *m_SamplerManager);
+	}
+
+	void Core::prepareSwapchainImageForPresent(const CommandStreamHandle &cmdStream) {
 		auto swapchainHandle = ImageHandle::createSwapchainImageHandle();
-		recordCommandsToStream(cmdStream, [swapchainHandle, this](const vk::CommandBuffer cmdBuffer) {
-			m_ImageManager->recordImageLayoutTransition(swapchainHandle, vk::ImageLayout::ePresentSrcKHR, cmdBuffer);
-		}, nullptr);
+		recordCommandsToStream(
+			cmdStream,
+			[swapchainHandle, this](const vk::CommandBuffer cmdBuffer) {
+				m_ImageManager->recordImageLayoutTransition(
+					swapchainHandle, 0, 0, vk::ImageLayout::ePresentSrcKHR, cmdBuffer);
+			},
+			nullptr);
+	}
+
+	void Core::prepareImageForSampling(const CommandStreamHandle &cmdStream,
+									   const ImageHandle &image, uint32_t mipLevelCount,
+									   uint32_t mipLevelOffset) {
+		recordCommandsToStream(
+			cmdStream,
+			[image, mipLevelCount, mipLevelOffset, this](const vk::CommandBuffer cmdBuffer) {
+				m_ImageManager->recordImageLayoutTransition(image, mipLevelCount, mipLevelOffset,
+															vk::ImageLayout::eShaderReadOnlyOptimal,
+															cmdBuffer);
+			},
+			nullptr);
+	}
+
+	void Core::prepareImageForStorage(const CommandStreamHandle &cmdStream,
+									  const ImageHandle &image, uint32_t mipLevelCount,
+									  uint32_t mipLevelOffset) {
+		recordCommandsToStream(
+			cmdStream,
+			[image, mipLevelCount, mipLevelOffset, this](const vk::CommandBuffer cmdBuffer) {
+				m_ImageManager->recordImageLayoutTransition(image, mipLevelCount, mipLevelOffset,
+															vk::ImageLayout::eGeneral, cmdBuffer);
+			},
+			nullptr);
+	}
+
+	void Core::prepareImageForAttachmentManually(const vk::CommandBuffer &cmdBuffer,
+												 const ImageHandle &image) {
+		transitionRendertargetsToAttachmentLayout({ image }, *m_ImageManager, cmdBuffer);
 	}
 
-	void Core::prepareImageForSampling(const CommandStreamHandle cmdStream, const ImageHandle image) {
-		recordCommandsToStream(cmdStream, [image, this](const vk::CommandBuffer cmdBuffer) {
-			m_ImageManager->recordImageLayoutTransition(image, vk::ImageLayout::eShaderReadOnlyOptimal, cmdBuffer);
-		}, nullptr);
+	void Core::updateImageLayoutManual(const vkcv::ImageHandle &image,
+									   const vk::ImageLayout layout) {
+		m_ImageManager->updateImageLayoutManual(image, layout);
 	}
 
-	void Core::prepareImageForStorage(const CommandStreamHandle cmdStream, const ImageHandle image) {
-		recordCommandsToStream(cmdStream, [image, this](const vk::CommandBuffer cmdBuffer) {
-			m_ImageManager->recordImageLayoutTransition(image, vk::ImageLayout::eGeneral, cmdBuffer);
-		}, nullptr);
+	void Core::recordImageMemoryBarrier(const CommandStreamHandle &cmdStream,
+										const ImageHandle &image) {
+		recordCommandsToStream(
+			cmdStream,
+			[image, this](const vk::CommandBuffer cmdBuffer) {
+				m_ImageManager->recordImageMemoryBarrier(image, cmdBuffer);
+			},
+			nullptr);
 	}
 
-	void Core::recordImageMemoryBarrier(const CommandStreamHandle cmdStream, const ImageHandle image) {
-		recordCommandsToStream(cmdStream, [image, this](const vk::CommandBuffer cmdBuffer) {
-			m_ImageManager->recordImageMemoryBarrier(image, cmdBuffer);
-		}, nullptr);
+	void Core::recordBufferMemoryBarrier(const CommandStreamHandle &cmdStream,
+										 const BufferHandle &buffer) {
+		recordCommandsToStream(
+			cmdStream,
+			[buffer, this](const vk::CommandBuffer cmdBuffer) {
+				m_BufferManager->recordBufferMemoryBarrier(buffer, cmdBuffer);
+			},
+			nullptr);
 	}
 
-	void Core::recordBufferMemoryBarrier(const CommandStreamHandle cmdStream, const BufferHandle buffer) {
-		recordCommandsToStream(cmdStream, [buffer, this](const vk::CommandBuffer cmdBuffer) {
-			m_BufferManager->recordBufferMemoryBarrier(buffer, cmdBuffer);
-		}, nullptr);
+	void Core::resolveMSAAImage(const CommandStreamHandle &cmdStream, const ImageHandle &src,
+								const ImageHandle &dst) {
+		recordCommandsToStream(
+			cmdStream,
+			[src, dst, this](const vk::CommandBuffer cmdBuffer) {
+				m_ImageManager->recordMSAAResolve(cmdBuffer, src, dst);
+			},
+			nullptr);
 	}
-	
+
 	vk::ImageView Core::getSwapchainImageView() const {
-    	return m_ImageManager->getVulkanImageView(vkcv::ImageHandle::createSwapchainImageHandle());
-    }
-	
-}
+		return m_ImageManager->getVulkanImageView(vkcv::ImageHandle::createSwapchainImageHandle());
+	}
+
+	void Core::recordMemoryBarrier(const CommandStreamHandle &cmdStream) {
+		recordCommandsToStream(
+			cmdStream,
+			[](const vk::CommandBuffer cmdBuffer) {
+				vk::MemoryBarrier barrier(
+					vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eMemoryRead,
+					vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eMemoryRead);
+
+				cmdBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
+										  vk::PipelineStageFlagBits::eAllCommands,
+										  vk::DependencyFlags(), 1, &barrier, 0, nullptr, 0,
+										  nullptr);
+			},
+			nullptr);
+	}
+
+	void Core::recordBlitImage(const CommandStreamHandle &cmdStream, const ImageHandle &src,
+							   const ImageHandle &dst, SamplerFilterType filterType) {
+		recordCommandsToStream(
+			cmdStream,
+			[&](const vk::CommandBuffer cmdBuffer) {
+				m_ImageManager->recordImageLayoutTransition(
+					src, 0, 0, vk::ImageLayout::eTransferSrcOptimal, cmdBuffer);
+
+				m_ImageManager->recordImageLayoutTransition(
+					dst, 0, 0, vk::ImageLayout::eTransferDstOptimal, cmdBuffer);
+
+				const std::array<vk::Offset3D, 2> srcOffsets = {
+					vk::Offset3D(0, 0, 0), vk::Offset3D(m_ImageManager->getImageWidth(src),
+														m_ImageManager->getImageHeight(src), 1)
+				};
+
+				const std::array<vk::Offset3D, 2> dstOffsets = {
+					vk::Offset3D(0, 0, 0), vk::Offset3D(m_ImageManager->getImageWidth(dst),
+														m_ImageManager->getImageHeight(dst), 1)
+				};
+
+				const bool srcDepth = isDepthFormat(m_ImageManager->getImageFormat(src));
+				const bool dstDepth = isDepthFormat(m_ImageManager->getImageFormat(dst));
+
+				const vk::ImageBlit blit = vk::ImageBlit(
+					vk::ImageSubresourceLayers(srcDepth ? vk::ImageAspectFlagBits::eDepth :
+														  vk::ImageAspectFlagBits::eColor,
+											   0, 0, 1),
+					srcOffsets,
+					vk::ImageSubresourceLayers(dstDepth ? vk::ImageAspectFlagBits::eDepth :
+														  vk::ImageAspectFlagBits::eColor,
+											   0, 0, 1),
+					dstOffsets);
+
+				cmdBuffer.blitImage(m_ImageManager->getVulkanImage(src),
+									vk::ImageLayout::eTransferSrcOptimal,
+									m_ImageManager->getVulkanImage(dst),
+									vk::ImageLayout::eTransferDstOptimal, 1, &blit,
+									filterType == SamplerFilterType::LINEAR ? vk::Filter::eLinear :
+																			  vk::Filter::eNearest);
+			},
+			nullptr);
+	}
+
+	void Core::setSwapchainImages(SwapchainHandle handle) {
+		const auto &swapchain = m_SwapchainManager->getSwapchain(handle);
+		const auto swapchainImages = m_SwapchainManager->getSwapchainImages(handle);
+		const auto swapchainImageViews = m_SwapchainManager->createSwapchainImageViews(handle);
+
+		m_ImageManager->setSwapchainImages(swapchainImages, swapchainImageViews,
+										   swapchain.m_Extent.width, swapchain.m_Extent.height,
+										   swapchain.m_Format);
+	}
+
+	static void setDebugObjectLabel(const vk::Device &device, const vk::ObjectType &type,
+									uint64_t handle, const std::string &label) {
+#ifdef VULKAN_DEBUG_LABELS
+		static PFN_vkSetDebugUtilsObjectNameEXT setDebugLabel =
+			reinterpret_cast<PFN_vkSetDebugUtilsObjectNameEXT>(
+				device.getProcAddr("vkSetDebugUtilsObjectNameEXT"));
+
+		if (!setDebugLabel) {
+			return;
+		}
+
+		const vk::DebugUtilsObjectNameInfoEXT debug(type, handle, label.c_str());
+
+		setDebugLabel(static_cast<VkDevice>(device),
+					  &(static_cast<const VkDebugUtilsObjectNameInfoEXT &>(debug)));
+#endif
+	}
+
+	void Core::setDebugLabel(const BufferHandle &handle, const std::string &label) {
+		if (!handle) {
+			vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle");
+			return;
+		}
+
+		setDebugObjectLabel(m_Context.getDevice(), vk::ObjectType::eBuffer,
+							uint64_t(static_cast<VkBuffer>(m_BufferManager->getBuffer(handle))),
+							label);
+	}
+
+	void Core::setDebugLabel(const PassHandle &handle, const std::string &label) {
+		if (!handle) {
+			vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle");
+			return;
+		}
+
+		setDebugObjectLabel(m_Context.getDevice(), vk::ObjectType::eRenderPass,
+							uint64_t(static_cast<VkRenderPass>(m_PassManager->getVkPass(handle))),
+							label);
+	}
+
+	void Core::setDebugLabel(const GraphicsPipelineHandle &handle, const std::string &label) {
+		if (!handle) {
+			vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle");
+			return;
+		}
+
+		setDebugObjectLabel(
+			m_Context.getDevice(), vk::ObjectType::ePipeline,
+			uint64_t(static_cast<VkPipeline>(m_GraphicsPipelineManager->getVkPipeline(handle))),
+			label);
+	}
+
+	void Core::setDebugLabel(const ComputePipelineHandle &handle, const std::string &label) {
+		if (!handle) {
+			vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle");
+			return;
+		}
+
+		setDebugObjectLabel(
+			m_Context.getDevice(), vk::ObjectType::ePipeline,
+			uint64_t(static_cast<VkPipeline>(m_ComputePipelineManager->getVkPipeline(handle))),
+			label);
+	}
+
+	void Core::setDebugLabel(const DescriptorSetHandle &handle, const std::string &label) {
+		if (!handle) {
+			vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle");
+			return;
+		}
+
+		setDebugObjectLabel(m_Context.getDevice(), vk::ObjectType::eDescriptorSet,
+							uint64_t(static_cast<VkDescriptorSet>(
+								m_DescriptorSetManager->getDescriptorSet(handle).vulkanHandle)),
+							label);
+	}
+
+	void Core::setDebugLabel(const SamplerHandle &handle, const std::string &label) {
+		if (!handle) {
+			vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle");
+			return;
+		}
+
+		setDebugObjectLabel(
+			m_Context.getDevice(), vk::ObjectType::eSampler,
+			uint64_t(static_cast<VkSampler>(m_SamplerManager->getVulkanSampler(handle))), label);
+	}
+
+	void Core::setDebugLabel(const ImageHandle &handle, const std::string &label) {
+		if (!handle) {
+			vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle");
+			return;
+		} else if (handle.isSwapchainImage()) {
+			vkcv_log(LogLevel::WARNING, "Can't set debug label to swapchain image");
+			return;
+		}
+
+		setDebugObjectLabel(m_Context.getDevice(), vk::ObjectType::eImage,
+							uint64_t(static_cast<VkImage>(m_ImageManager->getVulkanImage(handle))),
+							label);
+	}
+
+	void Core::setDebugLabel(const CommandStreamHandle &handle, const std::string &label) {
+		if (!handle) {
+			vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle");
+			return;
+		}
+
+		setDebugObjectLabel(m_Context.getDevice(), vk::ObjectType::eCommandBuffer,
+							uint64_t(static_cast<VkCommandBuffer>(
+								m_CommandStreamManager->getStreamCommandBuffer(handle))),
+							label);
+	}
+
+	void Core::run(const vkcv::WindowFrameFunction &frame) {
+		auto start = std::chrono::system_clock::now();
+		double t = 0.0;
+
+		if (!frame)
+			return;
+
+		while (Window::hasOpenWindow()) {
+			vkcv::Window::pollEvents();
+
+			auto end = std::chrono::system_clock::now();
+			auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
+			start = end;
+
+			double dt = 0.000001 * static_cast<double>(deltatime.count());
+
+			for (const auto &window : m_WindowManager->getWindowHandles()) {
+				uint32_t swapchainWidth, swapchainHeight;
+				if (!beginFrame(swapchainWidth, swapchainHeight, window)) {
+					continue;
+				}
+
+				frame(window, t, dt, swapchainWidth, swapchainHeight);
+				endFrame(window);
+			}
+
+			t += dt;
+		}
+	}
+
+	vk::RenderPass Core::getVulkanRenderPass(const PassHandle &handle) const {
+		return m_PassManager->getVkPass(handle);
+	}
+
+	vk::Pipeline Core::getVulkanPipeline(const GraphicsPipelineHandle &handle) const {
+		return m_GraphicsPipelineManager->getVkPipeline(handle);
+	}
+
+	vk::Pipeline Core::getVulkanPipeline(const ComputePipelineHandle &handle) const {
+		return m_ComputePipelineManager->getVkPipeline(handle);
+	}
+
+	vk::DescriptorSetLayout
+	Core::getVulkanDescriptorSetLayout(const DescriptorSetLayoutHandle &handle) const {
+		return m_DescriptorSetLayoutManager->getDescriptorSetLayout(handle).vulkanHandle;
+	}
+
+	vk::DescriptorSet Core::getVulkanDescriptorSet(const DescriptorSetHandle &handle) const {
+		return m_DescriptorSetManager->getDescriptorSet(handle).vulkanHandle;
+	}
+
+	vk::Buffer Core::getVulkanBuffer(const BufferHandle &handle) const {
+		return m_BufferManager->getBuffer(handle);
+	}
+
+	vk::Sampler Core::getVulkanSampler(const SamplerHandle &handle) const {
+		return m_SamplerManager->getVulkanSampler(handle);
+	}
+
+	vk::Image Core::getVulkanImage(const ImageHandle &handle) const {
+		return m_ImageManager->getVulkanImage(handle);
+	}
+
+	vk::ImageView Core::getVulkanImageView(const vkcv::ImageHandle &handle) const {
+		return m_ImageManager->getVulkanImageView(handle);
+	}
+
+	vk::DeviceMemory Core::getVulkanDeviceMemory(const BufferHandle &handle) const {
+		return m_BufferManager->getDeviceMemory(handle);
+	}
+
+	vk::DeviceMemory Core::getVulkanDeviceMemory(const ImageHandle &handle) const {
+		return m_ImageManager->getVulkanDeviceMemory(handle);
+	}
+
+} // namespace vkcv
diff --git a/src/vkcv/DescriptorBinding.cpp b/src/vkcv/DescriptorBinding.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..251fe45c80e66ac04745621bba79feed8a14eb97
--- /dev/null
+++ b/src/vkcv/DescriptorBinding.cpp
@@ -0,0 +1,13 @@
+#include "vkcv/DescriptorBinding.hpp"
+
+namespace vkcv {
+
+	bool DescriptorBinding::operator==(const DescriptorBinding &other) const {
+		return (this->bindingID == other.bindingID)
+			&& (this->descriptorType == other.descriptorType)
+			&& (this->descriptorCount == other.descriptorCount)
+			&& (this->shaderStages == other.shaderStages)
+			&& (this->variableCount == other.variableCount);
+	}
+
+} // namespace vkcv
diff --git a/src/vkcv/DescriptorConfig.cpp b/src/vkcv/DescriptorConfig.cpp
deleted file mode 100644
index 54e879ac7e6ec7825a4c003899e3c264454c547f..0000000000000000000000000000000000000000
--- a/src/vkcv/DescriptorConfig.cpp
+++ /dev/null
@@ -1,15 +0,0 @@
-#include "vkcv/DescriptorConfig.hpp"
-
-namespace vkcv {
-	DescriptorBinding::DescriptorBinding(
-		uint32_t bindingID,
-		DescriptorType descriptorType,
-		uint32_t descriptorCount,
-		ShaderStage shaderStage) noexcept
-		:
-		bindingID(bindingID),
-		descriptorType(descriptorType),
-		descriptorCount(descriptorCount),
-		shaderStage(shaderStage) {}
-	
-}
diff --git a/src/vkcv/DescriptorManager.cpp b/src/vkcv/DescriptorManager.cpp
deleted file mode 100644
index 8e565a766cd407dc33c0291d3d07b01d6d3066e7..0000000000000000000000000000000000000000
--- a/src/vkcv/DescriptorManager.cpp
+++ /dev/null
@@ -1,294 +0,0 @@
-#include "DescriptorManager.hpp"
-
-#include "vkcv/Logger.hpp"
-
-namespace vkcv
-{
-    DescriptorManager::DescriptorManager(vk::Device device) noexcept:
-        m_Device{ device }
-    {
-        /**
-         * Allocate the set size for the descriptor pools, namely 1000 units of each descriptor type below.
-		 * Finally, create an initial pool.
-         */
-		m_PoolSizes = { vk::DescriptorPoolSize(vk::DescriptorType::eSampler, 1000),
-													vk::DescriptorPoolSize(vk::DescriptorType::eSampledImage, 1000),
-													vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, 1000),
-													vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, 1000) };
-
-		m_PoolInfo = vk::DescriptorPoolCreateInfo({},
-			1000,
-			static_cast<uint32_t>(m_PoolSizes.size()),
-			m_PoolSizes.data());
-
-		allocateDescriptorPool();
-    }
-
-    DescriptorManager::~DescriptorManager() noexcept
-    {
-        for (uint64_t id = 0; id < m_DescriptorSets.size(); id++) {
-			destroyDescriptorSetById(id);
-        }
-		m_DescriptorSets.clear();
-		for (const auto &pool : m_Pools) {
-			m_Device.destroy(pool);
-		}
-    }
-
-    DescriptorSetHandle DescriptorManager::createDescriptorSet(const std::vector<DescriptorBinding>& bindings)
-    {
-        std::vector<vk::DescriptorSetLayoutBinding> setBindings = {};
-
-        //create each set's binding
-        for (uint32_t i = 0; i < bindings.size(); i++) {
-            vk::DescriptorSetLayoutBinding descriptorSetLayoutBinding(
-                bindings[i].bindingID,
-                convertDescriptorTypeFlag(bindings[i].descriptorType),
-                bindings[i].descriptorCount,
-                convertShaderStageFlag(bindings[i].shaderStage));
-            setBindings.push_back(descriptorSetLayoutBinding);
-        }
-
-        DescriptorSet set;
-
-        //create the descriptor set's layout from the bindings gathered above
-        vk::DescriptorSetLayoutCreateInfo layoutInfo({}, setBindings);
-        if(m_Device.createDescriptorSetLayout(&layoutInfo, nullptr, &set.layout) != vk::Result::eSuccess)
-        {
-			vkcv_log(LogLevel::ERROR, "Failed to create descriptor set layout");
-            return DescriptorSetHandle();
-        };
-        
-        //create and allocate the set based on the layout that have been gathered above
-        vk::DescriptorSetAllocateInfo allocInfo(m_Pools.back(), 1, &set.layout);
-        auto result = m_Device.allocateDescriptorSets(&allocInfo, &set.vulkanHandle);
-        if(result != vk::Result::eSuccess)
-        {
-			//create a new descriptor pool if the previous one ran out of memory
-			if (result == vk::Result::eErrorOutOfPoolMemory) {
-				allocateDescriptorPool();
-				allocInfo.setDescriptorPool(m_Pools.back());
-				result = m_Device.allocateDescriptorSets(&allocInfo, &set.vulkanHandle);
-			}
-			if (result != vk::Result::eSuccess) {
-				vkcv_log(LogLevel::ERROR, "Failed to create descriptor set (%s)",
-						 vk::to_string(result).c_str());
-				
-				m_Device.destroy(set.layout);
-				return DescriptorSetHandle();
-			}
-        };
-
-        const uint64_t id = m_DescriptorSets.size();
-
-        m_DescriptorSets.push_back(set);
-        return DescriptorSetHandle(id, [&](uint64_t id) { destroyDescriptorSetById(id); });
-    }
-    
-    struct WriteDescriptorSetInfo {
-		size_t imageInfoIndex;
-		size_t bufferInfoIndex;
-		uint32_t binding;
-		vk::DescriptorType type;
-    };
-
-	void DescriptorManager::writeDescriptorSet(
-		const DescriptorSetHandle	&handle,
-		const DescriptorWrites	&writes,
-		const ImageManager		&imageManager, 
-		const BufferManager		&bufferManager,
-		const SamplerManager	&samplerManager) {
-
-		vk::DescriptorSet set = m_DescriptorSets[handle.getId()].vulkanHandle;
-
-		std::vector<vk::DescriptorImageInfo> imageInfos;
-		std::vector<vk::DescriptorBufferInfo> bufferInfos;
-		
-		std::vector<WriteDescriptorSetInfo> writeInfos;
-
-		for (const auto& write : writes.sampledImageWrites) {
-		    vk::ImageLayout layout = write.useGeneralLayout ? vk::ImageLayout::eGeneral : vk::ImageLayout::eShaderReadOnlyOptimal;
-			const vk::DescriptorImageInfo imageInfo(
-				nullptr,
-				imageManager.getVulkanImageView(write.image, write.mipLevel),
-                layout
-			);
-			
-			imageInfos.push_back(imageInfo);
-			
-			WriteDescriptorSetInfo vulkanWrite = {
-					imageInfos.size(),
-					0,
-					write.binding,
-					vk::DescriptorType::eSampledImage,
-			};
-			
-			writeInfos.push_back(vulkanWrite);
-		}
-
-		for (const auto& write : writes.storageImageWrites) {
-			const vk::DescriptorImageInfo imageInfo(
-				nullptr,
-				imageManager.getVulkanImageView(write.image, write.mipLevel),
-				vk::ImageLayout::eGeneral
-			);
-			
-			imageInfos.push_back(imageInfo);
-			
-			WriteDescriptorSetInfo vulkanWrite = {
-					imageInfos.size(),
-					0,
-					write.binding,
-					vk::DescriptorType::eStorageImage
-			};
-			
-			writeInfos.push_back(vulkanWrite);
-		}
-
-		for (const auto& write : writes.uniformBufferWrites) {
-			const vk::DescriptorBufferInfo bufferInfo(
-				bufferManager.getBuffer(write.buffer),
-				static_cast<uint32_t>(0),
-				bufferManager.getBufferSize(write.buffer)
-			);
-			
-			bufferInfos.push_back(bufferInfo);
-
-			WriteDescriptorSetInfo vulkanWrite = {
-					0,
-					bufferInfos.size(),
-					write.binding,
-					vk::DescriptorType::eUniformBuffer
-			};
-			
-			writeInfos.push_back(vulkanWrite);
-		}
-
-		for (const auto& write : writes.storageBufferWrites) {
-			const vk::DescriptorBufferInfo bufferInfo(
-				bufferManager.getBuffer(write.buffer),
-				static_cast<uint32_t>(0),
-				bufferManager.getBufferSize(write.buffer)
-			);
-			
-			bufferInfos.push_back(bufferInfo);
-			
-			WriteDescriptorSetInfo vulkanWrite = {
-					0,
-					bufferInfos.size(),
-					write.binding,
-					vk::DescriptorType::eStorageBuffer
-			};
-			
-			writeInfos.push_back(vulkanWrite);
-		}
-
-		for (const auto& write : writes.samplerWrites) {
-			const vk::Sampler& sampler = samplerManager.getVulkanSampler(write.sampler);
-			
-			const vk::DescriptorImageInfo imageInfo(
-				sampler,
-				nullptr,
-				vk::ImageLayout::eGeneral
-			);
-			
-			imageInfos.push_back(imageInfo);
-
-			WriteDescriptorSetInfo vulkanWrite = {
-					imageInfos.size(),
-					0,
-					write.binding,
-					vk::DescriptorType::eSampler
-			};
-			
-			writeInfos.push_back(vulkanWrite);
-		}
-		
-		std::vector<vk::WriteDescriptorSet> vulkanWrites;
-		
-		for (const auto& write : writeInfos) {
-			vk::WriteDescriptorSet vulkanWrite(
-					set,
-					write.binding,
-					static_cast<uint32_t>(0),
-					1,
-					write.type,
-					(write.imageInfoIndex > 0? &(imageInfos[write.imageInfoIndex - 1]) : nullptr),
-					(write.bufferInfoIndex > 0? &(bufferInfos[write.bufferInfoIndex - 1]) : nullptr)
-			);
-			
-			vulkanWrites.push_back(vulkanWrite);
-		}
-		
-		m_Device.updateDescriptorSets(vulkanWrites, nullptr);
-	}
-
-	DescriptorSet DescriptorManager::getDescriptorSet(const DescriptorSetHandle handle) const {
-		return m_DescriptorSets[handle.getId()];
-	}
-
-    vk::DescriptorType DescriptorManager::convertDescriptorTypeFlag(DescriptorType type) {
-        switch (type)
-        {
-            case DescriptorType::UNIFORM_BUFFER:
-                return vk::DescriptorType::eUniformBuffer;
-            case DescriptorType::STORAGE_BUFFER:
-                return vk::DescriptorType::eStorageBuffer;
-            case DescriptorType::SAMPLER:
-                return vk::DescriptorType::eSampler;
-            case DescriptorType::IMAGE_SAMPLED:
-                return vk::DescriptorType::eSampledImage;
-			case DescriptorType::IMAGE_STORAGE:
-				return vk::DescriptorType::eStorageImage;
-            default:
-				vkcv_log(LogLevel::ERROR, "Unknown DescriptorType");
-                return vk::DescriptorType::eUniformBuffer;
-        }
-    }
-
-    vk::ShaderStageFlagBits DescriptorManager::convertShaderStageFlag(ShaderStage stage) {
-        switch (stage) 
-        {
-            case ShaderStage::VERTEX:
-                return vk::ShaderStageFlagBits::eVertex;
-            case ShaderStage::FRAGMENT:
-                return vk::ShaderStageFlagBits::eFragment;
-            case ShaderStage::TESS_CONTROL:
-                return vk::ShaderStageFlagBits::eTessellationControl;
-            case ShaderStage::TESS_EVAL:
-                return vk::ShaderStageFlagBits::eTessellationEvaluation;
-            case ShaderStage::GEOMETRY:
-                return vk::ShaderStageFlagBits::eGeometry;
-            case ShaderStage::COMPUTE:
-                return vk::ShaderStageFlagBits::eCompute;
-            default:
-                return vk::ShaderStageFlagBits::eAll;
-        }
-    }
-    
-    void DescriptorManager::destroyDescriptorSetById(uint64_t id) {
-		if (id >= m_DescriptorSets.size()) {
-			vkcv_log(LogLevel::ERROR, "Invalid id");
-			return;
-		}
-		
-		auto& set = m_DescriptorSets[id];
-		if (set.layout) {
-			m_Device.destroyDescriptorSetLayout(set.layout);
-			set.layout = nullptr;
-		}
-		// FIXME: descriptor set itself not destroyed
-	}
-
-	vk::DescriptorPool DescriptorManager::allocateDescriptorPool() {
-		vk::DescriptorPool pool;
-		if (m_Device.createDescriptorPool(&m_PoolInfo, nullptr, &pool) != vk::Result::eSuccess)
-		{
-			vkcv_log(LogLevel::WARNING, "Failed to allocate descriptor pool");
-			pool = nullptr;
-		};
-		m_Pools.push_back(pool);
-		return pool;
-	}
-
-}
\ No newline at end of file
diff --git a/src/vkcv/DescriptorManager.hpp b/src/vkcv/DescriptorManager.hpp
deleted file mode 100644
index d18be64f3b069af68cecce68f6fa623c81f8dfa4..0000000000000000000000000000000000000000
--- a/src/vkcv/DescriptorManager.hpp
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * @authors Artur Wasmut, Susanne D�tsch, Simeon Hermann
- * @file src/vkcv/DescriptorManager.cpp
- * @brief Creation and handling of descriptor sets and the respective descriptor pools
- */
-#include <vulkan/vulkan.hpp>
-
-#include "vkcv/Handles.hpp"
-#include "vkcv/DescriptorConfig.hpp"
-#include "vkcv/DescriptorWrites.hpp"
-
-#include "ImageManager.hpp"
-#include "vkcv/BufferManager.hpp"
-#include "SamplerManager.hpp"
-
-namespace vkcv
-{
-	class DescriptorManager
-	{
-	public:
-	    explicit DescriptorManager(vk::Device device) noexcept;
-	    ~DescriptorManager() noexcept;
-
-        DescriptorSetHandle createDescriptorSet(const std::vector<DescriptorBinding> &descriptorBindings);
-
-		void writeDescriptorSet(
-			const DescriptorSetHandle	&handle,
-			const DescriptorWrites  &writes,
-			const ImageManager      &imageManager,
-			const BufferManager     &bufferManager,
-			const SamplerManager    &samplerManager);
-
-		[[nodiscard]]
-		DescriptorSet getDescriptorSet(const DescriptorSetHandle handle) const;
-
-	private:
-		vk::Device m_Device;
-		std::vector<vk::DescriptorPool>	m_Pools;
-		std::vector<vk::DescriptorPoolSize> m_PoolSizes;
-		vk::DescriptorPoolCreateInfo m_PoolInfo;
-
-
-		/**
-		* Contains all the resource descriptions that were requested by the user in calls of createResourceDescription.
-		*/
-        std::vector<DescriptorSet> m_DescriptorSets;
-		
-		/**
-		* Converts the flags of the descriptor types from VulkanCV (vkcv) to Vulkan (vk).
-		* @param[in] vkcv flag of the DescriptorType (see DescriptorConfig.hpp)
-		* @return vk flag of the DescriptorType
-		*/
-		static vk::DescriptorType convertDescriptorTypeFlag(DescriptorType type);
-		/**
-		* Converts the flags of the shader stages from VulkanCV (vkcv) to Vulkan (vk).
-		* @param[in] vkcv flag of the ShaderStage (see ShaderProgram.hpp)
-		* @return vk flag of the ShaderStage
-		*/
-		static vk::ShaderStageFlagBits convertShaderStageFlag(ShaderStage stage);
-		
-		/**
-		* Destroys a specific resource description
-		* @param[in] the handle id of the respective resource description
-		*/
-		void destroyDescriptorSetById(uint64_t id);
-
-		/**
-		* creates a descriptor pool based on the poolSizes and poolInfo defined in the constructor
-		* is called initially in the constructor and then every time the pool runs out memory
-		* @return a DescriptorPool object
-		*/
-		vk::DescriptorPool allocateDescriptorPool();
-		
-	};
-}
\ No newline at end of file
diff --git a/src/vkcv/DescriptorSetLayoutManager.cpp b/src/vkcv/DescriptorSetLayoutManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..14fe4d519f0587d6ef3febf256de83f9f5428f42
--- /dev/null
+++ b/src/vkcv/DescriptorSetLayoutManager.cpp
@@ -0,0 +1,107 @@
+#include "DescriptorSetLayoutManager.hpp"
+
+#include "vkcv/Core.hpp"
+
+namespace vkcv {
+
+	uint64_t DescriptorSetLayoutManager::getIdFrom(const DescriptorSetLayoutHandle &handle) const {
+		return handle.getId();
+	}
+
+	DescriptorSetLayoutHandle
+	DescriptorSetLayoutManager::createById(uint64_t id, const HandleDestroyFunction &destroy) {
+		return DescriptorSetLayoutHandle(id, destroy);
+	}
+
+	void DescriptorSetLayoutManager::destroyById(uint64_t id) {
+		auto &layout = getById(id);
+
+		if (layout.layoutUsageCount > 1) {
+			layout.layoutUsageCount--;
+			return;
+		} else {
+			layout.layoutUsageCount = 0;
+		}
+
+		if (layout.vulkanHandle) {
+			getCore().getContext().getDevice().destroy(layout.vulkanHandle);
+			layout.vulkanHandle = nullptr;
+		}
+	}
+
+	DescriptorSetLayoutManager::DescriptorSetLayoutManager() noexcept :
+		HandleManager<DescriptorSetLayoutEntry, DescriptorSetLayoutHandle>() {}
+
+	DescriptorSetLayoutManager::~DescriptorSetLayoutManager() noexcept {
+		for (uint64_t id = 0; id < getCount(); id++) {
+			// Resets the usage count to zero for destruction.
+			getById(id).layoutUsageCount = 0;
+		}
+
+		clear();
+	}
+
+	DescriptorSetLayoutHandle
+	DescriptorSetLayoutManager::createDescriptorSetLayout(const DescriptorBindings &bindings) {
+		for (uint64_t id = 0; id < getCount(); id++) {
+			auto &layout = getById(id);
+
+			if (layout.descriptorBindings.size() != bindings.size())
+				continue;
+
+			if (layout.descriptorBindings == bindings) {
+				layout.layoutUsageCount++;
+				return createById(id, [&](uint64_t id) {
+					destroyById(id);
+				});
+			}
+		}
+
+		// create the descriptor set's layout and binding flags by iterating over its bindings
+		std::vector<vk::DescriptorSetLayoutBinding> bindingsVector = {};
+		std::vector<vk::DescriptorBindingFlags> bindingsFlags = {};
+
+		for (auto bindingElem : bindings) {
+			DescriptorBinding binding = bindingElem.second;
+			uint32_t bindingID = bindingElem.first;
+
+			bindingsVector.emplace_back(bindingID, getVkDescriptorType(binding.descriptorType),
+										binding.descriptorCount,
+										getShaderStageFlags(binding.shaderStages), nullptr);
+
+			vk::DescriptorBindingFlags flags;
+
+			if (binding.variableCount)
+				flags |= vk::DescriptorBindingFlagBits::eVariableDescriptorCount;
+
+			if (binding.partialBinding)
+				flags |= vk::DescriptorBindingFlagBits::ePartiallyBound;
+
+			bindingsFlags.push_back(flags);
+		}
+
+		vk::DescriptorSetLayoutBindingFlagsCreateInfo bindingFlagsInfo(bindingsFlags.size(),
+																	   bindingsFlags.data());
+
+		// create the descriptor set's layout from the binding data gathered above
+		vk::DescriptorSetLayout vulkanHandle;
+		vk::DescriptorSetLayoutCreateInfo layoutInfo(vk::DescriptorSetLayoutCreateFlags(),
+													 bindingsVector);
+		layoutInfo.setPNext(&bindingFlagsInfo);
+
+		auto result = getCore().getContext().getDevice().createDescriptorSetLayout(
+			&layoutInfo, nullptr, &vulkanHandle);
+		if (result != vk::Result::eSuccess) {
+			vkcv_log(LogLevel::ERROR, "Failed to create descriptor set layout");
+			return DescriptorSetLayoutHandle();
+		};
+
+		return add({ vulkanHandle, bindings, 1 });
+	}
+
+	const DescriptorSetLayoutEntry &DescriptorSetLayoutManager::getDescriptorSetLayout(
+		const DescriptorSetLayoutHandle &handle) const {
+		return (*this) [handle];
+	}
+
+} // namespace vkcv
diff --git a/src/vkcv/DescriptorSetLayoutManager.hpp b/src/vkcv/DescriptorSetLayoutManager.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..3e540e9daa74c533eef0f1fe9788abb1496fa008
--- /dev/null
+++ b/src/vkcv/DescriptorSetLayoutManager.hpp
@@ -0,0 +1,70 @@
+#pragma once
+/**
+ * @authors Artur Wasmut, Susanne D�tsch, Simeon Hermann, Tobias Frisch
+ * @file src/vkcv/DescriptorManager.cpp
+ * @brief Creation and handling of descriptor set layouts.
+ */
+#include <vulkan/vulkan.hpp>
+
+#include "vkcv/DescriptorBinding.hpp"
+
+#include "HandleManager.hpp"
+
+namespace vkcv {
+
+	/**
+	 * @brief Structure to store details about a descriptor set layout.
+	 */
+	struct DescriptorSetLayoutEntry {
+		vk::DescriptorSetLayout vulkanHandle;
+		DescriptorBindings descriptorBindings;
+		size_t layoutUsageCount;
+	};
+
+	/**
+	 * @brief Class to manage descriptor set layouts.
+	 */
+	class DescriptorSetLayoutManager :
+		public HandleManager<DescriptorSetLayoutEntry, DescriptorSetLayoutHandle> {
+		friend class Core;
+
+	private:
+		[[nodiscard]] uint64_t getIdFrom(const DescriptorSetLayoutHandle &handle) const override;
+
+		[[nodiscard]] DescriptorSetLayoutHandle
+		createById(uint64_t id, const HandleDestroyFunction &destroy) override;
+
+		/**
+		 * Destroys and deallocates descriptor set layout represented by a given
+		 * descriptor set layout handle id.
+		 *
+		 * @param id Descriptor set layout handle id
+		 */
+		void destroyById(uint64_t id) override;
+
+	public:
+		/**
+		 * @brief Constructor of the descriptor set layout manager
+		 */
+		DescriptorSetLayoutManager() noexcept;
+
+		/**
+		 * @brief Destructor of the descriptor set layout manager
+		 */
+		~DescriptorSetLayoutManager() noexcept override;
+
+		/**
+		 * @brief Creates a descriptor set layout with given descriptor bindings
+		 * or returns a matching handle.
+		 *
+		 * @param[in] bindings Descriptor bindings
+		 * @return Handle of descriptor set layout
+		 */
+		[[nodiscard]] DescriptorSetLayoutHandle
+		createDescriptorSetLayout(const DescriptorBindings &bindings);
+
+		[[nodiscard]] const DescriptorSetLayoutEntry &
+		getDescriptorSetLayout(const DescriptorSetLayoutHandle &handle) const;
+	};
+
+} // namespace vkcv
diff --git a/src/vkcv/DescriptorSetManager.cpp b/src/vkcv/DescriptorSetManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e6ac1c22c76373c7773a7c242805e41f628f66c7
--- /dev/null
+++ b/src/vkcv/DescriptorSetManager.cpp
@@ -0,0 +1,302 @@
+#include "DescriptorSetManager.hpp"
+
+#include "vkcv/Core.hpp"
+
+namespace vkcv {
+
+	bool DescriptorSetManager::init(Core &core,
+									DescriptorSetLayoutManager &descriptorSetLayoutManager) {
+		if (!HandleManager<DescriptorSetEntry, DescriptorSetHandle>::init(core)) {
+			return false;
+		}
+
+		m_DescriptorSetLayoutManager = &descriptorSetLayoutManager;
+
+		/**
+		 * Allocate the set size for the descriptor pools, namely 1000 units of each descriptor type
+		 * below. Finally, create an initial pool.
+		 */
+		m_PoolSizes = {
+			vk::DescriptorPoolSize(vk::DescriptorType::eSampler, 1000),
+			vk::DescriptorPoolSize(vk::DescriptorType::eSampledImage, 1000),
+			vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, 1000),
+			vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, 1000),
+			vk::DescriptorPoolSize(vk::DescriptorType::eUniformBufferDynamic, 1000),
+			vk::DescriptorPoolSize(vk::DescriptorType::eStorageBufferDynamic, 1000),    // for RTX
+			vk::DescriptorPoolSize(vk::DescriptorType::eAccelerationStructureKHR, 1000) // for RTX
+		};
+
+		m_PoolInfo = vk::DescriptorPoolCreateInfo(
+			vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, 1000,
+			static_cast<uint32_t>(m_PoolSizes.size()), m_PoolSizes.data());
+
+		return allocateDescriptorPool();
+	}
+
+	uint64_t DescriptorSetManager::getIdFrom(const DescriptorSetHandle &handle) const {
+		return handle.getId();
+	}
+
+	DescriptorSetHandle DescriptorSetManager::createById(uint64_t id,
+														 const HandleDestroyFunction &destroy) {
+		return DescriptorSetHandle(id, destroy);
+	}
+
+	void DescriptorSetManager::destroyById(uint64_t id) {
+		auto &set = getById(id);
+
+		if (set.vulkanHandle) {
+			getCore().getContext().getDevice().freeDescriptorSets(m_Pools [set.poolIndex], 1,
+																  &(set.vulkanHandle));
+			set.setLayoutHandle = DescriptorSetLayoutHandle();
+			set.vulkanHandle = nullptr;
+		}
+	}
+
+	vk::DescriptorPool DescriptorSetManager::allocateDescriptorPool() {
+		vk::DescriptorPool pool;
+		if (getCore().getContext().getDevice().createDescriptorPool(&m_PoolInfo, nullptr, &pool)
+			!= vk::Result::eSuccess) {
+			vkcv_log(LogLevel::WARNING, "Failed to allocate descriptor pool");
+			pool = nullptr;
+		} else {
+			m_Pools.push_back(pool);
+		}
+
+		return pool;
+	}
+
+	DescriptorSetManager::DescriptorSetManager() noexcept :
+		HandleManager<DescriptorSetEntry, DescriptorSetHandle>() {}
+
+	DescriptorSetManager::~DescriptorSetManager() noexcept {
+		clear();
+
+		for (const auto &pool : m_Pools) {
+			if (pool) {
+				getCore().getContext().getDevice().destroy(pool);
+			}
+		}
+	}
+
+	DescriptorSetHandle
+	DescriptorSetManager::createDescriptorSet(const DescriptorSetLayoutHandle &layout) {
+		// create and allocate the set based on the layout provided
+		const auto &setLayout = m_DescriptorSetLayoutManager->getDescriptorSetLayout(layout);
+
+		vk::DescriptorSet vulkanHandle;
+		vk::DescriptorSetAllocateInfo allocInfo(m_Pools.back(), 1, &setLayout.vulkanHandle);
+
+		uint32_t sumVariableDescriptorCounts = 0;
+		for (auto bindingElem : setLayout.descriptorBindings) {
+			auto binding = bindingElem.second;
+
+			if (binding.variableCount)
+				sumVariableDescriptorCounts += binding.descriptorCount;
+		}
+
+		vk::DescriptorSetVariableDescriptorCountAllocateInfo variableAllocInfo(
+			1, &sumVariableDescriptorCounts);
+
+		if (sumVariableDescriptorCounts > 0) {
+			allocInfo.setPNext(&variableAllocInfo);
+		}
+
+		auto result =
+			getCore().getContext().getDevice().allocateDescriptorSets(&allocInfo, &vulkanHandle);
+		if (result != vk::Result::eSuccess) {
+			// create a new descriptor pool if the previous one ran out of memory
+			if (result == vk::Result::eErrorOutOfPoolMemory) {
+				allocateDescriptorPool();
+				allocInfo.setDescriptorPool(m_Pools.back());
+				result = getCore().getContext().getDevice().allocateDescriptorSets(&allocInfo,
+																				   &vulkanHandle);
+			}
+
+			if (result != vk::Result::eSuccess) {
+				vkcv_log(LogLevel::ERROR, "Failed to create descriptor set (%s)",
+						 vk::to_string(result).c_str());
+				return {};
+			}
+		};
+
+		size_t poolIndex = (m_Pools.size() - 1);
+		return add({ vulkanHandle, layout, poolIndex });
+	}
+
+	/**
+	 * @brief Structure to store details to write to a descriptor set.
+	 */
+	struct WriteDescriptorSetInfo {
+		size_t imageInfoIndex;
+		size_t bufferInfoIndex;
+		size_t structureIndex;
+		uint32_t binding;
+		uint32_t arrayElementIndex;
+		uint32_t descriptorCount;
+		vk::DescriptorType type;
+	};
+
+	void DescriptorSetManager::writeDescriptorSet(const DescriptorSetHandle &handle,
+												  const DescriptorWrites &writes,
+												  const ImageManager &imageManager,
+												  const BufferManager &bufferManager,
+												  const SamplerManager &samplerManager) {
+		auto &set = (*this) [handle];
+
+		std::vector<vk::DescriptorImageInfo> imageInfos;
+		std::vector<vk::DescriptorBufferInfo> bufferInfos;
+
+		std::vector<vk::WriteDescriptorSetAccelerationStructureKHR> writeStructures;
+
+		std::vector<WriteDescriptorSetInfo> writeInfos;
+
+		for (const auto &write : writes.getSampledImageWrites()) {
+			const vk::ImageLayout layout =
+				(write.useGeneralLayout ? vk::ImageLayout::eGeneral :
+										  vk::ImageLayout::eShaderReadOnlyOptimal);
+
+			for (uint32_t i = 0; i < write.mipCount; i++) {
+				const vk::DescriptorImageInfo imageInfo(
+					nullptr,
+					imageManager.getVulkanImageView(write.image, write.mipLevel + i,
+													write.arrayView),
+					layout);
+
+				imageInfos.push_back(imageInfo);
+			}
+
+			WriteDescriptorSetInfo vulkanWrite = {
+				imageInfos.size() + 1 - write.mipCount,
+				0,
+				0,
+				write.binding,
+				write.arrayIndex,
+				write.mipCount,
+				vk::DescriptorType::eSampledImage,
+			};
+
+			writeInfos.push_back(vulkanWrite);
+		}
+
+		for (const auto &write : writes.getStorageImageWrites()) {
+			for (uint32_t i = 0; i < write.mipCount; i++) {
+				const vk::DescriptorImageInfo imageInfo(
+					nullptr,
+					imageManager.getVulkanImageView(write.image, write.mipLevel + i,
+													write.arrayView),
+					vk::ImageLayout::eGeneral);
+
+				imageInfos.push_back(imageInfo);
+			}
+
+			WriteDescriptorSetInfo vulkanWrite = {
+				imageInfos.size() + 1 - write.mipCount, 0, 0, write.binding, 0, write.mipCount,
+				vk::DescriptorType::eStorageImage
+			};
+
+			writeInfos.push_back(vulkanWrite);
+		}
+
+		for (const auto &write : writes.getUniformBufferWrites()) {
+			const size_t size = bufferManager.getBufferSize(write.buffer);
+			const uint32_t offset = std::clamp<uint32_t>(write.offset, 0, size);
+
+			const vk::DescriptorBufferInfo bufferInfo(
+				bufferManager.getBuffer(write.buffer), offset,
+				write.size == 0 ? size : std::min<uint32_t>(write.size, size - offset));
+
+			bufferInfos.push_back(bufferInfo);
+
+			WriteDescriptorSetInfo vulkanWrite = { 0,
+												   bufferInfos.size(),
+												   0,
+												   write.binding,
+												   0,
+												   1,
+												   write.dynamic ?
+													   vk::DescriptorType::eUniformBufferDynamic :
+													   vk::DescriptorType::eUniformBuffer };
+
+			writeInfos.push_back(vulkanWrite);
+		}
+
+		for (const auto &write : writes.getStorageBufferWrites()) {
+			const size_t size = bufferManager.getBufferSize(write.buffer);
+			const uint32_t offset = std::clamp<uint32_t>(write.offset, 0, size);
+
+			const vk::DescriptorBufferInfo bufferInfo(
+				bufferManager.getBuffer(write.buffer), offset,
+				write.size == 0 ? size : std::min<uint32_t>(write.size, size - offset));
+
+			bufferInfos.push_back(bufferInfo);
+
+			WriteDescriptorSetInfo vulkanWrite = { 0,
+												   bufferInfos.size(),
+												   0,
+												   write.binding,
+												   0,
+												   1,
+												   write.dynamic ?
+													   vk::DescriptorType::eStorageBufferDynamic :
+													   vk::DescriptorType::eStorageBuffer };
+
+			writeInfos.push_back(vulkanWrite);
+		}
+
+		for (const auto &write : writes.getSamplerWrites()) {
+			const vk::Sampler &sampler = samplerManager.getVulkanSampler(write.sampler);
+
+			const vk::DescriptorImageInfo imageInfo(sampler, nullptr, vk::ImageLayout::eGeneral);
+
+			imageInfos.push_back(imageInfo);
+
+			WriteDescriptorSetInfo vulkanWrite = {
+				imageInfos.size(), 0, 0, write.binding, 0, 1, vk::DescriptorType::eSampler
+			};
+
+			writeInfos.push_back(vulkanWrite);
+		}
+
+		for (const auto &write : writes.getAccelerationWrites()) {
+			const vk::WriteDescriptorSetAccelerationStructureKHR structureWrite(
+				write.structures.size(), write.structures.data());
+
+			writeStructures.push_back(structureWrite);
+
+			WriteDescriptorSetInfo vulkanWrite = { 0,
+												   0,
+												   writeStructures.size(),
+												   write.binding,
+												   0,
+												   1,
+												   vk::DescriptorType::eAccelerationStructureKHR };
+
+			writeInfos.push_back(vulkanWrite);
+		}
+
+		std::vector<vk::WriteDescriptorSet> vulkanWrites;
+
+		for (const auto &write : writeInfos) {
+			vk::WriteDescriptorSet vulkanWrite(
+				set.vulkanHandle, write.binding, write.arrayElementIndex, write.descriptorCount,
+				write.type,
+				(write.imageInfoIndex > 0 ? &(imageInfos [write.imageInfoIndex - 1]) : nullptr),
+				(write.bufferInfoIndex > 0 ? &(bufferInfos [write.bufferInfoIndex - 1]) : nullptr));
+
+			if (write.structureIndex > 0) {
+				vulkanWrite.setPNext(&(writeStructures [write.structureIndex - 1]));
+			}
+
+			vulkanWrites.push_back(vulkanWrite);
+		}
+
+		getCore().getContext().getDevice().updateDescriptorSets(vulkanWrites, nullptr);
+	}
+
+	const DescriptorSetEntry &
+	DescriptorSetManager::getDescriptorSet(const DescriptorSetHandle &handle) const {
+		return (*this) [handle];
+	}
+
+} // namespace vkcv
diff --git a/src/vkcv/DescriptorSetManager.hpp b/src/vkcv/DescriptorSetManager.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ae3f879ce4ef9d3b16f687bc237060c98591bd65
--- /dev/null
+++ b/src/vkcv/DescriptorSetManager.hpp
@@ -0,0 +1,104 @@
+#pragma once
+/**
+ * @authors Artur Wasmut, Susanne D�tsch, Simeon Hermann, Tobias Frisch
+ * @file src/vkcv/DescriptorManager.cpp
+ * @brief Creation and handling of descriptor sets and the respective descriptor pools.
+ */
+#include <vulkan/vulkan.hpp>
+
+#include "vkcv/DescriptorBinding.hpp"
+#include "vkcv/DescriptorWrites.hpp"
+
+#include "BufferManager.hpp"
+#include "DescriptorSetLayoutManager.hpp"
+#include "HandleManager.hpp"
+#include "ImageManager.hpp"
+#include "SamplerManager.hpp"
+
+namespace vkcv {
+
+	/**
+	 * @brief Structure to store details about a descriptor set.
+	 */
+	struct DescriptorSetEntry {
+		vk::DescriptorSet vulkanHandle;
+		DescriptorSetLayoutHandle setLayoutHandle;
+		size_t poolIndex;
+	};
+
+	/**
+	 * @brief Class to manage descriptor sets.
+	 */
+	class DescriptorSetManager : public HandleManager<DescriptorSetEntry, DescriptorSetHandle> {
+		friend class Core;
+
+	private:
+		DescriptorSetLayoutManager* m_DescriptorSetLayoutManager;
+
+		std::vector<vk::DescriptorPool> m_Pools;
+		std::vector<vk::DescriptorPoolSize> m_PoolSizes;
+		vk::DescriptorPoolCreateInfo m_PoolInfo;
+
+		bool init(Core &core, DescriptorSetLayoutManager &descriptorSetLayoutManager);
+
+		[[nodiscard]] uint64_t getIdFrom(const DescriptorSetHandle &handle) const override;
+
+		[[nodiscard]] DescriptorSetHandle createById(uint64_t id,
+													 const HandleDestroyFunction &destroy) override;
+
+		/**
+		 * Destroys and deallocates descriptor set represented by a given
+		 * descriptor set handle id.
+		 *
+		 * @param id Descriptor set handle id
+		 */
+		void destroyById(uint64_t id) override;
+
+		/**
+		 * @brief Creates a descriptor pool based on the poolSizes and poolInfo defined in the
+		 * constructor is called initially in the constructor and then every time the pool runs
+		 * out memory.
+		 *
+		 * @return a DescriptorPool object
+		 */
+		vk::DescriptorPool allocateDescriptorPool();
+
+	public:
+		/**
+		 * @brief Constructor of the descriptor set manager
+		 */
+		DescriptorSetManager() noexcept;
+
+		/**
+		 * @brief Destructor of the descriptor set manager
+		 */
+		~DescriptorSetManager() noexcept override;
+
+		/**
+		 * @brief Creates a descriptor set using a given descriptor set layout.
+		 *
+		 * @param[in] layout Handle of descriptor set layout
+		 * @return Handle of descriptor set
+		 */
+		[[nodiscard]] DescriptorSetHandle
+		createDescriptorSet(const DescriptorSetLayoutHandle &layout);
+
+		/**
+		 * @brief Writes to a descriptor set using writes and all required managers.
+		 *
+		 * @param[in] handle Handle of descriptor set
+		 * @param[in] writes Descriptor set writes
+		 * @param[in] imageManager Image manager
+		 * @param[in] bufferManager Buffer manager
+		 * @param[in] samplerManager Sampler manager
+		 */
+		void writeDescriptorSet(const DescriptorSetHandle &handle, const DescriptorWrites &writes,
+								const ImageManager &imageManager,
+								const BufferManager &bufferManager,
+								const SamplerManager &samplerManager);
+
+		[[nodiscard]] const DescriptorSetEntry &
+		getDescriptorSet(const DescriptorSetHandle &handle) const;
+	};
+
+} // namespace vkcv
diff --git a/src/vkcv/DescriptorSetUsage.cpp b/src/vkcv/DescriptorSetUsage.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..cfa2ba1998e124cec0f1fc851356d28009288e1b
--- /dev/null
+++ b/src/vkcv/DescriptorSetUsage.cpp
@@ -0,0 +1,15 @@
+
+#include "vkcv/DescriptorSetUsage.hpp"
+
+namespace vkcv {
+
+	DescriptorSetUsage useDescriptorSet(uint32_t location, const DescriptorSetHandle &descriptorSet,
+										const std::vector<uint32_t> &dynamicOffsets) {
+		DescriptorSetUsage usage;
+		usage.location = location;
+		usage.descriptorSet = descriptorSet;
+		usage.dynamicOffsets = dynamicOffsets;
+		return usage;
+	}
+
+} // namespace vkcv
diff --git a/src/vkcv/DescriptorWrites.cpp b/src/vkcv/DescriptorWrites.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a3b4c5f4fbc4212da078ec0a00a8a93a6c021d02
--- /dev/null
+++ b/src/vkcv/DescriptorWrites.cpp
@@ -0,0 +1,115 @@
+
+#include "vkcv/DescriptorWrites.hpp"
+
+namespace vkcv {
+
+	DescriptorWrites &DescriptorWrites::writeSampledImage(uint32_t binding,
+														  const ImageHandle& image,
+														  uint32_t mipLevel,
+														  bool useGeneralLayout,
+														  uint32_t arrayIndex,
+														  uint32_t mipCount,
+														  bool arrayView) {
+		SampledImageDescriptorWrite write;
+		write.binding = binding;
+		write.image = image;
+		write.mipLevel = mipLevel;
+		write.useGeneralLayout = useGeneralLayout;
+		write.arrayIndex = arrayIndex;
+		write.mipCount = mipCount;
+		write.arrayView = arrayView;
+		m_sampledImageWrites.push_back(write);
+		return *this;
+	}
+
+	DescriptorWrites &DescriptorWrites::writeStorageImage(uint32_t binding,
+														  const ImageHandle& image,
+														  uint32_t mipLevel,
+														  uint32_t mipCount,
+														  bool arrayView) {
+		StorageImageDescriptorWrite write;
+		write.binding = binding;
+		write.image = image;
+		write.mipLevel = mipLevel;
+		write.mipCount = mipCount;
+		write.arrayView = arrayView;
+		m_storageImageWrites.push_back(write);
+		return *this;
+	}
+
+	DescriptorWrites &DescriptorWrites::writeUniformBuffer(uint32_t binding,
+														   const BufferHandle& buffer,
+														   bool dynamic,
+														   uint32_t offset,
+														   uint32_t size) {
+		BufferDescriptorWrite write;
+		write.binding = binding;
+		write.buffer = buffer;
+		write.dynamic = dynamic;
+		write.offset = offset;
+		write.size = size;
+		m_uniformBufferWrites.push_back(write);
+		return *this;
+	}
+
+	DescriptorWrites &DescriptorWrites::writeStorageBuffer(uint32_t binding,
+														   const BufferHandle& buffer,
+														   bool dynamic,
+														   uint32_t offset,
+														   uint32_t size) {
+		BufferDescriptorWrite write;
+		write.binding = binding;
+		write.buffer = buffer;
+		write.dynamic = dynamic;
+		write.offset = offset;
+		write.size = size;
+		m_storageBufferWrites.push_back(write);
+		return *this;
+	}
+
+	DescriptorWrites &DescriptorWrites::writeSampler(uint32_t binding,
+													 const SamplerHandle& sampler) {
+		SamplerDescriptorWrite write;
+		write.binding = binding;
+		write.sampler = sampler;
+		m_samplerWrites.push_back(write);
+		return *this;
+	}
+
+	DescriptorWrites &DescriptorWrites::writeAcceleration(
+		uint32_t binding, const std::vector<vk::AccelerationStructureKHR> &structures) {
+		AccelerationDescriptorWrite write;
+		write.binding = binding;
+		write.structures = structures;
+		m_accelerationWrites.push_back(write);
+		return *this;
+	}
+
+	const std::vector<SampledImageDescriptorWrite> &
+	DescriptorWrites::getSampledImageWrites() const {
+		return m_sampledImageWrites;
+	}
+
+	const std::vector<StorageImageDescriptorWrite> &
+	DescriptorWrites::getStorageImageWrites() const {
+		return m_storageImageWrites;
+	}
+
+	const std::vector<BufferDescriptorWrite> &DescriptorWrites::getUniformBufferWrites() const {
+		return m_uniformBufferWrites;
+	}
+
+	const std::vector<BufferDescriptorWrite> &DescriptorWrites::getStorageBufferWrites() const {
+		return m_storageBufferWrites;
+	}
+
+	const std::vector<SamplerDescriptorWrite> &DescriptorWrites::getSamplerWrites() const {
+		return m_samplerWrites;
+	}
+
+	const std::vector<AccelerationDescriptorWrite> &
+	DescriptorWrites::getAccelerationWrites() const {
+		return m_accelerationWrites;
+	}
+
+} // namespace vkcv
diff --git a/src/vkcv/DispatchSize.cpp b/src/vkcv/DispatchSize.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..eb58f2477ac6dfa9fa8db15a3d134698f596ff32
--- /dev/null
+++ b/src/vkcv/DispatchSize.cpp
@@ -0,0 +1,59 @@
+
+#include "vkcv/DispatchSize.hpp"
+#include "vkcv/Logger.hpp"
+
+#include <cmath>
+
+namespace vkcv {
+
+	DispatchSize::DispatchSize(uint32_t count) : DispatchSize(count, 1, 1) {}
+
+	DispatchSize::DispatchSize(uint32_t dimensionX, uint32_t dimentionY, uint32_t dimensionZ) :
+		m_Dispatch({ dimensionX, dimentionY, dimensionZ }) {
+		check();
+	}
+
+	const uint32_t* DispatchSize::data() const {
+		return m_Dispatch.data();
+	}
+
+	uint32_t DispatchSize::operator[](size_t index) const {
+		return m_Dispatch.at(index);
+	}
+
+	uint32_t DispatchSize::x() const {
+		return m_Dispatch [0];
+	}
+
+	uint32_t DispatchSize::y() const {
+		return m_Dispatch [1];
+	}
+
+	uint32_t DispatchSize::z() const {
+		return m_Dispatch [2];
+	}
+
+	bool DispatchSize::check() const {
+		const uint32_t dimensionX = x();
+		const uint32_t dimensionY = y();
+		const uint32_t dimensionZ = z();
+
+		if ((dimensionX <= 0) || (dimensionY <= 0) || (dimensionZ <= 0)) {
+			vkcv_log(LogLevel::WARNING, "Dispatch size invalid: x = %u, y = %u, z = %u", dimensionX,
+					 dimensionY, dimensionZ);
+
+			return false;
+		} else {
+			return true;
+		}
+	}
+
+	DispatchSize dispatchInvocations(DispatchSize globalInvocations, DispatchSize groupSize) {
+		const uint32_t dimensionX = std::ceil(1.0f * globalInvocations.x() / groupSize.x());
+		const uint32_t dimensionY = std::ceil(1.0f * globalInvocations.y() / groupSize.y());
+		const uint32_t dimensionZ = std::ceil(1.0f * globalInvocations.z() / groupSize.z());
+
+		return DispatchSize(dimensionX, dimensionY, dimensionZ);
+	}
+
+} // namespace vkcv
diff --git a/src/vkcv/Downsampler.cpp b/src/vkcv/Downsampler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..961ade2944618cd1c28f214a61eb4487a2915cf3
--- /dev/null
+++ b/src/vkcv/Downsampler.cpp
@@ -0,0 +1,8 @@
+
+#include "vkcv/Downsampler.hpp"
+
+namespace vkcv {
+
+	Downsampler::Downsampler(Core &core) : m_core(core) {}
+
+} // namespace vkcv
diff --git a/src/vkcv/Drawcall.cpp b/src/vkcv/Drawcall.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b45cb6ecc8a31d098d65791ede15badb3dc24bec
--- /dev/null
+++ b/src/vkcv/Drawcall.cpp
@@ -0,0 +1,64 @@
+/**
+ * @authors Tobias Frisch
+ * @file src/vkcv/Drawcall.cpp
+ */
+
+#include "vkcv/Drawcall.hpp"
+
+namespace vkcv {
+
+	const std::vector<DescriptorSetUsage> &Drawcall::getDescriptorSetUsages() const {
+		return m_usages;
+	}
+
+	void Drawcall::useDescriptorSet(uint32_t location, const DescriptorSetHandle &descriptorSet,
+									const std::vector<uint32_t> &dynamicOffsets) {
+		DescriptorSetUsage usage;
+		usage.location = location;
+		usage.descriptorSet = descriptorSet;
+		usage.dynamicOffsets = dynamicOffsets;
+		m_usages.push_back(usage);
+	}
+
+	InstanceDrawcall::InstanceDrawcall(const VertexData &vertexData,
+									   uint32_t instanceCount) :
+		Drawcall(),
+		m_vertexData(vertexData),
+		m_instanceCount(instanceCount) {}
+
+	const VertexData &InstanceDrawcall::getVertexData() const {
+		return m_vertexData;
+	}
+
+	uint32_t InstanceDrawcall::getInstanceCount() const {
+		return m_instanceCount;
+	}
+
+	IndirectDrawcall::IndirectDrawcall(const BufferHandle &indirectDrawBuffer,
+									   const VertexData &vertexData,
+									   uint32_t drawCount) :
+		Drawcall(),
+		m_indirectDrawBuffer(indirectDrawBuffer),
+		m_vertexData(vertexData),
+		m_drawCount(drawCount) {
+	}
+
+	BufferHandle IndirectDrawcall::getIndirectDrawBuffer() const {
+		return m_indirectDrawBuffer;
+	}
+
+	const VertexData &IndirectDrawcall::getVertexData() const {
+		return m_vertexData;
+	}
+
+	uint32_t IndirectDrawcall::getDrawCount() const {
+		return m_drawCount;
+	}
+
+	TaskDrawcall::TaskDrawcall(uint32_t taskCount) : Drawcall(), m_taskCount(taskCount) {}
+
+	uint32_t TaskDrawcall::getTaskCount() const {
+		return m_taskCount;
+	}
+
+} // namespace vkcv
diff --git a/src/vkcv/DrawcallRecording.cpp b/src/vkcv/DrawcallRecording.cpp
deleted file mode 100644
index df7b7bbcb3fe278622cd160593eb750db00ec7b1..0000000000000000000000000000000000000000
--- a/src/vkcv/DrawcallRecording.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-#include <vkcv/DrawcallRecording.hpp>
-
-namespace vkcv {
-
-    void recordDrawcall(
-        const DrawcallInfo      &drawcall,
-        vk::CommandBuffer       cmdBuffer,
-        vk::PipelineLayout      pipelineLayout,
-        const PushConstantData  &pushConstantData,
-        const size_t            drawcallIndex) {
-
-        for (uint32_t i = 0; i < drawcall.mesh.vertexBufferBindings.size(); i++) {
-            const auto& vertexBinding = drawcall.mesh.vertexBufferBindings[i];
-            cmdBuffer.bindVertexBuffers(i, vertexBinding.buffer, vertexBinding.offset);
-        }
-
-        for (const auto& descriptorUsage : drawcall.descriptorSets) {
-            cmdBuffer.bindDescriptorSets(
-                vk::PipelineBindPoint::eGraphics,
-                pipelineLayout,
-                descriptorUsage.setLocation,
-                descriptorUsage.vulkanHandle,
-                nullptr);
-        }
-
-        const size_t drawcallPushConstantOffset = drawcallIndex * pushConstantData.sizePerDrawcall;
-        // char* cast because void* does not support pointer arithmetic
-        const void* drawcallPushConstantData = drawcallPushConstantOffset + (char*)pushConstantData.data;
-
-        cmdBuffer.pushConstants(
-            pipelineLayout,
-            vk::ShaderStageFlagBits::eAll,
-            0,
-            pushConstantData.sizePerDrawcall,
-            drawcallPushConstantData);
-
-        if (drawcall.mesh.indexBuffer) {
-            cmdBuffer.bindIndexBuffer(drawcall.mesh.indexBuffer, 0, vk::IndexType::eUint16);	//FIXME: choose proper size
-            cmdBuffer.drawIndexed(drawcall.mesh.indexCount, 1, 0, 0, {});
-        }
-        else {
-            cmdBuffer.draw(drawcall.mesh.indexCount, 1, 0, 0, {});
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/vkcv/FeatureManager.cpp b/src/vkcv/FeatureManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ab8e20dd35b27d19655fb34174bc86fba6455857
--- /dev/null
+++ b/src/vkcv/FeatureManager.cpp
@@ -0,0 +1,660 @@
+
+#include "vkcv/FeatureManager.hpp"
+
+#include <cstddef>
+#include <cstring>
+
+#ifdef _MSVC_LANG
+#include <type_traits>
+#endif
+
+namespace vkcv {
+
+#ifdef _MSVC_LANG
+#define typeof(var) std::decay<decltype((var))>::type
+#endif
+
+#define vkcv_check_init_features2(type) \
+	type supported;                     \
+	vk::PhysicalDeviceFeatures2 query;  \
+	query.setPNext(&supported);         \
+	m_physicalDevice.getFeatures2(&query)
+
+#define vkcv_check_feature(attribute)                                                         \
+	{                                                                                         \
+		const char* f = reinterpret_cast<const char*>(&(features));                           \
+		const char* s = reinterpret_cast<const char*>(&(supported));                          \
+		const vk::Bool32* fb =                                                                \
+			reinterpret_cast<const vk::Bool32*>(f + offsetof(typeof((features)), attribute)); \
+		const vk::Bool32* sb =                                                                \
+			reinterpret_cast<const vk::Bool32*>(s + offsetof(typeof((features)), attribute)); \
+		if ((*fb) && (!*sb)) {                                                                \
+			vkcv_log(((required) ? LogLevel::ERROR : LogLevel::WARNING),                      \
+					 "Feature '" #attribute "' is not supported");                            \
+			return false;                                                                     \
+		}                                                                                     \
+	}
+
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceFeatures &features,
+									  bool required) const {
+		const auto &supported = m_physicalDevice.getFeatures();
+
+		vkcv_check_feature(alphaToOne);
+		vkcv_check_feature(depthBiasClamp);
+		vkcv_check_feature(depthBounds);
+		vkcv_check_feature(depthClamp);
+		vkcv_check_feature(drawIndirectFirstInstance);
+		vkcv_check_feature(dualSrcBlend);
+		vkcv_check_feature(fillModeNonSolid);
+		vkcv_check_feature(fragmentStoresAndAtomics);
+		vkcv_check_feature(fullDrawIndexUint32);
+		vkcv_check_feature(geometryShader);
+		vkcv_check_feature(imageCubeArray);
+		vkcv_check_feature(independentBlend);
+		vkcv_check_feature(inheritedQueries);
+		vkcv_check_feature(largePoints);
+		vkcv_check_feature(logicOp);
+		vkcv_check_feature(multiDrawIndirect);
+		vkcv_check_feature(multiViewport);
+		vkcv_check_feature(occlusionQueryPrecise);
+		vkcv_check_feature(pipelineStatisticsQuery);
+		vkcv_check_feature(robustBufferAccess);
+		vkcv_check_feature(sampleRateShading);
+		vkcv_check_feature(samplerAnisotropy);
+		vkcv_check_feature(shaderClipDistance);
+		vkcv_check_feature(shaderCullDistance);
+		vkcv_check_feature(shaderFloat64);
+		vkcv_check_feature(shaderImageGatherExtended);
+		vkcv_check_feature(shaderInt16);
+		vkcv_check_feature(shaderInt64);
+		vkcv_check_feature(shaderResourceMinLod);
+		vkcv_check_feature(shaderResourceResidency);
+		vkcv_check_feature(shaderSampledImageArrayDynamicIndexing);
+		vkcv_check_feature(shaderStorageBufferArrayDynamicIndexing);
+		vkcv_check_feature(shaderStorageImageArrayDynamicIndexing);
+		vkcv_check_feature(shaderStorageImageExtendedFormats);
+		vkcv_check_feature(shaderStorageImageMultisample);
+		vkcv_check_feature(shaderStorageImageReadWithoutFormat);
+		vkcv_check_feature(shaderStorageImageWriteWithoutFormat);
+		vkcv_check_feature(shaderTessellationAndGeometryPointSize);
+		vkcv_check_feature(shaderUniformBufferArrayDynamicIndexing);
+		vkcv_check_feature(sparseBinding);
+		vkcv_check_feature(sparseResidency2Samples);
+		vkcv_check_feature(sparseResidency4Samples);
+		vkcv_check_feature(sparseResidency8Samples);
+		vkcv_check_feature(sparseResidency16Samples);
+		vkcv_check_feature(sparseResidencyAliased);
+		vkcv_check_feature(sparseResidencyBuffer);
+		vkcv_check_feature(sparseResidencyImage2D);
+		vkcv_check_feature(sparseResidencyImage3D);
+		vkcv_check_feature(tessellationShader);
+		vkcv_check_feature(textureCompressionASTC_LDR);
+		vkcv_check_feature(textureCompressionBC);
+		vkcv_check_feature(textureCompressionETC2);
+		vkcv_check_feature(variableMultisampleRate);
+		vkcv_check_feature(vertexPipelineStoresAndAtomics);
+		vkcv_check_feature(wideLines);
+		vkcv_check_feature(multiDrawIndirect);
+
+		return true;
+	}
+
+	bool FeatureManager::checkSupport(const vk::PhysicalDevice16BitStorageFeatures &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDevice16BitStorageFeatures);
+
+		vkcv_check_feature(storageBuffer16BitAccess);
+		vkcv_check_feature(storageInputOutput16);
+		vkcv_check_feature(storagePushConstant16);
+		vkcv_check_feature(uniformAndStorageBuffer16BitAccess);
+
+		return true;
+	}
+
+	bool FeatureManager::checkSupport(const vk::PhysicalDevice8BitStorageFeatures &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDevice8BitStorageFeatures);
+
+		vkcv_check_feature(storageBuffer8BitAccess);
+		vkcv_check_feature(storagePushConstant8);
+		vkcv_check_feature(uniformAndStorageBuffer8BitAccess);
+
+		return true;
+	}
+
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceBufferDeviceAddressFeatures &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceBufferDeviceAddressFeatures);
+
+		vkcv_check_feature(bufferDeviceAddress);
+		vkcv_check_feature(bufferDeviceAddressCaptureReplay);
+		vkcv_check_feature(bufferDeviceAddressMultiDevice);
+
+		return true;
+	}
+
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceDescriptorIndexingFeatures &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceDescriptorIndexingFeatures);
+
+		vkcv_check_feature(shaderInputAttachmentArrayDynamicIndexing);
+		vkcv_check_feature(shaderInputAttachmentArrayNonUniformIndexing);
+		vkcv_check_feature(shaderSampledImageArrayNonUniformIndexing);
+		vkcv_check_feature(shaderStorageBufferArrayNonUniformIndexing);
+		vkcv_check_feature(shaderStorageImageArrayNonUniformIndexing);
+		vkcv_check_feature(shaderStorageTexelBufferArrayDynamicIndexing);
+		vkcv_check_feature(shaderStorageTexelBufferArrayNonUniformIndexing);
+		vkcv_check_feature(shaderUniformBufferArrayNonUniformIndexing);
+		vkcv_check_feature(shaderUniformTexelBufferArrayDynamicIndexing);
+		vkcv_check_feature(shaderUniformTexelBufferArrayNonUniformIndexing);
+		vkcv_check_feature(descriptorBindingPartiallyBound);
+		vkcv_check_feature(descriptorBindingSampledImageUpdateAfterBind);
+		vkcv_check_feature(descriptorBindingStorageBufferUpdateAfterBind);
+		vkcv_check_feature(descriptorBindingStorageImageUpdateAfterBind);
+		vkcv_check_feature(descriptorBindingStorageTexelBufferUpdateAfterBind);
+		vkcv_check_feature(descriptorBindingUniformBufferUpdateAfterBind);
+		vkcv_check_feature(descriptorBindingUniformTexelBufferUpdateAfterBind);
+		vkcv_check_feature(descriptorBindingUpdateUnusedWhilePending);
+		vkcv_check_feature(descriptorBindingVariableDescriptorCount);
+		vkcv_check_feature(runtimeDescriptorArray);
+
+		return true;
+	}
+
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceHostQueryResetFeatures &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceHostQueryResetFeatures);
+
+		vkcv_check_feature(hostQueryReset);
+
+		return true;
+	}
+
+	bool
+	FeatureManager::checkSupport(const vk::PhysicalDeviceImagelessFramebufferFeatures &features,
+								 bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceImagelessFramebufferFeatures);
+
+		vkcv_check_feature(imagelessFramebuffer);
+
+		return true;
+	}
+
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceMultiviewFeatures &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceMultiviewFeatures);
+
+		vkcv_check_feature(multiview);
+		vkcv_check_feature(multiviewGeometryShader);
+		vkcv_check_feature(multiviewTessellationShader);
+
+		return true;
+	}
+
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceProtectedMemoryFeatures &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceProtectedMemoryFeatures);
+
+		vkcv_check_feature(protectedMemory);
+
+		return true;
+	}
+
+	bool
+	FeatureManager::checkSupport(const vk::PhysicalDeviceSamplerYcbcrConversionFeatures &features,
+								 bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceSamplerYcbcrConversionFeatures);
+
+		vkcv_check_feature(samplerYcbcrConversion);
+
+		return true;
+	}
+
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceScalarBlockLayoutFeatures &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceScalarBlockLayoutFeatures);
+
+		vkcv_check_feature(scalarBlockLayout);
+
+		return true;
+	}
+
+	bool FeatureManager::checkSupport(
+		const vk::PhysicalDeviceSeparateDepthStencilLayoutsFeatures &features,
+		bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceSeparateDepthStencilLayoutsFeatures);
+
+		vkcv_check_feature(separateDepthStencilLayouts);
+
+		return true;
+	}
+
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceShaderAtomicInt64Features &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceShaderAtomicInt64Features);
+
+		vkcv_check_feature(shaderBufferInt64Atomics);
+		vkcv_check_feature(shaderSharedInt64Atomics);
+
+		return true;
+	}
+
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceShaderFloat16Int8Features &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceShaderFloat16Int8Features);
+
+		vkcv_check_feature(shaderFloat16);
+		vkcv_check_feature(shaderInt8);
+
+		return true;
+	}
+
+	bool FeatureManager::checkSupport(
+		const vk::PhysicalDeviceShaderSubgroupExtendedTypesFeatures &features,
+		bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceShaderSubgroupExtendedTypesFeatures);
+
+		vkcv_check_feature(shaderSubgroupExtendedTypes);
+
+		return true;
+	}
+
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceTimelineSemaphoreFeatures &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceTimelineSemaphoreFeatures);
+
+		vkcv_check_feature(timelineSemaphore);
+
+		return true;
+	}
+
+	bool FeatureManager::checkSupport(
+		const vk::PhysicalDeviceUniformBufferStandardLayoutFeatures &features,
+		bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceUniformBufferStandardLayoutFeatures);
+
+		vkcv_check_feature(uniformBufferStandardLayout);
+
+		return true;
+	}
+
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceVariablePointersFeatures &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceVariablePointersFeatures);
+
+		vkcv_check_feature(variablePointers);
+		vkcv_check_feature(variablePointersStorageBuffer);
+
+		return true;
+	}
+
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceVulkanMemoryModelFeatures &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceVulkanMemoryModelFeatures);
+
+		vkcv_check_feature(vulkanMemoryModel);
+		vkcv_check_feature(vulkanMemoryModelDeviceScope);
+		vkcv_check_feature(vulkanMemoryModelAvailabilityVisibilityChains);
+
+		return true;
+	}
+
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceMeshShaderFeaturesNV &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceMeshShaderFeaturesNV);
+
+		vkcv_check_feature(taskShader);
+		vkcv_check_feature(meshShader);
+
+		return true;
+	}
+
+	bool
+	FeatureManager::checkSupport(const vk::PhysicalDeviceShaderAtomicFloatFeaturesEXT &features,
+								 bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceShaderAtomicFloatFeaturesEXT);
+
+		vkcv_check_feature(shaderBufferFloat32Atomics);
+		vkcv_check_feature(shaderBufferFloat32AtomicAdd);
+		vkcv_check_feature(shaderBufferFloat64Atomics);
+		vkcv_check_feature(shaderBufferFloat64AtomicAdd);
+		vkcv_check_feature(shaderSharedFloat32Atomics);
+		vkcv_check_feature(shaderSharedFloat32AtomicAdd);
+		vkcv_check_feature(shaderSharedFloat64Atomics);
+		vkcv_check_feature(shaderSharedFloat64AtomicAdd);
+		vkcv_check_feature(shaderImageFloat32Atomics);
+		vkcv_check_feature(shaderImageFloat32AtomicAdd);
+		vkcv_check_feature(sparseImageFloat32Atomics);
+		vkcv_check_feature(sparseImageFloat32AtomicAdd);
+
+		return true;
+	}
+
+	bool
+	FeatureManager::checkSupport(const vk::PhysicalDeviceShaderAtomicFloat2FeaturesEXT &features,
+								 bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceShaderAtomicFloat2FeaturesEXT);
+
+		vkcv_check_feature(shaderBufferFloat16Atomics);
+		vkcv_check_feature(shaderBufferFloat16AtomicAdd);
+		vkcv_check_feature(shaderBufferFloat16AtomicMinMax);
+		vkcv_check_feature(shaderBufferFloat32AtomicMinMax);
+		vkcv_check_feature(shaderBufferFloat64AtomicMinMax);
+		vkcv_check_feature(shaderSharedFloat16Atomics);
+		vkcv_check_feature(shaderSharedFloat16AtomicAdd);
+		vkcv_check_feature(shaderSharedFloat16AtomicMinMax);
+		vkcv_check_feature(shaderSharedFloat32AtomicMinMax);
+		vkcv_check_feature(shaderSharedFloat64AtomicMinMax);
+		vkcv_check_feature(shaderImageFloat32AtomicMinMax);
+		vkcv_check_feature(sparseImageFloat32AtomicMinMax);
+
+		return true;
+	}
+
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceVulkan12Features &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceVulkan12Features);
+
+		vkcv_check_feature(samplerMirrorClampToEdge);
+		vkcv_check_feature(drawIndirectCount);
+		vkcv_check_feature(storageBuffer8BitAccess);
+		vkcv_check_feature(uniformAndStorageBuffer8BitAccess);
+		vkcv_check_feature(storagePushConstant8);
+		vkcv_check_feature(shaderBufferInt64Atomics);
+		vkcv_check_feature(shaderSharedInt64Atomics);
+		vkcv_check_feature(shaderFloat16);
+		vkcv_check_feature(shaderInt8);
+		vkcv_check_feature(descriptorIndexing);
+		vkcv_check_feature(shaderInputAttachmentArrayDynamicIndexing);
+		vkcv_check_feature(shaderUniformTexelBufferArrayDynamicIndexing);
+		vkcv_check_feature(shaderStorageTexelBufferArrayDynamicIndexing);
+		vkcv_check_feature(shaderUniformBufferArrayNonUniformIndexing);
+		vkcv_check_feature(shaderSampledImageArrayNonUniformIndexing);
+		vkcv_check_feature(shaderStorageBufferArrayNonUniformIndexing);
+		vkcv_check_feature(shaderStorageImageArrayNonUniformIndexing);
+		vkcv_check_feature(shaderInputAttachmentArrayNonUniformIndexing);
+		vkcv_check_feature(shaderUniformTexelBufferArrayNonUniformIndexing);
+		vkcv_check_feature(shaderStorageTexelBufferArrayNonUniformIndexing);
+		vkcv_check_feature(descriptorBindingUniformBufferUpdateAfterBind);
+		vkcv_check_feature(descriptorBindingSampledImageUpdateAfterBind);
+		vkcv_check_feature(descriptorBindingStorageImageUpdateAfterBind);
+		vkcv_check_feature(descriptorBindingStorageBufferUpdateAfterBind);
+		vkcv_check_feature(descriptorBindingUniformTexelBufferUpdateAfterBind);
+		vkcv_check_feature(descriptorBindingStorageTexelBufferUpdateAfterBind);
+		vkcv_check_feature(descriptorBindingUpdateUnusedWhilePending);
+		vkcv_check_feature(descriptorBindingPartiallyBound);
+		vkcv_check_feature(descriptorBindingVariableDescriptorCount);
+		vkcv_check_feature(runtimeDescriptorArray);
+		vkcv_check_feature(samplerFilterMinmax);
+		vkcv_check_feature(scalarBlockLayout);
+		vkcv_check_feature(imagelessFramebuffer);
+		vkcv_check_feature(uniformBufferStandardLayout);
+		vkcv_check_feature(shaderSubgroupExtendedTypes);
+		vkcv_check_feature(separateDepthStencilLayouts);
+		vkcv_check_feature(hostQueryReset);
+		vkcv_check_feature(timelineSemaphore);
+		vkcv_check_feature(bufferDeviceAddress);
+		vkcv_check_feature(bufferDeviceAddressCaptureReplay);
+		vkcv_check_feature(bufferDeviceAddressMultiDevice);
+		vkcv_check_feature(vulkanMemoryModel);
+		vkcv_check_feature(vulkanMemoryModelDeviceScope);
+		vkcv_check_feature(vulkanMemoryModelAvailabilityVisibilityChains);
+		vkcv_check_feature(shaderOutputViewportIndex);
+		vkcv_check_feature(shaderOutputLayer);
+		vkcv_check_feature(subgroupBroadcastDynamicId);
+
+		return true;
+	}
+
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceVulkan11Features &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceVulkan11Features);
+
+		vkcv_check_feature(multiview);
+		vkcv_check_feature(multiviewGeometryShader);
+		vkcv_check_feature(multiviewTessellationShader);
+		vkcv_check_feature(protectedMemory);
+		vkcv_check_feature(samplerYcbcrConversion);
+		vkcv_check_feature(shaderDrawParameters);
+		vkcv_check_feature(storageBuffer16BitAccess);
+		vkcv_check_feature(storageInputOutput16);
+		vkcv_check_feature(storagePushConstant16);
+		vkcv_check_feature(uniformAndStorageBuffer16BitAccess);
+		vkcv_check_feature(variablePointers);
+		vkcv_check_feature(variablePointersStorageBuffer);
+
+		return true;
+	}
+
+	bool
+	FeatureManager::checkSupport(const vk::PhysicalDeviceAccelerationStructureFeaturesKHR &features,
+								 bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceAccelerationStructureFeaturesKHR);
+
+		vkcv_check_feature(accelerationStructure);
+		vkcv_check_feature(accelerationStructureCaptureReplay);
+		vkcv_check_feature(accelerationStructureIndirectBuild);
+		vkcv_check_feature(accelerationStructureHostCommands);
+		vkcv_check_feature(descriptorBindingAccelerationStructureUpdateAfterBind);
+
+		return true;
+	}
+
+	bool
+	FeatureManager::checkSupport(const vk::PhysicalDeviceRayTracingPipelineFeaturesKHR &features,
+								 bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceRayTracingPipelineFeaturesKHR);
+
+		vkcv_check_feature(rayTracingPipeline);
+		vkcv_check_feature(rayTracingPipelineShaderGroupHandleCaptureReplay);
+		vkcv_check_feature(rayTracingPipelineShaderGroupHandleCaptureReplayMixed);
+		vkcv_check_feature(rayTracingPipelineTraceRaysIndirect);
+		vkcv_check_feature(rayTraversalPrimitiveCulling);
+
+		return true;
+	}
+
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceVulkan13Features &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceVulkan13Features);
+
+		vkcv_check_feature(robustImageAccess);
+		vkcv_check_feature(inlineUniformBlock);
+		vkcv_check_feature(descriptorBindingInlineUniformBlockUpdateAfterBind);
+		vkcv_check_feature(pipelineCreationCacheControl);
+		vkcv_check_feature(privateData);
+		vkcv_check_feature(shaderDemoteToHelperInvocation);
+		vkcv_check_feature(shaderTerminateInvocation);
+		vkcv_check_feature(subgroupSizeControl);
+		vkcv_check_feature(computeFullSubgroups);
+		vkcv_check_feature(synchronization2);
+		vkcv_check_feature(textureCompressionASTC_HDR);
+		vkcv_check_feature(shaderZeroInitializeWorkgroupMemory);
+		vkcv_check_feature(dynamicRendering);
+		vkcv_check_feature(shaderIntegerDotProduct);
+		vkcv_check_feature(maintenance4);
+
+		return true;
+	}
+	
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceCoherentMemoryFeaturesAMD &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceCoherentMemoryFeaturesAMD);
+		
+		vkcv_check_feature(deviceCoherentMemory);
+		
+		return true;
+	}
+	
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceSubgroupSizeControlFeatures &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceSubgroupSizeControlFeatures);
+		
+		vkcv_check_feature(subgroupSizeControl);
+		vkcv_check_feature(computeFullSubgroups);
+		
+		return true;
+	}
+	
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceIndexTypeUint8FeaturesEXT &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceIndexTypeUint8FeaturesEXT);
+		
+		vkcv_check_feature(indexTypeUint8);
+		
+		return true;
+	}
+	
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceShaderTerminateInvocationFeatures &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceShaderTerminateInvocationFeatures);
+		
+		vkcv_check_feature(shaderTerminateInvocation);
+		
+		return true;
+	}
+
+	vk::BaseOutStructure* FeatureManager::findFeatureStructure(vk::StructureType type) const {
+		for (auto &base : m_featuresExtensions) {
+			if (base->sType == type) {
+				return base;
+			}
+		}
+
+		return nullptr;
+	}
+
+	const char* strclone(const char* str) {
+		if (!str) {
+			return nullptr;
+		}
+
+		const size_t length = strlen(str) + 1;
+
+		if (length <= 1) {
+			return nullptr;
+		}
+
+		char* clone = new char [length];
+		strcpy(clone, str);
+		return clone;
+	}
+
+	FeatureManager::FeatureManager(vk::PhysicalDevice &physicalDevice) :
+		m_physicalDevice(physicalDevice), m_supportedExtensions(), m_activeExtensions(),
+		m_featuresBase(), m_featuresExtensions() {
+		for (const auto &extension : m_physicalDevice.enumerateDeviceExtensionProperties()) {
+			const char* clone = strclone(extension.extensionName);
+
+			if (clone) {
+				m_supportedExtensions.push_back(clone);
+			}
+		}
+	}
+
+	FeatureManager::FeatureManager(FeatureManager &&other) noexcept :
+		m_physicalDevice(other.m_physicalDevice),
+		m_supportedExtensions(std::move(other.m_supportedExtensions)),
+		m_activeExtensions(std::move(other.m_activeExtensions)),
+		m_featuresBase(other.m_featuresBase),
+		m_featuresExtensions(std::move(other.m_featuresExtensions)) {
+		other.m_featuresExtensions.clear();
+		other.m_activeExtensions.clear();
+		other.m_supportedExtensions.clear();
+	}
+
+	FeatureManager::~FeatureManager() {
+		for (auto &features : m_featuresExtensions) {
+			delete features;
+		}
+
+		for (auto &extension : m_activeExtensions) {
+			delete [] extension;
+		}
+
+		for (auto &extension : m_supportedExtensions) {
+			delete [] extension;
+		}
+	}
+
+	FeatureManager &FeatureManager::operator=(FeatureManager &&other) noexcept {
+		m_physicalDevice = other.m_physicalDevice;
+		m_supportedExtensions = std::move(other.m_supportedExtensions);
+		m_activeExtensions = std::move(other.m_activeExtensions);
+		m_featuresBase = other.m_featuresBase;
+		m_featuresExtensions = std::move(other.m_featuresExtensions);
+
+		other.m_featuresExtensions.clear();
+		other.m_activeExtensions.clear();
+		other.m_supportedExtensions.clear();
+
+		return *this;
+	}
+
+	bool FeatureManager::isExtensionSupported(const std::string &extension) const {
+		for (const auto &supported : m_supportedExtensions) {
+			if (0 == strcmp(supported, extension.c_str())) {
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+	bool FeatureManager::useExtension(const std::string &extension, bool required) {
+		const char* clone = strclone(extension.c_str());
+
+		if (!clone) {
+			vkcv_log(LogLevel::WARNING, "Extension '%s' is not valid", extension.c_str());
+			return false;
+		}
+
+		if (!isExtensionSupported(extension)) {
+			vkcv_log((required ? LogLevel::ERROR : LogLevel::WARNING),
+					 "Extension '%s' is not supported", extension.c_str());
+
+			delete [] clone;
+			if (required) {
+				vkcv_log_throw_error("Required extension is not supported!");
+			}
+
+			return false;
+		}
+
+		m_activeExtensions.push_back(clone);
+		return true;
+	}
+
+	bool FeatureManager::isExtensionActive(const std::string &extension) const {
+		for (const auto &supported : m_activeExtensions) {
+			if (0 == strcmp(supported, extension.c_str())) {
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+	const std::vector<const char*> &FeatureManager::getActiveExtensions() const {
+		return m_activeExtensions;
+	}
+
+	bool FeatureManager::useFeatures(
+		const std::function<void(vk::PhysicalDeviceFeatures &)> &featureFunction, bool required) {
+		vk::PhysicalDeviceFeatures features = m_featuresBase.features;
+
+		featureFunction(features);
+
+		if (!checkSupport(features, required)) {
+			return false;
+		}
+
+		m_featuresBase.features = features;
+		return true;
+	}
+
+	const vk::PhysicalDeviceFeatures2 &FeatureManager::getFeatures() const {
+		return m_featuresBase;
+	}
+
+} // namespace vkcv
diff --git a/src/vkcv/Features.cpp b/src/vkcv/Features.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fcfdd7b9435b7f9ab9ad230fa8d77a9a0d66b80e
--- /dev/null
+++ b/src/vkcv/Features.cpp
@@ -0,0 +1,66 @@
+
+#include "vkcv/Features.hpp"
+
+namespace vkcv {
+
+	Features::Features(const std::initializer_list<std::string> &list) : m_features() {
+		for (const auto &extension : list) {
+			requireExtension(extension);
+		}
+	}
+
+	void Features::requireExtension(const std::string &extension) {
+		m_features.emplace_back([extension](FeatureManager &featureManager) {
+			return featureManager.useExtension(extension, true);
+		});
+	}
+
+	void Features::requireExtensionFeature(
+		const std::string &extension,
+		const std::function<void(vk::PhysicalDeviceFeatures &)> &featureFunction) {
+		m_features.emplace_back([extension, featureFunction](FeatureManager &featureManager) {
+			if (featureManager.useExtension(extension, true)) {
+				return featureManager.useFeatures(featureFunction, true);
+			} else {
+				return false;
+			}
+		});
+	}
+
+	void Features::requireFeature(
+		const std::function<void(vk::PhysicalDeviceFeatures &)> &featureFunction) {
+		m_features.emplace_back([featureFunction](FeatureManager &featureManager) {
+			return featureManager.useFeatures(featureFunction, true);
+		});
+	}
+
+	void Features::tryExtension(const std::string &extension) {
+		m_features.emplace_back([extension](FeatureManager &featureManager) {
+			return featureManager.useExtension(extension, false);
+		});
+	}
+
+	void Features::tryExtensionFeature(
+		const std::string &extension,
+		const std::function<void(vk::PhysicalDeviceFeatures &)> &featureFunction) {
+		m_features.emplace_back([extension, featureFunction](FeatureManager &featureManager) {
+			if (featureManager.useExtension(extension, false)) {
+				return featureManager.useFeatures(featureFunction, false);
+			} else {
+				return false;
+			}
+		});
+	}
+
+	void
+	Features::tryFeature(const std::function<void(vk::PhysicalDeviceFeatures &)> &featureFunction) {
+		m_features.emplace_back([featureFunction](FeatureManager &featureManager) {
+			return featureManager.useFeatures(featureFunction, false);
+		});
+	}
+
+	const std::vector<Feature> &Features::getList() const {
+		return m_features;
+	}
+
+} // namespace vkcv
diff --git a/src/vkcv/File.cpp b/src/vkcv/File.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f840fdb06850d6943864ef119883996a461a13fe
--- /dev/null
+++ b/src/vkcv/File.cpp
@@ -0,0 +1,58 @@
+
+#include "vkcv/File.hpp"
+
+#include <cstdlib>
+
+#ifdef _WIN32
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+
+#include "vkcv/Logger.hpp"
+
+namespace vkcv {
+
+	std::filesystem::path generateTemporaryFilePath() {
+		std::filesystem::path tmp = generateTemporaryDirectoryPath();
+
+		if (std::filesystem::is_directory(tmp)) {
+			return std::filesystem::path(tmp.string() + "W"); // add W for Wambo
+		} else {
+			return tmp;
+		}
+	}
+
+	std::filesystem::path generateTemporaryDirectoryPath() {
+		std::error_code code;
+		auto tmp = std::filesystem::temp_directory_path(code);
+
+		if (tmp.empty()) {
+			tmp = std::filesystem::current_path();
+		}
+
+		char name [16] = "vkcv_tmp_XXXXXX";
+
+#ifdef _WIN32
+		int err = _mktemp_s(name, 16);
+
+		if (err != 0) {
+			vkcv_log(LogLevel::ERROR, "Temporary file path could not be generated");
+			return "";
+		}
+#else
+		int fd = mkstemp(name); // creates a file locally
+
+		if (fd == -1) {
+			vkcv_log(LogLevel::ERROR, "Temporary file path could not be generated");
+			return "";
+		}
+
+		close(fd);
+		remove(name); // removes the local file again
+#endif
+
+		return tmp / name;
+	}
+
+} // namespace vkcv
diff --git a/src/vkcv/GraphicsPipelineConfig.cpp b/src/vkcv/GraphicsPipelineConfig.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..987cd3a74492a2b4f1428fa75e546ce7ffcba8b9
--- /dev/null
+++ b/src/vkcv/GraphicsPipelineConfig.cpp
@@ -0,0 +1,117 @@
+
+#include "vkcv/GraphicsPipelineConfig.hpp"
+
+namespace vkcv {
+
+	GraphicsPipelineConfig::GraphicsPipelineConfig() :
+		PipelineConfig(), m_PassHandle(), m_VertexLayout(),
+		m_Width(std::numeric_limits<uint32_t>::max()),
+		m_Height(std::numeric_limits<uint32_t>::max()) {}
+
+	GraphicsPipelineConfig::GraphicsPipelineConfig(
+		const ShaderProgram &program, const PassHandle &pass, const VertexLayout &vertexLayout,
+		const std::vector<DescriptorSetLayoutHandle> &layouts) :
+		PipelineConfig(program, layouts),
+		m_PassHandle(pass), m_VertexLayout(vertexLayout),
+		m_Width(std::numeric_limits<uint32_t>::max()),
+		m_Height(std::numeric_limits<uint32_t>::max()) {}
+
+	const PassHandle &GraphicsPipelineConfig::getPass() const {
+		return m_PassHandle;
+	}
+
+	const VertexLayout &GraphicsPipelineConfig::getVertexLayout() const {
+		return m_VertexLayout;
+	}
+
+	uint32_t GraphicsPipelineConfig::getWidth() const {
+		return m_Width;
+	}
+
+	uint32_t GraphicsPipelineConfig::getHeight() const {
+		return m_Height;
+	}
+
+	void GraphicsPipelineConfig::setResolution(uint32_t width, uint32_t height) {
+		m_Width = width;
+		m_Height = height;
+	}
+
+	bool GraphicsPipelineConfig::isViewportDynamic() const {
+		return ((m_Width == std::numeric_limits<uint32_t>::max())
+				&& (m_Height == std::numeric_limits<uint32_t>::max()));
+	}
+
+	bool GraphicsPipelineConfig::isUsingConservativeRasterization() const {
+		return m_UseConservativeRasterization;
+	}
+
+	void GraphicsPipelineConfig::setUsingConservativeRasterization(bool conservativeRasterization) {
+		m_UseConservativeRasterization = conservativeRasterization;
+	}
+
+	PrimitiveTopology GraphicsPipelineConfig::getPrimitiveTopology() const {
+		return m_PrimitiveTopology;
+	}
+
+	void GraphicsPipelineConfig::setPrimitiveTopology(PrimitiveTopology primitiveTopology) {
+		m_PrimitiveTopology = primitiveTopology;
+	}
+
+	BlendMode GraphicsPipelineConfig::getBlendMode() const {
+		return m_blendMode;
+	}
+
+	void GraphicsPipelineConfig::setBlendMode(BlendMode blendMode) {
+		m_blendMode = blendMode;
+	}
+
+	bool GraphicsPipelineConfig::isDepthClampingEnabled() const {
+		return m_EnableDepthClamping;
+	}
+
+	void GraphicsPipelineConfig::setDepthClampingEnabled(bool depthClamping) {
+		m_EnableDepthClamping = depthClamping;
+	}
+
+	CullMode GraphicsPipelineConfig::getCulling() const {
+		return m_Culling;
+	}
+
+	void GraphicsPipelineConfig::setCulling(CullMode cullMode) {
+		m_Culling = cullMode;
+	}
+
+	DepthTest GraphicsPipelineConfig::getDepthTest() const {
+		return m_DepthTest;
+	}
+
+	void GraphicsPipelineConfig::setDepthTest(DepthTest depthTest) {
+		m_DepthTest = depthTest;
+	}
+
+	bool GraphicsPipelineConfig::isWritingDepth() const {
+		return m_DepthWrite;
+	}
+
+	void GraphicsPipelineConfig::setWritingDepth(bool writingDepth) {
+		m_DepthWrite = writingDepth;
+	}
+
+	bool GraphicsPipelineConfig::isWritingAlphaToCoverage() const {
+		return m_AlphaToCoverage;
+	}
+
+	void GraphicsPipelineConfig::setWritingAlphaToCoverage(bool alphaToCoverage) {
+		m_AlphaToCoverage = alphaToCoverage;
+	}
+
+	uint32_t GraphicsPipelineConfig::getTesselationControlPoints() const {
+		return m_TessellationControlPoints;
+	}
+
+	void GraphicsPipelineConfig::setTesselationControlPoints(uint32_t tessellationControlPoints) {
+		m_TessellationControlPoints = tessellationControlPoints;
+	}
+
+} // namespace vkcv
diff --git a/src/vkcv/GraphicsPipelineManager.cpp b/src/vkcv/GraphicsPipelineManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9e1af7f7b044a1e24a27644bc988a2ab0c2bf4e8
--- /dev/null
+++ b/src/vkcv/GraphicsPipelineManager.cpp
@@ -0,0 +1,683 @@
+#include "GraphicsPipelineManager.hpp"
+
+#include "vkcv/Core.hpp"
+#include "vkcv/Image.hpp"
+#include "vkcv/Logger.hpp"
+#include "vkcv/Multisampling.hpp"
+
+namespace vkcv {
+
+	uint64_t GraphicsPipelineManager::getIdFrom(const GraphicsPipelineHandle &handle) const {
+		return handle.getId();
+	}
+
+	GraphicsPipelineHandle
+	GraphicsPipelineManager::createById(uint64_t id, const HandleDestroyFunction &destroy) {
+		return GraphicsPipelineHandle(id, destroy);
+	}
+
+	void GraphicsPipelineManager::destroyById(uint64_t id) {
+		auto &pipeline = getById(id);
+
+		if (pipeline.m_handle) {
+			getCore().getContext().getDevice().destroy(pipeline.m_handle);
+			pipeline.m_handle = nullptr;
+		}
+
+		if (pipeline.m_layout) {
+			getCore().getContext().getDevice().destroy(pipeline.m_layout);
+			pipeline.m_layout = nullptr;
+		}
+	}
+
+	GraphicsPipelineManager::GraphicsPipelineManager() noexcept :
+		HandleManager<GraphicsPipelineEntry, GraphicsPipelineHandle>() {}
+
+	GraphicsPipelineManager::~GraphicsPipelineManager() noexcept {
+		clear();
+	}
+
+	// currently assuming default 32 bit formats, no lower precision or normalized variants
+	// supported
+	vk::Format vertexFormatToVulkanFormat(const VertexAttachmentFormat format) {
+		switch (format) {
+		case VertexAttachmentFormat::FLOAT:
+			return vk::Format::eR32Sfloat;
+		case VertexAttachmentFormat::FLOAT2:
+			return vk::Format::eR32G32Sfloat;
+		case VertexAttachmentFormat::FLOAT3:
+			return vk::Format::eR32G32B32Sfloat;
+		case VertexAttachmentFormat::FLOAT4:
+			return vk::Format::eR32G32B32A32Sfloat;
+		case VertexAttachmentFormat::INT:
+			return vk::Format::eR32Sint;
+		case VertexAttachmentFormat::INT2:
+			return vk::Format::eR32G32Sint;
+		case VertexAttachmentFormat::INT3:
+			return vk::Format::eR32G32B32Sint;
+		case VertexAttachmentFormat::INT4:
+			return vk::Format::eR32G32B32A32Sint;
+		default:
+			vkcv_log(LogLevel::WARNING, "Unknown vertex format");
+			return vk::Format::eUndefined;
+		}
+	}
+
+	vk::PrimitiveTopology
+	primitiveTopologyToVulkanPrimitiveTopology(const PrimitiveTopology topology) {
+		switch (topology) {
+		case (PrimitiveTopology::PointList):
+			return vk::PrimitiveTopology::ePointList;
+		case (PrimitiveTopology::LineList):
+			return vk::PrimitiveTopology::eLineList;
+		case (PrimitiveTopology::TriangleList):
+			return vk::PrimitiveTopology::eTriangleList;
+		case (PrimitiveTopology::PatchList):
+			return vk::PrimitiveTopology::ePatchList;
+		default:
+			vkcv_log(LogLevel::ERROR, "Unknown primitive topology type");
+			return vk::PrimitiveTopology::eTriangleList;
+		}
+	}
+
+	vk::CompareOp depthTestToVkCompareOp(DepthTest depthTest) {
+		switch (depthTest) {
+		case (DepthTest::None):
+			return vk::CompareOp::eAlways;
+		case (DepthTest::Less):
+			return vk::CompareOp::eLess;
+		case (DepthTest::LessEqual):
+			return vk::CompareOp::eLessOrEqual;
+		case (DepthTest::Greater):
+			return vk::CompareOp::eGreater;
+		case (DepthTest::GreatherEqual):
+			return vk::CompareOp::eGreaterOrEqual;
+		case (DepthTest::Equal):
+			return vk::CompareOp::eEqual;
+		default:
+			vkcv_log(LogLevel::ERROR, "Unknown depth test enum");
+			return vk::CompareOp::eAlways;
+		}
+	}
+
+	vk::ShaderStageFlagBits shaderStageToVkShaderStage(ShaderStage stage) {
+		switch (stage) {
+		case ShaderStage::VERTEX:
+			return vk::ShaderStageFlagBits::eVertex;
+		case ShaderStage::FRAGMENT:
+			return vk::ShaderStageFlagBits::eFragment;
+		case ShaderStage::GEOMETRY:
+			return vk::ShaderStageFlagBits::eGeometry;
+		case ShaderStage::TESS_CONTROL:
+			return vk::ShaderStageFlagBits::eTessellationControl;
+		case ShaderStage::TESS_EVAL:
+			return vk::ShaderStageFlagBits::eTessellationEvaluation;
+		case ShaderStage::COMPUTE:
+			return vk::ShaderStageFlagBits::eCompute;
+		case ShaderStage::TASK:
+			return vk::ShaderStageFlagBits::eTaskNV;
+		case ShaderStage::MESH:
+			return vk::ShaderStageFlagBits::eMeshNV;
+		default:
+			vkcv_log(LogLevel::ERROR, "Unknown shader stage");
+			return vk::ShaderStageFlagBits::eAll;
+		}
+	}
+
+	bool createPipelineShaderStageCreateInfo(const ShaderProgram &shaderProgram, ShaderStage stage,
+											 vk::Device device,
+											 vk::PipelineShaderStageCreateInfo* outCreateInfo) {
+
+		assert(outCreateInfo);
+		std::vector<uint32_t> code = shaderProgram.getShaderBinary(stage);
+		vk::ShaderModuleCreateInfo vertexModuleInfo({}, code.size() * sizeof(uint32_t),
+													code.data());
+		vk::ShaderModule shaderModule;
+		if (device.createShaderModule(&vertexModuleInfo, nullptr, &shaderModule)
+			!= vk::Result::eSuccess)
+			return false;
+
+		const static auto entryName = "main";
+
+		*outCreateInfo = vk::PipelineShaderStageCreateInfo({}, shaderStageToVkShaderStage(stage),
+														   shaderModule, entryName, nullptr);
+		return true;
+	}
+
+	/**
+	 * Fills Vertex Attribute and Binding Description with the corresponding objects form the Vertex
+	 * Layout.
+	 * @param vertexAttributeDescriptions
+	 * @param vertexBindingDescriptions
+	 * @param existsVertexShader
+	 * @param config
+	 */
+	void fillVertexInputDescription(
+		std::vector<vk::VertexInputAttributeDescription> &vertexAttributeDescriptions,
+		std::vector<vk::VertexInputBindingDescription> &vertexBindingDescriptions,
+		const bool existsVertexShader, const GraphicsPipelineConfig &config) {
+
+		if (existsVertexShader) {
+			const VertexLayout &layout = config.getVertexLayout();
+
+			// iterate over the layout's specified, mutually exclusive buffer bindings that make up
+			// a vertex buffer
+			for (const auto &vertexBinding : layout.vertexBindings) {
+				vertexBindingDescriptions.emplace_back(vertexBinding.bindingLocation,
+													   vertexBinding.stride,
+													   vk::VertexInputRate::eVertex);
+
+				// iterate over the bindings' specified, mutually exclusive vertex input attachments
+				// that make up a vertex
+				for (const auto &vertexAttachment : vertexBinding.vertexAttachments) {
+					vertexAttributeDescriptions.emplace_back(
+						vertexAttachment.inputLocation, vertexBinding.bindingLocation,
+						vertexFormatToVulkanFormat(vertexAttachment.format),
+						vertexAttachment.offset % vertexBinding.stride);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Creates a Pipeline Vertex Input State Create Info Struct and fills it with Attribute and
+	 * Binding data.
+	 * @param vertexAttributeDescriptions
+	 * @param vertexBindingDescriptions
+	 * @return Pipeline Vertex Input State Create Info Struct
+	 */
+	vk::PipelineVertexInputStateCreateInfo createPipelineVertexInputStateCreateInfo(
+		std::vector<vk::VertexInputAttributeDescription> &vertexAttributeDescriptions,
+		std::vector<vk::VertexInputBindingDescription> &vertexBindingDescriptions) {
+
+		vk::PipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo(
+			{}, vertexBindingDescriptions.size(), vertexBindingDescriptions.data(),
+			vertexAttributeDescriptions.size(), vertexAttributeDescriptions.data());
+		return pipelineVertexInputStateCreateInfo;
+	}
+
+	/**
+	 * Creates a Pipeline Input Assembly State Create Info Struct with 'Primitive Restart' disabled.
+	 * @param config provides data for primitive topology.
+	 * @return Pipeline Input Assembly State Create Info Struct
+	 */
+	vk::PipelineInputAssemblyStateCreateInfo
+	createPipelineInputAssemblyStateCreateInfo(const GraphicsPipelineConfig &config) {
+		vk::PipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo(
+			{}, primitiveTopologyToVulkanPrimitiveTopology(config.getPrimitiveTopology()), false);
+
+		return pipelineInputAssemblyStateCreateInfo;
+	}
+
+	vk::PipelineTessellationStateCreateInfo
+	createPipelineTessellationStateCreateInfo(const GraphicsPipelineConfig &config) {
+		vk::PipelineTessellationStateCreateInfo pipelineTessellationStateCreateInfo(
+			{}, config.getTesselationControlPoints());
+
+		return pipelineTessellationStateCreateInfo;
+	}
+
+	/**
+	 * Creates a Pipeline Viewport State Create Info Struct with default set viewport and scissor
+	 * settings.
+	 * @param config provides with and height of the output window
+	 * @return Pipeline Viewport State Create Info Struct
+	 */
+	vk::PipelineViewportStateCreateInfo
+	createPipelineViewportStateCreateInfo(const GraphicsPipelineConfig &config) {
+		static vk::Viewport viewport;
+		static vk::Rect2D scissor;
+
+		viewport = vk::Viewport(0.f, 0.f, static_cast<float>(config.getWidth()),
+								static_cast<float>(config.getHeight()), 0.f, 1.f);
+
+		scissor = vk::Rect2D({ 0, 0 }, { config.getWidth(), config.getHeight() });
+
+		vk::PipelineViewportStateCreateInfo pipelineViewportStateCreateInfo({}, 1, &viewport, 1,
+																			&scissor);
+
+		return pipelineViewportStateCreateInfo;
+	}
+
+	/**
+	 * Creates a Pipeline Rasterization State Create Info Struct with default values set to:
+	 * Rasterizer Discard: Disabled
+	 * Polygon Mode: Fill
+	 * Front Face: Counter Clockwise
+	 * Depth Bias: Disabled
+	 * Line Width: 1.0
+	 * Depth Clamping and Culling Mode ist set by the Pipeline Config
+	 * @param config sets Depth Clamping and Culling Mode
+	 * @return Pipeline Rasterization State Create Info Struct
+	 */
+	vk::PipelineRasterizationStateCreateInfo createPipelineRasterizationStateCreateInfo(
+		const GraphicsPipelineConfig &config,
+		const vk::PhysicalDeviceConservativeRasterizationPropertiesEXT
+			&conservativeRasterProperties) {
+		vk::CullModeFlags cullMode;
+		switch (config.getCulling()) {
+		case CullMode::None:
+			cullMode = vk::CullModeFlagBits::eNone;
+			break;
+		case CullMode::Front:
+			cullMode = vk::CullModeFlagBits::eFront;
+			break;
+		case CullMode::Back:
+			cullMode = vk::CullModeFlagBits::eBack;
+			break;
+		case CullMode::Both:
+			cullMode = vk::CullModeFlagBits::eFrontAndBack;
+			break;
+		default:
+			vkcv_log(LogLevel::ERROR, "Unknown CullMode");
+			cullMode = vk::CullModeFlagBits::eNone;
+		}
+
+		vk::PipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo(
+			{}, config.isDepthClampingEnabled(), false, vk::PolygonMode::eFill, cullMode,
+			vk::FrontFace::eCounterClockwise, false, 0.f, 0.f, 0.f, 1.f);
+
+		static vk::PipelineRasterizationConservativeStateCreateInfoEXT conservativeRasterization;
+
+		if (config.isUsingConservativeRasterization()) {
+			const float overestimationSize =
+				1.0f - conservativeRasterProperties.primitiveOverestimationSize;
+			const float maxOverestimationSize =
+				conservativeRasterProperties.maxExtraPrimitiveOverestimationSize;
+
+			conservativeRasterization = vk::PipelineRasterizationConservativeStateCreateInfoEXT(
+				{}, vk::ConservativeRasterizationModeEXT::eOverestimate,
+				std::min(std::max(overestimationSize, 0.f), maxOverestimationSize));
+
+			pipelineRasterizationStateCreateInfo.pNext = &conservativeRasterization;
+		}
+
+		return pipelineRasterizationStateCreateInfo;
+	}
+
+	/**
+	 * Creates a Pipeline Multisample State Create Info Struct.
+	 * @param config set MSAA Sample Count Flag
+	 * @return Pipeline Multisample State Create Info Struct
+	 */
+	vk::PipelineMultisampleStateCreateInfo
+	createPipelineMultisampleStateCreateInfo(const GraphicsPipelineConfig &config,
+											 const PassConfig &passConfig) {
+		vk::PipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo(
+			{}, msaaToSampleCountFlagBits(passConfig.getMultisampling()), false, 0.f, nullptr,
+			config.isWritingAlphaToCoverage(), false);
+
+		return pipelineMultisampleStateCreateInfo;
+	}
+
+	/**
+	 * Creates a Pipeline Color Blend State Create Info Struct.
+	 * Currently only one blend mode is supported! There for, blending is set to additive.
+	 * @param config sets blend mode
+	 * @return
+	 */
+	vk::PipelineColorBlendStateCreateInfo
+	createPipelineColorBlendStateCreateInfo(const GraphicsPipelineConfig &config,
+											const PassConfig &passConfig) {
+		static std::vector<vk::PipelineColorBlendAttachmentState> colorBlendAttachmentStates;
+		
+		colorBlendAttachmentStates.clear();
+		colorBlendAttachmentStates.reserve(passConfig.getAttachments().size());
+		
+		for (const auto& attachment : passConfig.getAttachments()) {
+			if ((isDepthFormat(attachment.getFormat())) ||
+				(isStencilFormat(attachment.getFormat()))) {
+				continue;
+			}
+			
+			// currently set to additive, if not disabled
+			// BlendFactors must be set as soon as additional BlendModes are added
+			vk::PipelineColorBlendAttachmentState colorBlendAttachmentState (
+					config.getBlendMode() != BlendMode::None,
+					vk::BlendFactor::eOne,
+					vk::BlendFactor::eOne,
+					vk::BlendOp::eAdd,
+					vk::BlendFactor::eOne,
+					vk::BlendFactor::eOne,
+					vk::BlendOp::eAdd,
+					vk::ColorComponentFlags(
+							VK_COLOR_COMPONENT_R_BIT |
+							VK_COLOR_COMPONENT_G_BIT |
+							VK_COLOR_COMPONENT_B_BIT |
+							VK_COLOR_COMPONENT_A_BIT
+					)
+			);
+			
+			colorBlendAttachmentStates.push_back(colorBlendAttachmentState);
+		}
+
+		vk::PipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo(
+			{},
+			false,
+			vk::LogicOp::eClear,
+			colorBlendAttachmentStates.size(),
+			colorBlendAttachmentStates.data(),
+			{ 1.f, 1.f, 1.f, 1.f }
+		);
+
+		return pipelineColorBlendStateCreateInfo;
+	}
+
+	/**
+	 * Creates a Pipeline Layout Create Info Struct.
+	 * @param config sets Push Constant Size and Descriptor Layouts.
+	 * @return Pipeline Layout Create Info Struct
+	 */
+	vk::PipelineLayoutCreateInfo createPipelineLayoutCreateInfo(
+		const GraphicsPipelineConfig &config,
+		const std::vector<vk::DescriptorSetLayout> &descriptorSetLayouts) {
+		static vk::PushConstantRange pushConstantRange;
+
+		const size_t pushConstantsSize = config.getShaderProgram().getPushConstantsSize();
+		pushConstantRange =
+			vk::PushConstantRange(vk::ShaderStageFlagBits::eAll, 0, pushConstantsSize);
+
+		vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo({}, (descriptorSetLayouts),
+															  (pushConstantRange));
+
+		if (pushConstantsSize == 0) {
+			pipelineLayoutCreateInfo.pushConstantRangeCount = 0;
+		}
+
+		return pipelineLayoutCreateInfo;
+	}
+
+	/**
+	 * Creates a Pipeline Depth Stencil State Create Info Struct.
+	 * @param config sets if depth test in enabled or not.
+	 * @return Pipeline Layout Create Info Struct
+	 */
+	vk::PipelineDepthStencilStateCreateInfo
+	createPipelineDepthStencilStateCreateInfo(const GraphicsPipelineConfig &config) {
+		const vk::PipelineDepthStencilStateCreateInfo pipelineDepthStencilCreateInfo(
+			vk::PipelineDepthStencilStateCreateFlags(), config.getDepthTest() != DepthTest::None,
+			config.isWritingDepth(), depthTestToVkCompareOp(config.getDepthTest()), false, false,
+			{}, {}, 0.0f, 1.0f);
+
+		return pipelineDepthStencilCreateInfo;
+	}
+
+	/**
+	 * Creates a Pipeline Dynamic State Create Info Struct.
+	 * @param config sets whenever a dynamic viewport is used or not.
+	 * @return Pipeline Dynamic State Create Info Struct
+	 */
+	vk::PipelineDynamicStateCreateInfo
+	createPipelineDynamicStateCreateInfo(const GraphicsPipelineConfig &config) {
+		static std::vector<vk::DynamicState> dynamicStates;
+		dynamicStates.clear();
+
+		if (config.isViewportDynamic()) {
+			dynamicStates.push_back(vk::DynamicState::eViewport);
+			dynamicStates.push_back(vk::DynamicState::eScissor);
+		}
+
+		vk::PipelineDynamicStateCreateInfo dynamicStateCreateInfo(
+			{}, static_cast<uint32_t>(dynamicStates.size()), dynamicStates.data());
+
+		return dynamicStateCreateInfo;
+	}
+
+	GraphicsPipelineHandle
+	GraphicsPipelineManager::createPipeline(const GraphicsPipelineConfig &config,
+											const PassManager &passManager,
+											const DescriptorSetLayoutManager &descriptorManager) {
+		const vk::RenderPass &pass = passManager.getVkPass(config.getPass());
+
+		const auto &program = config.getShaderProgram();
+
+		const bool existsTaskShader = program.existsShader(ShaderStage::TASK);
+		const bool existsMeshShader = program.existsShader(ShaderStage::MESH);
+		const bool existsVertexShader = program.existsShader(ShaderStage::VERTEX);
+		const bool existsFragmentShader = program.existsShader(ShaderStage::FRAGMENT);
+		const bool existsGeometryShader = program.existsShader(ShaderStage::GEOMETRY);
+		const bool existsTessellationControlShader =
+			program.existsShader(ShaderStage::TESS_CONTROL);
+		const bool existsTessellationEvaluationShader =
+			program.existsShader(ShaderStage::TESS_EVAL);
+
+		const bool validGeometryStages =
+			((existsVertexShader
+			  && (existsTessellationControlShader == existsTessellationEvaluationShader))
+			 || (existsTaskShader && existsMeshShader));
+
+		if (!validGeometryStages) {
+			vkcv_log(LogLevel::ERROR, "Requires vertex or task and mesh shader");
+			return {};
+		}
+
+		if (!existsFragmentShader) {
+			vkcv_log(LogLevel::ERROR, "Requires fragment shader code");
+			return {};
+		}
+
+		std::vector<vk::PipelineShaderStageCreateInfo> shaderStages;
+		auto destroyShaderModules = [&shaderStages, this] {
+			for (auto stage : shaderStages) {
+				getCore().getContext().getDevice().destroyShaderModule(stage.module);
+			}
+			shaderStages.clear();
+		};
+
+		if (existsVertexShader) {
+			vk::PipelineShaderStageCreateInfo createInfo;
+			const bool success = createPipelineShaderStageCreateInfo(
+				program, ShaderStage::VERTEX, getCore().getContext().getDevice(), &createInfo);
+
+			if (success) {
+				shaderStages.push_back(createInfo);
+			} else {
+				destroyShaderModules();
+				return {};
+			}
+		}
+
+		if (existsTaskShader) {
+			vk::PipelineShaderStageCreateInfo createInfo;
+			const bool success = createPipelineShaderStageCreateInfo(
+				program, ShaderStage::TASK, getCore().getContext().getDevice(), &createInfo);
+
+			if (success) {
+				shaderStages.push_back(createInfo);
+			} else {
+				destroyShaderModules();
+				return {};
+			}
+		}
+
+		if (existsMeshShader) {
+			vk::PipelineShaderStageCreateInfo createInfo;
+			const bool success = createPipelineShaderStageCreateInfo(
+				program, ShaderStage::MESH, getCore().getContext().getDevice(), &createInfo);
+
+			if (success) {
+				shaderStages.push_back(createInfo);
+			} else {
+				destroyShaderModules();
+				return {};
+			}
+		}
+
+		{
+			vk::PipelineShaderStageCreateInfo createInfo;
+			const bool success = createPipelineShaderStageCreateInfo(
+				program, ShaderStage::FRAGMENT, getCore().getContext().getDevice(), &createInfo);
+
+			if (success) {
+				shaderStages.push_back(createInfo);
+			} else {
+				destroyShaderModules();
+				return {};
+			}
+		}
+
+		if (existsGeometryShader) {
+			vk::PipelineShaderStageCreateInfo createInfo;
+			const bool success = createPipelineShaderStageCreateInfo(
+				program, ShaderStage::GEOMETRY, getCore().getContext().getDevice(), &createInfo);
+
+			if (success) {
+				shaderStages.push_back(createInfo);
+			} else {
+				destroyShaderModules();
+				return {};
+			}
+		}
+
+		if (existsTessellationControlShader) {
+			vk::PipelineShaderStageCreateInfo createInfo;
+			const bool success = createPipelineShaderStageCreateInfo(
+				program, ShaderStage::TESS_CONTROL, getCore().getContext().getDevice(),
+				&createInfo);
+
+			if (success) {
+				shaderStages.push_back(createInfo);
+			} else {
+				destroyShaderModules();
+				return {};
+			}
+		}
+
+		if (existsTessellationEvaluationShader) {
+			vk::PipelineShaderStageCreateInfo createInfo;
+			const bool success = createPipelineShaderStageCreateInfo(
+				program, ShaderStage::TESS_EVAL, getCore().getContext().getDevice(), &createInfo);
+
+			if (success) {
+				shaderStages.push_back(createInfo);
+			} else {
+				destroyShaderModules();
+				return {};
+			}
+		}
+
+		const PassConfig &passConfig = passManager.getPassConfig(config.getPass());
+
+		// vertex input state
+		// Fill up VertexInputBindingDescription and VertexInputAttributeDescription Containers
+		std::vector<vk::VertexInputAttributeDescription> vertexAttributeDescriptions;
+		std::vector<vk::VertexInputBindingDescription> vertexBindingDescriptions;
+		fillVertexInputDescription(vertexAttributeDescriptions, vertexBindingDescriptions,
+								   existsVertexShader, config);
+
+		// Handover Containers to PipelineVertexInputStateCreateIngo Struct
+		vk::PipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo =
+			createPipelineVertexInputStateCreateInfo(vertexAttributeDescriptions,
+													 vertexBindingDescriptions);
+
+		// input assembly state
+		vk::PipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo =
+			createPipelineInputAssemblyStateCreateInfo(config);
+
+		// tesselation state
+		vk::PipelineTessellationStateCreateInfo pipelineTessellationStateCreateInfo =
+			createPipelineTessellationStateCreateInfo(config);
+
+		// viewport state
+		vk::PipelineViewportStateCreateInfo pipelineViewportStateCreateInfo =
+			createPipelineViewportStateCreateInfo(config);
+
+		// rasterization state
+		vk::PhysicalDeviceConservativeRasterizationPropertiesEXT conservativeRasterProperties;
+		vk::PhysicalDeviceProperties deviceProperties;
+		vk::PhysicalDeviceProperties2 deviceProperties2(deviceProperties);
+		deviceProperties2.pNext = &conservativeRasterProperties;
+		getCore().getContext().getPhysicalDevice().getProperties2(&deviceProperties2);
+		vk::PipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo =
+			createPipelineRasterizationStateCreateInfo(config, conservativeRasterProperties);
+
+		// multisample state
+		vk::PipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo =
+			createPipelineMultisampleStateCreateInfo(config, passConfig);
+
+		// color blend state
+		vk::PipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo =
+			createPipelineColorBlendStateCreateInfo(config, passConfig);
+
+		// Dynamic State
+		vk::PipelineDynamicStateCreateInfo dynamicStateCreateInfo =
+			createPipelineDynamicStateCreateInfo(config);
+
+		std::vector<vk::DescriptorSetLayout> descriptorSetLayouts;
+		descriptorSetLayouts.reserve(config.getDescriptorSetLayouts().size());
+		for (const auto &handle : config.getDescriptorSetLayouts()) {
+			descriptorSetLayouts.push_back(
+				descriptorManager.getDescriptorSetLayout(handle).vulkanHandle);
+		}
+
+		// pipeline layout
+		vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo =
+			createPipelineLayoutCreateInfo(config, descriptorSetLayouts);
+
+		vk::PipelineLayout vkPipelineLayout {};
+		if (getCore().getContext().getDevice().createPipelineLayout(&pipelineLayoutCreateInfo,
+																	nullptr, &vkPipelineLayout)
+			!= vk::Result::eSuccess) {
+			destroyShaderModules();
+			return {};
+		}
+
+		// Depth Stencil
+		const vk::PipelineDepthStencilStateCreateInfo depthStencilCreateInfo =
+			createPipelineDepthStencilStateCreateInfo(config);
+
+		const vk::PipelineDepthStencilStateCreateInfo* p_depthStencilCreateInfo = nullptr;
+
+		for (const auto &attachment : passConfig.getAttachments()) {
+			if ((isDepthFormat(attachment.getFormat()))
+				|| (isStencilFormat(attachment.getFormat()))) {
+				p_depthStencilCreateInfo = &depthStencilCreateInfo;
+				break;
+			}
+		}
+
+		// Get all setting structs together and create the Pipeline
+		const vk::GraphicsPipelineCreateInfo graphicsPipelineCreateInfo(
+			{}, static_cast<uint32_t>(shaderStages.size()), shaderStages.data(),
+			&pipelineVertexInputStateCreateInfo, &pipelineInputAssemblyStateCreateInfo,
+			&pipelineTessellationStateCreateInfo, &pipelineViewportStateCreateInfo,
+			&pipelineRasterizationStateCreateInfo, &pipelineMultisampleStateCreateInfo,
+			p_depthStencilCreateInfo, &pipelineColorBlendStateCreateInfo, &dynamicStateCreateInfo,
+			vkPipelineLayout, pass, 0, {}, 0);
+
+		vk::Pipeline vkPipeline {};
+		if (getCore().getContext().getDevice().createGraphicsPipelines(
+				nullptr, 1, &graphicsPipelineCreateInfo, nullptr, &vkPipeline)
+			!= vk::Result::eSuccess) {
+			// Catch runtime error if the creation of the pipeline fails.
+			// Destroy everything to keep the memory clean.
+			destroyShaderModules();
+			return {};
+		}
+
+		// Clean Up
+		destroyShaderModules();
+
+		// Hand over Handler to main Application
+		return add({ vkPipeline, vkPipelineLayout, config });
+	}
+
+	vk::Pipeline
+	GraphicsPipelineManager::getVkPipeline(const GraphicsPipelineHandle &handle) const {
+		auto &pipeline = (*this) [handle];
+		return pipeline.m_handle;
+	}
+
+	vk::PipelineLayout
+	GraphicsPipelineManager::getVkPipelineLayout(const GraphicsPipelineHandle &handle) const {
+		auto &pipeline = (*this) [handle];
+		return pipeline.m_layout;
+	}
+
+	const GraphicsPipelineConfig &
+	GraphicsPipelineManager::getPipelineConfig(const GraphicsPipelineHandle &handle) const {
+		auto &pipeline = (*this) [handle];
+		return pipeline.m_config;
+	}
+
+} // namespace vkcv
diff --git a/src/vkcv/GraphicsPipelineManager.hpp b/src/vkcv/GraphicsPipelineManager.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..602e1a96d29a4b78a3784c215074b9541bf5de47
--- /dev/null
+++ b/src/vkcv/GraphicsPipelineManager.hpp
@@ -0,0 +1,98 @@
+#pragma once
+
+/**
+ * @authors Mark Mints
+ * @file src/vkcv/PipelineManager.hpp
+ * @brief Creation and handling of Graphic Pipelines
+ */
+// TODO: Edit @brief: add graphics pipeline, but only then when the compute part is in an own class.
+// TODO: More authors? Do we need authors (general question)?
+
+#include "DescriptorSetLayoutManager.hpp"
+#include "HandleManager.hpp"
+#include "PassManager.hpp"
+#include "vkcv/GraphicsPipelineConfig.hpp"
+#include <vector>
+#include <vulkan/vulkan.hpp>
+
+namespace vkcv {
+
+	struct GraphicsPipelineEntry {
+		vk::Pipeline m_handle;
+		vk::PipelineLayout m_layout;
+		GraphicsPipelineConfig m_config;
+	};
+
+	/**
+	 * @brief Class to manage graphics pipelines.
+	 */
+	class GraphicsPipelineManager :
+		public HandleManager<GraphicsPipelineEntry, GraphicsPipelineHandle> {
+	private:
+		[[nodiscard]] uint64_t getIdFrom(const GraphicsPipelineHandle &handle) const override;
+
+		[[nodiscard]] GraphicsPipelineHandle
+		createById(uint64_t id, const HandleDestroyFunction &destroy) override;
+
+		/**
+		 * Destroys and deallocates graphics pipeline represented by a given
+		 * graphics pipeline handle id.
+		 *
+		 * @param id Graphics pipeline handle id
+		 */
+		void destroyById(uint64_t id) override;
+
+	public:
+		GraphicsPipelineManager() noexcept;
+
+		~GraphicsPipelineManager() noexcept override; // dtor
+
+		GraphicsPipelineManager(const GraphicsPipelineManager &other) = delete; // copy-ctor
+		GraphicsPipelineManager(GraphicsPipelineManager &&other) = delete;      // move-ctor;
+
+		GraphicsPipelineManager &
+		operator=(const GraphicsPipelineManager &other) = delete; // copy-assign op
+		GraphicsPipelineManager &
+		operator=(GraphicsPipelineManager &&other) = delete; // move-assign op
+
+		/**
+		 * Creates a Graphics Pipeline based on the set shader stages in the Config Struct.
+		 * This function is wrapped in /src/vkcv/Core.cpp by Core::createGraphicsPipeline(const
+		 * PipelineConfig &config). Therefore the passManager is filled already by the overall
+		 * context of an application. On application level it is necessary first to fill a
+		 * PipelineConfig Struct.
+		 * @param config Hands over all needed information for pipeline creation.
+		 * @param passManager Hands over the corresponding render pass.
+		 * @param descriptorManager Hands over the corresponding descriptor set layouts
+		 * @return A Handler to the created Graphics Pipeline Object.
+		 */
+		GraphicsPipelineHandle createPipeline(const GraphicsPipelineConfig &config,
+											  const PassManager &passManager,
+											  const DescriptorSetLayoutManager &descriptorManager);
+
+		/**
+		 * Returns a vk::Pipeline object by handle.
+		 * @param handle Directing to the requested pipeline.
+		 * @return vk::Pipeline.
+		 */
+		[[nodiscard]] vk::Pipeline getVkPipeline(const GraphicsPipelineHandle &handle) const;
+
+		/**
+		 * Returns a vk::PipelineLayout object by handle.
+		 * @param handle Directing to the requested pipeline.
+		 * @return vk::PipelineLayout.
+		 */
+		[[nodiscard]] vk::PipelineLayout
+		getVkPipelineLayout(const GraphicsPipelineHandle &handle) const;
+
+		/**
+		 * Returns the corresponding Pipeline Config Struct of a pipeline object directed by the
+		 * given Handler.
+		 * @param handle Directing to the requested pipeline.
+		 * @return Pipeline Config Struct
+		 */
+		[[nodiscard]] const GraphicsPipelineConfig &
+		getPipelineConfig(const GraphicsPipelineHandle &handle) const;
+	};
+
+} // namespace vkcv
diff --git a/src/vkcv/HandleManager.hpp b/src/vkcv/HandleManager.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a7b4b7aab30f4ce29d1f91c04336e733aba17b1b
--- /dev/null
+++ b/src/vkcv/HandleManager.hpp
@@ -0,0 +1,112 @@
+#pragma once
+
+#include <optional>
+#include <vector>
+
+#include "vkcv/Handles.hpp"
+#include "vkcv/Logger.hpp"
+
+namespace vkcv {
+
+	class Core;
+
+	template <typename T, typename H = Handle>
+	class HandleManager {
+		friend class Core;
+
+	private:
+		Core* m_core;
+		std::vector<T> m_entries;
+
+	protected:
+		HandleManager() noexcept : m_core(nullptr), m_entries() {}
+
+		virtual bool init(Core &core) {
+			if (m_core) {
+				vkcv_log(vkcv::LogLevel::ERROR, "Manager is already initialized");
+				return false;
+			}
+
+			if (!m_entries.empty()) {
+				vkcv_log(vkcv::LogLevel::WARNING,
+						 "Entries added before initialization will be erased");
+			}
+
+			m_core = &core;
+			m_entries.clear();
+			return true;
+		}
+
+		[[nodiscard]] const Core &getCore() const {
+			return *m_core;
+		}
+
+		[[nodiscard]] Core &getCore() {
+			return *m_core;
+		}
+
+		[[nodiscard]] size_t getCount() const {
+			return m_entries.size();
+		}
+
+		[[nodiscard]] const T &getById(uint64_t id) const {
+			if (id >= m_entries.size()) {
+				static T invalid;
+				vkcv_log(vkcv::LogLevel::ERROR, "Invalid handle id");
+				return invalid;
+			}
+
+			return m_entries [id];
+		}
+
+		[[nodiscard]] T &getById(uint64_t id) {
+			if (id >= m_entries.size()) {
+				static T invalid;
+				vkcv_log(vkcv::LogLevel::ERROR, "Invalid handle id");
+				return invalid;
+			}
+
+			return m_entries [id];
+		}
+
+		virtual uint64_t getIdFrom(const H &handle) const = 0;
+
+		[[nodiscard]] virtual const T &operator[](const H &handle) const {
+			const Handle &_handle = handle;
+			return getById(getIdFrom(static_cast<const H &>(_handle)));
+		}
+
+		[[nodiscard]] virtual T &operator[](const H &handle) {
+			const Handle &_handle = handle;
+			return getById(getIdFrom(static_cast<const H &>(_handle)));
+		}
+
+		H add(const T &entry) {
+			const uint64_t id = m_entries.size();
+			m_entries.push_back(entry);
+			return createById(id, [&](uint64_t id) {
+				destroyById(id);
+			});
+		}
+
+		virtual H createById(uint64_t id, const HandleDestroyFunction &destroy) = 0;
+
+		virtual void destroyById(uint64_t id) = 0;
+
+		void clear() {
+			for (uint64_t id = 0; id < m_entries.size(); id++) {
+				destroyById(id);
+			}
+		}
+
+	public:
+		HandleManager(HandleManager &&other) = delete;
+		HandleManager(const HandleManager &other) = delete;
+
+		HandleManager &operator=(HandleManager &&other) = delete;
+		HandleManager &operator=(const HandleManager &other) = delete;
+
+		virtual ~HandleManager() noexcept = default;
+	};
+
+} // namespace vkcv
diff --git a/src/vkcv/Handles.cpp b/src/vkcv/Handles.cpp
index 020489418c8e2db6ce2062d6fd20f06f90a05c37..64ab3080f5e6a649fa4fb0d8f772872713cb0140 100644
--- a/src/vkcv/Handles.cpp
+++ b/src/vkcv/Handles.cpp
@@ -1,99 +1,96 @@
 #include "vkcv/Handles.hpp"
 
+#include <iostream>
+#include <limits>
+
 namespace vkcv {
-	
-	Handle::Handle() :
-	m_id(UINT64_MAX), m_rc(nullptr), m_destroy(nullptr)
-	{}
-	
-	Handle::Handle(uint64_t id, const HandleDestroyFunction& destroy) :
-		m_id(id), m_rc(new uint64_t(1)), m_destroy(destroy)
-	{}
-	
+
+	Handle::Handle() : m_id(std::numeric_limits<uint64_t>::max()), m_rc(nullptr), m_destroy(nullptr) {}
+
+	Handle::Handle(uint64_t id, const HandleDestroyFunction &destroy) :
+		m_id(id), m_rc(new uint64_t(1)), m_destroy(destroy) {}
+
 	Handle::~Handle() {
-		if ((m_rc) && (--(*m_rc) == 0)) {
+		if ((m_rc) && (*m_rc > 0) && (--(*m_rc) == 0)) {
 			if (m_destroy) {
 				m_destroy(m_id);
 			}
-			
+
 			delete m_rc;
 		}
 	}
-	
+
 	Handle::Handle(const Handle &other) :
-		m_id(other.m_id),
-		m_rc(other.m_rc),
-		m_destroy(other.m_destroy)
-	{
+		m_id(other.m_id), m_rc(other.m_rc), m_destroy(other.m_destroy) {
 		if (m_rc) {
 			++(*m_rc);
 		}
 	}
-	
+
 	Handle::Handle(Handle &&other) noexcept :
-		m_id(other.m_id),
-		m_rc(other.m_rc),
-		m_destroy(other.m_destroy)
-	{
+		m_id(other.m_id), m_rc(other.m_rc), m_destroy(other.m_destroy) {
 		other.m_rc = nullptr;
+		other.m_destroy = nullptr;
 	}
-	
+
 	Handle &Handle::operator=(const Handle &other) {
 		if (&other == this) {
 			return *this;
 		}
-		
+
 		m_id = other.m_id;
 		m_rc = other.m_rc;
 		m_destroy = other.m_destroy;
-		
+
 		if (m_rc) {
 			++(*m_rc);
 		}
-		
+
 		return *this;
 	}
-	
+
 	Handle &Handle::operator=(Handle &&other) noexcept {
 		m_id = other.m_id;
 		m_rc = other.m_rc;
 		m_destroy = other.m_destroy;
-		
+
 		other.m_rc = nullptr;
-		
+		other.m_destroy = nullptr;
+
 		return *this;
 	}
-	
+
 	uint64_t Handle::getId() const {
 		return m_id;
 	}
-	
+
 	uint64_t Handle::getRC() const {
-		return m_rc? *m_rc : 0;
+		return m_rc ? *m_rc : 0;
 	}
-	
+
 	Handle::operator bool() const {
-		return (m_id < UINT64_MAX);
+		return (m_id < std::numeric_limits<uint64_t>::max());
 	}
-	
+
 	bool Handle::operator!() const {
-		return (m_id == UINT64_MAX);
+		return (m_id == std::numeric_limits<uint64_t>::max());
 	}
-	
-	std::ostream& operator << (std::ostream& out, const Handle& handle) {
+
+	std::ostream &operator<<(std::ostream &out, const Handle &handle) {
 		if (handle) {
-			return out << "[Handle: " << handle.getId() << ":" << handle.getRC() << "]";
+			return out << "[" << typeid(handle).name() << ": " << handle.getId() << ":"
+					   << handle.getRC() << "]";
 		} else {
-			return out << "[Handle: none]";
+			return out << "[" << typeid(handle).name() << ": none]";
 		}
 	}
-	
+
 	bool ImageHandle::isSwapchainImage() const {
-		return (getId() == UINT64_MAX - 1);
+		return (getId() == std::numeric_limits<uint64_t>::max() - 1);
 	}
-	
+
 	ImageHandle ImageHandle::createSwapchainImageHandle(const HandleDestroyFunction &destroy) {
-		return ImageHandle(uint64_t(UINT64_MAX - 1), destroy);
+		return ImageHandle(uint64_t(std::numeric_limits<uint64_t>::max() - 1), destroy);
 	}
-	
-}
+
+} // namespace vkcv
diff --git a/src/vkcv/Image.cpp b/src/vkcv/Image.cpp
index c48b015335e00f23a892bb96d3e89a2c0877ae61..fc6140fe4664096145aa08551422d919bcb4dc25 100644
--- a/src/vkcv/Image.cpp
+++ b/src/vkcv/Image.cpp
@@ -4,78 +4,97 @@
  * @brief class for image handles
  */
 #include "vkcv/Image.hpp"
+
 #include "ImageManager.hpp"
+#include "vkcv/Downsampler.hpp"
+
+namespace vkcv {
 
-namespace vkcv{
-	
 	bool isDepthFormat(const vk::Format format) {
 		switch (format) {
-			case(vk::Format::eD16Unorm):        return true;
-			case(vk::Format::eD16UnormS8Uint):  return true;
-			case(vk::Format::eD24UnormS8Uint):  return true;
-			case(vk::Format::eD32Sfloat):       return true;
-			case(vk::Format::eD32SfloatS8Uint): return true;
-			default:                            return false;
+		case (vk::Format::eD16Unorm):
+		case (vk::Format::eD16UnormS8Uint):
+		case (vk::Format::eD24UnormS8Uint):
+		case (vk::Format::eD32Sfloat):
+		case (vk::Format::eD32SfloatS8Uint):
+			return true;
+		default:
+			return false;
 		}
 	}
 
-	Image Image::create(
-		ImageManager*   manager,
-		vk::Format      format,
-		uint32_t        width,
-		uint32_t        height,
-		uint32_t        depth,
-		uint32_t        mipCount,
-		bool            supportStorage,
-		bool            supportColorAttachment)
-	{
-		return Image(manager, manager->createImage(width, height, depth, format, mipCount, supportStorage, supportColorAttachment));
+	bool isStencilFormat(const vk::Format format) {
+		switch (format) {
+		case (vk::Format::eS8Uint):
+		case (vk::Format::eD16UnormS8Uint):
+		case (vk::Format::eD24UnormS8Uint):
+		case (vk::Format::eD32SfloatS8Uint):
+			return true;
+		default:
+			return false;
+		}
 	}
-	
+
 	vk::Format Image::getFormat() const {
-		return m_manager->getImageFormat(m_handle);
+		return m_core->getImageFormat(m_handle);
 	}
-	
+
 	uint32_t Image::getWidth() const {
-		return m_manager->getImageWidth(m_handle);
+		return m_core->getImageWidth(m_handle);
 	}
-	
+
 	uint32_t Image::getHeight() const {
-		return m_manager->getImageHeight(m_handle);
+		return m_core->getImageHeight(m_handle);
 	}
-	
+
 	uint32_t Image::getDepth() const {
-		return m_manager->getImageDepth(m_handle);
+		return m_core->getImageDepth(m_handle);
 	}
 
-	void Image::switchLayout(vk::ImageLayout newLayout)
-	{
-		m_manager->switchImageLayoutImmediate(m_handle, newLayout);
+	void Image::switchLayout(vk::ImageLayout newLayout) {
+		m_core->switchImageLayout(m_handle, newLayout);
 	}
 
-	vkcv::ImageHandle Image::getHandle() const {
+	const vkcv::ImageHandle &Image::getHandle() const {
 		return m_handle;
 	}
 
-	uint32_t Image::getMipCount() const {
-		return m_manager->getImageMipCount(m_handle);
+	uint32_t Image::getMipLevels() const {
+		return m_core->getImageMipLevels(m_handle);
 	}
 
-	void Image::fill(void *data, size_t size) {
-		m_manager->fillImage(m_handle, data, size);
+	void Image::fill(const void* data, size_t size) {
+		m_core->fillImage(m_handle, data, size, 0, 0);
+	}
+	
+	void Image::fillLayer(uint32_t layer, const void* data, size_t size) {
+		m_core->fillImage(m_handle, data, size, layer, 1);
 	}
 
-	void Image::generateMipChainImmediate() {
-		m_manager->generateImageMipChainImmediate(m_handle);
+	void Image::recordMipChainGeneration(const vkcv::CommandStreamHandle &cmdStream,
+										 Downsampler &downsampler) {
+		downsampler.recordDownsampling(cmdStream, m_handle);
 	}
 
-	void Image::recordMipChainGeneration(const vkcv::CommandStreamHandle& cmdStream) {
-		m_manager->recordImageMipChainGenerationToCmdStream(cmdStream, m_handle);
+	Image image(Core &core, vk::Format format, uint32_t width, uint32_t height, uint32_t depth,
+				bool createMipChain, bool supportStorage, bool supportColorAttachment,
+				Multisampling multisampling) {
+		ImageConfig config (width, height, depth);
+		config.setSupportingStorage(supportStorage);
+		config.setSupportingColorAttachment(supportColorAttachment);
+		config.setMultisampling(multisampling);
+		return image(core, format, config, createMipChain);
 	}
 	
-	Image::Image(ImageManager* manager, const ImageHandle& handle) :
-		m_manager(manager),
-		m_handle(handle)
-	{}
+	Image image(Core &core, vk::Format format, const ImageConfig &config, bool createMipChain) {
+		return Image(
+				&core,
+				core.createImage(
+						format,
+						config,
+						createMipChain
+				)
+		);
+	}
 
-}
+} // namespace vkcv
diff --git a/src/vkcv/ImageConfig.cpp b/src/vkcv/ImageConfig.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d95a7ff6bb843b4405f119d6bec0e50a06658e0e
--- /dev/null
+++ b/src/vkcv/ImageConfig.cpp
@@ -0,0 +1,80 @@
+/**
+ * @authors Tobias Frisch
+ * @file vkcv/Image.cpp
+ * @brief Structure for image configuration
+ */
+#include "vkcv/ImageConfig.hpp"
+
+namespace vkcv {
+	
+	ImageConfig::ImageConfig(uint32_t width,
+							 uint32_t height,
+							 uint32_t depth)
+	: m_width(width),
+	  m_height(height),
+	  m_depth(depth),
+	  
+	  m_supportStorage(false),
+	  m_supportColorAttachment(false),
+	  m_cubeMapImage(false),
+	  
+	  m_msaa(Multisampling::None)
+	{}
+	
+	uint32_t ImageConfig::getWidth() const {
+		return m_width;
+	}
+	
+	void ImageConfig::setWidth(uint32_t width) {
+		m_width = width;
+	}
+	
+	uint32_t ImageConfig::getHeight() const {
+		return m_height;
+	}
+	
+	void ImageConfig::setHeight(uint32_t height) {
+		m_height = height;
+	}
+	
+	uint32_t ImageConfig::getDepth() const {
+		return m_depth;
+	}
+	
+	void ImageConfig::setDepth(uint32_t depth) {
+		m_depth = depth;
+	}
+	
+	bool ImageConfig::isSupportingStorage() const {
+		return m_supportStorage;
+	}
+	
+	void ImageConfig::setSupportingStorage(bool supportStorage) {
+		m_supportStorage = supportStorage;
+	}
+	
+	bool ImageConfig::isSupportingColorAttachment() const {
+		return m_supportColorAttachment;
+	}
+	
+	void ImageConfig::setSupportingColorAttachment(bool supportColorAttachment) {
+		m_supportColorAttachment = supportColorAttachment;
+	}
+	
+	bool ImageConfig::isCubeMapImage() const {
+		return m_cubeMapImage;
+	}
+	
+	void ImageConfig::setCubeMapImage(bool cubeMapImage) {
+		m_cubeMapImage = cubeMapImage;
+	}
+	
+	Multisampling ImageConfig::getMultisampling() const {
+		return m_msaa;
+	}
+	
+	void ImageConfig::setMultisampling(Multisampling msaa) {
+		m_msaa = msaa;
+	}
+	
+}
\ No newline at end of file
diff --git a/src/vkcv/ImageLayoutTransitions.cpp b/src/vkcv/ImageLayoutTransitions.cpp
deleted file mode 100644
index 8d31c64ccbcbf33e259714f8c581c920738190b4..0000000000000000000000000000000000000000
--- a/src/vkcv/ImageLayoutTransitions.cpp
+++ /dev/null
@@ -1,68 +0,0 @@
-#include "ImageLayoutTransitions.hpp"
-#include "vkcv/Image.hpp"
-
-namespace vkcv {
-	vk::ImageMemoryBarrier createImageLayoutTransitionBarrier(const ImageManager::Image &image, vk::ImageLayout newLayout) {
-
-		vk::ImageAspectFlags aspectFlags;
-		if (isDepthFormat(image.m_format)) {
-			aspectFlags = vk::ImageAspectFlagBits::eDepth;
-		}
-		else {
-			aspectFlags = vk::ImageAspectFlagBits::eColor;
-		}
-
-		vk::ImageSubresourceRange imageSubresourceRange(
-			aspectFlags,
-			0,
-			image.m_viewPerMip.size(),
-			0,
-			image.m_layers
-		);
-
-		// TODO: precise AccessFlagBits, will require a lot of context
-		return vk::ImageMemoryBarrier(
-			vk::AccessFlagBits::eMemoryWrite,
-			vk::AccessFlagBits::eMemoryRead,
-			image.m_layout,
-			newLayout,
-			VK_QUEUE_FAMILY_IGNORED,
-			VK_QUEUE_FAMILY_IGNORED,
-			image.m_handle,
-			imageSubresourceRange);
-	}
-
-	vk::ImageMemoryBarrier createSwapchainImageLayoutTransitionBarrier(
-		vk::Image       vulkanHandle, 
-		vk::ImageLayout oldLayout, 
-		vk::ImageLayout newLayout) {
-
-		vk::ImageSubresourceRange imageSubresourceRange(
-			vk::ImageAspectFlagBits::eColor,
-			0,
-			1,
-			0,
-			1);
-
-		// TODO: precise AccessFlagBits, will require a lot of context
-		return vk::ImageMemoryBarrier(
-			vk::AccessFlagBits::eMemoryWrite,
-			vk::AccessFlagBits::eMemoryRead,
-			oldLayout,
-			newLayout,
-			VK_QUEUE_FAMILY_IGNORED,
-			VK_QUEUE_FAMILY_IGNORED,
-			vulkanHandle,
-			imageSubresourceRange);
-	}
-
-	void recordImageBarrier(vk::CommandBuffer cmdBuffer, vk::ImageMemoryBarrier barrier) {
-		cmdBuffer.pipelineBarrier(
-			vk::PipelineStageFlagBits::eTopOfPipe,
-			vk::PipelineStageFlagBits::eBottomOfPipe,
-			{},
-			nullptr,
-			nullptr,
-			barrier);
-	}
-}
\ No newline at end of file
diff --git a/src/vkcv/ImageLayoutTransitions.hpp b/src/vkcv/ImageLayoutTransitions.hpp
deleted file mode 100644
index 5c147f133a6492746ad410367e5e627be000d7be..0000000000000000000000000000000000000000
--- a/src/vkcv/ImageLayoutTransitions.hpp
+++ /dev/null
@@ -1,13 +0,0 @@
-#pragma once
-#include <vulkan/vulkan.hpp>
-#include "ImageManager.hpp"
-
-namespace vkcv {
-	vk::ImageMemoryBarrier createImageLayoutTransitionBarrier(const ImageManager::Image& image, vk::ImageLayout newLayout);
-	vk::ImageMemoryBarrier createSwapchainImageLayoutTransitionBarrier(
-		vk::Image       vulkanHandle,
-		vk::ImageLayout oldLayout,
-		vk::ImageLayout newLayout);
-
-	void recordImageBarrier(vk::CommandBuffer cmdBuffer, vk::ImageMemoryBarrier barrier);
-}
\ No newline at end of file
diff --git a/src/vkcv/ImageManager.cpp b/src/vkcv/ImageManager.cpp
index a3364ce0dfd6f59dc78c85b43570eea25cfc052d..4e65cc45f83915861ef4b67c388e5ae35ef9d35c 100644
--- a/src/vkcv/ImageManager.cpp
+++ b/src/vkcv/ImageManager.cpp
@@ -1,110 +1,187 @@
 /**
- * @authors Lars Hoerttrich
+ * @authors Lars Hoerttrich, Tobias Frisch
  * @file vkcv/ImageManager.cpp
  * @brief class creating and managing images
  */
 #include "ImageManager.hpp"
 #include "vkcv/Core.hpp"
-#include "ImageLayoutTransitions.hpp"
+#include "vkcv/Image.hpp"
 #include "vkcv/Logger.hpp"
+#include "vkcv/Multisampling.hpp"
+#include "vkcv/TypeGuard.hpp"
 
 #include <algorithm>
 
 namespace vkcv {
-
-	ImageManager::Image::Image(
-		vk::Image                   handle,
-		vk::DeviceMemory            memory,
-		std::vector<vk::ImageView>  views,
-		uint32_t                    width,
-		uint32_t                    height,
-		uint32_t                    depth,
-		vk::Format                  format,
-		uint32_t                    layers)
-		:
-		m_handle(handle),
-		m_memory(memory),
-        m_viewPerMip(views),
-		m_width(width),
-		m_height(height),
-		m_depth(depth),
-		m_format(format),
-		m_layers(layers)
-	{}
-
-	/**
-	 * @brief searches memory type index for image allocation, combines requirements of image and application
-	 * @param physicalMemoryProperties Memory Properties of physical device
-	 * @param typeBits Bit field for suitable memory types
-	 * @param requirements Property flags that are required
-	 * @return memory type index for image
-	 */
-	uint32_t searchImageMemoryType(const vk::PhysicalDeviceMemoryProperties& physicalMemoryProperties, uint32_t typeBits, vk::MemoryPropertyFlags requirements) {
-		const uint32_t memoryCount = physicalMemoryProperties.memoryTypeCount;
-		for (uint32_t memoryIndex = 0; memoryIndex < memoryCount; ++memoryIndex) {
-			const uint32_t memoryTypeBits = (1 << memoryIndex);
-			const bool isRequiredMemoryType = typeBits & memoryTypeBits;
-
-			const vk::MemoryPropertyFlags properties =
-				physicalMemoryProperties.memoryTypes[memoryIndex].propertyFlags;
-			const bool hasRequiredProperties =
-				(properties & requirements) == requirements;
-
-			if (isRequiredMemoryType && hasRequiredProperties)
-				return static_cast<int32_t>(memoryIndex);
+	
+	bool ImageManager::init(Core &core, BufferManager &bufferManager) {
+		if (!HandleManager<ImageEntry, ImageHandle>::init(core)) {
+			return false;
 		}
-
-		// failed to find memory type
-		return -1;
+		
+		m_bufferManager = &bufferManager;
+		m_swapchainImages.clear();
+		return true;
 	}
-
-	ImageManager::ImageManager(BufferManager& bufferManager) noexcept :
-		m_core(nullptr), m_bufferManager(bufferManager), m_images()
-	{
+	
+	uint64_t ImageManager::getIdFrom(const ImageHandle &handle) const {
+		return handle.getId();
 	}
-
-	ImageManager::~ImageManager() noexcept {
-		for (uint64_t id = 0; id < m_images.size(); id++) {
-			destroyImageById(id);
+	
+	ImageHandle ImageManager::createById(uint64_t id, const HandleDestroyFunction &destroy) {
+		return ImageHandle(id, destroy);
+	}
+	
+	void ImageManager::destroyById(uint64_t id) {
+		auto &image = getById(id);
+		
+		const vk::Device &device = getCore().getContext().getDevice();
+		
+		for (auto &view : image.m_viewPerMip) {
+			if (view) {
+				device.destroyImageView(view);
+				view = nullptr;
+			}
+		}
+		
+		for (auto &view : image.m_arrayViewPerMip) {
+			if (view) {
+				device.destroyImageView(view);
+				view = nullptr;
+			}
+		}
+		
+		const vma::Allocator &allocator = getCore().getContext().getAllocator();
+		
+		if (image.m_handle) {
+			allocator.destroyImage(image.m_handle, image.m_allocation);
+			
+			image.m_handle = nullptr;
+			image.m_allocation = nullptr;
+		}
+	}
+	
+	const BufferManager &ImageManager::getBufferManager() const {
+		return *m_bufferManager;
+	}
+	
+	BufferManager &ImageManager::getBufferManager() {
+		return *m_bufferManager;
+	}
+	
+	void ImageManager::recordImageMipGenerationToCmdBuffer(vk::CommandBuffer cmdBuffer,
+														   const ImageHandle &handle) {
+		auto &image = (*this) [handle];
+		recordImageLayoutTransition(handle, 0, 0, vk::ImageLayout::eGeneral, cmdBuffer);
+		
+		vk::ImageAspectFlags aspectMask = isDepthImageFormat(image.m_format) ?
+										  vk::ImageAspectFlagBits::eDepth :
+										  vk::ImageAspectFlagBits::eColor;
+		
+		uint32_t srcWidth = image.m_width;
+		uint32_t srcHeight = image.m_height;
+		uint32_t srcDepth = image.m_depth;
+		
+		auto half = [](uint32_t in) {
+			return std::max<uint32_t>(in / 2, 1);
+		};
+		
+		uint32_t dstWidth = half(srcWidth);
+		uint32_t dstHeight = half(srcHeight);
+		uint32_t dstDepth = half(srcDepth);
+		
+		for (uint32_t srcMip = 0; srcMip < image.m_viewPerMip.size() - 1; srcMip++) {
+			uint32_t dstMip = srcMip + 1;
+			vk::ImageBlit region(
+					vk::ImageSubresourceLayers(aspectMask, srcMip, 0, 1),
+					{ vk::Offset3D(0, 0, 0), vk::Offset3D(srcWidth, srcHeight, srcDepth) },
+					vk::ImageSubresourceLayers(aspectMask, dstMip, 0, 1),
+					{ vk::Offset3D(0, 0, 0), vk::Offset3D(dstWidth, dstHeight, dstDepth) });
+			
+			cmdBuffer.blitImage(image.m_handle, vk::ImageLayout::eGeneral, image.m_handle,
+								vk::ImageLayout::eGeneral, region, vk::Filter::eLinear);
+			
+			srcWidth = dstWidth;
+			srcHeight = dstHeight;
+			srcDepth = dstDepth;
+			
+			dstWidth = half(dstWidth);
+			dstHeight = half(dstHeight);
+			dstDepth = half(dstDepth);
+			
+			recordImageMemoryBarrier(handle, cmdBuffer);
+		}
+	}
+	
+	const ImageEntry &ImageManager::operator[](const ImageHandle &handle) const {
+		if (handle.isSwapchainImage()) {
+			return m_swapchainImages [m_currentSwapchainInputImage];
 		}
-		for (const auto swapchainImage : m_swapchainImages) {
+		
+		return HandleManager<ImageEntry, ImageHandle>::operator[](handle);
+	}
+	
+	ImageEntry &ImageManager::operator[](const ImageHandle &handle) {
+		if (handle.isSwapchainImage()) {
+			return m_swapchainImages [m_currentSwapchainInputImage];
+		}
+		
+		return HandleManager<ImageEntry, ImageHandle>::operator[](handle);
+	}
+	
+	ImageManager::ImageManager() noexcept :
+			HandleManager<ImageEntry, ImageHandle>(), m_bufferManager(nullptr), m_swapchainImages(),
+			m_currentSwapchainInputImage(0) {}
+	
+	ImageManager::~ImageManager() noexcept {
+		clear();
+		
+		for (const auto &swapchainImage : m_swapchainImages) {
 			for (const auto view : swapchainImage.m_viewPerMip) {
-				m_core->getContext().getDevice().destroy(view);
+				getCore().getContext().getDevice().destroy(view);
 			}
 		}
 	}
 	
 	bool isDepthImageFormat(vk::Format format) {
-		if ((format == vk::Format::eD16Unorm) || (format == vk::Format::eD16UnormS8Uint) ||
-			(format == vk::Format::eD24UnormS8Uint) || (format == vk::Format::eD32Sfloat) ||
-			(format == vk::Format::eD32SfloatS8Uint)) {
+		if ((format == vk::Format::eD16Unorm) || (format == vk::Format::eD16UnormS8Uint)
+			|| (format == vk::Format::eD24UnormS8Uint) || (format == vk::Format::eD32Sfloat)
+			|| (format == vk::Format::eD32SfloatS8Uint)) {
 			return true;
 		} else {
 			return false;
 		}
 	}
-
-	ImageHandle ImageManager::createImage(
-		uint32_t    width, 
-		uint32_t    height, 
-		uint32_t    depth, 
-		vk::Format  format, 
-		uint32_t    mipCount,
-		bool        supportStorage, 
-		bool        supportColorAttachment)
-	{
-		const vk::PhysicalDevice& physicalDevice = m_core->getContext().getPhysicalDevice();
-		
+	
+	ImageHandle ImageManager::createImage(vk::Format format,
+										  uint32_t mipCount,
+										  const ImageConfig& config) {
+		const vk::PhysicalDevice &physicalDevice = getCore().getContext().getPhysicalDevice();
 		const vk::FormatProperties formatProperties = physicalDevice.getFormatProperties(format);
 		
 		vk::ImageCreateFlags createFlags;
 		vk::ImageUsageFlags imageUsageFlags = (
-				vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eTransferSrc
+				vk::ImageUsageFlagBits::eSampled |
+				vk::ImageUsageFlagBits::eTransferDst |
+				vk::ImageUsageFlagBits::eTransferSrc
 		);
-		if (supportStorage) {
+		
+		vk::ImageTiling imageTiling = vk::ImageTiling::eOptimal;
+		
+		if (config.isSupportingStorage()) {
 			imageUsageFlags |= vk::ImageUsageFlagBits::eStorage;
+			
+			if (!(formatProperties.optimalTilingFeatures
+				  & vk::FormatFeatureFlagBits::eStorageImage)) {
+				imageTiling = vk::ImageTiling::eLinear;
+				
+				if (!(formatProperties.linearTilingFeatures
+					  & vk::FormatFeatureFlagBits::eStorageImage))
+					return {};
+			}
 		}
-		if (supportColorAttachment) {
+		
+		if (config.isSupportingColorAttachment()) {
 			imageUsageFlags |= vk::ImageUsageFlagBits::eColorAttachment;
 		}
 		
@@ -113,14 +190,15 @@ namespace vkcv {
 		if (isDepthFormat) {
 			imageUsageFlags |= vk::ImageUsageFlagBits::eDepthStencilAttachment;
 		}
-
-		const vk::Device& device = m_core->getContext().getDevice();
-
+		
+		const vma::Allocator &allocator = getCore().getContext().getAllocator();
+		uint32_t requiredArrayLayers = 1;
+		
 		vk::ImageType imageType = vk::ImageType::e3D;
 		vk::ImageViewType imageViewType = vk::ImageViewType::e3D;
 		
-		if (depth <= 1) {
-			if (height <= 1) {
+		if (config.getDepth() <= 1) {
+			if (config.getHeight() <= 1) {
 				imageType = vk::ImageType::e1D;
 				imageViewType = vk::ImageViewType::e1D;
 			} else {
@@ -134,50 +212,73 @@ namespace vkcv {
 			imageViewType = vk::ImageViewType::e2D;
 		}
 		
-		vk::ImageTiling imageTiling = vk::ImageTiling::eOptimal;
+		if (config.isCubeMapImage()) {
+			requiredArrayLayers = 6;
+			
+			imageViewType = vk::ImageViewType::eCube;
+			createFlags |= vk::ImageCreateFlagBits::eCubeCompatible;
+		} else
+		if (vk::ImageType::e3D == imageType) {
+			createFlags |= vk::ImageCreateFlagBits::e2DArrayCompatible;
+		}
 		
 		if (!formatProperties.optimalTilingFeatures) {
 			if (!formatProperties.linearTilingFeatures)
-				return ImageHandle();
+				return {};
 			
 			imageTiling = vk::ImageTiling::eLinear;
 		}
 		
-		const vk::ImageFormatProperties imageFormatProperties = 
-			physicalDevice.getImageFormatProperties(format, imageType, imageTiling, imageUsageFlags);
+		const vk::ImageFormatProperties imageFormatProperties = (
+				physicalDevice.getImageFormatProperties(
+						format,
+						imageType,
+						imageTiling,
+						imageUsageFlags
+				)
+		);
 		
-		const uint32_t arrayLayers = std::min<uint32_t>(1, imageFormatProperties.maxArrayLayers);
+		const uint32_t arrayLayers = std::min<uint32_t>(
+				requiredArrayLayers,
+				imageFormatProperties.maxArrayLayers
+		);
 		
 		const vk::ImageCreateInfo imageCreateInfo(
-			createFlags,
-			imageType,
-			format,
-			vk::Extent3D(width, height, depth),
-			mipCount,
-			arrayLayers,
-			vk::SampleCountFlagBits::e1,
-			imageTiling,
-			imageUsageFlags,
-			vk::SharingMode::eExclusive,
-			{},
-			vk::ImageLayout::eUndefined
+				createFlags,
+				imageType,
+				format,
+				vk::Extent3D(
+						config.getWidth(),
+						config.getHeight(),
+						config.getDepth()
+				),
+				mipCount,
+				arrayLayers,
+				msaaToSampleCountFlagBits(
+						config.getMultisampling()
+				),
+				imageTiling,
+				imageUsageFlags,
+				vk::SharingMode::eExclusive,
+				{},
+				vk::ImageLayout::eUndefined
 		);
-
-		vk::Image image = device.createImage(imageCreateInfo);
 		
-		const vk::MemoryRequirements requirements = device.getImageMemoryRequirements(image);
-
-		vk::MemoryPropertyFlags memoryTypeFlags = vk::MemoryPropertyFlagBits::eDeviceLocal;
-
-		const uint32_t memoryTypeIndex = searchImageMemoryType(
-			physicalDevice.getMemoryProperties(),
-			requirements.memoryTypeBits,
-			memoryTypeFlags
+		auto imageAllocation = allocator.createImage(
+				imageCreateInfo,
+				vma::AllocationCreateInfo(
+						vma::AllocationCreateFlags(),
+						vma::MemoryUsage::eGpuOnly,
+						vk::MemoryPropertyFlagBits::eDeviceLocal,
+						vk::MemoryPropertyFlagBits::eDeviceLocal,
+						0,
+						vma::Pool(),
+						nullptr
+				)
 		);
-
-		vk::DeviceMemory memory = device.allocateMemory(vk::MemoryAllocateInfo(requirements.size, memoryTypeIndex));
-		device.bindImageMemory(image, memory, 0);
-
+		
+		vk::Image image = imageAllocation.first;
+		vma::Allocation allocation = imageAllocation.second;
 		vk::ImageAspectFlags aspectFlags;
 		
 		if (isDepthFormat) {
@@ -186,219 +287,303 @@ namespace vkcv {
 			aspectFlags = vk::ImageAspectFlagBits::eColor;
 		}
 		
+		const vk::Device &device = getCore().getContext().getDevice();
+		
 		std::vector<vk::ImageView> views;
-		for (int mip = 0; mip < mipCount; mip++) {
+		std::vector<vk::ImageView> arrayViews;
+		
+		for (uint32_t mip = 0; mip < mipCount; mip++) {
 			const vk::ImageViewCreateInfo imageViewCreateInfo(
-				{},
-				image,
-				imageViewType,
-				format,
-				vk::ComponentMapping(
-					vk::ComponentSwizzle::eIdentity,
-					vk::ComponentSwizzle::eIdentity,
-					vk::ComponentSwizzle::eIdentity,
-					vk::ComponentSwizzle::eIdentity
-				),
-				vk::ImageSubresourceRange(
-					aspectFlags,
-					mip,
-					mipCount - mip,
-					0,
-					arrayLayers
-				)
+					{},
+					image,
+					imageViewType,
+					format,
+					vk::ComponentMapping(
+							vk::ComponentSwizzle::eIdentity,
+							vk::ComponentSwizzle::eIdentity,
+							vk::ComponentSwizzle::eIdentity,
+							vk::ComponentSwizzle::eIdentity
+					),
+					vk::ImageSubresourceRange(
+							aspectFlags,
+							mip,
+							mipCount - mip,
+							0,
+							arrayLayers
+					)
 			);
-
+			
 			views.push_back(device.createImageView(imageViewCreateInfo));
 		}
 		
-		const uint64_t id = m_images.size();
-		m_images.push_back(Image(image, memory, views, width, height, depth, format, arrayLayers));
-		return ImageHandle(id, [&](uint64_t id) { destroyImageById(id); });
-	}
-	
-	ImageHandle ImageManager::createSwapchainImage() {
-		return ImageHandle::createSwapchainImageHandle();
-	}
-	
-	vk::Image ImageManager::getVulkanImage(const ImageHandle &handle) const {
-
-		if (handle.isSwapchainImage()) {
-			m_swapchainImages[m_currentSwapchainInputImage].m_handle;
-		}
-
-		const uint64_t id = handle.getId();
-		if (id >= m_images.size()) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return nullptr;
+		for (uint32_t mip = 0; mip < mipCount; mip++) {
+			const vk::ImageViewCreateInfo imageViewCreateInfo(
+					{},
+					image,
+					vk::ImageViewType::e2DArray,
+					format,
+					vk::ComponentMapping(
+							vk::ComponentSwizzle::eIdentity,
+							vk::ComponentSwizzle::eIdentity,
+							vk::ComponentSwizzle::eIdentity,
+							vk::ComponentSwizzle::eIdentity
+					),
+					vk::ImageSubresourceRange(
+							aspectFlags,
+							mip,
+							1,
+							0,
+							arrayLayers
+					)
+			);
+			
+			arrayViews.push_back(device.createImageView(imageViewCreateInfo));
 		}
 		
-		auto& image = m_images[id];
+		std::vector<vk::ImageLayout> layers;
+		layers.resize(arrayLayers, vk::ImageLayout::eUndefined);
 		
+		return add({
+			image,
+			allocation,
+			views,
+			arrayViews,
+			config.getWidth(),
+			config.getHeight(),
+			config.getDepth(),
+			format,
+			layers,
+			config.isSupportingStorage()
+		});
+	}
+	
+	vk::Image ImageManager::getVulkanImage(const ImageHandle &handle) const {
+		auto &image = (*this) [handle];
 		return image.m_handle;
 	}
 	
 	vk::DeviceMemory ImageManager::getVulkanDeviceMemory(const ImageHandle &handle) const {
-
 		if (handle.isSwapchainImage()) {
 			vkcv_log(LogLevel::ERROR, "Swapchain image has no memory");
 			return nullptr;
 		}
-
-		const uint64_t id = handle.getId();
-		if (id >= m_images.size()) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return nullptr;
-		}
 		
-		auto& image = m_images[id];
+		auto &image = (*this) [handle];
+		const vma::Allocator &allocator = getCore().getContext().getAllocator();
+		
+		auto info = allocator.getAllocationInfo(image.m_allocation);
 		
-		return image.m_memory;
+		return info.deviceMemory;
 	}
 	
-	vk::ImageView ImageManager::getVulkanImageView(const ImageHandle &handle, const size_t mipLevel) const {
-		
+	vk::ImageView ImageManager::getVulkanImageView(const ImageHandle &handle, size_t mipLevel,
+												   bool arrayView) const {
 		if (handle.isSwapchainImage()) {
-			return m_swapchainImages[m_currentSwapchainInputImage].m_viewPerMip[0];
-		}
-
-		const uint64_t id = handle.getId();
-		if (id >= m_images.size()) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return nullptr;
+			return m_swapchainImages [m_currentSwapchainInputImage].m_viewPerMip [0];
 		}
 		
-		const auto& image = m_images[id];
-
-		if (mipLevel >= m_images.size()) {
+		const auto &image = (*this) [handle];
+		const auto &views = arrayView ? image.m_arrayViewPerMip : image.m_viewPerMip;
+		
+		if (mipLevel >= views.size()) {
 			vkcv_log(LogLevel::ERROR, "Image does not have requested mipLevel");
 			return nullptr;
 		}
-
-		return image.m_viewPerMip[mipLevel];
+		
+		return views [mipLevel];
 	}
 	
-	void ImageManager::switchImageLayoutImmediate(const ImageHandle& handle, vk::ImageLayout newLayout) {
-		uint64_t id = handle.getId();
+	static vk::ImageMemoryBarrier createImageLayoutTransitionBarrier(const ImageEntry &image,
+																	 uint32_t mipLevelCount,
+																	 uint32_t mipLevelOffset,
+																	 vk::ImageLayout newLayout) {
+		vk::ImageAspectFlags aspectFlags;
+		if (isDepthFormat(image.m_format)) {
+			aspectFlags = vk::ImageAspectFlagBits::eDepth;
+		} else {
+			aspectFlags = vk::ImageAspectFlagBits::eColor;
+		}
 		
-		const bool isSwapchainImage = handle.isSwapchainImage();
-
-		if (id >= m_images.size() && !isSwapchainImage) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return;
+		const uint32_t mipLevelsMax = image.m_viewPerMip.size();
+		
+		if (mipLevelOffset > mipLevelsMax) {
+			mipLevelOffset = mipLevelsMax;
+		}
+		
+		if ((!mipLevelCount) || (mipLevelOffset + mipLevelCount > mipLevelsMax)) {
+			mipLevelCount = mipLevelsMax - mipLevelOffset;
 		}
 		
-		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
-		const auto transitionBarrier = createImageLayoutTransitionBarrier(image, newLayout);
+		vk::ImageSubresourceRange imageSubresourceRange(
+				aspectFlags,
+				mipLevelOffset,
+				mipLevelCount,
+				0,
+				static_cast<uint32_t>(image.m_layers.size())
+		);
 		
-		SubmitInfo submitInfo;
-		submitInfo.queueType = QueueType::Graphics;
+		// TODO: precise AccessFlagBits, will require a lot of context
+		vk::ImageMemoryBarrier barrier (
+				vk::AccessFlagBits::eMemoryWrite,
+				vk::AccessFlagBits::eMemoryRead,
+				image.m_layers[0],
+				newLayout,
+				VK_QUEUE_FAMILY_IGNORED,
+				VK_QUEUE_FAMILY_IGNORED,
+				image.m_handle,
+				imageSubresourceRange
+		);
 		
-		m_core->recordAndSubmitCommandsImmediate(
-			submitInfo,
-			[transitionBarrier](const vk::CommandBuffer& commandBuffer) {
-			// TODO: precise PipelineStageFlagBits, will require a lot of context
-			commandBuffer.pipelineBarrier(
-				vk::PipelineStageFlagBits::eTopOfPipe,
-				vk::PipelineStageFlagBits::eBottomOfPipe,
+		return barrier;
+	}
+	
+	void ImageManager::switchImageLayoutImmediate(const ImageHandle &handle,
+												  vk::ImageLayout newLayout) {
+		auto &image = (*this) [handle];
+		const auto transitionBarrier = createImageLayoutTransitionBarrier(image, 0, 0, newLayout);
+		
+		auto &core = getCore();
+		auto stream = core.createCommandStream(QueueType::Graphics);
+		
+		core.recordCommandsToStream(
+				stream,
+				[transitionBarrier](const vk::CommandBuffer &commandBuffer) {
+					// TODO: precise PipelineStageFlagBits, will require a lot of context
+					commandBuffer.pipelineBarrier(
+							vk::PipelineStageFlagBits::eTopOfPipe,
+							vk::PipelineStageFlagBits::eBottomOfPipe,
+							{},
+							nullptr,
+							nullptr,
+							transitionBarrier
+					);
+				},
+				nullptr
+		);
+		
+		core.submitCommandStream(stream, false);
+		
+		for (auto& layer : image.m_layers) {
+			layer = newLayout;
+		}
+	}
+	
+	void ImageManager::recordImageLayoutTransition(const ImageHandle &handle,
+												   uint32_t mipLevelCount, uint32_t mipLevelOffset,
+												   vk::ImageLayout newLayout,
+												   vk::CommandBuffer cmdBuffer) {
+		auto &image = (*this) [handle];
+		const auto transitionBarrier = createImageLayoutTransitionBarrier(
+				image,
+				mipLevelCount,
+				mipLevelOffset,
+				newLayout
+		);
+		
+		cmdBuffer.pipelineBarrier(
+				vk::PipelineStageFlagBits::eAllCommands,
+				vk::PipelineStageFlagBits::eAllCommands,
 				{},
 				nullptr,
 				nullptr,
 				transitionBarrier
-				);
-			},
-			nullptr);
-		image.m_layout = newLayout;
-	}
-
-	void ImageManager::recordImageLayoutTransition(
-		const ImageHandle& handle, 
-		vk::ImageLayout newLayout, 
-		vk::CommandBuffer cmdBuffer) {
-
-		const uint64_t id = handle.getId();
-		const bool isSwapchainImage = handle.isSwapchainImage();
-
-		if (id >= m_images.size() && !isSwapchainImage) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return;
+		);
+		
+		for (auto& layer : image.m_layers) {
+			layer = newLayout;
 		}
-
-		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
-		const auto transitionBarrier = createImageLayoutTransitionBarrier(image, newLayout);
-		recordImageBarrier(cmdBuffer, transitionBarrier);
-		image.m_layout = newLayout;
 	}
-
-	void ImageManager::recordImageMemoryBarrier(
-		const ImageHandle& handle,
-		vk::CommandBuffer cmdBuffer) {
-
-		const uint64_t id = handle.getId();
-		const bool isSwapchainImage = handle.isSwapchainImage();
-
-		if (id >= m_images.size() && !isSwapchainImage) {
-			std::cerr << "Error: ImageManager::recordImageMemoryBarrier invalid handle" << std::endl;
-			return;
-		}
-
-		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
-		const auto transitionBarrier = createImageLayoutTransitionBarrier(image, image.m_layout);
-		recordImageBarrier(cmdBuffer, transitionBarrier);
+	
+	void ImageManager::recordImageMemoryBarrier(const ImageHandle &handle,
+												vk::CommandBuffer cmdBuffer) {
+		auto &image = (*this) [handle];
+		const auto transitionBarrier = createImageLayoutTransitionBarrier(
+				image,
+				0,
+				0,
+				image.m_layers[0]
+		);
+		
+		cmdBuffer.pipelineBarrier(
+				vk::PipelineStageFlagBits::eAllCommands,
+				vk::PipelineStageFlagBits::eAllCommands,
+				{},
+				nullptr,
+				nullptr,
+				transitionBarrier
+		);
 	}
 	
-	constexpr uint32_t getChannelsByFormat(vk::Format format) {
+	constexpr uint32_t getBytesPerPixel(vk::Format format) {
 		switch (format) {
 			case vk::Format::eR8Unorm:
 				return 1;
+			case vk::Format::eR16Unorm:
+				return 2;
+			case vk::Format::eR32Uint:
 			case vk::Format::eR8G8B8A8Srgb:
+			case vk::Format::eR8G8B8A8Unorm:
 				return 4;
+			case vk::Format::eR16G16B16A16Sfloat:
+				return 8;
+			case vk::Format::eR32G32B32A32Sfloat:
+				return 16;
 			default:
-				std::cerr << "Check format instead of guessing, please!" << std::endl;
+				std::cerr << "Unknown image format" << std::endl;
 				return 4;
 		}
 	}
 	
-	void ImageManager::fillImage(const ImageHandle& handle, void* data, size_t size)
-	{
-		const uint64_t id = handle.getId();
-		
+	void ImageManager::fillImage(const ImageHandle &handle,
+								 const void* data,
+								 size_t size,
+								 uint32_t firstLayer,
+								 uint32_t layerCount) {
 		if (handle.isSwapchainImage()) {
 			vkcv_log(LogLevel::ERROR, "Swapchain image cannot be filled");
 			return;
 		}
-
-		if (id >= m_images.size()) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
+		
+		auto &image = (*this) [handle];
+		
+		const auto imageLayerCount = static_cast<uint32_t>(image.m_layers.size());
+		const uint32_t baseArrayLayer = std::min<uint32_t>(firstLayer, imageLayerCount);
+		
+		if (baseArrayLayer >= image.m_layers.size()) {
 			return;
 		}
 		
-		auto& image = m_images[id];
+		uint32_t arrayLayerCount;
+		
+		if (layerCount > 0) {
+			arrayLayerCount = std::min<uint32_t>(layerCount, imageLayerCount - baseArrayLayer);
+		} else {
+			arrayLayerCount = imageLayerCount - baseArrayLayer;
+		}
 		
-		switchImageLayoutImmediate(
-				handle,
-				vk::ImageLayout::eTransferDstOptimal);
+		switchImageLayoutImmediate(handle, vk::ImageLayout::eTransferDstOptimal);
 		
-		uint32_t channels = getChannelsByFormat(image.m_format);
 		const size_t image_size = (
-				image.m_width * image.m_height * image.m_depth * channels
+				image.m_width * image.m_height * image.m_depth * getBytesPerPixel(image.m_format)
 		);
 		
 		const size_t max_size = std::min(size, image_size);
 		
-		BufferHandle bufferHandle = m_bufferManager.createBuffer(
-				BufferType::STAGING, max_size, BufferMemoryType::HOST_VISIBLE
+		BufferHandle bufferHandle = getBufferManager().createBuffer(
+				TypeGuard(1), BufferType::STAGING, BufferMemoryType::DEVICE_LOCAL, max_size, false
 		);
 		
-		m_bufferManager.fillBuffer(bufferHandle, data, max_size, 0);
+		getBufferManager().fillBuffer(bufferHandle, data, max_size, 0);
 		
-		vk::Buffer stagingBuffer = m_bufferManager.getBuffer(bufferHandle);
+		vk::Buffer stagingBuffer = getBufferManager().getBuffer(bufferHandle);
 		
-		SubmitInfo submitInfo;
-		submitInfo.queueType = QueueType::Transfer;
+		auto &core = getCore();
+		auto stream = core.createCommandStream(QueueType::Transfer);
 		
-		m_core->recordAndSubmitCommandsImmediate(
-				submitInfo,
-				[&image, &stagingBuffer](const vk::CommandBuffer& commandBuffer) {
+		core.recordCommandsToStream(
+				stream,
+				[&image, &stagingBuffer, &baseArrayLayer, &arrayLayerCount]
+						(const vk::CommandBuffer &commandBuffer) {
 					vk::ImageAspectFlags aspectFlags;
 					
 					if (isDepthImageFormat(image.m_format)) {
@@ -407,16 +592,11 @@ namespace vkcv {
 						aspectFlags = vk::ImageAspectFlagBits::eColor;
 					}
 					
-					const vk::BufferImageCopy region (
+					const vk::BufferImageCopy region(
 							0,
 							0,
 							0,
-							vk::ImageSubresourceLayers(
-									aspectFlags,
-									0,
-									0,
-									image.m_layers
-							),
+							vk::ImageSubresourceLayers(aspectFlags, 0, baseArrayLayer, arrayLayerCount),
 							vk::Offset3D(0, 0, 0),
 							vk::Extent3D(image.m_width, image.m_height, image.m_depth)
 					);
@@ -430,216 +610,132 @@ namespace vkcv {
 					);
 				},
 				[&]() {
-					switchImageLayoutImmediate(
-							handle,
-							vk::ImageLayout::eShaderReadOnlyOptimal
-					);
+					switchImageLayoutImmediate(handle, vk::ImageLayout::eShaderReadOnlyOptimal);
 				}
 		);
+		
+		core.submitCommandStream(stream, false);
 	}
-
-	void ImageManager::recordImageMipGenerationToCmdBuffer(vk::CommandBuffer cmdBuffer, const ImageHandle& handle) {
-
-		const auto id = handle.getId();
-		if (id >= m_images.size()) {
-			vkcv_log(vkcv::LogLevel::ERROR, "Invalid image handle");
-			return;
-		}
-
-		auto& image = m_images[id];
-		recordImageLayoutTransition(handle, vk::ImageLayout::eGeneral, cmdBuffer);
-
-		vk::ImageAspectFlags aspectMask = isDepthImageFormat(image.m_format) ?
-			vk::ImageAspectFlagBits::eDepth : vk::ImageAspectFlagBits::eColor;
-
-		uint32_t srcWidth = image.m_width;
-		uint32_t srcHeight = image.m_height;
-		uint32_t srcDepth = image.m_depth;
-
-		auto half = [](uint32_t in) {
-			return std::max<uint32_t>(in / 2, 1);
-		};
-
-		uint32_t dstWidth = half(srcWidth);
-		uint32_t dstHeight = half(srcHeight);
-		uint32_t dstDepth = half(srcDepth);
-
-		for (uint32_t srcMip = 0; srcMip < image.m_viewPerMip.size() - 1; srcMip++) {
-			uint32_t dstMip = srcMip + 1;
-			vk::ImageBlit region(
-				vk::ImageSubresourceLayers(aspectMask, srcMip, 0, 1),
-				{ vk::Offset3D(0, 0, 0), vk::Offset3D(srcWidth, srcHeight, srcDepth) },
-				vk::ImageSubresourceLayers(aspectMask, dstMip, 0, 1),
-				{ vk::Offset3D(0, 0, 0), vk::Offset3D(dstWidth, dstHeight, dstDepth) });
-
-			cmdBuffer.blitImage(
-				image.m_handle,
-				vk::ImageLayout::eGeneral,
-				image.m_handle,
-				vk::ImageLayout::eGeneral,
-				region,
-				vk::Filter::eLinear);
-
-			srcWidth = dstWidth;
-			srcHeight = dstHeight;
-			srcDepth = dstDepth;
-
-			dstWidth = half(dstWidth);
-			dstHeight = half(dstHeight);
-			dstDepth = half(dstDepth);
-
-			recordImageMemoryBarrier(handle, cmdBuffer);
-		}
-	}
-
-	void ImageManager::generateImageMipChainImmediate(const ImageHandle& handle) {
-
-		const auto& device = m_core->getContext().getDevice();
-
-		SubmitInfo submitInfo;
-		submitInfo.queueType = QueueType::Graphics;
-
-		if (handle.isSwapchainImage()) {
-			vkcv_log(vkcv::LogLevel::ERROR, "You cannot generate a mip chain for the swapchain, what are you smoking?");
-			return;
-		}
-
-		const auto record = [this, handle](const vk::CommandBuffer cmdBuffer) {
-			recordImageMipGenerationToCmdBuffer(cmdBuffer, handle);
-		};
-
-		m_core->recordAndSubmitCommandsImmediate(submitInfo, record, nullptr);
-	}
-
+	
 	void ImageManager::recordImageMipChainGenerationToCmdStream(
-		const vkcv::CommandStreamHandle& cmdStream,
-		const ImageHandle& handle) {
-
+			const vkcv::CommandStreamHandle &cmdStream, const ImageHandle &handle) {
 		const auto record = [this, handle](const vk::CommandBuffer cmdBuffer) {
 			recordImageMipGenerationToCmdBuffer(cmdBuffer, handle);
 		};
-		m_core->recordCommandsToStream(cmdStream, record, nullptr);
+		
+		getCore().recordCommandsToStream(cmdStream, record, nullptr);
 	}
-
-	uint32_t ImageManager::getImageWidth(const ImageHandle &handle) const {
-		const uint64_t id = handle.getId();
-		const bool isSwapchainImage = handle.isSwapchainImage();
-
-		if (id >= m_images.size() && !isSwapchainImage) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return 0;
-		}
+	
+	void ImageManager::recordMSAAResolve(vk::CommandBuffer cmdBuffer, const ImageHandle &src,
+										 const ImageHandle &dst) {
+		auto &srcImage = (*this) [src];
+		auto &dstImage = (*this) [dst];
+		
+		const auto srcLayerCount = static_cast<uint32_t>(srcImage.m_layers.size());
+		const auto dstLayerCount = static_cast<uint32_t>(dstImage.m_layers.size());
+		
+		vk::ImageResolve region(
+				vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, srcLayerCount),
+				vk::Offset3D(0, 0, 0),
+				vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, dstLayerCount),
+				vk::Offset3D(0, 0, 0),
+				vk::Extent3D(dstImage.m_width, dstImage.m_height, dstImage.m_depth)
+		);
 		
-		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
+		recordImageLayoutTransition(src, 0, 0, vk::ImageLayout::eTransferSrcOptimal, cmdBuffer);
+		recordImageLayoutTransition(dst, 0, 0, vk::ImageLayout::eTransferDstOptimal, cmdBuffer);
 		
+		cmdBuffer.resolveImage(
+				srcImage.m_handle,
+				srcImage.m_layers[0],
+				dstImage.m_handle,
+				dstImage.m_layers[0],
+				region
+		);
+	}
+	
+	uint32_t ImageManager::getImageWidth(const ImageHandle &handle) const {
+		auto &image = (*this) [handle];
 		return image.m_width;
 	}
 	
 	uint32_t ImageManager::getImageHeight(const ImageHandle &handle) const {
-		const uint64_t id = handle.getId();
-		const bool isSwapchainImage = handle.isSwapchainImage();
-		
-		if (id >= m_images.size() && !isSwapchainImage) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return 0;
-		}
-		
-		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
-		
+		auto &image = (*this) [handle];
 		return image.m_height;
 	}
 	
 	uint32_t ImageManager::getImageDepth(const ImageHandle &handle) const {
-		const uint64_t id = handle.getId();
-		const bool isSwapchainImage = handle.isSwapchainImage();
-
-		if (id >= m_images.size() && !isSwapchainImage) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return 0;
-		}
-		
-		auto& image = isSwapchainImage ? m_swapchainImages[m_currentSwapchainInputImage] : m_images[id];
-		
+		auto &image = (*this) [handle];
 		return image.m_depth;
 	}
 	
-	void ImageManager::destroyImageById(uint64_t id)
-	{
-		if (id >= m_images.size()) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return;
-		}
-		
-		auto& image = m_images[id];
-
-		const vk::Device& device = m_core->getContext().getDevice();
-		
-		for (auto& view : image.m_viewPerMip) {
-			if (view) {
-				device.destroyImageView(view);
-				view = nullptr;
-			}
-		}
-
-		if (image.m_memory) {
-			device.freeMemory(image.m_memory);
-			image.m_memory = nullptr;
-		}
-
-		if (image.m_handle) {
-			device.destroyImage(image.m_handle);
-			image.m_handle = nullptr;
-		}
+	vk::Format ImageManager::getImageFormat(const ImageHandle &handle) const {
+		auto &image = (*this) [handle];
+		return image.m_format;
 	}
-
-	vk::Format ImageManager::getImageFormat(const ImageHandle& handle) const {
-
-		const uint64_t id = handle.getId();
-		const bool isSwapchainFormat = handle.isSwapchainImage();
-
-		if (id >= m_images.size() && !isSwapchainFormat) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return vk::Format::eUndefined;
+	
+	bool ImageManager::isImageSupportingStorage(const ImageHandle &handle) const {
+		if (handle.isSwapchainImage()) {
+			return false;
 		}
-
-		return isSwapchainFormat ? m_swapchainImages[m_currentSwapchainInputImage].m_format : m_images[id].m_format;
+		
+		auto &image = (*this) [handle];
+		return image.m_storage;
 	}
-
-	uint32_t ImageManager::getImageMipCount(const ImageHandle& handle) const {
-		const uint64_t id = handle.getId();
-		const bool isSwapchainFormat = handle.isSwapchainImage();
-
+	
+	uint32_t ImageManager::getImageMipCount(const ImageHandle &handle) const {
 		if (handle.isSwapchainImage()) {
 			return 1;
 		}
-
-		if (id >= m_images.size()) {
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return 0;
-		}
-
-		return m_images[id].m_viewPerMip.size();
+		
+		auto &image = (*this) [handle];
+		return image.m_viewPerMip.size();
 	}
-
+	
+	uint32_t ImageManager::getImageArrayLayers(const ImageHandle &handle) const {
+		auto &image = (*this) [handle];
+		return static_cast<uint32_t>(image.m_layers.size());
+	}
+	
 	void ImageManager::setCurrentSwapchainImageIndex(int index) {
 		m_currentSwapchainInputImage = index;
 	}
-
-	void ImageManager::setSwapchainImages(const std::vector<vk::Image>& images, std::vector<vk::ImageView> views, 
-		uint32_t width, uint32_t height, vk::Format format) {
-
+	
+	void ImageManager::setSwapchainImages(const std::vector<vk::Image> &images,
+										  const std::vector<vk::ImageView> &views, uint32_t width,
+										  uint32_t height, vk::Format format) {
+		
 		// destroy old views
-		for (auto image : m_swapchainImages) {
-			for (const auto& view : image.m_viewPerMip) {
-				m_core->getContext().getDevice().destroyImageView(view);
+		for (const auto &image : m_swapchainImages) {
+			for (const auto &view : image.m_viewPerMip) {
+				getCore().getContext().getDevice().destroyImageView(view);
 			}
 		}
-
+		
 		assert(images.size() == views.size());
 		m_swapchainImages.clear();
-		for (int i = 0; i < images.size(); i++) {
-			m_swapchainImages.push_back(Image(images[i], nullptr, { views[i] }, width, height, 1, format, 1));
+		for (size_t i = 0; i < images.size(); i++) {
+			m_swapchainImages.push_back({
+					images [i],
+					nullptr,
+					{ views [i] },
+					{},
+					width,
+					height,
+					1,
+					format,
+					{ vk::ImageLayout::eUndefined },
+					false
+			});
 		}
 	}
-
-}
\ No newline at end of file
+	
+	void ImageManager::updateImageLayoutManual(const vkcv::ImageHandle &handle,
+											   vk::ImageLayout layout) {
+		auto &image = (*this) [handle];
+		for (auto& layer : image.m_layers) {
+			layer = layout;
+		}
+	}
+	
+} // namespace vkcv
\ No newline at end of file
diff --git a/src/vkcv/ImageManager.hpp b/src/vkcv/ImageManager.hpp
index ecba7eb5959c1d78a0be41e0b3ac555bffd92d95..7d9c278d7c85fbfb5f8206ad52f99913e5b58d7e 100644
--- a/src/vkcv/ImageManager.hpp
+++ b/src/vkcv/ImageManager.hpp
@@ -5,124 +5,140 @@
  * @brief class creating and managing images
  */
 #include <vector>
+#include <vk_mem_alloc.hpp>
 #include <vulkan/vulkan.hpp>
 
-#include "vkcv/BufferManager.hpp"
-#include "vkcv/Handles.hpp"
+#include "BufferManager.hpp"
+#include "HandleManager.hpp"
+#include "vkcv/ImageConfig.hpp"
 
 namespace vkcv {
 
-	class ImageManager
-	{
+	/**
+	 * @brief Determine whether an image format is valid
+	 * for depth buffers.
+	 *
+	 * @param[in] format Image format
+	 * @return True, if the format is usable for depth buffers, otherwise false.
+	 */
+	bool isDepthImageFormat(vk::Format format);
+
+	struct ImageEntry {
+		vk::Image m_handle;
+		vma::Allocation m_allocation;
+
+		std::vector<vk::ImageView> m_viewPerMip;
+		std::vector<vk::ImageView> m_arrayViewPerMip;
+
+		uint32_t m_width;
+		uint32_t m_height;
+		uint32_t m_depth;
+
+		vk::Format m_format;
+		std::vector<vk::ImageLayout> m_layers;
+		bool m_storage;
+	};
+
+	/**
+	 * @brief Class to manage the creation, destruction, allocation
+	 * and filling of images.
+	 */
+	class ImageManager : HandleManager<ImageEntry, ImageHandle> {
 		friend class Core;
-	public:
-		struct Image
-		{
-			vk::Image                   m_handle;
-			vk::DeviceMemory            m_memory;
-			std::vector<vk::ImageView>  m_viewPerMip;
-			uint32_t                    m_width     = 0;
-			uint32_t                    m_height    = 0;
-			uint32_t                    m_depth     = 0;
-			vk::Format                  m_format;
-			uint32_t                    m_layers    = 1;
-			vk::ImageLayout             m_layout    = vk::ImageLayout::eUndefined;
-		private:
-			// struct is public so utility functions can access members, but only ImageManager can create Image
-			friend ImageManager;
-			Image(
-				vk::Image                   handle,
-				vk::DeviceMemory            memory,
-				std::vector<vk::ImageView>  views,
-				uint32_t                    width,
-				uint32_t                    height,
-				uint32_t                    depth,
-				vk::Format                  format,
-				uint32_t                    layers);
-
-			Image();
-		};
+
 	private:
-		
-		Core* m_core;
-		BufferManager& m_bufferManager;
-		
-		std::vector<Image> m_images;
-		std::vector<Image> m_swapchainImages;
+		BufferManager* m_bufferManager;
+
+		std::vector<ImageEntry> m_swapchainImages;
 		int m_currentSwapchainInputImage;
-		
-		ImageManager(BufferManager& bufferManager) noexcept;
-		
+
+		bool init(Core &core, BufferManager &bufferManager);
+
+		[[nodiscard]] uint64_t getIdFrom(const ImageHandle &handle) const override;
+
+		[[nodiscard]] ImageHandle createById(uint64_t id,
+											 const HandleDestroyFunction &destroy) override;
+
 		/**
 		 * Destroys and deallocates image represented by a given
 		 * image handle id.
 		 *
 		 * @param id Image handle id
 		 */
-		void destroyImageById(uint64_t id);
+		void destroyById(uint64_t id) override;
+
+		[[nodiscard]] const BufferManager &getBufferManager() const;
 
-		void recordImageMipGenerationToCmdBuffer(vk::CommandBuffer cmdBuffer, const ImageHandle& handle);
+		[[nodiscard]] BufferManager &getBufferManager();
+
+		void recordImageMipGenerationToCmdBuffer(vk::CommandBuffer cmdBuffer,
+												 const ImageHandle &handle);
+
+	protected:
+		[[nodiscard]] virtual const ImageEntry &
+		operator[](const ImageHandle &handle) const override;
+
+		[[nodiscard]] virtual ImageEntry &operator[](const ImageHandle &handle) override;
 
 	public:
-		~ImageManager() noexcept;
-		ImageManager(ImageManager&& other) = delete;
-		ImageManager(const ImageManager& other) = delete;
-
-		ImageManager& operator=(ImageManager&& other) = delete;
-		ImageManager& operator=(const ImageManager& other) = delete;
-		
-		ImageHandle createImage(
-			uint32_t    width, 
-			uint32_t    height, 
-			uint32_t    depth, 
-			vk::Format  format, 
-			uint32_t    mipCount,
-			bool        supportStorage, 
-			bool        supportColorAttachment);
-		
-		ImageHandle createSwapchainImage();
-		
-		[[nodiscard]]
-		vk::Image getVulkanImage(const ImageHandle& handle) const;
-		
-		[[nodiscard]]
-		vk::DeviceMemory getVulkanDeviceMemory(const ImageHandle& handle) const;
-		
-		[[nodiscard]]
-		vk::ImageView getVulkanImageView(const ImageHandle& handle, const size_t mipLevel = 0) const;
-
-		void switchImageLayoutImmediate(const ImageHandle& handle, vk::ImageLayout newLayout);
-		void recordImageLayoutTransition(
-			const ImageHandle& handle, 
-			vk::ImageLayout newLayout, 
-			vk::CommandBuffer cmdBuffer);
-
-		void recordImageMemoryBarrier(
-			const ImageHandle& handle,
-			vk::CommandBuffer cmdBuffer);
-
-		void fillImage(const ImageHandle& handle, void* data, size_t size);
-		void generateImageMipChainImmediate(const ImageHandle& handle);
-		void recordImageMipChainGenerationToCmdStream(const vkcv::CommandStreamHandle& cmdStream, const ImageHandle& handle);
-		
-		[[nodiscard]]
-		uint32_t getImageWidth(const ImageHandle& handle) const;
-		
-		[[nodiscard]]
-		uint32_t getImageHeight(const ImageHandle& handle) const;
-		
-		[[nodiscard]]
-		uint32_t getImageDepth(const ImageHandle& handle) const;
-		
-		[[nodiscard]]
-		vk::Format getImageFormat(const ImageHandle& handle) const;
-
-		[[nodiscard]]
-		uint32_t getImageMipCount(const ImageHandle& handle) const;
+		ImageManager() noexcept;
+
+		~ImageManager() noexcept override;
+
+		[[nodiscard]] ImageHandle createImage(vk::Format format,
+											  uint32_t mipCount,
+											  const ImageConfig& config);
+
+		[[nodiscard]] vk::Image getVulkanImage(const ImageHandle &handle) const;
+
+		[[nodiscard]] vk::DeviceMemory getVulkanDeviceMemory(const ImageHandle &handle) const;
+
+		[[nodiscard]] vk::ImageView getVulkanImageView(const ImageHandle &handle,
+													   size_t mipLevel = 0,
+													   bool arrayView = false) const;
+
+		void switchImageLayoutImmediate(const ImageHandle &handle, vk::ImageLayout newLayout);
+
+		void recordImageLayoutTransition(const ImageHandle &handle, uint32_t mipLevelCount,
+										 uint32_t mipLevelOffset, vk::ImageLayout newLayout,
+										 vk::CommandBuffer cmdBuffer);
+
+		void recordImageMemoryBarrier(const ImageHandle &handle, vk::CommandBuffer cmdBuffer);
+
+		void fillImage(const ImageHandle &handle,
+					   const void* data,
+					   size_t size,
+					   uint32_t firstLayer,
+					   uint32_t layerCount);
+
+		void recordImageMipChainGenerationToCmdStream(const vkcv::CommandStreamHandle &cmdStream,
+													  const ImageHandle &handle);
+
+		void recordMSAAResolve(vk::CommandBuffer cmdBuffer, const ImageHandle &src,
+							   const ImageHandle &dst);
+
+		[[nodiscard]] uint32_t getImageWidth(const ImageHandle &handle) const;
+
+		[[nodiscard]] uint32_t getImageHeight(const ImageHandle &handle) const;
+
+		[[nodiscard]] uint32_t getImageDepth(const ImageHandle &handle) const;
+
+		[[nodiscard]] vk::Format getImageFormat(const ImageHandle &handle) const;
+
+		[[nodiscard]] bool isImageSupportingStorage(const ImageHandle &handle) const;
+
+		[[nodiscard]] uint32_t getImageMipCount(const ImageHandle &handle) const;
+
+		[[nodiscard]] uint32_t getImageArrayLayers(const ImageHandle &handle) const;
 
 		void setCurrentSwapchainImageIndex(int index);
-		void setSwapchainImages(const std::vector<vk::Image>& images, std::vector<vk::ImageView> views,
-			uint32_t width, uint32_t height, vk::Format format);
 
+		void setSwapchainImages(const std::vector<vk::Image> &images,
+								const std::vector<vk::ImageView> &views, uint32_t width,
+								uint32_t height, vk::Format format);
+
+		// if manual vulkan work, e.g. ImGui integration, changes an image layout this function must
+		// be used to update the internal image state
+		void updateImageLayoutManual(const vkcv::ImageHandle &handle, vk::ImageLayout layout);
 	};
-}
\ No newline at end of file
+} // namespace vkcv
\ No newline at end of file
diff --git a/src/vkcv/Multisampling.cpp b/src/vkcv/Multisampling.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..168ba0cf198491fb19cc29797d35d624f194b15d
--- /dev/null
+++ b/src/vkcv/Multisampling.cpp
@@ -0,0 +1,38 @@
+#include <vkcv/Logger.hpp>
+#include <vkcv/Multisampling.hpp>
+
+namespace vkcv {
+
+	vk::SampleCountFlagBits msaaToSampleCountFlagBits(Multisampling msaa) {
+		switch (msaa) {
+		case Multisampling::None:
+			return vk::SampleCountFlagBits::e1;
+		case Multisampling::MSAA2X:
+			return vk::SampleCountFlagBits::e2;
+		case Multisampling::MSAA4X:
+			return vk::SampleCountFlagBits::e4;
+		case Multisampling::MSAA8X:
+			return vk::SampleCountFlagBits::e8;
+		default:
+			vkcv_log(vkcv::LogLevel::ERROR, "Unknown Multisampling enum setting");
+			return vk::SampleCountFlagBits::e1;
+		}
+	}
+
+	uint32_t msaaToSampleCount(Multisampling msaa) {
+		switch (msaa) {
+		case Multisampling::None:
+			return 1;
+		case Multisampling::MSAA2X:
+			return 2;
+		case Multisampling::MSAA4X:
+			return 4;
+		case Multisampling::MSAA8X:
+			return 8;
+		default:
+			vkcv_log(vkcv::LogLevel::ERROR, "Unknown Multisampling enum setting");
+			return 1;
+		}
+	}
+
+} // namespace vkcv
\ No newline at end of file
diff --git a/src/vkcv/Pass.cpp b/src/vkcv/Pass.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..147d715110b6a20bf1035ddd4f05270916a03d3f
--- /dev/null
+++ b/src/vkcv/Pass.cpp
@@ -0,0 +1,38 @@
+
+#include "vkcv/Pass.hpp"
+
+namespace vkcv {
+
+	PassHandle passFormats(Core &core, const std::vector<vk::Format> &formats, bool clear,
+						   Multisampling multisampling) {
+		AttachmentDescriptions attachments;
+
+		for (const auto format : formats) {
+			attachments.emplace_back(format,
+									 clear ? AttachmentOperation::CLEAR : AttachmentOperation::LOAD,
+									 AttachmentOperation::STORE);
+		}
+
+		const PassConfig config(attachments, multisampling);
+		return core.createPass(config);
+	}
+
+	PassHandle passFormat(Core &core, vk::Format format, bool clear, Multisampling multisampling) {
+		return passFormats(core, { format }, clear, multisampling);
+	}
+
+	PassHandle passSwapchain(Core &core, const SwapchainHandle &swapchain,
+							 const std::vector<vk::Format> &formats, bool clear,
+							 Multisampling multisampling) {
+		std::vector<vk::Format> swapchainFormats (formats);
+
+		for (auto &format : swapchainFormats) {
+			if (vk::Format::eUndefined == format) {
+				format = core.getSwapchainFormat(swapchain);
+			}
+		}
+
+		return passFormats(core, swapchainFormats, clear, multisampling);
+	}
+
+} // namespace vkcv
diff --git a/src/vkcv/PassConfig.cpp b/src/vkcv/PassConfig.cpp
index 602f1d3e2a8100ebd9bbb83772312d3d659abe86..72310f209484a355afc461d72022ad9aa1cf4cbd 100644
--- a/src/vkcv/PassConfig.cpp
+++ b/src/vkcv/PassConfig.cpp
@@ -1,19 +1,62 @@
+
 #include "vkcv/PassConfig.hpp"
+#include "vkcv/Image.hpp"
+
+namespace vkcv {
+
+	AttachmentDescription::AttachmentDescription(vk::Format format, AttachmentOperation load,
+												 AttachmentOperation store) :
+		m_format(format),
+		m_load_op(load), m_store_op(store), m_clear_value() {
+		if (isDepthFormat(format)) {
+			setClearValue(vk::ClearValue(vk::ClearDepthStencilValue(1.0f, 0)));
+		} else {
+			setClearValue(vk::ClearValue(
+				vk::ClearColorValue(std::array<float, 4> { 0.0f, 0.0f, 0.0f, 0.0f })));
+		}
+	}
+
+	AttachmentDescription::AttachmentDescription(vk::Format format, AttachmentOperation load,
+												 AttachmentOperation store,
+												 const vk::ClearValue &clear) :
+		m_format(format),
+		m_load_op(load), m_store_op(store), m_clear_value(clear) {}
+
+	vk::Format AttachmentDescription::getFormat() const {
+		return m_format;
+	}
+
+	AttachmentOperation AttachmentDescription::getLoadOperation() const {
+		return m_load_op;
+	}
+
+	AttachmentOperation AttachmentDescription::getStoreOperation() const {
+		return m_store_op;
+	}
+
+	void AttachmentDescription::setClearValue(const vk::ClearValue &clear) {
+		m_clear_value = clear;
+	}
+
+	const vk::ClearValue &AttachmentDescription::getClearValue() const {
+		return m_clear_value;
+	}
+
+	PassConfig::PassConfig() : m_attachments(), m_multisampling(Multisampling::None) {}
+
+	PassConfig::PassConfig(const AttachmentDescriptions &attachments, Multisampling multisampling) :
+		m_attachments(attachments), m_multisampling(multisampling) {}
+
+	const AttachmentDescriptions &PassConfig::getAttachments() const {
+		return m_attachments;
+	}
+
+	void PassConfig::setMultisampling(Multisampling multisampling) {
+		m_multisampling = multisampling;
+	}
+
+	Multisampling PassConfig::getMultisampling() const {
+		return m_multisampling;
+	}
 
-#include <utility>
-
-namespace vkcv
-{
-    AttachmentDescription::AttachmentDescription(
-		AttachmentOperation store_op,
-		AttachmentOperation load_op,
-		vk::Format format) noexcept :
-	store_operation{store_op},
-	load_operation{load_op},
-	format(format)
-    {};
-
-    PassConfig::PassConfig(std::vector<AttachmentDescription> attachments) noexcept :
-    attachments{std::move(attachments)}
-    {}
-}
\ No newline at end of file
+} // namespace vkcv
diff --git a/src/vkcv/PassManager.cpp b/src/vkcv/PassManager.cpp
index c34b0d3631c48561f42eb7f21ba5578156910f51..9b160bcf8c62eb5e3fe829a49d2e43d3f0eef1cb 100644
--- a/src/vkcv/PassManager.cpp
+++ b/src/vkcv/PassManager.cpp
@@ -1,175 +1,145 @@
 #include "PassManager.hpp"
+#include "vkcv/Core.hpp"
 #include "vkcv/Image.hpp"
 
-namespace vkcv
-{
-    static vk::ImageLayout getVkLayoutFromAttachLayout(AttachmentLayout layout)
-    {
-        switch(layout)
-        {
-            case AttachmentLayout::GENERAL:
-                return vk::ImageLayout::eGeneral;
-            case AttachmentLayout::COLOR_ATTACHMENT:
-                return vk::ImageLayout::eColorAttachmentOptimal;
-            case AttachmentLayout::SHADER_READ_ONLY:
-                return vk::ImageLayout::eShaderReadOnlyOptimal;
-            case AttachmentLayout::DEPTH_STENCIL_ATTACHMENT:
-                return vk::ImageLayout::eDepthStencilAttachmentOptimal;
-            case AttachmentLayout::DEPTH_STENCIL_READ_ONLY:
-                return vk::ImageLayout::eDepthStencilReadOnlyOptimal;
-            case AttachmentLayout::PRESENTATION:
-                return vk::ImageLayout::ePresentSrcKHR;
-            default:
-                return vk::ImageLayout::eUndefined;
-        }
-    }
-
-    static vk::AttachmentStoreOp getVkStoreOpFromAttachOp(AttachmentOperation op)
-    {
-        switch(op)
-        {
-            case AttachmentOperation::STORE:
-                return vk::AttachmentStoreOp::eStore;
-            default:
-                return vk::AttachmentStoreOp::eDontCare;
-        }
-    }
-
-    static vk::AttachmentLoadOp getVKLoadOpFromAttachOp(AttachmentOperation op)
-    {
-        switch(op)
-        {
-            case AttachmentOperation::LOAD:
-                return vk::AttachmentLoadOp::eLoad;
-            case AttachmentOperation::CLEAR:
-                return vk::AttachmentLoadOp::eClear;
-            default:
-                return vk::AttachmentLoadOp::eDontCare;
-        }
-    }
-
-    PassManager::PassManager(vk::Device device) noexcept :
-    m_Device{device},
-    m_Passes{}
-    {}
-
-    PassManager::~PassManager() noexcept
-    {
-    	for (uint64_t id = 0; id < m_Passes.size(); id++) {
-			destroyPassById(id);
-    	}
-    }
-
-    PassHandle PassManager::createPass(const PassConfig &config)
-    {
-        // description of all {color, input, depth/stencil} attachments of the render pass
-        std::vector<vk::AttachmentDescription> attachmentDescriptions{};
-
-        // individual references to color attachments (of a subpass)
-        std::vector<vk::AttachmentReference> colorAttachmentReferences{};
-        // individual reference to depth attachment (of a subpass)
-        vk::AttachmentReference depthAttachmentReference{};
-        vk::AttachmentReference *pDepthAttachment = nullptr;	//stays nullptr if no depth attachment used
-
-        for (uint32_t i = 0; i < config.attachments.size(); i++)
-        {
-            // TODO: Renderpass struct should hold proper format information
-            vk::Format      format = config.attachments[i].format;
-            vk::ImageLayout layout;
-
-            if (isDepthFormat(config.attachments[i].format))
-            {
-                layout                              = vk::ImageLayout::eDepthStencilAttachmentOptimal;
-                depthAttachmentReference.attachment = i;
-                depthAttachmentReference.layout     = layout;
-                pDepthAttachment                    = &depthAttachmentReference;
-            }
-            else
-            {
-                layout = vk::ImageLayout::eColorAttachmentOptimal;
-                vk::AttachmentReference attachmentRef(i, layout);
-                colorAttachmentReferences.push_back(attachmentRef);
-            }
-
-            vk::AttachmentDescription attachmentDesc(
-                {},
-                format,
-                vk::SampleCountFlagBits::e1,
-                getVKLoadOpFromAttachOp(config.attachments[i].load_operation),
-                getVkStoreOpFromAttachOp(config.attachments[i].store_operation),
-                vk::AttachmentLoadOp::eDontCare,
-                vk::AttachmentStoreOp::eDontCare,
-                layout,
-                layout);
-
-            attachmentDescriptions.push_back(attachmentDesc);
-        }
-        
-        const vk::SubpassDescription subpassDescription(
-            {},
-            vk::PipelineBindPoint::eGraphics,
-            0,
-            {},
-            static_cast<uint32_t>(colorAttachmentReferences.size()),
-            colorAttachmentReferences.data(),
-            {},
-            pDepthAttachment,
-            0,
-            {});
-
-        const vk::RenderPassCreateInfo passInfo(
-            {},
-            static_cast<uint32_t>(attachmentDescriptions.size()),
-            attachmentDescriptions.data(),
-            1,
-            &subpassDescription,
-            0,
-            {});
-
-        vk::RenderPass renderPass = m_Device.createRenderPass(passInfo);
-
-        const uint64_t id = m_Passes.size();
-        m_Passes.push_back({ renderPass, config });
-        return PassHandle(id, [&](uint64_t id) { destroyPassById(id); });
-    }
-
-    vk::RenderPass PassManager::getVkPass(const PassHandle &handle) const
-    {
-    	const uint64_t id = handle.getId();
-    	
-    	if (id >= m_Passes.size()) {
-    		return nullptr;
-    	}
-    	
-    	auto& pass = m_Passes[id];
-    	
-        return pass.m_Handle;
-    }
-    
-    const PassConfig& PassManager::getPassConfig(const PassHandle &handle) const {
-		const uint64_t id = handle.getId();
-	
-		if (id >= m_Passes.size()) {
-			static PassConfig emptyConfig = PassConfig({});
-			return emptyConfig;
+namespace vkcv {
+
+	static vk::AttachmentStoreOp getVkStoreOpFromAttachOp(AttachmentOperation op) {
+		switch (op) {
+		case AttachmentOperation::STORE:
+			return vk::AttachmentStoreOp::eStore;
+		default:
+			return vk::AttachmentStoreOp::eDontCare;
 		}
-	
-		auto& pass = m_Passes[id];
-	
-		return pass.m_Config;
-    }
-    
-    void PassManager::destroyPassById(uint64_t id) {
-    	if (id >= m_Passes.size()) {
-    		return;
-    	}
-    	
-    	auto& pass = m_Passes[id];
-	
+	}
+
+	static vk::AttachmentLoadOp getVKLoadOpFromAttachOp(AttachmentOperation op) {
+		switch (op) {
+		case AttachmentOperation::LOAD:
+			return vk::AttachmentLoadOp::eLoad;
+		case AttachmentOperation::CLEAR:
+			return vk::AttachmentLoadOp::eClear;
+		default:
+			return vk::AttachmentLoadOp::eDontCare;
+		}
+	}
+
+	uint64_t PassManager::getIdFrom(const PassHandle &handle) const {
+		return handle.getId();
+	}
+
+	PassHandle PassManager::createById(uint64_t id, const HandleDestroyFunction &destroy) {
+		return PassHandle(id, destroy);
+	}
+
+	void PassManager::destroyById(uint64_t id) {
+		auto &pass = getById(id);
+
 		if (pass.m_Handle) {
-			m_Device.destroy(pass.m_Handle);
+			getCore().getContext().getDevice().destroy(pass.m_Handle);
 			pass.m_Handle = nullptr;
 		}
-    }
-    
-}
+	}
+
+	PassManager::PassManager() noexcept : HandleManager<PassEntry, PassHandle>() {}
+
+	PassManager::~PassManager() noexcept {
+		clear();
+	}
+
+	PassHandle PassManager::createPass(const PassConfig &config) {
+		// description of all {color, input, depth/stencil} attachments of the render pass
+		std::vector<vk::AttachmentDescription> attachmentDescriptions {};
+
+		// individual references to color attachments (of a subpass)
+		std::vector<vk::AttachmentReference> colorAttachmentReferences {};
+		// individual reference to depth attachment (of a subpass)
+		vk::AttachmentReference depthStencilAttachmentRef;
+
+		// stays nullptr if no depth attachment used
+		vk::AttachmentReference* pDepthStencil = nullptr;
+
+		const auto &featureManager = getCore().getContext().getFeatureManager();
+
+		const bool separateDepthStencil =
+			(featureManager.checkFeatures<vk::PhysicalDeviceSeparateDepthStencilLayoutsFeaturesKHR>(
+				vk::StructureType::ePhysicalDeviceSeparateDepthStencilLayoutsFeaturesKHR,
+				[](const vk::PhysicalDeviceSeparateDepthStencilLayoutsFeaturesKHR &features) {
+					return features.separateDepthStencilLayouts;
+				}));
+
+		const auto &attachments = config.getAttachments();
+
+		std::vector<vk::ImageLayout> layouts;
+		layouts.reserve(attachments.size());
+
+		for (uint32_t i = 0; i < attachments.size(); i++) {
+			vk::Format format = attachments [i].getFormat();
+			vk::ImageLayout layout;
+
+			bool depthFormat = isDepthFormat(attachments [i].getFormat());
+			bool stencilFormat = isStencilFormat(attachments [i].getFormat());
+
+			if ((separateDepthStencil) && (depthFormat) && (!stencilFormat)) {
+				layout = vk::ImageLayout::eDepthAttachmentOptimal;
+			} else if ((separateDepthStencil) && (!depthFormat) && (stencilFormat)) {
+				layout = vk::ImageLayout::eStencilAttachmentOptimal;
+			} else if ((depthFormat) || (stencilFormat)) {
+				layout = vk::ImageLayout::eDepthStencilAttachmentOptimal;
+			} else {
+				layout = vk::ImageLayout::eColorAttachmentOptimal;
+			}
+
+			if ((depthFormat) || (stencilFormat)) {
+				depthStencilAttachmentRef = vk::AttachmentReference(i, layout);
+				pDepthStencil = &depthStencilAttachmentRef;
+			} else {
+				vk::AttachmentReference attachmentRef(i, layout);
+				colorAttachmentReferences.push_back(attachmentRef);
+			}
+
+			vk::AttachmentDescription attachmentDesc(
+				{}, format, msaaToSampleCountFlagBits(config.getMultisampling()),
+				getVKLoadOpFromAttachOp(attachments [i].getLoadOperation()),
+				getVkStoreOpFromAttachOp(attachments [i].getStoreOperation()),
+				vk::AttachmentLoadOp::eDontCare, vk::AttachmentStoreOp::eDontCare, layout, layout);
+
+			if (stencilFormat) {
+				attachmentDesc.setStencilLoadOp(attachmentDesc.loadOp);
+				attachmentDesc.setStencilStoreOp(attachmentDesc.storeOp);
+			}
+
+			attachmentDescriptions.push_back(attachmentDesc);
+			layouts.push_back(layout);
+		}
+
+		const vk::SubpassDescription subpassDescription(
+			{}, vk::PipelineBindPoint::eGraphics, 0, {},
+			static_cast<uint32_t>(colorAttachmentReferences.size()),
+			colorAttachmentReferences.data(), {}, pDepthStencil, 0, {});
+
+		const vk::RenderPassCreateInfo passInfo(
+			{}, static_cast<uint32_t>(attachmentDescriptions.size()), attachmentDescriptions.data(),
+			1, &subpassDescription, 0, {});
+
+		vk::RenderPass renderPass = getCore().getContext().getDevice().createRenderPass(passInfo);
+
+		return add({ renderPass, config, layouts });
+	}
+
+	vk::RenderPass PassManager::getVkPass(const PassHandle &handle) const {
+		auto &pass = (*this) [handle];
+		return pass.m_Handle;
+	}
+
+	const PassConfig &PassManager::getPassConfig(const PassHandle &handle) const {
+		auto &pass = (*this) [handle];
+		return pass.m_Config;
+	}
+
+	const std::vector<vk::ImageLayout> &PassManager::getLayouts(const PassHandle &handle) const {
+		auto &pass = (*this) [handle];
+		return pass.m_Layouts;
+	}
+
+} // namespace vkcv
diff --git a/src/vkcv/PassManager.hpp b/src/vkcv/PassManager.hpp
index 661a8b277ecb446c4bbaeeb63560ffde28c31d99..fefbffaedc2b98da88b7ce625dbfc6f3ae448b48 100644
--- a/src/vkcv/PassManager.hpp
+++ b/src/vkcv/PassManager.hpp
@@ -1,43 +1,52 @@
 #pragma once
 
-#include <vulkan/vulkan.hpp>
 #include <vector>
-#include "vkcv/Handles.hpp"
+#include <vulkan/vulkan.hpp>
+
+#include "HandleManager.hpp"
 #include "vkcv/PassConfig.hpp"
 
-namespace vkcv
-{
-    class PassManager
-    {
-    private:
-    	struct Pass {
-			vk::RenderPass m_Handle;
-			PassConfig m_Config;
-    	};
-    	
-        vk::Device m_Device;
-        std::vector<Pass> m_Passes;
-        
-        void destroyPassById(uint64_t id);
-        
-    public:
-        PassManager() = delete; // no default ctor
-        explicit PassManager(vk::Device device) noexcept; // ctor
-        ~PassManager() noexcept; // dtor
-
-        PassManager(const PassManager &other) = delete; // copy-ctor
-        PassManager(PassManager &&other) = delete; // move-ctor;
-
-        PassManager & operator=(const PassManager &other) = delete; // copy-assign op
-        PassManager & operator=(PassManager &&other) = delete; // move-assign op
-
-        PassHandle createPass(const PassConfig &config);
-
-        [[nodiscard]]
-        vk::RenderPass getVkPass(const PassHandle &handle) const;
-        
-        [[nodiscard]]
-        const PassConfig& getPassConfig(const PassHandle &handle) const;
-        
-    };
-}
+namespace vkcv {
+
+	struct PassEntry {
+		vk::RenderPass m_Handle;
+		PassConfig m_Config;
+		std::vector<vk::ImageLayout> m_Layouts;
+	};
+
+	/**
+	 * @brief Class to manage the creation and destruction of passes.
+	 */
+	class PassManager : public HandleManager<PassEntry, PassHandle> {
+		friend class Core;
+
+	private:
+		[[nodiscard]] uint64_t getIdFrom(const PassHandle &handle) const override;
+
+		[[nodiscard]] PassHandle createById(uint64_t id,
+											const HandleDestroyFunction &destroy) override;
+
+		/**
+		 * Destroys and deallocates pass represented by a given
+		 * pass handle id.
+		 *
+		 * @param id Pass handle id
+		 */
+		void destroyById(uint64_t id) override;
+
+	public:
+		PassManager() noexcept;
+
+		~PassManager() noexcept override; // dtor
+
+		[[nodiscard]] PassHandle createPass(const PassConfig &config);
+
+		[[nodiscard]] vk::RenderPass getVkPass(const PassHandle &handle) const;
+
+		[[nodiscard]] const PassConfig &getPassConfig(const PassHandle &handle) const;
+
+		[[nodiscard]] const std::vector<vk::ImageLayout> &
+		getLayouts(const PassHandle &handle) const;
+	};
+
+} // namespace vkcv
diff --git a/src/vkcv/PipelineConfig.cpp b/src/vkcv/PipelineConfig.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e6fb1ceb9ead48369e8f944725a139bb3b7428a1
--- /dev/null
+++ b/src/vkcv/PipelineConfig.cpp
@@ -0,0 +1,38 @@
+
+#include "vkcv/PipelineConfig.hpp"
+
+namespace vkcv {
+
+	PipelineConfig::PipelineConfig() : m_ShaderProgram(), m_DescriptorSetLayouts() {}
+
+	PipelineConfig::PipelineConfig(const ShaderProgram &program,
+								   const std::vector<DescriptorSetLayoutHandle> &layouts) :
+		m_ShaderProgram(program),
+		m_DescriptorSetLayouts(layouts) {}
+
+	void PipelineConfig::setShaderProgram(const ShaderProgram &program) {
+		m_ShaderProgram = program;
+	}
+
+	const ShaderProgram &PipelineConfig::getShaderProgram() const {
+		return m_ShaderProgram;
+	}
+
+	void PipelineConfig::addDescriptorSetLayout(const DescriptorSetLayoutHandle &layout) {
+		m_DescriptorSetLayouts.push_back(layout);
+	}
+
+	void
+	PipelineConfig::addDescriptorSetLayouts(const std::vector<DescriptorSetLayoutHandle> &layouts) {
+		m_DescriptorSetLayouts.reserve(m_DescriptorSetLayouts.size() + layouts.size());
+
+		for (const auto &layout : layouts) {
+			m_DescriptorSetLayouts.push_back(layout);
+		}
+	}
+
+	const std::vector<DescriptorSetLayoutHandle> &PipelineConfig::getDescriptorSetLayouts() const {
+		return m_DescriptorSetLayouts;
+	}
+
+} // namespace vkcv
diff --git a/src/vkcv/PipelineManager.cpp b/src/vkcv/PipelineManager.cpp
deleted file mode 100644
index df36442efc2992bf16b6e82245ef9753dad95e5d..0000000000000000000000000000000000000000
--- a/src/vkcv/PipelineManager.cpp
+++ /dev/null
@@ -1,433 +0,0 @@
-#include "PipelineManager.hpp"
-#include "vkcv/Image.hpp"
-#include "vkcv/Logger.hpp"
-
-namespace vkcv
-{
-
-    PipelineManager::PipelineManager(vk::Device device) noexcept :
-    m_Device{device},
-    m_Pipelines{}
-    {}
-
-    PipelineManager::~PipelineManager() noexcept
-    {
-    	for (uint64_t id = 0; id < m_Pipelines.size(); id++) {
-			destroyPipelineById(id);
-    	}
-    }
-
-	// currently assuming default 32 bit formats, no lower precision or normalized variants supported
-	vk::Format vertexFormatToVulkanFormat(const VertexAttachmentFormat format) {
-		switch (format) {
-		case VertexAttachmentFormat::FLOAT:
-			return vk::Format::eR32Sfloat;
-		case VertexAttachmentFormat::FLOAT2:
-			return vk::Format::eR32G32Sfloat;
-		case VertexAttachmentFormat::FLOAT3:
-			return vk::Format::eR32G32B32Sfloat;
-		case VertexAttachmentFormat::FLOAT4:
-			return vk::Format::eR32G32B32A32Sfloat;
-		case VertexAttachmentFormat::INT:
-			return vk::Format::eR32Sint;
-		case VertexAttachmentFormat::INT2:
-			return vk::Format::eR32G32Sint;
-		case VertexAttachmentFormat::INT3:
-			return vk::Format::eR32G32B32Sint;
-		case VertexAttachmentFormat::INT4:
-			return vk::Format::eR32G32B32A32Sint;
-		default:
-			vkcv_log(LogLevel::WARNING, "Unknown vertex format");
-			return vk::Format::eUndefined;
-		}
-	}
-
-    vk::PrimitiveTopology primitiveTopologyToVulkanPrimitiveTopology(const PrimitiveTopology topology) {
-        switch (topology) {
-        case(PrimitiveTopology::PointList):     return vk::PrimitiveTopology::ePointList;
-        case(PrimitiveTopology::LineList):      return vk::PrimitiveTopology::eLineList;
-        case(PrimitiveTopology::TriangleList):  return vk::PrimitiveTopology::eTriangleList;
-        default: std::cout << "Error: Unknown primitive topology type" << std::endl; return vk::PrimitiveTopology::eTriangleList;
-        }
-    }
-
-    PipelineHandle PipelineManager::createPipeline(const PipelineConfig &config, PassManager& passManager)
-    {
-		const vk::RenderPass &pass = passManager.getVkPass(config.m_PassHandle);
-    	
-        const bool existsVertexShader = config.m_ShaderProgram.existsShader(ShaderStage::VERTEX);
-        const bool existsFragmentShader = config.m_ShaderProgram.existsShader(ShaderStage::FRAGMENT);
-        if (!(existsVertexShader && existsFragmentShader))
-        {
-			vkcv_log(LogLevel::ERROR, "Requires vertex and fragment shader code");
-            return PipelineHandle();
-        }
-
-        // vertex shader stage
-        std::vector<char> vertexCode = config.m_ShaderProgram.getShader(ShaderStage::VERTEX).shaderCode;
-        vk::ShaderModuleCreateInfo vertexModuleInfo({}, vertexCode.size(), reinterpret_cast<uint32_t*>(vertexCode.data()));
-        vk::ShaderModule vertexModule{};
-        if (m_Device.createShaderModule(&vertexModuleInfo, nullptr, &vertexModule) != vk::Result::eSuccess)
-            return PipelineHandle();
-
-        vk::PipelineShaderStageCreateInfo pipelineVertexShaderStageInfo(
-                {},
-                vk::ShaderStageFlagBits::eVertex,
-                vertexModule,
-                "main",
-                nullptr
-        );
-
-        // fragment shader stage
-        std::vector<char> fragCode = config.m_ShaderProgram.getShader(ShaderStage::FRAGMENT).shaderCode;
-        vk::ShaderModuleCreateInfo fragmentModuleInfo({}, fragCode.size(), reinterpret_cast<uint32_t*>(fragCode.data()));
-        vk::ShaderModule fragmentModule{};
-        if (m_Device.createShaderModule(&fragmentModuleInfo, nullptr, &fragmentModule) != vk::Result::eSuccess)
-        {
-            m_Device.destroy(vertexModule);
-            return PipelineHandle();
-        }
-
-        vk::PipelineShaderStageCreateInfo pipelineFragmentShaderStageInfo(
-                {},
-                vk::ShaderStageFlagBits::eFragment,
-                fragmentModule,
-                "main",
-                nullptr
-        );
-
-        // vertex input state
-
-        // Fill up VertexInputBindingDescription and VertexInputAttributeDescription Containers
-        std::vector<vk::VertexInputAttributeDescription>	vertexAttributeDescriptions;
-		std::vector<vk::VertexInputBindingDescription>		vertexBindingDescriptions;
-
-        const VertexLayout &layout = config.m_VertexLayout;
-
-        // iterate over the layout's specified, mutually exclusive buffer bindings that make up a vertex buffer
-        for (const auto &vertexBinding : layout.vertexBindings)
-        {
-            vertexBindingDescriptions.emplace_back(vertexBinding.bindingLocation,
-                                                   vertexBinding.stride,
-                                                   vk::VertexInputRate::eVertex);
-
-            // iterate over the bindings' specified, mutually exclusive vertex input attachments that make up a vertex
-            for(const auto &vertexAttachment: vertexBinding.vertexAttachments)
-            {
-                vertexAttributeDescriptions.emplace_back(vertexAttachment.inputLocation,
-                                                         vertexBinding.bindingLocation,
-                                                         vertexFormatToVulkanFormat(vertexAttachment.format),
-                                                         vertexAttachment.offset % vertexBinding.stride);
-
-            }
-        }
-
-        // Handover Containers to PipelineVertexInputStateCreateIngo Struct
-        vk::PipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo(
-                {},
-                vertexBindingDescriptions.size(),
-                vertexBindingDescriptions.data(),
-                vertexAttributeDescriptions.size(),
-                vertexAttributeDescriptions.data()
-        );
-
-        // input assembly state
-        vk::PipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo(
-            {},
-            primitiveTopologyToVulkanPrimitiveTopology(config.m_PrimitiveTopology),
-            false
-        );
-
-        // viewport state
-        vk::Viewport viewport(0.f, 0.f, static_cast<float>(config.m_Width), static_cast<float>(config.m_Height), 0.f, 1.f);
-        vk::Rect2D scissor({ 0,0 }, { config.m_Width, config.m_Height });
-        vk::PipelineViewportStateCreateInfo pipelineViewportStateCreateInfo({}, 1, &viewport, 1, &scissor);
-
-        // rasterization state
-        vk::PipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo(
-                {},
-                false,
-                false,
-                vk::PolygonMode::eFill,
-                vk::CullModeFlagBits::eNone,
-                vk::FrontFace::eCounterClockwise,
-                false,
-                0.f,
-                0.f,
-                0.f,
-                1.f
-        );
-        vk::PipelineRasterizationConservativeStateCreateInfoEXT conservativeRasterization;
-        if (config.m_UseConservativeRasterization) {
-            conservativeRasterization = vk::PipelineRasterizationConservativeStateCreateInfoEXT(
-                {}, 
-                vk::ConservativeRasterizationModeEXT::eOverestimate,
-                0.f);
-            pipelineRasterizationStateCreateInfo.pNext = &conservativeRasterization;
-        }
-
-        // multisample state
-        vk::PipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo(
-                {},
-                vk::SampleCountFlagBits::e1,
-                false,
-                0.f,
-                nullptr,
-                false,
-                false
-        );
-
-        // color blend state
-        vk::ColorComponentFlags colorWriteMask(VK_COLOR_COMPONENT_R_BIT |
-                                               VK_COLOR_COMPONENT_G_BIT |
-                                               VK_COLOR_COMPONENT_B_BIT |
-                                               VK_COLOR_COMPONENT_A_BIT);
-        vk::PipelineColorBlendAttachmentState colorBlendAttachmentState(
-                false,
-                vk::BlendFactor::eOne,
-                vk::BlendFactor::eOne,
-                vk::BlendOp::eAdd,
-                vk::BlendFactor::eOne,
-                vk::BlendFactor::eOne,
-                vk::BlendOp::eAdd,
-                colorWriteMask
-        );
-        vk::PipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo(
-                {},
-                false,
-                vk::LogicOp::eClear,
-                1,	//TODO: hardcoded to one
-                &colorBlendAttachmentState,
-                { 1.f,1.f,1.f,1.f }
-        );
-
-		const size_t matrixPushConstantSize = config.m_ShaderProgram.getPushConstantSize();
-		const vk::PushConstantRange pushConstantRange(vk::ShaderStageFlagBits::eAll, 0, matrixPushConstantSize);
-
-        // pipeline layout
-        vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo(
-			{},
-			(config.m_DescriptorLayouts),
-			(pushConstantRange));
-
-        vk::PipelineLayout vkPipelineLayout{};
-        if (m_Device.createPipelineLayout(&pipelineLayoutCreateInfo, nullptr, &vkPipelineLayout) != vk::Result::eSuccess)
-        {
-            m_Device.destroy(vertexModule);
-            m_Device.destroy(fragmentModule);
-            return PipelineHandle();
-        }
-	
-		const vk::PipelineDepthStencilStateCreateInfo depthStencilCreateInfo(
-				vk::PipelineDepthStencilStateCreateFlags(),
-				true,
-				true,
-				vk::CompareOp::eLessOrEqual,
-				false,
-				false,
-				{},
-				{},
-				0.0f,
-				1.0f
-		);
-	
-		const vk::PipelineDepthStencilStateCreateInfo* p_depthStencilCreateInfo = nullptr;
-		
-		const PassConfig& passConfig = passManager.getPassConfig(config.m_PassHandle);
-		
-		for (const auto& attachment : passConfig.attachments) {
-			if (isDepthFormat(attachment.format)) {
-				p_depthStencilCreateInfo = &depthStencilCreateInfo;
-				break;
-			}
-		}
-
-		std::vector<vk::DynamicState> dynamicStates = {};
-		if(config.m_UseDynamicViewport)
-        {
-		    dynamicStates.push_back(vk::DynamicState::eViewport);
-		    dynamicStates.push_back(vk::DynamicState::eScissor);
-        }
-
-        vk::PipelineDynamicStateCreateInfo dynamicStateCreateInfo({},
-                                                            static_cast<uint32_t>(dynamicStates.size()),
-                                                            dynamicStates.data());
-
-        // graphics pipeline create
-        std::vector<vk::PipelineShaderStageCreateInfo> shaderStages = { pipelineVertexShaderStageInfo, pipelineFragmentShaderStageInfo };
-
-		const char *geometryShaderName = "main";	// outside of if to make sure it stays in scope
-		vk::ShaderModule geometryModule;
-		if (config.m_ShaderProgram.existsShader(ShaderStage::GEOMETRY)) {
-			const vkcv::Shader geometryShader = config.m_ShaderProgram.getShader(ShaderStage::GEOMETRY);
-			const auto& geometryCode = geometryShader.shaderCode;
-			const vk::ShaderModuleCreateInfo geometryModuleInfo({}, geometryCode.size(), reinterpret_cast<const uint32_t*>(geometryCode.data()));
-			if (m_Device.createShaderModule(&geometryModuleInfo, nullptr, &geometryModule) != vk::Result::eSuccess) {
-				return PipelineHandle();
-			}
-			vk::PipelineShaderStageCreateInfo geometryStage({}, vk::ShaderStageFlagBits::eGeometry, geometryModule, geometryShaderName);
-			shaderStages.push_back(geometryStage);
-		}
-
-        const vk::GraphicsPipelineCreateInfo graphicsPipelineCreateInfo(
-                {},
-                static_cast<uint32_t>(shaderStages.size()),
-                shaderStages.data(),
-                &pipelineVertexInputStateCreateInfo,
-                &pipelineInputAssemblyStateCreateInfo,
-                nullptr,
-                &pipelineViewportStateCreateInfo,
-                &pipelineRasterizationStateCreateInfo,
-                &pipelineMultisampleStateCreateInfo,
-				p_depthStencilCreateInfo,
-                &pipelineColorBlendStateCreateInfo,
-                &dynamicStateCreateInfo,
-                vkPipelineLayout,
-                pass,
-                0,
-                {},
-                0
-        );
-
-        vk::Pipeline vkPipeline{};
-        if (m_Device.createGraphicsPipelines(nullptr, 1, &graphicsPipelineCreateInfo, nullptr, &vkPipeline) != vk::Result::eSuccess)
-        {
-            m_Device.destroy(vertexModule);
-            m_Device.destroy(fragmentModule);
-            if (geometryModule) {
-                m_Device.destroy(geometryModule);
-            }
-            m_Device.destroy();
-            return PipelineHandle();
-        }
-
-        m_Device.destroy(vertexModule);
-        m_Device.destroy(fragmentModule);
-        if (geometryModule) {
-            m_Device.destroy(geometryModule);
-        }
-        
-        const uint64_t id = m_Pipelines.size();
-        m_Pipelines.push_back({ vkPipeline, vkPipelineLayout, config });
-        return PipelineHandle(id, [&](uint64_t id) { destroyPipelineById(id); });
-    }
-
-    vk::Pipeline PipelineManager::getVkPipeline(const PipelineHandle &handle) const
-    {
-		const uint64_t id = handle.getId();
-	
-		if (id >= m_Pipelines.size()) {
-			return nullptr;
-		}
-	
-		auto& pipeline = m_Pipelines[id];
-	
-		return pipeline.m_handle;
-    }
-
-    vk::PipelineLayout PipelineManager::getVkPipelineLayout(const PipelineHandle &handle) const
-    {
-    	const uint64_t id = handle.getId();
-    	
-		if (id >= m_Pipelines.size()) {
-			return nullptr;
-		}
-	
-		auto& pipeline = m_Pipelines[id];
-    	
-        return pipeline.m_layout;
-    }
-    
-    void PipelineManager::destroyPipelineById(uint64_t id) {
-    	if (id >= m_Pipelines.size()) {
-    		return;
-    	}
-    	
-    	auto& pipeline = m_Pipelines[id];
-    	
-    	if (pipeline.m_handle) {
-			m_Device.destroy(pipeline.m_handle);
-			pipeline.m_handle = nullptr;
-    	}
-	
-		if (pipeline.m_layout) {
-			m_Device.destroy(pipeline.m_layout);
-			pipeline.m_layout = nullptr;
-		}
-    }
-
-    const PipelineConfig& PipelineManager::getPipelineConfig(const PipelineHandle &handle) const
-    {
-        const uint64_t id = handle.getId();
-        
-        if (id >= m_Pipelines.size()) {
-        	static PipelineConfig dummyConfig;
-			vkcv_log(LogLevel::ERROR, "Invalid handle");
-			return dummyConfig;
-        }
-        
-        return m_Pipelines[id].m_config;
-    }
-
-    PipelineHandle PipelineManager::createComputePipeline(
-        const ShaderProgram &shaderProgram, 
-        const std::vector<vk::DescriptorSetLayout> &descriptorSetLayouts) {
-
-        // Temporally handing over the Shader Program instead of a pipeline config
-        vk::ShaderModule computeModule{};
-        if (createShaderModule(computeModule, shaderProgram, ShaderStage::COMPUTE) != vk::Result::eSuccess)
-            return PipelineHandle();
-
-        vk::PipelineShaderStageCreateInfo pipelineComputeShaderStageInfo(
-                {},
-                vk::ShaderStageFlagBits::eCompute,
-                computeModule,
-                "main",
-                nullptr
-        );
-
-        vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo({}, descriptorSetLayouts);
-
-        const size_t pushConstantSize = shaderProgram.getPushConstantSize();
-        vk::PushConstantRange pushConstantRange(vk::ShaderStageFlagBits::eCompute, 0, pushConstantSize);
-        if (pushConstantSize > 0) {
-            pipelineLayoutCreateInfo.setPushConstantRangeCount(1);
-            pipelineLayoutCreateInfo.setPPushConstantRanges(&pushConstantRange);
-        }
-
-        vk::PipelineLayout vkPipelineLayout{};
-        if (m_Device.createPipelineLayout(&pipelineLayoutCreateInfo, nullptr, &vkPipelineLayout) != vk::Result::eSuccess)
-        {
-            m_Device.destroy(computeModule);
-            return PipelineHandle();
-        }
-
-        vk::ComputePipelineCreateInfo computePipelineCreateInfo{};
-        computePipelineCreateInfo.stage = pipelineComputeShaderStageInfo;
-        computePipelineCreateInfo.layout = vkPipelineLayout;
-
-        vk::Pipeline vkPipeline;
-        if (m_Device.createComputePipelines(nullptr, 1, &computePipelineCreateInfo, nullptr, &vkPipeline)!= vk::Result::eSuccess)
-        {
-            m_Device.destroy(computeModule);
-            return PipelineHandle();
-        }
-
-        m_Device.destroy(computeModule);
-
-        const uint64_t id = m_Pipelines.size();
-        m_Pipelines.push_back({ vkPipeline, vkPipelineLayout, PipelineConfig() });
-
-        return PipelineHandle(id, [&](uint64_t id) { destroyPipelineById(id); });
-    }
-
-    // There is an issue for refactoring the Pipeline Manager.
-    // While including Compute Pipeline Creation, some private helper functions where introduced:
-
-    vk::Result PipelineManager::createShaderModule(vk::ShaderModule &module, const ShaderProgram &shaderProgram, const ShaderStage stage)
-    {
-        std::vector<char> code = shaderProgram.getShader(stage).shaderCode;
-        vk::ShaderModuleCreateInfo moduleInfo({}, code.size(), reinterpret_cast<uint32_t*>(code.data()));
-        return m_Device.createShaderModule(&moduleInfo, nullptr, &module);
-    }
-}
\ No newline at end of file
diff --git a/src/vkcv/PipelineManager.hpp b/src/vkcv/PipelineManager.hpp
deleted file mode 100644
index b153eb4632b844e84b92953fe8abf6666a13e0c9..0000000000000000000000000000000000000000
--- a/src/vkcv/PipelineManager.hpp
+++ /dev/null
@@ -1,53 +0,0 @@
-#pragma once
-
-#include <vulkan/vulkan.hpp>
-#include <vector>
-#include "vkcv/Handles.hpp"
-#include "vkcv/PipelineConfig.hpp"
-#include "PassManager.hpp"
-
-namespace vkcv
-{
-    class PipelineManager
-    {
-    private:
-    	struct Pipeline {
-			vk::Pipeline m_handle;
-			vk::PipelineLayout m_layout;
-			PipelineConfig m_config;
-    	};
-    	
-        vk::Device m_Device;
-        std::vector<Pipeline> m_Pipelines;
-        
-        void destroyPipelineById(uint64_t id);
-
-        vk::Result createShaderModule(vk::ShaderModule &module, const ShaderProgram &shaderProgram, ShaderStage stage);
-
-    public:
-        PipelineManager() = delete; // no default ctor
-        explicit PipelineManager(vk::Device device) noexcept; // ctor
-        ~PipelineManager() noexcept; // dtor
-
-        PipelineManager(const PipelineManager &other) = delete; // copy-ctor
-        PipelineManager(PipelineManager &&other) = delete; // move-ctor;
-
-        PipelineManager & operator=(const PipelineManager &other) = delete; // copy-assign op
-        PipelineManager & operator=(PipelineManager &&other) = delete; // move-assign op
-
-        PipelineHandle createPipeline(const PipelineConfig &config, PassManager& passManager);
-
-        PipelineHandle createComputePipeline(
-            const ShaderProgram& shaderProgram,
-            const std::vector<vk::DescriptorSetLayout>& descriptorSetLayouts);
-
-        [[nodiscard]]
-        vk::Pipeline getVkPipeline(const PipelineHandle &handle) const;
-
-        [[nodiscard]]
-        vk::PipelineLayout getVkPipelineLayout(const PipelineHandle &handle) const;
-
-        [[nodiscard]]
-        const PipelineConfig &getPipelineConfig(const PipelineHandle &handle) const;
-    };
-}
diff --git a/src/vkcv/PushConstants.cpp b/src/vkcv/PushConstants.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..df49f0a5ee08fc793e047f8755347fe47a2a3180
--- /dev/null
+++ b/src/vkcv/PushConstants.cpp
@@ -0,0 +1,38 @@
+#include <vkcv/PushConstants.hpp>
+
+namespace vkcv {
+
+	PushConstants::PushConstants(size_t sizePerDrawcall) : m_typeGuard(sizePerDrawcall), m_data() {}
+
+	PushConstants::PushConstants(const TypeGuard &guard) : m_typeGuard(guard), m_data() {}
+
+	size_t PushConstants::getSizePerDrawcall() const {
+		return m_typeGuard.typeSize();
+	}
+
+	size_t PushConstants::getFullSize() const {
+		return m_data.size();
+	}
+
+	size_t PushConstants::getDrawcallCount() const {
+		return getFullSize() / getSizePerDrawcall();
+	}
+
+	void PushConstants::clear() {
+		m_data.clear();
+	}
+
+	const void* PushConstants::getDrawcallData(size_t index) const {
+		const size_t offset = (index * getSizePerDrawcall());
+		return reinterpret_cast<const void*>(m_data.data() + offset);
+	}
+
+	const void* PushConstants::getData() const {
+		if (m_data.empty()) {
+			return nullptr;
+		} else {
+			return m_data.data();
+		}
+	}
+
+} // namespace vkcv
diff --git a/src/vkcv/QueueManager.cpp b/src/vkcv/QueueManager.cpp
index df6c74cccf6c4652adc6a4c78802f282ea6ae293..d292383ab58ff1e34343382828e7a5f43b6b6a33 100644
--- a/src/vkcv/QueueManager.cpp
+++ b/src/vkcv/QueueManager.cpp
@@ -1,218 +1,88 @@
 
+#include <iostream>
 #include <limits>
 #include <unordered_set>
-#include <iostream>
 
-#include "vkcv/QueueManager.hpp"
 #include "vkcv/Logger.hpp"
+#include "vkcv/QueueManager.hpp"
 
 namespace vkcv {
 
-    /**
-     * Given the @p physicalDevice and the @p queuePriorities, the @p queueCreateInfos are computed. First, the requested
-     * queues are sorted by priority depending on the availability of queues in the queue families of the given
-     * @p physicalDevice. Then check, if all requested queues are creatable. If so, the @p queueCreateInfos will be computed.
-     * Furthermore, lists of index pairs (queueFamilyIndex, queueIndex) for later referencing of the separate queues will
-     * be computed.
-     * @param[in] physicalDevice The physical device
-     * @param[in] queuePriorities The queue priorities used for the computation of @p queueCreateInfos
-     * @param[in] queueFlags The queue flags requesting the queues
-     * @param[in,out] queueCreateInfos The queue create info structures to be created
-     * @param[in,out] queuePairsGraphics The list of index pairs (queueFamilyIndex, queueIndex) of queues of type
-     *      vk::QueueFlagBits::eGraphics
-     * @param[in,out] queuePairsCompute The list of index pairs (queueFamilyIndex, queueIndex) of queues of type
-     *      vk::QueueFlagBits::eCompute
-     * @param[in,out] queuePairsTransfer The list of index pairs (queueFamilyIndex, queueIndex) of queues of type
-     *      vk::QueueFlagBits::eTransfer
-     * @throws std::runtime_error If the requested queues from @p queueFlags are not creatable due to insufficient availability.
-     */
-    void QueueManager::queueCreateInfosQueueHandles(vk::PhysicalDevice &physicalDevice,
-                                      std::vector<float> &queuePriorities,
-                                      std::vector<vk::QueueFlagBits> &queueFlags,
-                                      std::vector<vk::DeviceQueueCreateInfo> &queueCreateInfos,
-                                      std::vector<std::pair<int, int>> &queuePairsGraphics,
-                                      std::vector<std::pair<int, int>> &queuePairsCompute,
-                                      std::vector<std::pair<int, int>> &queuePairsTransfer)
-    {
-        queueCreateInfos = {};
-        queuePairsGraphics = {};
-        queuePairsCompute = {};
-        queuePairsTransfer = {};
-        std::vector<vk::QueueFamilyProperties> qFamilyProperties = physicalDevice.getQueueFamilyProperties();
-
-        //check priorities of flags -> the lower prioCount the higher the priority
-        std::vector<int> prios;
-        for(auto flag: queueFlags) {
-            int prioCount = 0;
-            for (int i = 0; i < qFamilyProperties.size(); i++) {
-                prioCount += (static_cast<uint32_t>(flag & qFamilyProperties[i].queueFlags) != 0) * qFamilyProperties[i].queueCount;
-            }
-            prios.push_back(prioCount);
-        }
-        //resort flags with heighest priority before allocating the queues
-        std::vector<vk::QueueFlagBits> newFlags;
-        for(int i = 0; i < prios.size(); i++) {
-            auto minElem = std::min_element(prios.begin(), prios.end());
-            int index = minElem - prios.begin();
-            newFlags.push_back(queueFlags[index]);
-            prios[index] = std::numeric_limits<int>::max();
-        }
-
-        // create requested queues and check if more requested queues are supported
-        // herefore: create vector that updates available queues in each queue family
-        // structure: [qFamily_0, ..., qFamily_n] where
-        // - qFamily_i = [GraphicsCount, ComputeCount, TransferCount], 0 <= i <= n
-        std::vector<std::vector<int>> queueFamilyStatus, initialQueueFamilyStatus;
-
-        for (auto qFamily : qFamilyProperties) {
-            int graphicsCount = int(static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eGraphics) != 0) * qFamily.queueCount;
-            int computeCount = int(static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eCompute) != 0) * qFamily.queueCount;
-            int transferCount = int(static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eTransfer) != 0) * qFamily.queueCount;
-            queueFamilyStatus.push_back({graphicsCount, computeCount, transferCount});
-        }
-
-        initialQueueFamilyStatus = queueFamilyStatus;
-        // check if every queue with the specified queue flag can be created
-        // this automatically checks for queue flag support!
-        for (auto qFlag : newFlags) {
-            bool found;
-            switch (qFlag) {
-                case vk::QueueFlagBits::eGraphics:
-                    found = false;
-                    for (int i = 0; i < queueFamilyStatus.size() && !found; i++) {
-                        if (queueFamilyStatus[i][0] > 0) {
-                            queuePairsGraphics.push_back(std::pair(i, initialQueueFamilyStatus[i][0] - queueFamilyStatus[i][0]));
-                            queueFamilyStatus[i][0]--;
-                            queueFamilyStatus[i][1]--;
-                            queueFamilyStatus[i][2]--;
-                            found = true;
-                        }
-                    }
-                    if (!found) {
-                        for (int i = 0; i < queueFamilyStatus.size() && !found; i++) {
-                            if (initialQueueFamilyStatus[i][0] > 0) {
-                                queuePairsGraphics.push_back(std::pair(i, 0));
-                                found = true;
-                            }
-                        }
-	
-						vkcv_log(LogLevel::WARNING, "Not enough %s queues", vk::to_string(qFlag).c_str());
-                    }
-                    break;
-                case vk::QueueFlagBits::eCompute:
-                    found = false;
-                    for (int i = 0; i < queueFamilyStatus.size() && !found; i++) {
-                        if (queueFamilyStatus[i][1] > 0) {
-                            queuePairsCompute.push_back(std::pair(i, initialQueueFamilyStatus[i][1] - queueFamilyStatus[i][1]));
-                            queueFamilyStatus[i][0]--;
-                            queueFamilyStatus[i][1]--;
-                            queueFamilyStatus[i][2]--;
-                            found = true;
-                        }
-                    }
-                    if (!found) {
-                        for (int i = 0; i < queueFamilyStatus.size() && !found; i++) {
-                            if (initialQueueFamilyStatus[i][1] > 0) {
-                                queuePairsCompute.push_back(std::pair(i, 0));
-                                found = true;
-                            }
-                        }
-                        
-						vkcv_log(LogLevel::WARNING, "Not enough %s queues", vk::to_string(qFlag).c_str());
-                    }
-                    break;
-                case vk::QueueFlagBits::eTransfer:
-                    found = false;
-                    for (int i = 0; i < queueFamilyStatus.size() && !found; i++) {
-                        if (queueFamilyStatus[i][2] > 0) {
-                            queuePairsTransfer.push_back(std::pair(i, initialQueueFamilyStatus[i][2] - queueFamilyStatus[i][2]));
-                            queueFamilyStatus[i][0]--;
-                            queueFamilyStatus[i][1]--;
-                            queueFamilyStatus[i][2]--;
-                            found = true;
-                        }
-                    }
-                    if (!found) {
-                        for (int i = 0; i < queueFamilyStatus.size() && !found; i++) {
-                            if (initialQueueFamilyStatus[i][2] > 0) {
-                                queuePairsTransfer.push_back(std::pair(i, 0));
-                                found = true;
-                            }
-                        }
-	
-						vkcv_log(LogLevel::WARNING, "Not enough %s queues", vk::to_string(qFlag).c_str());
-                    }
-                    break;
-                default:
-                    throw std::runtime_error("Invalid input for queue flag bits. Valid inputs are 'vk::QueueFlagBits::eGraphics', 'vk::QueueFlagBits::eCompute' and 'vk::QueueFlagBits::eTransfer'.");
-            }
-        }
-
-        // create all requested queues
-        for (int i = 0; i < qFamilyProperties.size(); i++) {
-            uint32_t create = std::abs(initialQueueFamilyStatus[i][0] - queueFamilyStatus[i][0]);
-            if (create > 0) {
-                vk::DeviceQueueCreateInfo qCreateInfo(
-                        vk::DeviceQueueCreateFlags(),
-                        i,
-                        create,
-                        queuePriorities.data()
-                );
-                queueCreateInfos.push_back(qCreateInfo);
-            }
-        }
-    }
-
-    /**
-     * Computes the queue handles from @p queuePairs
-     * @param device The device
-     * @param queuePairs The queuePairs that were created separately for each queue type (e.g., vk::QueueFlagBits::eGraphics)
-     * @return An array of queue handles based on the @p queuePairs
-     */
-    std::vector<Queue> getQueues(const vk::Device& device, const std::vector<std::pair<int, int>>& queuePairs) {
-        std::vector<Queue> queues;
-        
-        for (auto q : queuePairs) {
-            const int queueFamilyIndex = q.first; // the queueIndex of the queue family
-            const int queueIndex = q.second;   // the queueIndex within a queue family
-            
-			queues.push_back({ queueFamilyIndex, queueIndex, device.getQueue(queueFamilyIndex, queueIndex) });
-        }
-        
-        return queues;
-    }
-
-
-    QueueManager QueueManager::create(vk::Device device,
-                                      std::vector<std::pair<int, int>> &queuePairsGraphics,
-                                      std::vector<std::pair<int, int>> &queuePairsCompute,
-                                      std::vector<std::pair<int, int>> &queuePairsTransfer) {
-
-        std::vector<Queue> graphicsQueues = getQueues(device, queuePairsGraphics);
-        std::vector<Queue> computeQueues = getQueues(device, queuePairsCompute );
-        std::vector<Queue> transferQueues = getQueues(device, queuePairsTransfer);
-
-    	return QueueManager( std::move(graphicsQueues), std::move(computeQueues), std::move(transferQueues), 0);
+	/**
+	 * Computes the queue handles from @p queuePairs
+	 * @param device The device
+	 * @param queuePairs The queuePairs that were created separately for each queue type (e.g.,
+	 * vk::QueueFlagBits::eGraphics)
+	 * @return An array of queue handles based on the @p queuePairs
+	 */
+	std::vector<Queue> getQueues(const vk::Device &device,
+								 const std::vector<std::pair<int, int>> &queuePairs) {
+		std::vector<Queue> queues;
+
+		for (auto q : queuePairs) {
+			const int queueFamilyIndex = q.first; // the queueIndex of the queue family
+			const int queueIndex = q.second;      // the queueIndex within a queue family
+
+			queues.push_back(
+				{ queueFamilyIndex, queueIndex, device.getQueue(queueFamilyIndex, queueIndex) });
+		}
+
+		return queues;
 	}
 
-	QueueManager::QueueManager(std::vector<Queue>&& graphicsQueues, std::vector<Queue>&& computeQueues, std::vector<Queue>&& transferQueues, size_t presentIndex)
-	: m_graphicsQueues(graphicsQueues), m_computeQueues(computeQueues), m_transferQueues(transferQueues), m_presentIndex(presentIndex)
-    {}
+	QueueManager QueueManager::create(vk::Device device,
+									  const std::vector<std::pair<int, int>> &queuePairsGraphics,
+									  const std::vector<std::pair<int, int>> &queuePairsCompute,
+									  const std::vector<std::pair<int, int>> &queuePairsTransfer) {
 
-    const Queue &QueueManager::getPresentQueue() const {
-        return m_graphicsQueues[m_presentIndex];
-    }
+		std::vector<Queue> graphicsQueues = getQueues(device, queuePairsGraphics);
+		std::vector<Queue> computeQueues = getQueues(device, queuePairsCompute);
+		std::vector<Queue> transferQueues = getQueues(device, queuePairsTransfer);
 
-    const std::vector<Queue> &QueueManager::getGraphicsQueues() const {
-        return m_graphicsQueues;
-    }
+		return QueueManager(std::move(graphicsQueues), std::move(computeQueues),
+							std::move(transferQueues), 0);
+	}
 
-    const std::vector<Queue> &QueueManager::getComputeQueues() const {
-        return m_computeQueues;
-    }
+	uint32_t QueueManager::checkSurfaceSupport(const vk::PhysicalDevice &physicalDevice,
+											   const vk::SurfaceKHR &surface) {
+		std::vector<vk::QueueFamilyProperties> qFamilyProperties =
+			physicalDevice.getQueueFamilyProperties();
+
+		for (uint32_t i = 0; i < qFamilyProperties.size(); i++) {
+			vk::Bool32 presentSupport;
+
+			if ((vk::Result::eSuccess
+				 == physicalDevice.getSurfaceSupportKHR(i, surface, &presentSupport))
+				&& (presentSupport == VK_TRUE)) {
+				return i;
+			}
+		}
+
+		vkcv_log(LogLevel::WARNING, "No supported present queue");
+		return 0;
+	}
 
-    const std::vector<Queue> &QueueManager::getTransferQueues() const {
-        return m_transferQueues;
-    }
+	QueueManager::QueueManager(std::vector<Queue> &&graphicsQueues,
+							   std::vector<Queue> &&computeQueues,
+							   std::vector<Queue> &&transferQueues, size_t presentIndex) :
+		m_graphicsQueues(graphicsQueues),
+		m_computeQueues(computeQueues), m_transferQueues(transferQueues),
+		m_presentIndex(presentIndex) {}
+
+	const Queue &QueueManager::getPresentQueue() const {
+		return m_graphicsQueues [m_presentIndex];
+	}
+
+	const std::vector<Queue> &QueueManager::getGraphicsQueues() const {
+		return m_graphicsQueues;
+	}
+
+	const std::vector<Queue> &QueueManager::getComputeQueues() const {
+		return m_computeQueues;
+	}
+
+	const std::vector<Queue> &QueueManager::getTransferQueues() const {
+		return m_transferQueues;
+	}
 
-}
\ No newline at end of file
+} // namespace vkcv
\ No newline at end of file
diff --git a/src/vkcv/Sampler.cpp b/src/vkcv/Sampler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9f07e096ed5cd65c578eca0dee6c6e6693b5a260
--- /dev/null
+++ b/src/vkcv/Sampler.cpp
@@ -0,0 +1,21 @@
+
+#include "vkcv/Sampler.hpp"
+
+namespace vkcv {
+
+	SamplerHandle samplerLinear(Core &core, bool clampToEdge) {
+		return core.createSampler(vkcv::SamplerFilterType::LINEAR, vkcv::SamplerFilterType::LINEAR,
+								  vkcv::SamplerMipmapMode::LINEAR,
+								  clampToEdge ? vkcv::SamplerAddressMode::CLAMP_TO_EDGE :
+												vkcv::SamplerAddressMode::REPEAT);
+	}
+
+	SamplerHandle samplerNearest(Core &core, bool clampToEdge) {
+		return core.createSampler(vkcv::SamplerFilterType::NEAREST,
+								  vkcv::SamplerFilterType::NEAREST,
+								  vkcv::SamplerMipmapMode::NEAREST,
+								  clampToEdge ? vkcv::SamplerAddressMode::CLAMP_TO_EDGE :
+												vkcv::SamplerAddressMode::REPEAT);
+	}
+
+} // namespace vkcv
diff --git a/src/vkcv/SamplerManager.cpp b/src/vkcv/SamplerManager.cpp
index a6ebb95b5e237dcd06ed8041b3f16489f7339d6a..6907c6b1c0f2a58822b01180304ead671d995eeb 100644
--- a/src/vkcv/SamplerManager.cpp
+++ b/src/vkcv/SamplerManager.cpp
@@ -3,123 +3,130 @@
 #include "vkcv/Core.hpp"
 
 namespace vkcv {
-	
-	SamplerManager::SamplerManager(const vk::Device& device) noexcept :
-		m_device(device), m_samplers()
-	{}
-	
-	SamplerManager::~SamplerManager() {
-		for (uint64_t id = 0; id < m_samplers.size(); id++) {
-			destroySamplerById(id);
+
+	uint64_t SamplerManager::getIdFrom(const SamplerHandle &handle) const {
+		return handle.getId();
+	}
+
+	SamplerHandle SamplerManager::createById(uint64_t id, const HandleDestroyFunction &destroy) {
+		return SamplerHandle(id, destroy);
+	}
+
+	void SamplerManager::destroyById(uint64_t id) {
+		auto &sampler = getById(id);
+
+		if (sampler) {
+			getCore().getContext().getDevice().destroySampler(sampler);
+			sampler = nullptr;
 		}
 	}
-	
+
+	SamplerManager::SamplerManager() noexcept : HandleManager<vk::Sampler, SamplerHandle>() {}
+
+	SamplerManager::~SamplerManager() noexcept {
+		clear();
+	}
+
 	SamplerHandle SamplerManager::createSampler(SamplerFilterType magFilter,
 												SamplerFilterType minFilter,
 												SamplerMipmapMode mipmapMode,
-												SamplerAddressMode addressMode) {
+												SamplerAddressMode addressMode, float mipLodBias,
+												SamplerBorderColor borderColor) {
 		vk::Filter vkMagFilter;
 		vk::Filter vkMinFilter;
 		vk::SamplerMipmapMode vkMipmapMode;
 		vk::SamplerAddressMode vkAddressMode;
-		
+		vk::BorderColor vkBorderColor;
+
 		switch (magFilter) {
-			case SamplerFilterType::NEAREST:
-				vkMagFilter = vk::Filter::eNearest;
-				break;
-			case SamplerFilterType::LINEAR:
-				vkMagFilter = vk::Filter::eLinear;
-				break;
-			default:
-				return SamplerHandle();
+		case SamplerFilterType::NEAREST:
+			vkMagFilter = vk::Filter::eNearest;
+			break;
+		case SamplerFilterType::LINEAR:
+			vkMagFilter = vk::Filter::eLinear;
+			break;
+		default:
+			return SamplerHandle();
 		}
-		
+
 		switch (minFilter) {
-			case SamplerFilterType::NEAREST:
-				vkMinFilter = vk::Filter::eNearest;
-				break;
-			case SamplerFilterType::LINEAR:
-				vkMinFilter = vk::Filter::eLinear;
-				break;
-			default:
-				return SamplerHandle();
+		case SamplerFilterType::NEAREST:
+			vkMinFilter = vk::Filter::eNearest;
+			break;
+		case SamplerFilterType::LINEAR:
+			vkMinFilter = vk::Filter::eLinear;
+			break;
+		default:
+			return SamplerHandle();
 		}
-		
+
 		switch (mipmapMode) {
-			case SamplerMipmapMode::NEAREST:
-				vkMipmapMode = vk::SamplerMipmapMode::eNearest;
-				break;
-			case SamplerMipmapMode::LINEAR:
-				vkMipmapMode = vk::SamplerMipmapMode::eLinear;
-				break;
-			default:
-				return SamplerHandle();
+		case SamplerMipmapMode::NEAREST:
+			vkMipmapMode = vk::SamplerMipmapMode::eNearest;
+			break;
+		case SamplerMipmapMode::LINEAR:
+			vkMipmapMode = vk::SamplerMipmapMode::eLinear;
+			break;
+		default:
+			return SamplerHandle();
 		}
-		
+
 		switch (addressMode) {
-			case SamplerAddressMode::REPEAT:
-				vkAddressMode = vk::SamplerAddressMode::eRepeat;
-				break;
-			case SamplerAddressMode::MIRRORED_REPEAT:
-				vkAddressMode = vk::SamplerAddressMode::eMirroredRepeat;
-				break;
-			case SamplerAddressMode::CLAMP_TO_EDGE:
-				vkAddressMode = vk::SamplerAddressMode::eClampToEdge;
-				break;
-			case SamplerAddressMode::MIRROR_CLAMP_TO_EDGE:
-				vkAddressMode = vk::SamplerAddressMode::eMirrorClampToEdge;
-				break;
-			default:
-				return SamplerHandle();
+		case SamplerAddressMode::REPEAT:
+			vkAddressMode = vk::SamplerAddressMode::eRepeat;
+			break;
+		case SamplerAddressMode::MIRRORED_REPEAT:
+			vkAddressMode = vk::SamplerAddressMode::eMirroredRepeat;
+			break;
+		case SamplerAddressMode::CLAMP_TO_EDGE:
+			vkAddressMode = vk::SamplerAddressMode::eClampToEdge;
+			break;
+		case SamplerAddressMode::MIRROR_CLAMP_TO_EDGE:
+			vkAddressMode = vk::SamplerAddressMode::eMirrorClampToEdge;
+			break;
+		case SamplerAddressMode::CLAMP_TO_BORDER:
+			vkAddressMode = vk::SamplerAddressMode::eClampToBorder;
+			break;
+		default:
+			return SamplerHandle();
 		}
-		
-		const vk::SamplerCreateInfo samplerCreateInfo (
-				vk::SamplerCreateFlags(),
-				vkMagFilter,
-				vkMinFilter,
-				vkMipmapMode,
-				vkAddressMode,
-				vkAddressMode,
-				vkAddressMode,
-				0.0f,
-				false,
-				16.0f,
-				false,
-				vk::CompareOp::eAlways,
-				0.0f,
-				16.0f,
-				vk::BorderColor::eIntOpaqueBlack,
-				false
-		);
-		
-		const vk::Sampler sampler = m_device.createSampler(samplerCreateInfo);
-		
-		const uint64_t id = m_samplers.size();
-		m_samplers.push_back(sampler);
-		return SamplerHandle(id, [&](uint64_t id) { destroySamplerById(id); });
-	}
-	
-	vk::Sampler SamplerManager::getVulkanSampler(const SamplerHandle &handle) const {
-		const uint64_t id = handle.getId();
-		
-		if (id >= m_samplers.size()) {
-			return nullptr;
+
+		switch (borderColor) {
+		case SamplerBorderColor::INT_ZERO_OPAQUE:
+			vkBorderColor = vk::BorderColor::eIntOpaqueBlack;
+			break;
+		case SamplerBorderColor::INT_ZERO_TRANSPARENT:
+			vkBorderColor = vk::BorderColor::eIntTransparentBlack;
+			break;
+		case SamplerBorderColor::FLOAT_ZERO_OPAQUE:
+			vkBorderColor = vk::BorderColor::eFloatOpaqueBlack;
+			break;
+		case SamplerBorderColor::FLOAT_ZERO_TRANSPARENT:
+			vkBorderColor = vk::BorderColor::eFloatTransparentBlack;
+			break;
+		case SamplerBorderColor::INT_ONE_OPAQUE:
+			vkBorderColor = vk::BorderColor::eIntOpaqueWhite;
+			break;
+		case SamplerBorderColor::FLOAT_ONE_OPAQUE:
+			vkBorderColor = vk::BorderColor::eFloatOpaqueWhite;
+			break;
+		default:
+			return SamplerHandle();
 		}
-		
-		return m_samplers[id];
+
+		const vk::SamplerCreateInfo samplerCreateInfo(
+			vk::SamplerCreateFlags(), vkMagFilter, vkMinFilter, vkMipmapMode, vkAddressMode,
+			vkAddressMode, vkAddressMode, mipLodBias, false, 16.0f, false, vk::CompareOp::eAlways,
+			-1000.0f, 1000.0f, vkBorderColor, false);
+
+		const vk::Sampler sampler =
+			getCore().getContext().getDevice().createSampler(samplerCreateInfo);
+
+		return add(sampler);
 	}
-	
-	void SamplerManager::destroySamplerById(uint64_t id) {
-		if (id >= m_samplers.size()) {
-			return;
-		}
-		
-		auto& sampler = m_samplers[id];
-		
-		if (sampler) {
-			m_device.destroySampler(sampler);
-			sampler = nullptr;
-		}
+
+	vk::Sampler SamplerManager::getVulkanSampler(const SamplerHandle &handle) const {
+		return (*this) [handle];
 	}
 
-}
+} // namespace vkcv
diff --git a/src/vkcv/SamplerManager.hpp b/src/vkcv/SamplerManager.hpp
index 511176d4f87633a8691ca730ecc383e2588d8cf0..5ac033037f401ca97a658f2bec2d818110d2aa6a 100644
--- a/src/vkcv/SamplerManager.hpp
+++ b/src/vkcv/SamplerManager.hpp
@@ -3,40 +3,36 @@
 #include <vector>
 #include <vulkan/vulkan.hpp>
 
-#include "vkcv/Handles.hpp"
+#include "HandleManager.hpp"
+
 #include "vkcv/Sampler.hpp"
 
 namespace vkcv {
-	
-	class Core;
-	
-	class SamplerManager {
+
+	/**
+	 * @brief Class to manage the creation and destruction of samplers.
+	 */
+	class SamplerManager : public HandleManager<vk::Sampler, SamplerHandle> {
 		friend class Core;
+
 	private:
-		vk::Device m_device;
-		std::vector<vk::Sampler> m_samplers;
-		
-		explicit SamplerManager(const vk::Device& device) noexcept;
-		
-		void destroySamplerById(uint64_t id);
-		
+		[[nodiscard]] uint64_t getIdFrom(const SamplerHandle &handle) const override;
+
+		[[nodiscard]] SamplerHandle createById(uint64_t id,
+											   const HandleDestroyFunction &destroy) override;
+
+		void destroyById(uint64_t id) override;
+
 	public:
-		~SamplerManager();
-		
-		SamplerManager(const SamplerManager& other) = delete;
-		SamplerManager(SamplerManager&& other) = delete;
-		
-		SamplerManager& operator=(const SamplerManager& other) = delete;
-		SamplerManager& operator=(SamplerManager&& other) = delete;
-		
-		SamplerHandle createSampler(SamplerFilterType magFilter,
-							  		SamplerFilterType minFilter,
-							  		SamplerMipmapMode mipmapMode,
-							  		SamplerAddressMode addressMode);
-		
-		[[nodiscard]]
-		vk::Sampler getVulkanSampler(const SamplerHandle& handle) const;
-	
+		SamplerManager() noexcept;
+
+		~SamplerManager() noexcept override;
+
+		SamplerHandle createSampler(SamplerFilterType magFilter, SamplerFilterType minFilter,
+									SamplerMipmapMode mipmapMode, SamplerAddressMode addressMode,
+									float mipLodBias, SamplerBorderColor borderColor);
+
+		[[nodiscard]] vk::Sampler getVulkanSampler(const SamplerHandle &handle) const;
 	};
-	
-}
+
+} // namespace vkcv
diff --git a/src/vkcv/ShaderProgram.cpp b/src/vkcv/ShaderProgram.cpp
index 971797d9a42d071a1730ebf31a0b554f92fa361f..508f4de536fc193f9b213c87e7de7e3fe0679959 100644
--- a/src/vkcv/ShaderProgram.cpp
+++ b/src/vkcv/ShaderProgram.cpp
@@ -1,5 +1,5 @@
 /**
- * @authors Simeon Hermann, Leonie Franken
+ * @authors Simeon Hermann, Leonie Franken, Tobias Frisch
  * @file src/vkcv/ShaderProgram.cpp
  * @brief ShaderProgram class to handle and prepare the shader stages for a graphics pipeline
  */
@@ -8,217 +8,351 @@
 #include "vkcv/Logger.hpp"
 
 namespace vkcv {
-    /**
-     * Reads the file of a given shader code.
-     * Only used within the class.
-     * @param[in] relative path to the shader code
-     * @return vector of chars as a buffer for the code
-     */
-	std::vector<char> readShaderCode(const std::filesystem::path &shaderPath) {
-		std::ifstream file (shaderPath.string(), std::ios::ate | std::ios::binary);
-		
+	/**
+	 * Reads the file of a given shader code.
+	 * Only used within the class.
+	 * @param[in] relative path to the shader code
+	 * @return vector of chars as a buffer for the code
+	 */
+	std::vector<uint32_t> readShaderCode(const std::filesystem::path &shaderPath) {
+		std::ifstream file(shaderPath.string(), std::ios::ate | std::ios::binary);
+
 		if (!file.is_open()) {
-			vkcv_log(LogLevel::ERROR, "The file could not be opened");
-			return std::vector<char>{};
+			vkcv_log(LogLevel::ERROR, "The file could not be opened: %s", shaderPath.c_str());
+			return std::vector<uint32_t>();
 		}
-		
+
 		size_t fileSize = (size_t)file.tellg();
-		std::vector<char> buffer(fileSize);
-		
+
+		if (fileSize % sizeof(uint32_t) != 0) {
+			vkcv_log(LogLevel::ERROR, "The file is not a valid shader: %s", shaderPath.c_str());
+			return std::vector<uint32_t>();
+		}
+
+		std::vector<uint32_t> buffer(fileSize / sizeof(uint32_t));
+
 		file.seekg(0);
-		file.read(buffer.data(), fileSize);
+		file.read(reinterpret_cast<char*>(buffer.data()), fileSize);
 		file.close();
-		
-        return buffer;
+
+		return buffer;
 	}
 
-	VertexAttachmentFormat convertFormat(spirv_cross::SPIRType::BaseType basetype, uint32_t vecsize){
-        switch (basetype) {
-            case spirv_cross::SPIRType::Int:
-                switch (vecsize) {
-                    case 1:
-                        return VertexAttachmentFormat::INT;
-                    case 2:
-                        return VertexAttachmentFormat::INT2;
-                    case 3:
-                        return VertexAttachmentFormat::INT3;
-                    case 4:
-                        return VertexAttachmentFormat::INT4;
-                    default:
-                        break;
-                }
-                break;
-            case spirv_cross::SPIRType::Float:
-                switch (vecsize) {
-                    case 1:
-                        return VertexAttachmentFormat::FLOAT;
-                    case 2:
-                        return VertexAttachmentFormat::FLOAT2;
-                    case 3:
-                        return VertexAttachmentFormat::FLOAT3;
-                    case 4:
-                        return VertexAttachmentFormat::FLOAT4;
-                    default:
-                        break;
-                }
-                break;
-            default:
-                break;
-        }
-		
+	VertexAttachmentFormat convertFormat(spirv_cross::SPIRType::BaseType basetype,
+										 uint32_t vecsize) {
+		switch (basetype) {
+		case spirv_cross::SPIRType::Int:
+			switch (vecsize) {
+			case 1:
+				return VertexAttachmentFormat::INT;
+			case 2:
+				return VertexAttachmentFormat::INT2;
+			case 3:
+				return VertexAttachmentFormat::INT3;
+			case 4:
+				return VertexAttachmentFormat::INT4;
+			default:
+				break;
+			}
+			break;
+		case spirv_cross::SPIRType::Float:
+			switch (vecsize) {
+			case 1:
+				return VertexAttachmentFormat::FLOAT;
+			case 2:
+				return VertexAttachmentFormat::FLOAT2;
+			case 3:
+				return VertexAttachmentFormat::FLOAT3;
+			case 4:
+				return VertexAttachmentFormat::FLOAT4;
+			default:
+				break;
+			}
+			break;
+		default:
+			break;
+		}
+
 		vkcv_log(LogLevel::WARNING, "Unknown vertex format");
-        return VertexAttachmentFormat::FLOAT;
+		return VertexAttachmentFormat::FLOAT;
 	}
 
 	ShaderProgram::ShaderProgram() noexcept :
-	m_Shaders{},
-    m_VertexAttachments{},
-    m_DescriptorSets{}
-	{}
-
-	bool ShaderProgram::addShader(ShaderStage shaderStage, const std::filesystem::path &shaderPath)
-	{
-	    if(m_Shaders.find(shaderStage) != m_Shaders.end()) {
+		m_Shaders {}, m_VertexAttachments {}, m_DescriptorSets {} {}
+
+	bool ShaderProgram::addShader(ShaderStage stage, const std::filesystem::path &path) {
+		if (m_Shaders.find(stage) != m_Shaders.end()) {
 			vkcv_log(LogLevel::WARNING, "Overwriting existing shader stage");
 		}
 
-	    const std::vector<char> shaderCode = readShaderCode(shaderPath);
-	    
-	    if (shaderCode.empty()) {
+		const std::vector<uint32_t> shaderCode = readShaderCode(path);
+
+		if (shaderCode.empty()) {
 			return false;
 		} else {
-            Shader shader{shaderCode, shaderStage};
-            m_Shaders.insert(std::make_pair(shaderStage, shader));
-            reflectShader(shaderStage);
-            return true;
-        }
+			m_Shaders.insert(std::make_pair(stage, shaderCode));
+			reflectShader(stage);
+			return true;
+		}
 	}
 
-    const Shader &ShaderProgram::getShader(ShaderStage shaderStage) const
-    {
-	    return m_Shaders.at(shaderStage);
+	const std::vector<uint32_t> &ShaderProgram::getShaderBinary(ShaderStage stage) const {
+		return m_Shaders.at(stage);
 	}
 
-    bool ShaderProgram::existsShader(ShaderStage shaderStage) const
-    {
-	    if(m_Shaders.find(shaderStage) == m_Shaders.end())
-	        return false;
-	    else
-	        return true;
-    }
-
-    void ShaderProgram::reflectShader(ShaderStage shaderStage)
-    {
-        auto shaderCodeChar = m_Shaders.at(shaderStage).shaderCode;
-        std::vector<uint32_t> shaderCode;
+	bool ShaderProgram::existsShader(ShaderStage stage) const {
+		if (m_Shaders.find(stage) == m_Shaders.end())
+			return false;
+		else
+			return true;
+	}
 
-        for (uint32_t i = 0; i < shaderCodeChar.size()/4; i++)
-            shaderCode.push_back(((uint32_t*) shaderCodeChar.data())[i]);
+	void ShaderProgram::reflectShader(ShaderStage shaderStage) {
+		auto shaderCode = m_Shaders.at(shaderStage);
 
-        spirv_cross::Compiler comp(move(shaderCode));
-        spirv_cross::ShaderResources resources = comp.get_shader_resources();
+		spirv_cross::Compiler comp(shaderCode);
+		spirv_cross::ShaderResources resources = comp.get_shader_resources();
 
-        //reflect vertex input
-		if (shaderStage == ShaderStage::VERTEX)
-		{
+		// reflect vertex input
+		if (shaderStage == ShaderStage::VERTEX) {
 			// spirv-cross API (hopefully) returns the stage_inputs in order
-			for (uint32_t i = 0; i < resources.stage_inputs.size(); i++)
-			{
-                // spirv-cross specific objects
-				auto& stage_input = resources.stage_inputs[i];
-				const spirv_cross::SPIRType& base_type = comp.get_type(stage_input.base_type_id);
+			for (uint32_t i = 0; i < resources.stage_inputs.size(); i++) {
+				// spirv-cross specific objects
+				auto &stage_input = resources.stage_inputs [i];
+				const spirv_cross::SPIRType &base_type = comp.get_type(stage_input.base_type_id);
 
 				// vertex input location
-				const uint32_t attachment_loc = comp.get_decoration(stage_input.id, spv::DecorationLocation);
-                // vertex input name
-                const std::string attachment_name = stage_input.name;
+				const uint32_t attachment_loc =
+					comp.get_decoration(stage_input.id, spv::DecorationLocation);
+				// vertex input name
+				const std::string attachment_name = stage_input.name;
 				// vertex input format (implies its size)
-				const VertexAttachmentFormat attachment_format = convertFormat(base_type.basetype, base_type.vecsize);
+				const VertexAttachmentFormat attachment_format =
+					convertFormat(base_type.basetype, base_type.vecsize);
 
-                m_VertexAttachments.emplace_back(attachment_loc, attachment_name, attachment_format);
-            }
+				m_VertexAttachments.push_back(
+					{ attachment_loc, attachment_name, attachment_format, 0 });
+			}
 		}
 
-		//reflect descriptor sets (uniform buffer, storage buffer, sampler, sampled image, storage image)
-        std::vector<std::pair<uint32_t, DescriptorBinding>> bindings;
-        int32_t maxSetID = -1;
-        for (uint32_t i = 0; i < resources.uniform_buffers.size(); i++) {
-            auto& u = resources.uniform_buffers[i];
-            const spirv_cross::SPIRType& base_type = comp.get_type(u.base_type_id);
-            std::pair descriptor(comp.get_decoration(u.id, spv::DecorationDescriptorSet),
-                DescriptorBinding(comp.get_decoration(u.id, spv::DecorationBinding), DescriptorType::UNIFORM_BUFFER, base_type.vecsize, shaderStage));
-            bindings.push_back(descriptor);
-            if ((int32_t)comp.get_decoration(u.id, spv::DecorationDescriptorSet) > maxSetID) 
-                maxSetID = comp.get_decoration(u.id, spv::DecorationDescriptorSet);
-        }
-
-        for (uint32_t i = 0; i < resources.storage_buffers.size(); i++) {
-            auto& u = resources.storage_buffers[i];
-            const spirv_cross::SPIRType& base_type = comp.get_type(u.base_type_id);
-            std::pair descriptor(comp.get_decoration(u.id, spv::DecorationDescriptorSet),
-                DescriptorBinding(comp.get_decoration(u.id, spv::DecorationBinding), DescriptorType::STORAGE_BUFFER, base_type.vecsize, shaderStage));
-            bindings.push_back(descriptor);
-            if ((int32_t)comp.get_decoration(u.id, spv::DecorationDescriptorSet) > maxSetID) 
-                maxSetID = comp.get_decoration(u.id, spv::DecorationDescriptorSet);
-        }
-
-        for (uint32_t i = 0; i < resources.separate_samplers.size(); i++) {
-            auto& u = resources.separate_samplers[i];
-            const spirv_cross::SPIRType& base_type = comp.get_type(u.base_type_id);
-            std::pair descriptor(comp.get_decoration(u.id, spv::DecorationDescriptorSet),
-                DescriptorBinding(comp.get_decoration(u.id, spv::DecorationBinding), DescriptorType::SAMPLER, base_type.vecsize, shaderStage));
-            bindings.push_back(descriptor);
-            if ((int32_t)comp.get_decoration(u.id, spv::DecorationDescriptorSet) > maxSetID) 
-                maxSetID = comp.get_decoration(u.id, spv::DecorationDescriptorSet);
-        }
-
-        for (uint32_t i = 0; i < resources.separate_images.size(); i++) {
-            auto& u = resources.separate_images[i];
-            const spirv_cross::SPIRType& base_type = comp.get_type(u.base_type_id);
-            std::pair descriptor(comp.get_decoration(u.id, spv::DecorationDescriptorSet),
-                DescriptorBinding(comp.get_decoration(u.id, spv::DecorationBinding), DescriptorType::IMAGE_SAMPLED, base_type.vecsize, shaderStage));
-            bindings.push_back(descriptor);
-            if ((int32_t)comp.get_decoration(u.id, spv::DecorationDescriptorSet) > maxSetID)
-                maxSetID = comp.get_decoration(u.id, spv::DecorationDescriptorSet);
-
-        }
-
-        for (uint32_t i = 0; i < resources.storage_images.size(); i++) {
-            auto& u = resources.storage_images[i];
-            const spirv_cross::SPIRType& base_type = comp.get_type(u.base_type_id);
-            std::pair descriptor(comp.get_decoration(u.id, spv::DecorationDescriptorSet),
-                DescriptorBinding(comp.get_decoration(u.id, spv::DecorationBinding), DescriptorType::IMAGE_STORAGE, base_type.vecsize, shaderStage));
-            bindings.push_back(descriptor);
-            if ((int32_t)comp.get_decoration(u.id, spv::DecorationDescriptorSet) > maxSetID)
-                maxSetID = comp.get_decoration(u.id, spv::DecorationDescriptorSet);
-        }
-        if (maxSetID != -1) {
-            if((int32_t)m_DescriptorSets.size() <= maxSetID) m_DescriptorSets.resize(maxSetID + 1);
-            for (const auto &binding : bindings) {
-                m_DescriptorSets[binding.first].push_back(binding.second);
-            }
-        }
-
-        //reflect push constants
+		// reflect descriptor sets (uniform buffer, storage buffer, sampler, sampled image, storage
+		// image)
+		std::vector<std::pair<uint32_t, DescriptorBinding>> bindings;
+
+		for (uint32_t i = 0; i < resources.uniform_buffers.size(); i++) {
+			auto &u = resources.uniform_buffers [i];
+			const spirv_cross::SPIRType &base_type = comp.get_type(u.base_type_id);
+			const spirv_cross::SPIRType &type = comp.get_type(u.type_id);
+
+			uint32_t setID = comp.get_decoration(u.id, spv::DecorationDescriptorSet);
+			uint32_t bindingID = comp.get_decoration(u.id, spv::DecorationBinding);
+
+			uint32_t descriptorCount = base_type.vecsize;
+			bool variableCount = false;
+			// query whether reflected resources are qualified as one-dimensional array
+			if (type.array_size_literal [0]) {
+				descriptorCount = type.array [0];
+				if (type.array [0] == 0)
+					variableCount = true;
+			}
+
+			DescriptorBinding binding {
+				bindingID,     DescriptorType::UNIFORM_BUFFER, descriptorCount, shaderStage,
+				variableCount,
+				variableCount // partialBinding == variableCount
+			};
+
+			auto insertionResult =
+				m_DescriptorSets [setID].insert(std::make_pair(bindingID, binding));
+			if (!insertionResult.second) {
+				insertionResult.first->second.shaderStages |= shaderStage;
+				
+				vkcv_log(LogLevel::WARNING,
+						 "Attempting to overwrite already existing binding %u at set ID %u.",
+						 bindingID, setID);
+			}
+		}
+
+		for (uint32_t i = 0; i < resources.storage_buffers.size(); i++) {
+			auto &u = resources.storage_buffers [i];
+			const spirv_cross::SPIRType &base_type = comp.get_type(u.base_type_id);
+			const spirv_cross::SPIRType &type = comp.get_type(u.type_id);
+
+			uint32_t setID = comp.get_decoration(u.id, spv::DecorationDescriptorSet);
+			uint32_t bindingID = comp.get_decoration(u.id, spv::DecorationBinding);
+
+			uint32_t descriptorCount = base_type.vecsize;
+			bool variableCount = false;
+			// query whether reflected resources are qualified as one-dimensional array
+			if (type.array_size_literal [0]) {
+				descriptorCount = type.array [0];
+				if (type.array [0] == 0)
+					variableCount = true;
+			}
+
+			DescriptorBinding binding {
+				bindingID,     DescriptorType::STORAGE_BUFFER, descriptorCount, shaderStage,
+				variableCount,
+				variableCount // partialBinding == variableCount
+			};
+
+			auto insertionResult =
+				m_DescriptorSets [setID].insert(std::make_pair(bindingID, binding));
+			if (!insertionResult.second) {
+				insertionResult.first->second.shaderStages |= shaderStage;
+				
+				vkcv_log(LogLevel::WARNING,
+						 "Attempting to overwrite already existing binding %u at set ID %u.",
+						 bindingID, setID);
+			}
+		}
+
+		for (uint32_t i = 0; i < resources.separate_samplers.size(); i++) {
+			auto &u = resources.separate_samplers [i];
+			const spirv_cross::SPIRType &base_type = comp.get_type(u.base_type_id);
+			const spirv_cross::SPIRType &type = comp.get_type(u.type_id);
+
+			uint32_t setID = comp.get_decoration(u.id, spv::DecorationDescriptorSet);
+			uint32_t bindingID = comp.get_decoration(u.id, spv::DecorationBinding);
+
+			uint32_t descriptorCount = base_type.vecsize;
+			bool variableCount = false;
+			// query whether reflected resources are qualified as one-dimensional array
+			if (type.array_size_literal [0]) {
+				descriptorCount = type.array [0];
+				if (type.array [0] == 0)
+					variableCount = true;
+			}
+
+			DescriptorBinding binding {
+				bindingID,    DescriptorType::SAMPLER, descriptorCount, shaderStage, variableCount,
+				variableCount // partialBinding == variableCount
+			};
+
+			auto insertionResult =
+				m_DescriptorSets [setID].insert(std::make_pair(bindingID, binding));
+			if (!insertionResult.second) {
+				insertionResult.first->second.shaderStages |= shaderStage;
+				
+				vkcv_log(LogLevel::WARNING,
+						 "Attempting to overwrite already existing binding %u at set ID %u.",
+						 bindingID, setID);
+			}
+		}
+
+		for (uint32_t i = 0; i < resources.separate_images.size(); i++) {
+			auto &u = resources.separate_images [i];
+			const spirv_cross::SPIRType &base_type = comp.get_type(u.base_type_id);
+			const spirv_cross::SPIRType &type = comp.get_type(u.type_id);
+
+			uint32_t setID = comp.get_decoration(u.id, spv::DecorationDescriptorSet);
+			uint32_t bindingID = comp.get_decoration(u.id, spv::DecorationBinding);
+
+			uint32_t descriptorCount = base_type.vecsize;
+			bool variableCount = false;
+			// query whether reflected resources are qualified as one-dimensional array
+			if (type.array_size_literal [0]) {
+				descriptorCount = type.array [0];
+				if (type.array [0] == 0)
+					variableCount = true;
+			}
+
+			DescriptorBinding binding {
+				bindingID,     DescriptorType::IMAGE_SAMPLED, descriptorCount, shaderStage,
+				variableCount,
+				variableCount // partialBinding == variableCount
+			};
+
+			auto insertionResult =
+				m_DescriptorSets [setID].insert(std::make_pair(bindingID, binding));
+			if (!insertionResult.second) {
+				insertionResult.first->second.shaderStages |= shaderStage;
+				
+				vkcv_log(LogLevel::WARNING,
+						 "Attempting to overwrite already existing binding %u at set ID %u.",
+						 bindingID, setID);
+			}
+		}
+
+		for (uint32_t i = 0; i < resources.storage_images.size(); i++) {
+			auto &u = resources.storage_images [i];
+			const spirv_cross::SPIRType &base_type = comp.get_type(u.base_type_id);
+			const spirv_cross::SPIRType &type = comp.get_type(u.type_id);
+
+			uint32_t setID = comp.get_decoration(u.id, spv::DecorationDescriptorSet);
+			uint32_t bindingID = comp.get_decoration(u.id, spv::DecorationBinding);
+
+			uint32_t descriptorCount = base_type.vecsize;
+			bool variableCount = false;
+			// query whether reflected resources are qualified as one-dimensional array
+			if (type.array_size_literal [0]) {
+				descriptorCount = type.array [0];
+				if (type.array [0] == 0)
+					variableCount = true;
+			}
+
+			DescriptorBinding binding {
+				bindingID,     DescriptorType::IMAGE_STORAGE, descriptorCount, shaderStage,
+				variableCount,
+				variableCount // partialBinding == variableCount
+			};
+
+			auto insertionResult =
+				m_DescriptorSets [setID].insert(std::make_pair(bindingID, binding));
+			if (!insertionResult.second) {
+				insertionResult.first->second.shaderStages |= shaderStage;
+				
+				vkcv_log(LogLevel::WARNING,
+						 "Attempting to overwrite already existing binding %u at set ID %u.",
+						 bindingID, setID);
+			}
+		}
+
+		// Used to reflect acceleration structure bindings for RTX.
+		for (uint32_t i = 0; i < resources.acceleration_structures.size(); i++) {
+			auto &u = resources.acceleration_structures [i];
+			const spirv_cross::SPIRType &base_type = comp.get_type(u.base_type_id);
+
+			uint32_t setID = comp.get_decoration(u.id, spv::DecorationDescriptorSet);
+			uint32_t bindingID = comp.get_decoration(u.id, spv::DecorationBinding);
+			auto binding = DescriptorBinding { bindingID,
+											   DescriptorType::ACCELERATION_STRUCTURE_KHR,
+											   base_type.vecsize,
+											   shaderStage,
+											   false,
+											   false };
+
+			auto insertionResult =
+				m_DescriptorSets [setID].insert(std::make_pair(bindingID, binding));
+			if (!insertionResult.second) {
+				insertionResult.first->second.shaderStages |= shaderStage;
+				
+				vkcv_log(LogLevel::WARNING,
+						 "Attempting to overwrite already existing binding %u at set ID %u.",
+						 bindingID, setID);
+			}
+		}
+
+		// reflect push constants
 		for (const auto &pushConstantBuffer : resources.push_constant_buffers) {
 			for (const auto &range : comp.get_active_buffer_ranges(pushConstantBuffer.id)) {
 				const size_t size = range.range + range.offset;
-				m_pushConstantSize = std::max(m_pushConstantSize, size);
+				m_pushConstantsSize = std::max(m_pushConstantsSize, size);
 			}
 		}
-    }
+	}
 
-    const std::vector<VertexAttachment> &ShaderProgram::getVertexAttachments() const
-    {
-        return m_VertexAttachments;
+	const VertexAttachments &ShaderProgram::getVertexAttachments() const {
+		return m_VertexAttachments;
 	}
 
-    const std::vector<std::vector<DescriptorBinding>>& ShaderProgram::getReflectedDescriptors() const {
-        return m_DescriptorSets;
-    }
+	const std::unordered_map<uint32_t, DescriptorBindings> &
+	ShaderProgram::getReflectedDescriptors() const {
+		return m_DescriptorSets;
+	}
 
-	size_t ShaderProgram::getPushConstantSize() const
-	{
-		return m_pushConstantSize;
+	size_t ShaderProgram::getPushConstantsSize() const {
+		return m_pushConstantsSize;
 	}
-}
+} // namespace vkcv
+
diff --git a/src/vkcv/Swapchain.cpp b/src/vkcv/Swapchain.cpp
deleted file mode 100644
index 2c5b3530c396bc3532aa94cb59a120e3555291bf..0000000000000000000000000000000000000000
--- a/src/vkcv/Swapchain.cpp
+++ /dev/null
@@ -1,261 +0,0 @@
-#include <vkcv/Swapchain.hpp>
-#include <utility>
-
-#include <GLFW/glfw3.h>
-
-namespace vkcv
-{
-    /**
-    * creates surface and checks availability
-    * @param window current window for the surface
-    * @param instance Vulkan-Instance
-    * @param physicalDevice Vulkan-PhysicalDevice
-    * @return created surface
-    */
-    vk::SurfaceKHR createSurface(GLFWwindow* window, const vk::Instance& instance, const vk::PhysicalDevice& physicalDevice) {
-        //create surface
-        VkSurfaceKHR surface;
-        if (glfwCreateWindowSurface(VkInstance(instance), window, nullptr, &surface) != VK_SUCCESS) {
-            throw std::runtime_error("failed to create a window surface!");
-        }
-        vk::Bool32 surfaceSupport = false;
-        if (physicalDevice.getSurfaceSupportKHR(0, vk::SurfaceKHR(surface), &surfaceSupport) != vk::Result::eSuccess && surfaceSupport != true) {
-            throw std::runtime_error("surface is not supported by the device!");
-        }
-
-        return vk::SurfaceKHR(surface);
-    }
-
-    Swapchain::Swapchain(const Surface &surface,
-                         vk::SwapchainKHR swapchain,
-                         vk::Format format,
-                         vk::ColorSpaceKHR colorSpace,
-                         vk::PresentModeKHR presentMode,
-                         uint32_t imageCount,
-						 vk::Extent2D extent) noexcept :
-			m_Surface(surface),
-			m_Swapchain(swapchain),
-			m_Format(format),
-			m_ColorSpace(colorSpace),
-			m_PresentMode(presentMode),
-			m_ImageCount(imageCount),
-			m_Extent(extent),
-			m_RecreationRequired(false)
-    {}
-    
-    Swapchain::Swapchain(const Swapchain &other) :
-			m_Surface(other.m_Surface),
-			m_Swapchain(other.m_Swapchain),
-			m_Format(other.m_Format),
-			m_ColorSpace(other.m_ColorSpace),
-			m_PresentMode(other.m_PresentMode),
-			m_ImageCount(other.m_ImageCount),
-			m_Extent(other.m_Extent),
-			m_RecreationRequired(other.m_RecreationRequired.load())
-	{}
-
-    const vk::SwapchainKHR& Swapchain::getSwapchain() const {
-        return m_Swapchain;
-    }
-
-    vk::SurfaceKHR Swapchain::getSurface() const {
-        return m_Surface.handle;
-    }
-
-    vk::Format Swapchain::getFormat() const{
-        return m_Format;
-    }
-
-    /**
-     * chooses Extent and clapms values to the available
-     * @param physicalDevice Vulkan-PhysicalDevice
-     * @param surface of the swapchain
-     * @param window of the current application
-     * @return chosen Extent for the surface
-     */
-    vk::Extent2D chooseExtent(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface, const Window &window){
-        vk::SurfaceCapabilitiesKHR surfaceCapabilities;
-        if(physicalDevice.getSurfaceCapabilitiesKHR(surface,&surfaceCapabilities) != vk::Result::eSuccess){
-            throw std::runtime_error("cannot get surface capabilities. There is an issue with the surface.");
-        }
-
-        VkExtent2D extent2D = {
-                static_cast<uint32_t>(window.getWidth()),
-                static_cast<uint32_t>(window.getHeight())
-        };
-        
-        extent2D.width = std::max(surfaceCapabilities.minImageExtent.width, std::min(surfaceCapabilities.maxImageExtent.width, extent2D.width));
-        extent2D.height = std::max(surfaceCapabilities.minImageExtent.height, std::min(surfaceCapabilities.maxImageExtent.height, extent2D.height));
-
-        return extent2D;
-    }
-
-    /**
-     * chooses Surface Format for the current surface
-     * @param physicalDevice Vulkan-PhysicalDevice
-     * @param surface of the swapchain
-     * @return available Format
-     */
-    vk::SurfaceFormatKHR chooseSurfaceFormat(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface) {
-        uint32_t formatCount;
-        physicalDevice.getSurfaceFormatsKHR(surface, &formatCount, nullptr);
-        std::vector<vk::SurfaceFormatKHR> availableFormats(formatCount);
-        if (physicalDevice.getSurfaceFormatsKHR(surface, &formatCount, &availableFormats[0]) != vk::Result::eSuccess) {
-            throw std::runtime_error("Failed to get surface formats");
-        }
-
-        for (const auto& availableFormat : availableFormats) {
-            if (availableFormat.format == vk::Format::eB8G8R8A8Unorm  && availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) {
-                return availableFormat;
-            }
-        }
-        return availableFormats[0];
-    }
-
-    /**
-     * returns vk::PresentModeKHR::eMailbox if available or vk::PresentModeKHR::eFifo otherwise
-     * @param physicalDevice Vulkan-PhysicalDevice
-     * @param surface of the swapchain
-     * @return available PresentationMode
-     */
-    vk::PresentModeKHR choosePresentMode(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface) {
-        uint32_t modeCount;
-        physicalDevice.getSurfacePresentModesKHR( surface, &modeCount, nullptr );
-        std::vector<vk::PresentModeKHR> availablePresentModes(modeCount);
-        if (physicalDevice.getSurfacePresentModesKHR(surface, &modeCount, &availablePresentModes[0]) != vk::Result::eSuccess) {
-            throw std::runtime_error("Failed to get presentation modes");
-        }
-
-        for (const auto& availablePresentMode : availablePresentModes) {
-            if (availablePresentMode == vk::PresentModeKHR::eMailbox) {
-                return availablePresentMode;
-            }
-        }
-        // The FIFO present mode is guaranteed by the spec to be supported
-        return vk::PresentModeKHR::eFifo;
-    }
-
-    /**
-     * returns the minImageCount +1 for at least doublebuffering, if it's greater than maxImageCount return maxImageCount
-     * @param physicalDevice Vulkan-PhysicalDevice
-     * @param surface of the swapchain
-     * @return available ImageCount
-     */
-    uint32_t chooseImageCount(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface) {
-        vk::SurfaceCapabilitiesKHR surfaceCapabilities;
-        if(physicalDevice.getSurfaceCapabilitiesKHR(surface, &surfaceCapabilities) != vk::Result::eSuccess){
-            throw std::runtime_error("cannot get surface capabilities. There is an issue with the surface.");
-        }
-
-        uint32_t imageCount = surfaceCapabilities.minImageCount + 1;    // minImageCount should always be at least 2; set to 3 for triple buffering
-        // check if requested image count is supported
-        if (surfaceCapabilities.maxImageCount > 0 && imageCount > surfaceCapabilities.maxImageCount) {
-            imageCount = surfaceCapabilities.maxImageCount;
-        }
-
-        return imageCount;
-    }
-    /**
-     * creates and returns a swapchain with default specs
-     * @param window of the current application
-     * @param context that keeps instance, physicalDevice and a device.
-     * @return swapchain
-     */
-    Swapchain Swapchain::create(const Window &window, const Context &context) {
-        const vk::Instance& instance = context.getInstance();
-        const vk::PhysicalDevice& physicalDevice = context.getPhysicalDevice();
-        const vk::Device& device = context.getDevice();
-
-        Surface surface;
-        surface.handle       = createSurface(window.getWindow(), instance, physicalDevice);
-        surface.formats      = physicalDevice.getSurfaceFormatsKHR(surface.handle);
-        surface.capabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface.handle);
-        surface.presentModes = physicalDevice.getSurfacePresentModesKHR(surface.handle);
-
-        vk::Extent2D chosenExtent = chooseExtent(physicalDevice, surface.handle, window);
-        vk::SurfaceFormatKHR chosenSurfaceFormat = chooseSurfaceFormat(physicalDevice, surface.handle);
-        vk::PresentModeKHR chosenPresentMode = choosePresentMode(physicalDevice, surface.handle);
-        uint32_t chosenImageCount = chooseImageCount(physicalDevice, surface.handle);
-
-        vk::SwapchainCreateInfoKHR swapchainCreateInfo(
-                vk::SwapchainCreateFlagsKHR(),  //flags
-                surface.handle,    // surface
-                chosenImageCount,  // minImageCount TODO: how many do we need for our application?? "must be less than or equal to the value returned in maxImageCount" -> 3 for Triple Buffering, else 2 for Double Buffering (should be the standard)
-                chosenSurfaceFormat.format,   // imageFormat
-                chosenSurfaceFormat.colorSpace,   // imageColorSpace
-                chosenExtent,   // imageExtent
-                1,  // imageArrayLayers TODO: should we only allow non-stereoscopic applications? yes -> 1, no -> ? "must be greater than 0, less or equal to maxImageArrayLayers"
-                vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eStorage,  // imageUsage TODO: what attachments? only color? depth?
-                vk::SharingMode::eExclusive,    // imageSharingMode TODO: which sharing mode? "VK_SHARING_MODE_EXCLUSIV access exclusive to a single queue family, better performance", "VK_SHARING_MODE_CONCURRENT access from multiple queues"
-                0,  // queueFamilyIndexCount, the number of queue families having access to the image(s) of the swapchain when imageSharingMode is VK_SHARING_MODE_CONCURRENT
-                nullptr,    // pQueueFamilyIndices, the pointer to an array of queue family indices having access to the images(s) of the swapchain when imageSharingMode is VK_SHARING_MODE_CONCURRENT
-                vk::SurfaceTransformFlagBitsKHR::eIdentity, // preTransform, transformations applied onto the image before display
-                vk::CompositeAlphaFlagBitsKHR::eOpaque, // compositeAlpha, TODO: how to handle transparent pixels? do we need transparency? If no -> opaque
-                chosenPresentMode,    // presentMode
-                true,   // clipped
-                nullptr // oldSwapchain
-        );
-
-        vk::SwapchainKHR swapchain = device.createSwapchainKHR(swapchainCreateInfo);
-
-        return Swapchain(surface,
-                         swapchain,
-                         chosenSurfaceFormat.format,
-                         chosenSurfaceFormat.colorSpace,
-                         chosenPresentMode,
-                         chosenImageCount,
-						 chosenExtent);
-    }
-    
-    bool Swapchain::shouldUpdateSwapchain() const {
-    	return m_RecreationRequired;
-    }
-    
-    void Swapchain::updateSwapchain(const Context &context, const Window &window) {
-    	if (!m_RecreationRequired.exchange(false))
-    		return;
-    	
-		vk::SwapchainKHR oldSwapchain = m_Swapchain;
-		vk::Extent2D extent2D = chooseExtent(context.getPhysicalDevice(), m_Surface.handle, window);
-	
-		vk::SwapchainCreateInfoKHR swapchainCreateInfo(
-				vk::SwapchainCreateFlagsKHR(),
-				m_Surface.handle,
-				m_ImageCount,
-				m_Format,
-				m_ColorSpace,
-				extent2D,
-				1,
-				vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eStorage,
-				vk::SharingMode::eExclusive,
-				0,
-				nullptr,
-				vk::SurfaceTransformFlagBitsKHR::eIdentity,
-				vk::CompositeAlphaFlagBitsKHR::eOpaque,
-				m_PresentMode,
-				true,
-				oldSwapchain
-		);
-	
-		m_Swapchain = context.getDevice().createSwapchainKHR(swapchainCreateInfo);
-		context.getDevice().destroySwapchainKHR(oldSwapchain);
-		
-		m_Extent = extent2D;
-    }
-
-    void Swapchain::signalSwapchainRecreation() {
-		m_RecreationRequired = true;
-    }
-    
-    const vk::Extent2D& Swapchain::getExtent() const {
-    	return m_Extent;
-    }
-
-    Swapchain::~Swapchain() {
-        // needs to be destroyed by creator
-    }
-
-	uint32_t Swapchain::getImageCount() const {
-		return m_ImageCount;
-	}
-}
diff --git a/src/vkcv/SwapchainManager.cpp b/src/vkcv/SwapchainManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..27efa5031853552ab9a2926d37ba3cb64cdbdc6c
--- /dev/null
+++ b/src/vkcv/SwapchainManager.cpp
@@ -0,0 +1,328 @@
+#include "SwapchainManager.hpp"
+
+#include <GLFW/glfw3.h>
+
+#include "vkcv/Core.hpp"
+
+namespace vkcv {
+
+	uint64_t SwapchainManager::getIdFrom(const SwapchainHandle &handle) const {
+		return handle.getId();
+	}
+
+	SwapchainHandle SwapchainManager::createById(uint64_t id,
+												 const HandleDestroyFunction &destroy) {
+		return SwapchainHandle(id, destroy);
+	}
+
+	void SwapchainManager::destroyById(uint64_t id) {
+		auto &swapchain = getById(id);
+
+		if (swapchain.m_Swapchain) {
+			getCore().getContext().getDevice().destroySwapchainKHR(swapchain.m_Swapchain);
+			swapchain.m_Swapchain = nullptr;
+		}
+
+		if (swapchain.m_Surface) {
+			getCore().getContext().getInstance().destroySurfaceKHR(swapchain.m_Surface);
+			swapchain.m_Surface = nullptr;
+		}
+	}
+
+	SwapchainManager::SwapchainManager() noexcept :
+		HandleManager<SwapchainEntry, SwapchainHandle>() {}
+
+	SwapchainManager::~SwapchainManager() noexcept {
+		clear();
+	}
+
+	/**
+	 * @brief Creates vulkan surface and checks availability.
+	 *
+	 * @param[in,out] window Current window for the surface
+	 * @param[in,out] instance Vulkan-Instance
+	 * @param[in,out] physicalDevice Vulkan-PhysicalDevice
+	 * @param[out] surface Vulkan-Surface
+	 * @return Created vulkan surface
+	 */
+	static bool createVulkanSurface(GLFWwindow* window, const vk::Instance &instance,
+									const vk::PhysicalDevice &physicalDevice,
+									vk::SurfaceKHR &surface) {
+		VkSurfaceKHR api_surface;
+
+		if (glfwCreateWindowSurface(VkInstance(instance), window, nullptr, &api_surface)
+			!= VK_SUCCESS) {
+			vkcv_log(LogLevel::ERROR, "Failed to create a window surface");
+			return false;
+		}
+
+		vk::Bool32 surfaceSupport = false;
+		surface = vk::SurfaceKHR(api_surface);
+
+		if ((physicalDevice.getSurfaceSupportKHR(0, surface, &surfaceSupport)
+			 != vk::Result::eSuccess)
+			|| (!surfaceSupport)) {
+			vkcv_log(LogLevel::ERROR, "Surface is not supported by the device");
+			instance.destroy(surface);
+			surface = nullptr;
+			return false;
+		}
+
+		return true;
+	}
+
+	/**
+	 * @brief Chooses an Extent and clamps values to the available capabilities.
+	 *
+	 * @param physicalDevice Vulkan-PhysicalDevice
+	 * @param surface Vulkan-Surface of the swapchain
+	 * @param window Window of the current application
+	 * @return Chosen Extent for the surface
+	 */
+	static vk::Extent2D chooseExtent(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface,
+									 const Window &window) {
+		int fb_width, fb_height;
+		window.getFramebufferSize(fb_width, fb_height);
+
+		VkExtent2D extent2D = { static_cast<uint32_t>(fb_width), static_cast<uint32_t>(fb_height) };
+
+		vk::SurfaceCapabilitiesKHR surfaceCapabilities;
+		if (physicalDevice.getSurfaceCapabilitiesKHR(surface, &surfaceCapabilities)
+			!= vk::Result::eSuccess) {
+			vkcv_log(LogLevel::WARNING, "The capabilities of the surface can not be retrieved");
+
+			extent2D.width = std::max(MIN_SURFACE_SIZE, extent2D.width);
+			extent2D.height = std::max(MIN_SURFACE_SIZE, extent2D.height);
+		} else {
+			extent2D.width =
+				std::max(surfaceCapabilities.minImageExtent.width,
+						 std::min(surfaceCapabilities.maxImageExtent.width, extent2D.width));
+			extent2D.height =
+				std::max(surfaceCapabilities.minImageExtent.height,
+						 std::min(surfaceCapabilities.maxImageExtent.height, extent2D.height));
+		}
+
+		return extent2D;
+	}
+
+	/**
+	 * @brief Chooses Surface Format for the current surface
+	 *
+	 * @param physicalDevice Vulkan-PhysicalDevice
+	 * @param surface Vulkan-Surface of the swapchain
+	 * @return Available Format
+	 */
+	static vk::SurfaceFormatKHR chooseSurfaceFormat(vk::PhysicalDevice physicalDevice,
+													vk::SurfaceKHR surface) {
+		std::vector<vk::SurfaceFormatKHR> availableFormats =
+			physicalDevice.getSurfaceFormatsKHR(surface);
+
+		for (const auto &availableFormat : availableFormats) {
+			if (availableFormat.format == vk::Format::eB8G8R8A8Unorm
+				&& availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) {
+				return availableFormat;
+			}
+		}
+
+		return availableFormats [0];
+	}
+
+	/**
+	 * @brief Returns vk::PresentModeKHR::eMailbox if available or
+	 * vk::PresentModeKHR::eFifo otherwise
+	 *
+	 * @param physicalDevice Vulkan-PhysicalDevice
+	 * @param surface Vulkan-Surface of the swapchain
+	 * @return Available PresentationMode
+	 */
+	static vk::PresentModeKHR choosePresentMode(vk::PhysicalDevice physicalDevice,
+												vk::SurfaceKHR surface) {
+		std::vector<vk::PresentModeKHR> availablePresentModes =
+			physicalDevice.getSurfacePresentModesKHR(surface);
+
+		for (const auto &availablePresentMode : availablePresentModes) {
+			if (availablePresentMode == vk::PresentModeKHR::eMailbox) {
+				return availablePresentMode;
+			}
+		}
+		// The FIFO present mode is guaranteed by the spec to be supported
+		return vk::PresentModeKHR::eFifo;
+	}
+
+	/**
+	 * @brief Returns the minImageCount +1 for at least double buffering,
+	 * if it's greater than maxImageCount return maxImageCount
+	 *
+	 * @param physicalDevice Vulkan-PhysicalDevice
+	 * @param surface Vulkan-Surface of the swapchain
+	 * @return Available image count
+	 */
+	static uint32_t chooseImageCount(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface) {
+		vk::SurfaceCapabilitiesKHR surfaceCapabilities =
+			physicalDevice.getSurfaceCapabilitiesKHR(surface);
+
+		// minImageCount should always be at least 2; set to 3 for triple buffering
+		uint32_t imageCount = surfaceCapabilities.minImageCount + 1;
+
+		// check if requested image count is supported
+		if (surfaceCapabilities.maxImageCount > 0
+			&& imageCount > surfaceCapabilities.maxImageCount) {
+			imageCount = surfaceCapabilities.maxImageCount;
+		}
+
+		return imageCount;
+	}
+
+	static bool createVulkanSwapchain(const Context &context, const Window &window,
+									  SwapchainEntry &entry) {
+		const vk::PhysicalDevice &physicalDevice = context.getPhysicalDevice();
+		const vk::Device &device = context.getDevice();
+
+		entry.m_Extent = chooseExtent(physicalDevice, entry.m_Surface, window);
+
+		if ((entry.m_Extent.width < MIN_SURFACE_SIZE)
+			|| (entry.m_Extent.height < MIN_SURFACE_SIZE)) {
+			return false;
+		}
+
+		vk::SurfaceFormatKHR chosenSurfaceFormat =
+			chooseSurfaceFormat(physicalDevice, entry.m_Surface);
+		vk::PresentModeKHR chosenPresentMode = choosePresentMode(physicalDevice, entry.m_Surface);
+		uint32_t chosenImageCount = chooseImageCount(physicalDevice, entry.m_Surface);
+
+		entry.m_Format = chosenSurfaceFormat.format;
+		entry.m_ColorSpace = chosenSurfaceFormat.colorSpace;
+
+		vk::SwapchainCreateInfoKHR swapchainCreateInfo(
+			vk::SwapchainCreateFlagsKHR(), entry.m_Surface, chosenImageCount, entry.m_Format,
+			entry.m_ColorSpace, entry.m_Extent, 1,
+			vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eStorage,
+			vk::SharingMode::eExclusive, 0, nullptr, vk::SurfaceTransformFlagBitsKHR::eIdentity,
+			vk::CompositeAlphaFlagBitsKHR::eOpaque, chosenPresentMode, true, entry.m_Swapchain);
+
+		entry.m_Swapchain = device.createSwapchainKHR(swapchainCreateInfo);
+		return true;
+	}
+
+	SwapchainHandle SwapchainManager::createSwapchain(Window &window) {
+		const vk::Instance &instance = getCore().getContext().getInstance();
+		const vk::PhysicalDevice &physicalDevice = getCore().getContext().getPhysicalDevice();
+
+		vk::SurfaceKHR surfaceHandle;
+		if (!createVulkanSurface(window.getWindow(), instance, physicalDevice, surfaceHandle)) {
+			return {};
+		}
+
+		uint32_t presentQueueIndex =
+			QueueManager::checkSurfaceSupport(physicalDevice, surfaceHandle);
+
+		const vk::Extent2D extent = chooseExtent(physicalDevice, surfaceHandle, window);
+		const vk::SurfaceFormatKHR format = chooseSurfaceFormat(physicalDevice, surfaceHandle);
+
+		SwapchainEntry entry { nullptr,          false,
+
+							   surfaceHandle,    presentQueueIndex,
+							   extent,           format.format,
+							   format.colorSpace };
+
+		if (!createVulkanSwapchain(getCore().getContext(), window, entry)) {
+			instance.destroySurfaceKHR(surfaceHandle);
+			return {};
+		}
+
+		window.m_swapchainHandle = add(entry);
+		return window.m_swapchainHandle;
+	}
+
+	SwapchainEntry &SwapchainManager::getSwapchain(const SwapchainHandle &handle) {
+		return (*this) [handle];
+	}
+
+	bool SwapchainManager::shouldUpdateSwapchain(const SwapchainHandle &handle) const {
+		return (*this) [handle].m_RecreationRequired;
+	}
+
+	void SwapchainManager::updateSwapchain(const SwapchainHandle &handle, const Window &window) {
+		auto &swapchain = (*this) [handle];
+
+		if (!swapchain.m_RecreationRequired) {
+			return;
+		} else {
+			swapchain.m_RecreationRequired = false;
+		}
+
+		vk::SwapchainKHR oldSwapchain = swapchain.m_Swapchain;
+
+		if (createVulkanSwapchain(getCore().getContext(), window, swapchain)) {
+			if (oldSwapchain) {
+				getCore().getContext().getDevice().destroySwapchainKHR(oldSwapchain);
+			}
+		} else {
+			signalRecreation(handle);
+		}
+	}
+
+	void SwapchainManager::signalRecreation(const SwapchainHandle &handle) {
+		(*this) [handle].m_RecreationRequired = true;
+	}
+
+	vk::Format SwapchainManager::getFormat(const SwapchainHandle &handle) const {
+		return (*this) [handle].m_Format;
+	}
+
+	uint32_t SwapchainManager::getImageCount(const SwapchainHandle &handle) const {
+		auto &swapchain = (*this) [handle];
+
+		uint32_t imageCount;
+		if (vk::Result::eSuccess
+			!= getCore().getContext().getDevice().getSwapchainImagesKHR(swapchain.m_Swapchain,
+																		&imageCount, nullptr)) {
+			return 0;
+		} else {
+			return imageCount;
+		}
+	}
+
+	const vk::Extent2D &SwapchainManager::getExtent(const SwapchainHandle &handle) const {
+		return (*this) [handle].m_Extent;
+	}
+
+	uint32_t SwapchainManager::getPresentQueueIndex(const SwapchainHandle &handle) const {
+		return (*this) [handle].m_PresentQueueIndex;
+	}
+
+	vk::ColorSpaceKHR SwapchainManager::getSurfaceColorSpace(const SwapchainHandle &handle) const {
+		return (*this) [handle].m_ColorSpace;
+	}
+
+	std::vector<vk::Image>
+	SwapchainManager::getSwapchainImages(const SwapchainHandle &handle) const {
+		return getCore().getContext().getDevice().getSwapchainImagesKHR(
+			(*this) [handle].m_Swapchain);
+	}
+
+	std::vector<vk::ImageView>
+	SwapchainManager::createSwapchainImageViews(SwapchainHandle &handle) {
+		std::vector<vk::Image> images = getSwapchainImages(handle);
+		auto &swapchain = (*this) [handle];
+
+		std::vector<vk::ImageView> imageViews;
+		imageViews.reserve(images.size());
+		// here can be swizzled with vk::ComponentSwizzle if needed
+		vk::ComponentMapping componentMapping(vk::ComponentSwizzle::eR, vk::ComponentSwizzle::eG,
+											  vk::ComponentSwizzle::eB, vk::ComponentSwizzle::eA);
+
+		vk::ImageSubresourceRange subResourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1);
+
+		for (auto image : images) {
+			vk::ImageViewCreateInfo imageViewCreateInfo(vk::ImageViewCreateFlags(), image,
+														vk::ImageViewType::e2D, swapchain.m_Format,
+														componentMapping, subResourceRange);
+
+			imageViews.push_back(
+				getCore().getContext().getDevice().createImageView(imageViewCreateInfo));
+		}
+
+		return imageViews;
+	}
+} // namespace vkcv
\ No newline at end of file
diff --git a/src/vkcv/SwapchainManager.hpp b/src/vkcv/SwapchainManager.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..27262ec65da2b841597347c965fa462fae6a0d4c
--- /dev/null
+++ b/src/vkcv/SwapchainManager.hpp
@@ -0,0 +1,159 @@
+#pragma once
+/**
+ * @authors Tobias Frisch
+ * @file vkcv/SwapchainManager.hpp
+ * @brief Class to manage the swapchains and their surfaces.
+ */
+
+#include <atomic>
+#include <vector>
+#include <vulkan/vulkan.hpp>
+
+#include "vkcv/Window.hpp"
+
+#include "HandleManager.hpp"
+
+namespace vkcv {
+
+	const uint32_t MIN_SURFACE_SIZE = 2;
+
+	/**
+	 * @brief Structure to handle swapchains.
+	 */
+	struct SwapchainEntry {
+		vk::SwapchainKHR m_Swapchain;
+		bool m_RecreationRequired;
+
+		vk::SurfaceKHR m_Surface;
+		uint32_t m_PresentQueueIndex;
+		vk::Extent2D m_Extent;
+		vk::Format m_Format;
+		vk::ColorSpaceKHR m_ColorSpace;
+	};
+
+	/**
+	 * @brief Class to manage the creation, destruction and
+	 * allocation of swapchains.
+	 */
+	class SwapchainManager : public HandleManager<SwapchainEntry, SwapchainHandle> {
+		friend class Core;
+
+	private:
+		[[nodiscard]] uint64_t getIdFrom(const SwapchainHandle &handle) const override;
+
+		[[nodiscard]] SwapchainHandle createById(uint64_t id,
+												 const HandleDestroyFunction &destroy) override;
+
+		/**
+		 * @brief Destroys a specific swapchain by a given id
+		 *
+		 * @param[in] id ID of the swapchain to be destroyed
+		 */
+		void destroyById(uint64_t id) override;
+
+	public:
+		SwapchainManager() noexcept;
+
+		/**
+		 * destroys every swapchain
+		 */
+		~SwapchainManager() noexcept override;
+
+		/**
+		 * creates a swapchain and returns the handle
+		 * @param window of the to  creatable window
+		 * @return the swapchainHandle of the created swapchain
+		 */
+		SwapchainHandle createSwapchain(Window &window);
+
+		/**
+		 * @param handle of the swapchain to get
+		 * @return the reference of the swapchain
+		 */
+		[[nodiscard]] SwapchainEntry &getSwapchain(const SwapchainHandle &handle);
+
+		/**
+		 * @brief Checks whether the swapchain needs to be recreated.
+		 *
+		 * @param[in] handle Swapchain handle
+		 * @return True, if the swapchain should be updated,
+		 * otherwise false.
+		 */
+		bool shouldUpdateSwapchain(const SwapchainHandle &handle) const;
+
+		/**
+		 * @brief Updates and recreates the swapchain.
+		 *
+		 * @param[in] handle Swapchain handle
+		 * @param[in] window that the new swapchain gets bound to
+		 */
+		void updateSwapchain(const SwapchainHandle &handle, const Window &window);
+
+		/**
+		 * @brief Signals the swapchain to be recreated.
+		 *
+		 * @param[in] handle Swapchain handle
+		 */
+		void signalRecreation(const SwapchainHandle &handle);
+
+		/**
+		 * @brief Returns the image format for the current surface
+		 * of the swapchain.
+		 *
+		 * @param[in] handle Swapchain handle
+		 * @return Swapchain image format
+		 */
+		[[nodiscard]] vk::Format getFormat(const SwapchainHandle &handle) const;
+
+		/**
+		 * @brief Returns the amount of images for the swapchain.
+		 *
+		 * @param[in] handle Swapchain handle
+		 * @return Number of images
+		 */
+		uint32_t getImageCount(const SwapchainHandle &handle) const;
+
+		/**
+		 * @brief Returns the extent from the current surface of
+		 * the swapchain.
+		 *
+		 * @param[in] handle Swapchain handle
+		 * @return Extent of the swapchains surface
+		 */
+		[[nodiscard]] const vk::Extent2D &getExtent(const SwapchainHandle &handle) const;
+
+		/**
+		 * @brief Returns the present queue index to be used with
+		 * the swapchain and its current surface.
+		 *
+		 * @param[in] handle Swapchain handle
+		 * @return Present queue family index
+		 */
+		[[nodiscard]] uint32_t getPresentQueueIndex(const SwapchainHandle &handle) const;
+
+		/**
+		 * @brief Returns the color space of the surface from
+		 * a swapchain.
+		 *
+		 * @param[in] handle Swapchain handle
+		 * @return Color space
+		 */
+		[[nodiscard]] vk::ColorSpaceKHR getSurfaceColorSpace(const SwapchainHandle &handle) const;
+
+		/**
+		 * gets the swapchain images
+		 * @param handle of the swapchain
+		 * @return a vector of the swapchain images
+		 */
+		[[nodiscard]] std::vector<vk::Image>
+		getSwapchainImages(const SwapchainHandle &handle) const;
+
+		/**
+		 * creates the swapchain imageViews for the swapchain
+		 * @param handle of the swapchain which ImageViews should be created
+		 * @return a ov ImageViews of the swapchain
+		 */
+		[[nodiscard]] std::vector<vk::ImageView> createSwapchainImageViews(SwapchainHandle &handle);
+	};
+
+} // namespace vkcv
\ No newline at end of file
diff --git a/src/vkcv/SyncResources.cpp b/src/vkcv/SyncResources.cpp
deleted file mode 100644
index 9c27fe32452e0ae648565020d92891764ececb3f..0000000000000000000000000000000000000000
--- a/src/vkcv/SyncResources.cpp
+++ /dev/null
@@ -1,33 +0,0 @@
-#include "vkcv/SyncResources.hpp"
-
-namespace vkcv {
-	SyncResources createSyncResources(const vk::Device& device) {
-		SyncResources resources;
-
-		const vk::SemaphoreCreateFlags semaphoreFlags = vk::SemaphoreCreateFlagBits();
-		const vk::SemaphoreCreateInfo semaphoreInfo(semaphoreFlags);
-		resources.renderFinished			= device.createSemaphore(semaphoreInfo, nullptr, {});
-		resources.swapchainImageAcquired	= device.createSemaphore(semaphoreInfo);
-
-		resources.presentFinished			= createFence(device);
-		
-		return resources;
-	}
-
-	void destroySyncResources(const vk::Device& device, const SyncResources& resources) {
-		device.destroySemaphore(resources.renderFinished);
-		device.destroySemaphore(resources.swapchainImageAcquired);
-		device.destroyFence(resources.presentFinished);
-	}
-
-	vk::Fence createFence(const vk::Device& device) {
-		const vk::FenceCreateFlags fenceFlags = vk::FenceCreateFlagBits();
-		vk::FenceCreateInfo fenceInfo(fenceFlags);
-		return device.createFence(fenceInfo, nullptr, {});
-	}
-
-	void waitForFence(const vk::Device& device, const vk::Fence fence) {
-		const auto result = device.waitForFences(fence, true, UINT64_MAX);
-		assert(result == vk::Result::eSuccess);
-	}
-}
\ No newline at end of file
diff --git a/src/vkcv/TypeGuard.cpp b/src/vkcv/TypeGuard.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..742071bb7b8d055ff1e3037a825052b0f24ae84a
--- /dev/null
+++ b/src/vkcv/TypeGuard.cpp
@@ -0,0 +1,84 @@
+#include <vkcv/TypeGuard.hpp>
+
+#include <string.h>
+#include <vkcv/Logger.hpp>
+
+namespace vkcv {
+
+#ifndef NDEBUG
+	bool TypeGuard::checkType(const char* name, size_t hash, size_t size) const {
+		if (!checkTypeSize(size)) {
+			return false;
+		}
+
+		if ((!m_typeName) || (!name)) {
+			return true;
+		}
+
+		if (m_typeHash != hash) {
+			vkcv_log(LogLevel::WARNING,
+					 "Hash (%lu) does not match the specified hash of the type guard (%lu)", hash,
+					 m_typeHash);
+
+			return false;
+		}
+
+		if (strcmp(m_typeName, name) != 0) {
+			vkcv_log(LogLevel::WARNING,
+					 "Name (%s) does not match the specified name of the type guard (%s)", name,
+					 m_typeName);
+
+			return false;
+		} else {
+			return true;
+		}
+	}
+#endif
+
+	bool TypeGuard::checkTypeSize(size_t size) const {
+		if (m_typeSize != size) {
+			vkcv_log(LogLevel::WARNING,
+					 "Size (%lu) does not match the specified size of the type guard (%lu)", size,
+					 m_typeSize);
+
+			return false;
+		} else {
+			return true;
+		}
+	}
+
+	TypeGuard::TypeGuard(size_t size) :
+#ifndef NDEBUG
+		m_typeName(nullptr), m_typeHash(0),
+#endif
+		m_typeSize(size) {
+	}
+
+	TypeGuard::TypeGuard(const std::type_info &info, size_t size) :
+#ifndef NDEBUG
+		m_typeName(info.name()), m_typeHash(info.hash_code()),
+#endif
+		m_typeSize(size) {
+	}
+
+	bool TypeGuard::operator==(const TypeGuard &other) const {
+#ifndef NDEBUG
+		return checkType(other.m_typeName, other.m_typeHash, other.m_typeSize);
+#else
+		return checkTypeSize(other.m_typeSize);
+#endif
+	}
+
+	bool TypeGuard::operator!=(const TypeGuard &other) const {
+#ifndef NDEBUG
+		return !checkType(other.m_typeName, other.m_typeHash, other.m_typeSize);
+#else
+		return !checkTypeSize(other.m_typeSize);
+#endif
+	}
+
+	size_t TypeGuard::typeSize() const {
+		return m_typeSize;
+	}
+
+} // namespace vkcv
diff --git a/src/vkcv/VertexData.cpp b/src/vkcv/VertexData.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a1fcfb3692fcc6051e237e759ce29c38e4c688f4
--- /dev/null
+++ b/src/vkcv/VertexData.cpp
@@ -0,0 +1,41 @@
+
+#include "vkcv/VertexData.hpp"
+
+namespace vkcv {
+
+	VertexBufferBinding vertexBufferBinding(const BufferHandle &buffer, size_t offset) {
+		VertexBufferBinding binding;
+		binding.buffer = buffer;
+		binding.offset = offset;
+		return binding;
+	}
+
+	VertexData::VertexData(const std::vector<VertexBufferBinding> &bindings) :
+		m_bindings(bindings), m_indices(), m_indexBitCount(IndexBitCount::Bit16), m_count(0) {}
+
+	const std::vector<VertexBufferBinding> &VertexData::getVertexBufferBindings() const {
+		return m_bindings;
+	}
+
+	void VertexData::setIndexBuffer(const BufferHandle &indices, IndexBitCount indexBitCount) {
+		m_indices = indices;
+		m_indexBitCount = indexBitCount;
+	}
+
+	const BufferHandle &VertexData::getIndexBuffer() const {
+		return m_indices;
+	}
+
+	IndexBitCount VertexData::getIndexBitCount() const {
+		return m_indexBitCount;
+	}
+
+	void VertexData::setCount(size_t count) {
+		m_count = count;
+	}
+
+	size_t VertexData::getCount() const {
+		return m_count;
+	}
+
+} // namespace vkcv
diff --git a/src/vkcv/VertexLayout.cpp b/src/vkcv/VertexLayout.cpp
index fa079a3264ae47b32461bda26485adb97b0be280..27347019a711a252c7e0b7c91232410e0b49fc1f 100644
--- a/src/vkcv/VertexLayout.cpp
+++ b/src/vkcv/VertexLayout.cpp
@@ -6,57 +6,59 @@
 #include "vkcv/Logger.hpp"
 
 namespace vkcv {
-    uint32_t getFormatSize(VertexAttachmentFormat format) {
-        switch (format) {
-            case VertexAttachmentFormat::FLOAT:
-                return 4;
-            case VertexAttachmentFormat::FLOAT2:
-                return 8;
-            case VertexAttachmentFormat::FLOAT3:
-                return 12;
-            case VertexAttachmentFormat::FLOAT4:
-                return 16;
-            case VertexAttachmentFormat::INT:
-                return 4;
-            case VertexAttachmentFormat::INT2:
-                return 8;
-            case VertexAttachmentFormat::INT3:
-                return 12;
-            case VertexAttachmentFormat::INT4:
-                return 16;
-            default:
-				vkcv_log(LogLevel::WARNING, "No format given");
-                return 0;
-        }
-    }
-
-    VertexAttachment::VertexAttachment(uint32_t inputLocation, const std::string &name, VertexAttachmentFormat format) noexcept:
-            inputLocation{inputLocation},
-            name{name},
-            format{format},
-            offset{0}
-    {}
-
-
-    VertexBinding::VertexBinding(uint32_t bindingLocation, const std::vector<VertexAttachment> &attachments) noexcept :
-    bindingLocation{bindingLocation},
-    stride{0},
-    vertexAttachments{attachments}
-    {
-        uint32_t offset = 0;
-        for (auto &attachment : vertexAttachments)
-        {
-            offset += getFormatSize(attachment.format);
-            attachment.offset = offset;
-        }
-        stride = offset;
-    }
-
-    VertexLayout::VertexLayout() noexcept :
-    vertexBindings{}
-    {}
-
-    VertexLayout::VertexLayout(const std::vector<VertexBinding> &bindings) noexcept :
-    vertexBindings{bindings}
-    {}
-}
\ No newline at end of file
+
+	uint32_t getFormatSize(VertexAttachmentFormat format) {
+		switch (format) {
+		case VertexAttachmentFormat::FLOAT:
+			return 4;
+		case VertexAttachmentFormat::FLOAT2:
+			return 8;
+		case VertexAttachmentFormat::FLOAT3:
+			return 12;
+		case VertexAttachmentFormat::FLOAT4:
+			return 16;
+		case VertexAttachmentFormat::INT:
+			return 4;
+		case VertexAttachmentFormat::INT2:
+			return 8;
+		case VertexAttachmentFormat::INT3:
+			return 12;
+		case VertexAttachmentFormat::INT4:
+			return 16;
+		default:
+			vkcv_log(LogLevel::WARNING, "No format given");
+			return 0;
+		}
+	}
+
+	VertexBinding createVertexBinding(uint32_t bindingLocation,
+									  const VertexAttachments &attachments) {
+		VertexBinding binding { bindingLocation, 0, attachments };
+		uint32_t offset = 0;
+
+		for (auto &attachment : binding.vertexAttachments) {
+			attachment.offset = offset;
+			offset += getFormatSize(attachment.format);
+		}
+
+		binding.stride = offset;
+		return binding;
+	}
+
+	VertexBindings createVertexBindings(const VertexAttachments &attachments) {
+		VertexBindings bindings;
+		bindings.reserve(attachments.size());
+
+		for (const auto& attachment : attachments) {
+			bindings.push_back(createVertexBinding(attachment.inputLocation, { attachment }));
+		}
+
+		return bindings;
+	}
+	
+	VertexLayout createVertexLayout(const VertexBindings &bindings) {
+		VertexLayout layout { bindings };
+		return layout;
+	}
+	
+} // namespace vkcv
\ No newline at end of file
diff --git a/src/vkcv/Window.cpp b/src/vkcv/Window.cpp
index 03a58a23b994209c7a0ee195732dc98543f0eddc..f989f1b84014aaca0a703e57d27151faa960a8bd 100644
--- a/src/vkcv/Window.cpp
+++ b/src/vkcv/Window.cpp
@@ -1,76 +1,153 @@
-/**
- * @authors Sebastian Gaida
- * @file src/vkcv/Window.cpp
- * @brief Window class to handle a basic rendering surface and input
- */
 
 #include <GLFW/glfw3.h>
+#include <thread>
+#include <vector>
+
 #include "vkcv/Window.hpp"
 
 namespace vkcv {
 
+	void Window_onMouseButtonEvent(GLFWwindow* callbackWindow, int button, int action, int mods) {
+		auto window = static_cast<Window*>(glfwGetWindowUserPointer(callbackWindow));
+
+		if (window != nullptr) {
+			window->e_mouseButton(button, action, mods);
+		}
+	}
+
+	void Window_onMouseMoveEvent(GLFWwindow* callbackWindow, double x, double y) {
+		auto window = static_cast<Window*>(glfwGetWindowUserPointer(callbackWindow));
+
+		if (window != nullptr) {
+			window->e_mouseMove(x, y);
+		}
+	}
+
+	void Window_onMouseScrollEvent(GLFWwindow* callbackWindow, double xoffset, double yoffset) {
+		auto window = static_cast<Window*>(glfwGetWindowUserPointer(callbackWindow));
+
+		if (window != nullptr) {
+			window->e_mouseScroll(xoffset, yoffset);
+		}
+	}
+
+	void Window_onResize(GLFWwindow* callbackWindow, int width, int height) {
+		auto window = static_cast<Window*>(glfwGetWindowUserPointer(callbackWindow));
+
+		if (window != nullptr) {
+			window->e_resize(width, height);
+		}
+	}
+
+	void Window_onKeyEvent(GLFWwindow* callbackWindow, int key, int scancode, int action,
+						   int mods) {
+		auto window = static_cast<Window*>(glfwGetWindowUserPointer(callbackWindow));
+
+		if (window != nullptr) {
+			window->e_key(key, scancode, action, mods);
+		}
+	}
+
+	void Window_onCharEvent(GLFWwindow* callbackWindow, unsigned int c) {
+		auto window = static_cast<Window*>(glfwGetWindowUserPointer(callbackWindow));
+
+		if (window != nullptr) {
+			window->e_char(c);
+		}
+	}
+
 	static std::vector<GLFWwindow*> s_Windows;
 
-    Window::Window(GLFWwindow *window) :
-    m_window(window),
-	e_mouseButton(true),
-	e_mouseMove(true),
-	e_mouseScroll(true),
-	e_resize(true),
-	e_key(true),
-	e_char(true),
-	e_gamepad(true)
-    {
-		glfwSetWindowUserPointer(m_window, this);
-	
-		// combine Callbacks with Events
-		glfwSetMouseButtonCallback(m_window, Window::onMouseButtonEvent);
-		glfwSetCursorPosCallback(m_window, Window::onMouseMoveEvent);
-		glfwSetWindowSizeCallback(m_window, Window::onResize);
-		glfwSetKeyCallback(m_window, Window::onKeyEvent);
-		glfwSetScrollCallback(m_window, Window::onMouseScrollEvent);
-		glfwSetCharCallback(m_window, Window::onCharEvent);
-    }
-
-    Window::~Window() {
-        Window::e_mouseButton.unlock();
-        Window::e_mouseMove.unlock();
-        Window::e_mouseScroll.unlock();
-        Window::e_resize.unlock();
-        Window::e_key.unlock();
-        Window::e_char.unlock();
-        Window::e_gamepad.unlock();
-
-		s_Windows.erase(std::find(s_Windows.begin(), s_Windows.end(), m_window));
-        glfwDestroyWindow(m_window);
-
-        if(s_Windows.empty()) {
-            glfwTerminate();
-        }
-    }
-
-    Window Window::create( const char *windowTitle, int width, int height, bool resizable) {
-		if(s_Windows.empty()) {
+	void Window_onGamepadEvent(int gamepadIndex) {
+		Window::getFocusedWindow().e_gamepad(gamepadIndex);
+	}
+
+	static GLFWwindow* createGLFWWindow(const char* windowTitle, int width, int height,
+										bool resizable) {
+		if (s_Windows.empty()) {
 			glfwInit();
 		}
-	
+
 		width = std::max(width, 1);
 		height = std::max(height, 1);
-	
+
+		glfwDefaultWindowHints();
 		glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
 		glfwWindowHint(GLFW_RESIZABLE, resizable ? GLFW_TRUE : GLFW_FALSE);
-		GLFWwindow *window = glfwCreateWindow(width, height, windowTitle, nullptr, nullptr);
-	
-		s_Windows.push_back(window);
-	
-		return Window(window);
-    }
-
-    void Window::pollEvents() {
-
-    	for (auto glfwWindow : s_Windows) {
-			auto window = static_cast<Window *>(glfwGetWindowUserPointer(glfwWindow));
-			
+
+		GLFWwindow* window = glfwCreateWindow(width, height, windowTitle, nullptr, nullptr);
+
+		if (window) {
+			s_Windows.push_back(window);
+		}
+
+		return window;
+	}
+
+	static void bindGLFWWindow(GLFWwindow* windowHandle, Window* window) {
+		if (!windowHandle) {
+			return;
+		}
+
+		glfwSetWindowUserPointer(windowHandle, window);
+
+		// combine Callbacks with Events
+		glfwSetMouseButtonCallback(windowHandle, Window_onMouseButtonEvent);
+		glfwSetCursorPosCallback(windowHandle, Window_onMouseMoveEvent);
+		glfwSetWindowSizeCallback(windowHandle, Window_onResize);
+		glfwSetKeyCallback(windowHandle, Window_onKeyEvent);
+		glfwSetScrollCallback(windowHandle, Window_onMouseScrollEvent);
+		glfwSetCharCallback(windowHandle, Window_onCharEvent);
+	}
+
+	Window::Window() :
+		m_title(), m_resizable(false), m_shouldClose(false), m_window(nullptr), e_mouseButton(true),
+		e_mouseMove(true), e_mouseScroll(true), e_resize(true), e_key(true), e_char(true),
+		e_gamepad(true) {}
+
+	Window::Window(const std::string &title, int width, int height, bool resizable) :
+		m_title(title), m_resizable(resizable), m_shouldClose(false),
+		m_window(createGLFWWindow(title.c_str(), width, height, resizable)), e_mouseButton(true),
+		e_mouseMove(true), e_mouseScroll(true), e_resize(true), e_key(true), e_char(true),
+		e_gamepad(true) {
+		bindGLFWWindow(m_window, this);
+	}
+
+	Window::~Window() {
+		Window::e_mouseButton.unlock();
+		Window::e_mouseMove.unlock();
+		Window::e_mouseScroll.unlock();
+		Window::e_resize.unlock();
+		Window::e_key.unlock();
+		Window::e_char.unlock();
+		Window::e_gamepad.unlock();
+		Window::e_resize.remove(m_resizeHandle);
+		if (m_window) {
+			s_Windows.erase(std::find(s_Windows.begin(), s_Windows.end(), m_window));
+			glfwDestroyWindow(m_window);
+		}
+
+		if (s_Windows.empty()) {
+			glfwTerminate();
+		}
+	}
+
+	bool Window::hasOpenWindow() {
+		for (auto glfwWindow : s_Windows) {
+			auto window = static_cast<Window*>(glfwGetWindowUserPointer(glfwWindow));
+
+			if (window->isOpen()) {
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+	void Window::pollEvents() {
+		for (auto glfwWindow : s_Windows) {
+			auto window = static_cast<Window*>(glfwGetWindowUserPointer(glfwWindow));
+
 			window->e_mouseButton.unlock();
 			window->e_mouseMove.unlock();
 			window->e_mouseScroll.unlock();
@@ -78,19 +155,20 @@ namespace vkcv {
 			window->e_key.unlock();
 			window->e_char.unlock();
 			window->e_gamepad.unlock();
-    	}
+		}
+
+		glfwPollEvents();
 
-        glfwPollEvents();
-    	
-    	for (int gamepadIndex = GLFW_JOYSTICK_1; gamepadIndex <= GLFW_JOYSTICK_LAST; gamepadIndex++) {
-    		if (glfwJoystickPresent(gamepadIndex)) {
-				onGamepadEvent(gamepadIndex);
+		for (int gamepadIndex = GLFW_JOYSTICK_1; gamepadIndex <= GLFW_JOYSTICK_LAST;
+			 gamepadIndex++) {
+			if (glfwJoystickPresent(gamepadIndex)) {
+				Window_onGamepadEvent(gamepadIndex);
 			}
 		}
-	
+
 		for (auto glfwWindow : s_Windows) {
-			auto window = static_cast<Window *>(glfwGetWindowUserPointer(glfwWindow));
-		
+			auto window = static_cast<Window*>(glfwGetWindowUserPointer(glfwWindow));
+
 			window->e_mouseButton.lock();
 			window->e_mouseMove.lock();
 			window->e_mouseScroll.lock();
@@ -98,87 +176,99 @@ namespace vkcv {
 			window->e_key.lock();
 			window->e_char.lock();
 			window->e_gamepad.lock();
+
+			window->m_shouldClose |= glfwWindowShouldClose(glfwWindow);
+		}
+	}
+
+	const std::vector<std::string> &Window::getExtensions() {
+		static std::vector<std::string> extensions;
+
+		if (extensions.empty()) {
+			if (s_Windows.empty()) {
+				glfwInit();
+			}
+
+			uint32_t glfwExtensionCount = 0;
+			const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
+
+			for (uint32_t i = 0; i < glfwExtensionCount; i++) {
+				extensions.emplace_back(glfwExtensions [i]);
+			}
+
+			if (s_Windows.empty()) {
+				glfwTerminate();
+			}
+		}
+
+		return extensions;
+	}
+
+	bool Window::isOpen() const {
+		if (!m_window) {
+			return false;
 		}
-    }
 
-    void Window::onMouseButtonEvent(GLFWwindow *callbackWindow, int button, int action, int mods) {
-        auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
+		return !m_shouldClose;
+	}
 
-        if (window != nullptr) {
-            window->e_mouseButton(button, action, mods);
-        }
-    }
+	const std::string &Window::getTitle() const {
+		return m_title;
+	}
 
-    void Window::onMouseMoveEvent(GLFWwindow *callbackWindow, double x, double y) {
-        auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
+	int Window::getWidth() const {
+		int width = 0;
 
-        if (window != nullptr) {
-            window->e_mouseMove(x, y);
-        }
-    }
+		if (m_window) {
+			glfwGetWindowSize(m_window, &width, nullptr);
+		}
 
-    void Window::onMouseScrollEvent(GLFWwindow *callbackWindow, double xoffset, double yoffset) {
-        auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
+		return std::max(width, 1);
+	}
 
-        if (window != nullptr) {
-            window->e_mouseScroll(xoffset, yoffset);
-        }
-    }
+	int Window::getHeight() const {
+		int height = 0;
 
-    void Window::onResize(GLFWwindow *callbackWindow, int width, int height) {
-        auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
+		if (m_window) {
+			glfwGetWindowSize(m_window, nullptr, &height);
+		}
 
-        if (window != nullptr) {
-            window->e_resize(width, height);
-        }
-    }
+		return std::max(height, 1);
+	}
 
-    void Window::onKeyEvent(GLFWwindow *callbackWindow, int key, int scancode, int action, int mods) {
-        auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
+	bool Window::isResizable() const {
+		return m_resizable;
+	}
 
-        if (window != nullptr) {
-            window->e_key(key, scancode, action, mods);
-        }
-    }
-    
-    void Window::onCharEvent(GLFWwindow *callbackWindow, unsigned int c) {
-		auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
-	
-		if (window != nullptr) {
-			window->e_char(c);
+	GLFWwindow* Window::getWindow() const {
+		return m_window;
+	}
+
+	void Window::getFramebufferSize(int &width, int &height) const {
+		if (m_window) {
+			glfwGetFramebufferSize(m_window, &width, &height);
+		} else {
+			width = 0;
+			height = 0;
 		}
-    }
-
-    void Window::onGamepadEvent(int gamepadIndex) {
-        int activeWindowIndex = std::find_if(s_Windows.begin(),
-                                             s_Windows.end(),
-                                             [](GLFWwindow* window){return glfwGetWindowAttrib(window, GLFW_FOCUSED);})
-                                - s_Windows.begin();
-        activeWindowIndex *= (activeWindowIndex < s_Windows.size());    // fixes index getting out of bounds (e.g. if there is no focused window)
-        auto window = static_cast<Window *>(glfwGetWindowUserPointer(s_Windows[activeWindowIndex]));
-
-        if (window != nullptr) {
-            window->e_gamepad(gamepadIndex);
-        }
-    }
-
-    bool Window::isWindowOpen() const {
-        return !glfwWindowShouldClose(m_window);
-    }
-
-    int Window::getWidth() const {
-        int width;
-        glfwGetWindowSize(m_window, &width, nullptr);
-        return width;
-    }
-
-    int Window::getHeight() const {
-        int height;
-        glfwGetWindowSize(m_window, nullptr, &height);
-        return height;
-    }
-
-    GLFWwindow *Window::getWindow() const {
-        return m_window;
-    }
-}
+	}
+
+	Window &Window::getFocusedWindow() {
+		static Window defaultWindow;
+
+		auto activeWindowIterator =
+			std::find_if(s_Windows.begin(), s_Windows.end(), [](GLFWwindow* window) {
+				return glfwGetWindowAttrib(window, GLFW_FOCUSED);
+			});
+
+		if (activeWindowIterator == s_Windows.end()) {
+			return defaultWindow;
+		}
+		Window &window = *static_cast<Window*>(glfwGetWindowUserPointer(*activeWindowIterator));
+		return window;
+	}
+
+	SwapchainHandle Window::getSwapchain() const {
+		return m_swapchainHandle;
+	}
+} // namespace vkcv
diff --git a/src/vkcv/WindowManager.cpp b/src/vkcv/WindowManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..058ee21933ee00df49bd158ded4e8a38979f67c3
--- /dev/null
+++ b/src/vkcv/WindowManager.cpp
@@ -0,0 +1,66 @@
+#include "WindowManager.hpp"
+
+namespace vkcv {
+
+	uint64_t WindowManager::getIdFrom(const WindowHandle &handle) const {
+		return handle.getId();
+	}
+
+	WindowHandle WindowManager::createById(uint64_t id, const HandleDestroyFunction &destroy) {
+		return WindowHandle(id, destroy);
+	}
+
+	void WindowManager::destroyById(uint64_t id) {
+		auto &window = getById(id);
+
+		if (window) {
+			delete window;
+			window = nullptr;
+		}
+	}
+
+	WindowManager::WindowManager() noexcept : HandleManager<Window*, WindowHandle>() {}
+
+	WindowManager::~WindowManager() noexcept {
+		clear();
+	}
+
+	WindowHandle WindowManager::createWindow(SwapchainManager &swapchainManager,
+											 const std::string &applicationName,
+											 uint32_t windowWidth, uint32_t windowHeight,
+											 bool resizeable) {
+		auto window = new Window(applicationName, static_cast<int>(windowWidth),
+								 static_cast<int>(windowHeight), resizeable);
+
+		SwapchainHandle swapchainHandle = swapchainManager.createSwapchain(*window);
+
+		if (resizeable) {
+			const event_handle<int, int> &resizeHandle =
+				window->e_resize.add([&, handle = swapchainHandle](int width, int height) {
+					// copy handle because it would run out of scope and be invalid
+					swapchainManager.signalRecreation(handle);
+				});
+
+			window->m_resizeHandle = resizeHandle;
+		}
+
+		return add(window);
+	}
+
+	Window &WindowManager::getWindow(const WindowHandle &handle) const {
+		return *(*this) [handle];
+	}
+
+	std::vector<WindowHandle> WindowManager::getWindowHandles() const {
+		std::vector<WindowHandle> handles;
+
+		for (size_t id = 0; id < getCount(); id++) {
+			if (getById(id)->isOpen()) {
+				handles.push_back(WindowHandle(id));
+			}
+		}
+
+		return handles;
+	}
+
+} // namespace vkcv
\ No newline at end of file
diff --git a/src/vkcv/WindowManager.hpp b/src/vkcv/WindowManager.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..71b107c71baca467b035cc326abc7e2d488ad586
--- /dev/null
+++ b/src/vkcv/WindowManager.hpp
@@ -0,0 +1,70 @@
+#pragma once
+
+#include <GLFW/glfw3.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "vkcv/Window.hpp"
+
+#include "HandleManager.hpp"
+#include "SwapchainManager.hpp"
+
+namespace vkcv {
+
+	/**
+	 * @brief Class to manage the windows of applications.
+	 */
+	class WindowManager : public HandleManager<Window*, WindowHandle> {
+		friend class Core;
+
+	private:
+		[[nodiscard]] uint64_t getIdFrom(const WindowHandle &handle) const override;
+
+		[[nodiscard]] WindowHandle createById(uint64_t id,
+											  const HandleDestroyFunction &destroy) override;
+
+		/**
+		 * Destroys a specific window by a given id.
+		 *
+		 * @param[in] id ID of the window to be destroyed
+		 */
+		void destroyById(uint64_t id) override;
+
+	public:
+		WindowManager() noexcept;
+
+		/**
+		 * destroys every window
+		 */
+		~WindowManager() noexcept override;
+
+		/**
+		 * creates a window and returns it's  handle
+		 * @param swapchainManager for swapchain creation
+		 * @param applicationName name of the window
+		 * @param windowWidth
+		 * @param windowHeight
+		 * @param resizeable if the window is resizable
+		 * @return window handle
+		 */
+		WindowHandle createWindow(SwapchainManager &swapchainManager,
+								  const std::string &applicationName, uint32_t windowWidth,
+								  uint32_t windowHeight, bool resizeable);
+
+		/**
+		 * @param handle of the window to get
+		 * @return the reference of the window
+		 */
+		[[nodiscard]] Window &getWindow(const WindowHandle &handle) const;
+
+		/**
+		 * Returns a list of window handles for current active
+		 * and open windows.
+		 *
+		 * @return List of window handles
+		 */
+		[[nodiscard]] std::vector<WindowHandle> getWindowHandles() const;
+	};
+
+} // namespace vkcv
\ No newline at end of file