Showing preview only (555K chars total). Download the full file or copy to clipboard to get everything.
Repository: hyperlogic/splatapult
Branch: main
Commit: e87960fdf4a5
Files: 96
Total size: 525.8 KB
Directory structure:
gitextract_t4nb5sit/
├── .github/
│ └── workflows/
│ └── build.yaml
├── .gitignore
├── .gitmodules
├── BUILD.md
├── CMakeLists.txt
├── LICENSE
├── README.md
├── data/
│ ├── test.ply
│ └── test_vr.json
├── font/
│ └── JetBrainsMono-Medium.json
├── meta-quest/
│ ├── cp_from_sdk
│ └── splatapult/
│ ├── .gitignore
│ ├── Projects/
│ │ └── Android/
│ │ ├── AndroidManifest.xml
│ │ ├── build.bat
│ │ ├── build.gradle
│ │ ├── build.py
│ │ ├── gradle.properties
│ │ ├── jni/
│ │ │ ├── Android.mk
│ │ │ └── Application.mk
│ │ └── settings.gradle
│ ├── assets/
│ │ ├── .gitignore
│ │ └── donotedelete.txt
│ └── java/
│ └── com/
│ └── oculus/
│ └── NativeActivity.java
├── shader/
│ ├── carpet_frag.glsl
│ ├── carpet_vert.glsl
│ ├── debugdraw_frag.glsl
│ ├── debugdraw_vert.glsl
│ ├── desktop_frag.glsl
│ ├── desktop_vert.glsl
│ ├── multi_radixsort.glsl
│ ├── multi_radixsort_histograms.glsl
│ ├── point_frag.glsl
│ ├── point_geom.glsl
│ ├── point_vert.glsl
│ ├── presort_compute.glsl
│ ├── single_radixsort.glsl
│ ├── splat_frag.glsl
│ ├── splat_geom.glsl
│ ├── splat_peel_frag.glsl
│ ├── splat_vert.glsl
│ ├── text_frag.glsl
│ └── text_vert.glsl
├── src/
│ ├── android_main.cpp
│ ├── app.cpp
│ ├── app.h
│ ├── camerapathrenderer.cpp
│ ├── camerapathrenderer.h
│ ├── camerasconfig.cpp
│ ├── camerasconfig.h
│ ├── core/
│ │ ├── binaryattribute.cpp
│ │ ├── binaryattribute.h
│ │ ├── debugrenderer.cpp
│ │ ├── debugrenderer.h
│ │ ├── framebuffer.cpp
│ │ ├── framebuffer.h
│ │ ├── image.cpp
│ │ ├── image.h
│ │ ├── inputbuddy.cpp
│ │ ├── inputbuddy.h
│ │ ├── log.cpp
│ │ ├── log.h
│ │ ├── optionparser.h
│ │ ├── program.cpp
│ │ ├── program.h
│ │ ├── statemachine.h
│ │ ├── textrenderer.cpp
│ │ ├── textrenderer.h
│ │ ├── texture.cpp
│ │ ├── texture.h
│ │ ├── util.cpp
│ │ ├── util.h
│ │ ├── vertexbuffer.cpp
│ │ ├── vertexbuffer.h
│ │ ├── xrbuddy.cpp
│ │ └── xrbuddy.h
│ ├── flycam.cpp
│ ├── flycam.h
│ ├── gaussiancloud.cpp
│ ├── gaussiancloud.h
│ ├── magiccarpet.cpp
│ ├── magiccarpet.h
│ ├── maincontext.h
│ ├── ply.cpp
│ ├── ply.h
│ ├── pointcloud.cpp
│ ├── pointcloud.h
│ ├── pointrenderer.cpp
│ ├── pointrenderer.h
│ ├── radix_sort.hpp
│ ├── sdl_main.cpp
│ ├── splatrenderer.cpp
│ ├── splatrenderer.h
│ ├── vrconfig.cpp
│ └── vrconfig.h
├── tasks.py
└── vcpkg.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/build.yaml
================================================
name: Linux/Windows Build
run-name: ${{ github.actor }} is building splatapult
on: [push]
jobs:
build-linux:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: true
- name: Install System Packages
uses: ConorMacBride/install-package@v1
with:
apt: libxmu-dev libxi-dev libgl-dev libxcb-glx0-dev libglu1-mesa-dev libxxf86vm-dev libxrandr-dev
- name: Install CMake
uses: lukka/get-cmake@v3.29.0
- name: Create Build Dir
run: mkdir build
- name: CMake Configure
working-directory: build
run: cmake -DSHIPPING=ON -DCMAKE_TOOLCHAIN_FILE="../vcpkg/scripts/buildsystems/vcpkg.cmake" ..
- name: CMake Build
working-directory: build
run: cmake --build . --config=Release
build-windows:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: true
- name: Get Microsoft Visual Studio for C++
uses: ilammy/msvc-dev-cmd@v1
- name: Install CMake
uses: lukka/get-cmake@v3.29.0
- name: Create Build Dir
run: mkdir build
- name: CMake Configure
working-directory: build
run: cmake -DSHIPPING=ON -DCMAKE_TOOLCHAIN_FILE="../vcpkg/scripts/buildsystems/vcpkg.cmake" ..
- name: CMake Build
working-directory: build
run: cmake --build . --config=Release
- name: Upload Artifiacts
uses: actions/upload-artifact@v4
with:
name: windows-build
path: build/Release
================================================
FILE: .gitignore
================================================
build/
meta-quest/ovr_openxr_mobile_sdk_59.0/
meta-quest/ovr_openxr_mobile_sdk_59.0.zip
data/*
!data/test.ply
!data/test_vr.json
vcpkg_installed/
# ignore archived release zip
splatapult*.zip
================================================
FILE: .gitmodules
================================================
[submodule "vcpkg"]
path = vcpkg
url = https://github.com/microsoft/vcpkg
================================================
FILE: BUILD.md
================================================
Windows Build (vcpkg submodule)
-----------------------
* Install Visual Studio 2022
* Install cmake 3.27.1
* Ensure splatapult has the vcpkg submodule.
Either clone with the --recursive flag so that the vcpkg submodule is added
or execute `git submodule init` and `git submodule update` after a regular clone.
* Bootstrap vcpkg
- `cd vcpkg`
- `bootstrap-vcpg.bat`
* Execute cmake to create a Visual Studio solution file
- `mkdir build`
- `cd build`
- `cmake .. -G "Visual Studio 17 2022" -DCMAKE_TOOLCHAIN_FILE=vcpkg/scripts/buildsystems/vcpkg.cmake`
* Build exe by using Visual Studio by loading splatapult.sln or from the command line:
- `cmake --build . --config=Release`
Windows Shipping Builds
-------------------------
The SHIPPING cmake option is used to create a release version of splataplut.
A post build step will copy all of the the data folders (font, shader, texture) into the build directory.
And the resulting exe will use that copy. You can then zip up the folder and distrubute it to users.
* To create a shipping build:
- `mkdir build`
- `cd build`
- `cmake .. -G "Visual Studio 17 2022" -DCMAKE_TOOLCHAIN_FILE=vcpkg/scripts/buildsystems/vcpkg.cmake -DSHIPPING=ON`
- `cmake --build . --config=Release`
Linux Build (vcpkg submodule)
--------------------
* Install dependencies
- `sudo apt-get install clang`
- `sudo apt-get install cmake`
- `sudo apt-get install freeglut3-dev`
- `sudo apt-get install libopenxr-dev`
* Ensure splatapult has the vcpkg submodule.
Either clone with the --recursive flag so that the vcpkg submodule is added
or execute `git submodule init` and `git submodule update` after a regular clone.
* Bootstrap vcpkg
- `cd vcpkg`
- `bootstrap-vcpg.sh`
* Execute cmake to create a Makefile
- `mkdir build`
- `cd build`
- `cmake .. -G "Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=vcpkg/scripts/buildsystems/vcpkg.cmake`
* build executable
- `cmake --build . --config=Release`
*EXPERIMENTAL* Meta Quest Build (OUT OF DATE)
--------------------
NOTE: Although the quest build functions it is much to slow for most scenes.
A Quest2 headset can only run a scene consisting of 25k splats.
* Use vcpkg to install the following packages:
- `vcpkg install glm:arm64-android`
- `vcpkg install libpng:arm64-android`
- `vcpkg install nlohmann-json:arm64-android`
* Set the following environment var ANDROID_VCPKG_DIR to point to vcpkg/installed/arm64-android.
* Download the [Meta OpenXR Mobile SDK 59.0](https://developer.oculus.com/downloads/package/oculus-openxr-mobile-sdk/)
* Install [Android Studio Bumble Bee, Patch 3](https://developer.android.com/studio/archive)
newer versions do not work with Meta OpenXR Mobile SDK 59.0.
Follow this guide to setup [Android Studio](https://developer.oculus.com/documentation/native/android/mobile-studio-setup-android/)
* Copy the ovr_openxr_mobile_sdk_59.0 dir into the meta-quest dir.
* Copy the meta-quest/splatapult dir to ovr_openxr_mobile_sdk_59.0/XrSamples/splataplut
* Open the ovr_openxr_mobile_sdk_59.0/XrSamples/splatapult in AndroidStudio.
* Sync and Build
================================================
FILE: CMakeLists.txt
================================================
# See BUILD.md for more info
# Windows cheat sheet
# > mkdir build
# > cd build
# > cmake -DSHIPPING=ON -DCMAKE_TOOLCHAIN_FILE="C:\msys64\home\hyperlogic\code\vcpkg\scripts\buildsystems\vcpkg.cmake" ..
# > cmake --build . --config Release
cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
set(PROJECT_NAME splatapult)
project(${PROJECT_NAME} LANGUAGES CXX)
option(SHIPPING "Build for shipping" OFF)
if(UNIX)
find_package(X11 REQUIRED)
endif()
if(WIN32)
# kind of a hack, I want to be able to include glm/glm.hpp on windows and linux
include_directories(${VCPKG_INSTALLED_DIR}/x64-windows/include)
endif()
# opengl
find_package(OpenGL REQUIRED)
include_directories(${GL_INCLUDE_DIRS})
# sdl2
find_package(SDL2 CONFIG REQUIRED)
# glew
find_package(GLEW REQUIRED)
# glm
find_package(glm REQUIRED)
# png
find_package(PNG REQUIRED)
# nlohmann-json
if (WIN32)
find_package(nlohmann_json CONFIG REQUIRED)
endif()
# eigen
find_package(Eigen3 CONFIG REQUIRED)
# tracy
if(WIN32 AND NOT SHIPPING)
find_package(Tracy CONFIG REQUIRED)
add_compile_definitions(TRACY_ENABLE)
set(TRACY_LIBRARIES, Tracy::TracyClient)
else()
set(TRACY_LIBRARIES, "")
endif()
# openxr-loader
find_package(OpenXR CONFIG REQUIRED)
# src
include_directories(src)
add_executable(${PROJECT_NAME}
src/core/binaryattribute.cpp
src/core/debugrenderer.cpp
src/core/framebuffer.cpp
src/core/image.cpp
src/core/inputbuddy.cpp
src/core/log.cpp
src/core/program.cpp
src/core/texture.cpp
src/core/util.cpp
src/core/vertexbuffer.cpp
src/core/textrenderer.cpp
src/core/xrbuddy.cpp
src/app.cpp
src/camerasconfig.cpp
src/camerapathrenderer.cpp
src/flycam.cpp
src/gaussiancloud.cpp
src/magiccarpet.cpp
src/ply.cpp
src/pointcloud.cpp
src/pointrenderer.cpp
src/sdl_main.cpp
src/splatrenderer.cpp
src/vrconfig.cpp
)
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
if(WIN32)
# Comment this out to see SDL_Log output for debugging
# set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS /SUBSYSTEM:WINDOWS)
endif()
if(WIN32)
if(SHIPPING)
target_link_libraries(${PROJECT_NAME} PRIVATE
${OPENGL_LIBRARIES}
$<TARGET_NAME_IF_EXISTS:SDL2::SDL2main>
$<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static>
GLEW::GLEW
glm::glm
PNG::PNG
nlohmann_json::nlohmann_json
Eigen3::Eigen
OpenXR::headers
OpenXR::openxr_loader
)
else()
target_link_libraries(${PROJECT_NAME} PRIVATE
${OPENGL_LIBRARIES}
$<TARGET_NAME_IF_EXISTS:SDL2::SDL2main>
$<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static>
GLEW::GLEW
glm::glm
PNG::PNG
nlohmann_json::nlohmann_json
Eigen3::Eigen
Tracy::TracyClient
OpenXR::headers
OpenXR::openxr_loader
)
endif()
else()
target_link_libraries(${PROJECT_NAME} PRIVATE
${OPENGL_LIBRARIES}
$<TARGET_NAME_IF_EXISTS:SDL2::SDL2main>
$<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static>
GLEW::GLEW
glm::glm
PNG::PNG
# nlohmann_json::nlohmann_json
Eigen3::Eigen
OpenXR::headers
OpenXR::openxr_loader
${X11_LIBRARIES}
)
endif()
if(SHIPPING)
add_compile_definitions(SHIPPING)
# Copy required data directories to the executable directory
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/font
$<TARGET_FILE_DIR:${PROJECT_NAME}>/font
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/shader
$<TARGET_FILE_DIR:${PROJECT_NAME}>/shader
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/texture
$<TARGET_FILE_DIR:${PROJECT_NAME}>/texture
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/data/test.ply
$<TARGET_FILE_DIR:${PROJECT_NAME}>/data/test.ply
)
endif()
================================================
FILE: LICENSE
================================================
MIT License
Copyright 2024 Anthony J. Thibault
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.
================================================
FILE: README.md
================================================
Splatapult
----------------------------------------------

[<img src="https://i.ytimg.com/vi/18DuNJRZbzQ/maxresdefault.jpg" width="50%">](https://www.youtube.com/watch?v=18DuNJRZbzQ "Splatapult Demo")

A program to display 3d gaussian splats files
splatapult [OPTIONS] ply_filename
Options
-------------
-v, --openxr
launch app in vr mode, using openxr runtime
-f, --fullscreen
launch window in fullscreen
-d, --debug
enable debug logging
--fp16
Use 16-bit half-precision floating frame buffer, to reduce color banding artifacts
--fp32
Use 32-bit floating point frame buffer, to reduce color banding even more
--nosh
Don't load/render full sh, this will reduce memory usage and higher performance
-h, --help
show help
Desktop Controls
--------------------
* wasd - move
* q, e - roll
* t, g - up, down
* arrow keys - look
* right mouse button - hold down for mouse look.
* gamepad - if present, right stick to rotate, left stick to move, bumpers to roll
* c - toggle between initial SfM point cloud (if present) and gaussian splats.
* n - jump to next camera
* p - jump to previous camera
* y - toggle rendering of camera frustums
* h - toggle rendering of camera path
* return - save the current position and orientation of the world into a vr.json file.
VR Controls
---------------
* left stick - move
* right stick - snap turn
* f - show hide floor carpet.
* single grab - translate the world.
* double grab - rotate and translate the world.
* triple grab - (double grab while trigger is depressed) scale, rotate and translate the world.
* c - toggle between initial SfM point cloud (if present) and gaussian splats.
* y - toggle rendering of camera frustums
* h - toggle rendering of camera path
* return - save the current position and orientation/scale of the world into a vr.json file.
Config Files
----------------------
If a "_vr.json" file is found, it will be used to determine the proper starting position, scale and orienation for vr mode.
You can create your own _vr.json file by manipulating the scene via grab in vr mode, then press return to save.
Splatapult supports the same dir structure that [Gaussian Splatting](https://github.com/graphdeco-inria/gaussian-splatting) code will output.
Which is as follows:
```
dir/
point_cloud/
iteration_30000/
point_cloud.ply
point_cloud_vr.json
input.ply
cameras.json
```
input.ply contains the point cloud and cameras.json will contain the camera orientations from the SfM stage.
If the "cameras.json" file is found in the same dir as the ply_filename or it's parent dirs, it will be loaded.
The 'n' and 'p' keys can then be used to cycle thru the camera viewpoints.
It will also support files downloaded from lumalabs.ai, but in this case there will be no point clouds or cameras.
```
dir/
mycapture.ply
mycapture_vr.json
```
Build Info
--------------------
See [BUILD.md](BUILD.md) for information on building Splatapult from source.
================================================
FILE: data/test_vr.json
================================================
{
"floorMat": [[-0.705639, -0.312374, -0.636, -0.573335], [-1.64858e-05, 0.897588, -0.440836, -1.78893], [0.708571, -0.311061, -0.633378, -0.512137], [0, 0, 0, 1]]
}
================================================
FILE: font/JetBrainsMono-Medium.json
================================================
{
"texture_width": 1024,
"glyph_metrics": {
"32": {
"ascii_index": 32,
"xy_lower_left": [-0.039216, -0.039216],
"xy_upper_right": [0.039216, 0.039216],
"uv_lower_left": [0.000000, 0.992188],
"uv_upper_right": [0.007812, 1.000000],
"advance": [0.451613, 0.000000]
},
"33": {
"ascii_index": 33,
"xy_lower_left": [0.122075, -0.047280],
"xy_upper_right": [0.329538, 0.595667],
"uv_lower_left": [0.099609, 0.923828],
"uv_upper_right": [0.123047, 1.000000],
"advance": [0.451613, 0.000000]
},
"34": {
"ascii_index": 34,
"xy_lower_left": [0.057559, 0.291429],
"xy_upper_right": [0.394054, 0.595667],
"uv_lower_left": [0.199219, 0.964844],
"uv_upper_right": [0.238281, 1.000000],
"advance": [0.451613, 0.000000]
},
"35": {
"ascii_index": 35,
"xy_lower_left": [-0.015022, -0.039216],
"xy_upper_right": [0.474700, 0.595667],
"uv_lower_left": [0.298828, 0.924805],
"uv_upper_right": [0.356445, 1.000000],
"advance": [0.451613, 0.000000]
},
"36": {
"ascii_index": 36,
"xy_lower_left": [0.009171, -0.144054],
"xy_upper_right": [0.450506, 0.708570],
"uv_lower_left": [0.398438, 0.898438],
"uv_upper_right": [0.450195, 1.000000],
"advance": [0.451613, 0.000000]
},
"37": {
"ascii_index": 37,
"xy_lower_left": [-0.039216, -0.047280],
"xy_upper_right": [0.490829, 0.595667],
"uv_lower_left": [0.498047, 0.923828],
"uv_upper_right": [0.560547, 1.000000],
"advance": [0.451613, 0.000000]
},
"38": {
"ascii_index": 38,
"xy_lower_left": [-0.023087, -0.047280],
"xy_upper_right": [0.506958, 0.603732],
"uv_lower_left": [0.597656, 0.922852],
"uv_upper_right": [0.660156, 1.000000],
"advance": [0.451613, 0.000000]
},
"39": {
"ascii_index": 39,
"xy_lower_left": [0.146268, 0.291429],
"xy_upper_right": [0.313409, 0.595667],
"uv_lower_left": [0.697266, 0.964844],
"uv_upper_right": [0.715820, 1.000000],
"advance": [0.451613, 0.000000]
},
"40": {
"ascii_index": 40,
"xy_lower_left": [0.097881, -0.135990],
"xy_upper_right": [0.410183, 0.676312],
"uv_lower_left": [0.796875, 0.903320],
"uv_upper_right": [0.833008, 1.000000],
"advance": [0.451613, 0.000000]
},
"41": {
"ascii_index": 41,
"xy_lower_left": [0.041429, -0.135990],
"xy_upper_right": [0.361796, 0.676312],
"uv_lower_left": [0.896484, 0.903320],
"uv_upper_right": [0.933594, 1.000000],
"advance": [0.451613, 0.000000]
},
"42": {
"ascii_index": 42,
"xy_lower_left": [-0.015022, 0.033365],
"xy_upper_right": [0.474700, 0.515022],
"uv_lower_left": [0.000000, 0.843750],
"uv_upper_right": [0.057617, 0.900391],
"advance": [0.451613, 0.000000]
},
"43": {
"ascii_index": 43,
"xy_lower_left": [0.009171, 0.033365],
"xy_upper_right": [0.450506, 0.474700],
"uv_lower_left": [0.099609, 0.848633],
"uv_upper_right": [0.151367, 0.900391],
"advance": [0.451613, 0.000000]
},
"44": {
"ascii_index": 44,
"xy_lower_left": [0.114010, -0.160183],
"xy_upper_right": [0.329538, 0.152119],
"uv_lower_left": [0.199219, 0.864258],
"uv_upper_right": [0.223633, 0.900391],
"advance": [0.451613, 0.000000]
},
"45": {
"ascii_index": 45,
"xy_lower_left": [0.065623, 0.178526],
"xy_upper_right": [0.394054, 0.329538],
"uv_lower_left": [0.298828, 0.883789],
"uv_upper_right": [0.336914, 0.900391],
"advance": [0.451613, 0.000000]
},
"46": {
"ascii_index": 46,
"xy_lower_left": [0.122075, -0.047280],
"xy_upper_right": [0.337603, 0.160183],
"uv_lower_left": [0.398438, 0.876953],
"uv_upper_right": [0.422852, 0.900391],
"advance": [0.451613, 0.000000]
},
"47": {
"ascii_index": 47,
"xy_lower_left": [0.009171, -0.119861],
"xy_upper_right": [0.442441, 0.676312],
"uv_lower_left": [0.498047, 0.805664],
"uv_upper_right": [0.548828, 0.900391],
"advance": [0.451613, 0.000000]
},
"48": {
"ascii_index": 48,
"xy_lower_left": [0.017236, -0.047280],
"xy_upper_right": [0.442441, 0.603732],
"uv_lower_left": [0.597656, 0.823242],
"uv_upper_right": [0.647461, 0.900391],
"advance": [0.451613, 0.000000]
},
"49": {
"ascii_index": 49,
"xy_lower_left": [0.025300, -0.039216],
"xy_upper_right": [0.458571, 0.595667],
"uv_lower_left": [0.697266, 0.825195],
"uv_upper_right": [0.748047, 0.900391],
"advance": [0.451613, 0.000000]
},
"50": {
"ascii_index": 50,
"xy_lower_left": [0.009171, -0.039216],
"xy_upper_right": [0.442441, 0.603732],
"uv_lower_left": [0.796875, 0.824219],
"uv_upper_right": [0.847656, 0.900391],
"advance": [0.451613, 0.000000]
},
"51": {
"ascii_index": 51,
"xy_lower_left": [0.009171, -0.047280],
"xy_upper_right": [0.434377, 0.595667],
"uv_lower_left": [0.896484, 0.824219],
"uv_upper_right": [0.946289, 0.900391],
"advance": [0.451613, 0.000000]
},
"52": {
"ascii_index": 52,
"xy_lower_left": [0.009171, -0.039216],
"xy_upper_right": [0.418248, 0.595667],
"uv_lower_left": [0.000000, 0.725586],
"uv_upper_right": [0.047852, 0.800781],
"advance": [0.451613, 0.000000]
},
"53": {
"ascii_index": 53,
"xy_lower_left": [0.009171, -0.047280],
"xy_upper_right": [0.434377, 0.595667],
"uv_lower_left": [0.099609, 0.724609],
"uv_upper_right": [0.149414, 0.800781],
"advance": [0.451613, 0.000000]
},
"54": {
"ascii_index": 54,
"xy_lower_left": [0.001107, -0.047280],
"xy_upper_right": [0.450506, 0.595667],
"uv_lower_left": [0.199219, 0.724609],
"uv_upper_right": [0.251953, 0.800781],
"advance": [0.451613, 0.000000]
},
"55": {
"ascii_index": 55,
"xy_lower_left": [0.017236, -0.039216],
"xy_upper_right": [0.458571, 0.595667],
"uv_lower_left": [0.298828, 0.725586],
"uv_upper_right": [0.350586, 0.800781],
"advance": [0.451613, 0.000000]
},
"56": {
"ascii_index": 56,
"xy_lower_left": [0.001107, -0.047280],
"xy_upper_right": [0.450506, 0.603732],
"uv_lower_left": [0.398438, 0.723633],
"uv_upper_right": [0.451172, 0.800781],
"advance": [0.451613, 0.000000]
},
"57": {
"ascii_index": 57,
"xy_lower_left": [0.001107, -0.039216],
"xy_upper_right": [0.450506, 0.603732],
"uv_lower_left": [0.498047, 0.724609],
"uv_upper_right": [0.550781, 0.800781],
"advance": [0.451613, 0.000000]
},
"58": {
"ascii_index": 58,
"xy_lower_left": [0.122075, -0.047280],
"xy_upper_right": [0.337603, 0.466635],
"uv_lower_left": [0.597656, 0.740234],
"uv_upper_right": [0.622070, 0.800781],
"advance": [0.451613, 0.000000]
},
"59": {
"ascii_index": 59,
"xy_lower_left": [0.105946, -0.168248],
"xy_upper_right": [0.337603, 0.466635],
"uv_lower_left": [0.697266, 0.725586],
"uv_upper_right": [0.723633, 0.800781],
"advance": [0.451613, 0.000000]
},
"60": {
"ascii_index": 60,
"xy_lower_left": [0.017236, 0.001107],
"xy_upper_right": [0.434377, 0.498893],
"uv_lower_left": [0.796875, 0.742188],
"uv_upper_right": [0.845703, 0.800781],
"advance": [0.451613, 0.000000]
},
"61": {
"ascii_index": 61,
"xy_lower_left": [0.017236, 0.089817],
"xy_upper_right": [0.434377, 0.426312],
"uv_lower_left": [0.896484, 0.761719],
"uv_upper_right": [0.945312, 0.800781],
"advance": [0.451613, 0.000000]
},
"62": {
"ascii_index": 62,
"xy_lower_left": [0.017236, 0.001107],
"xy_upper_right": [0.434377, 0.498893],
"uv_lower_left": [0.000000, 0.642578],
"uv_upper_right": [0.048828, 0.701172],
"advance": [0.451613, 0.000000]
},
"63": {
"ascii_index": 63,
"xy_lower_left": [0.049494, -0.047280],
"xy_upper_right": [0.410183, 0.595667],
"uv_lower_left": [0.099609, 0.625000],
"uv_upper_right": [0.141602, 0.701172],
"advance": [0.451613, 0.000000]
},
"64": {
"ascii_index": 64,
"xy_lower_left": [-0.015022, -0.176312],
"xy_upper_right": [0.466635, 0.603732],
"uv_lower_left": [0.199219, 0.608398],
"uv_upper_right": [0.255859, 0.701172],
"advance": [0.451613, 0.000000]
},
"65": {
"ascii_index": 65,
"xy_lower_left": [-0.006958, -0.039216],
"xy_upper_right": [0.466635, 0.595667],
"uv_lower_left": [0.298828, 0.625977],
"uv_upper_right": [0.354492, 0.701172],
"advance": [0.451613, 0.000000]
},
"66": {
"ascii_index": 66,
"xy_lower_left": [0.025300, -0.039216],
"xy_upper_right": [0.450506, 0.595667],
"uv_lower_left": [0.398438, 0.625977],
"uv_upper_right": [0.448242, 0.701172],
"advance": [0.451613, 0.000000]
},
"67": {
"ascii_index": 67,
"xy_lower_left": [0.025300, -0.047280],
"xy_upper_right": [0.442441, 0.603732],
"uv_lower_left": [0.498047, 0.624023],
"uv_upper_right": [0.546875, 0.701172],
"advance": [0.451613, 0.000000]
},
"68": {
"ascii_index": 68,
"xy_lower_left": [0.025300, -0.039216],
"xy_upper_right": [0.434377, 0.595667],
"uv_lower_left": [0.597656, 0.625977],
"uv_upper_right": [0.645508, 0.701172],
"advance": [0.451613, 0.000000]
},
"69": {
"ascii_index": 69,
"xy_lower_left": [0.033365, -0.039216],
"xy_upper_right": [0.442441, 0.595667],
"uv_lower_left": [0.697266, 0.625977],
"uv_upper_right": [0.745117, 0.701172],
"advance": [0.451613, 0.000000]
},
"70": {
"ascii_index": 70,
"xy_lower_left": [0.025300, -0.039216],
"xy_upper_right": [0.442441, 0.595667],
"uv_lower_left": [0.796875, 0.625977],
"uv_upper_right": [0.845703, 0.701172],
"advance": [0.451613, 0.000000]
},
"71": {
"ascii_index": 71,
"xy_lower_left": [0.017236, -0.047280],
"xy_upper_right": [0.442441, 0.603732],
"uv_lower_left": [0.896484, 0.624023],
"uv_upper_right": [0.946289, 0.701172],
"advance": [0.451613, 0.000000]
},
"72": {
"ascii_index": 72,
"xy_lower_left": [0.025300, -0.039216],
"xy_upper_right": [0.434377, 0.595667],
"uv_lower_left": [0.000000, 0.526367],
"uv_upper_right": [0.047852, 0.601562],
"advance": [0.451613, 0.000000]
},
"73": {
"ascii_index": 73,
"xy_lower_left": [0.033365, -0.039216],
"xy_upper_right": [0.418248, 0.595667],
"uv_lower_left": [0.099609, 0.526367],
"uv_upper_right": [0.144531, 0.601562],
"advance": [0.451613, 0.000000]
},
"74": {
"ascii_index": 74,
"xy_lower_left": [-0.015022, -0.047280],
"xy_upper_right": [0.418248, 0.595667],
"uv_lower_left": [0.199219, 0.525391],
"uv_upper_right": [0.250000, 0.601562],
"advance": [0.451613, 0.000000]
},
"75": {
"ascii_index": 75,
"xy_lower_left": [0.025300, -0.039216],
"xy_upper_right": [0.466635, 0.595667],
"uv_lower_left": [0.298828, 0.526367],
"uv_upper_right": [0.350586, 0.601562],
"advance": [0.451613, 0.000000]
},
"76": {
"ascii_index": 76,
"xy_lower_left": [0.057559, -0.039216],
"xy_upper_right": [0.458571, 0.595667],
"uv_lower_left": [0.398438, 0.526367],
"uv_upper_right": [0.445312, 0.601562],
"advance": [0.451613, 0.000000]
},
"77": {
"ascii_index": 77,
"xy_lower_left": [0.009171, -0.039216],
"xy_upper_right": [0.450506, 0.595667],
"uv_lower_left": [0.498047, 0.526367],
"uv_upper_right": [0.549805, 0.601562],
"advance": [0.451613, 0.000000]
},
"78": {
"ascii_index": 78,
"xy_lower_left": [0.025300, -0.039216],
"xy_upper_right": [0.434377, 0.595667],
"uv_lower_left": [0.597656, 0.526367],
"uv_upper_right": [0.645508, 0.601562],
"advance": [0.451613, 0.000000]
},
"79": {
"ascii_index": 79,
"xy_lower_left": [0.017236, -0.047280],
"xy_upper_right": [0.434377, 0.603732],
"uv_lower_left": [0.697266, 0.524414],
"uv_upper_right": [0.746094, 0.601562],
"advance": [0.451613, 0.000000]
},
"80": {
"ascii_index": 80,
"xy_lower_left": [0.025300, -0.039216],
"xy_upper_right": [0.458571, 0.595667],
"uv_lower_left": [0.796875, 0.526367],
"uv_upper_right": [0.847656, 0.601562],
"advance": [0.451613, 0.000000]
},
"81": {
"ascii_index": 81,
"xy_lower_left": [0.017236, -0.176312],
"xy_upper_right": [0.442441, 0.603732],
"uv_lower_left": [0.896484, 0.508789],
"uv_upper_right": [0.946289, 0.601562],
"advance": [0.451613, 0.000000]
},
"82": {
"ascii_index": 82,
"xy_lower_left": [0.025300, -0.039216],
"xy_upper_right": [0.458571, 0.595667],
"uv_lower_left": [0.000000, 0.426758],
"uv_upper_right": [0.050781, 0.501953],
"advance": [0.451613, 0.000000]
},
"83": {
"ascii_index": 83,
"xy_lower_left": [0.009171, -0.047280],
"xy_upper_right": [0.450506, 0.603732],
"uv_lower_left": [0.099609, 0.424805],
"uv_upper_right": [0.151367, 0.501953],
"advance": [0.451613, 0.000000]
},
"84": {
"ascii_index": 84,
"xy_lower_left": [-0.006958, -0.039216],
"xy_upper_right": [0.458571, 0.595667],
"uv_lower_left": [0.199219, 0.426758],
"uv_upper_right": [0.253906, 0.501953],
"advance": [0.451613, 0.000000]
},
"85": {
"ascii_index": 85,
"xy_lower_left": [0.025300, -0.047280],
"xy_upper_right": [0.434377, 0.595667],
"uv_lower_left": [0.298828, 0.425781],
"uv_upper_right": [0.346680, 0.501953],
"advance": [0.451613, 0.000000]
},
"86": {
"ascii_index": 86,
"xy_lower_left": [-0.006958, -0.039216],
"xy_upper_right": [0.466635, 0.595667],
"uv_lower_left": [0.398438, 0.426758],
"uv_upper_right": [0.454102, 0.501953],
"advance": [0.451613, 0.000000]
},
"87": {
"ascii_index": 87,
"xy_lower_left": [-0.031151, -0.039216],
"xy_upper_right": [0.482764, 0.595667],
"uv_lower_left": [0.498047, 0.426758],
"uv_upper_right": [0.558594, 0.501953],
"advance": [0.451613, 0.000000]
},
"88": {
"ascii_index": 88,
"xy_lower_left": [-0.015022, -0.039216],
"xy_upper_right": [0.466635, 0.595667],
"uv_lower_left": [0.597656, 0.426758],
"uv_upper_right": [0.654297, 0.501953],
"advance": [0.451613, 0.000000]
},
"89": {
"ascii_index": 89,
"xy_lower_left": [-0.015022, -0.039216],
"xy_upper_right": [0.474700, 0.595667],
"uv_lower_left": [0.697266, 0.426758],
"uv_upper_right": [0.754883, 0.501953],
"advance": [0.451613, 0.000000]
},
"90": {
"ascii_index": 90,
"xy_lower_left": [0.017236, -0.039216],
"xy_upper_right": [0.434377, 0.595667],
"uv_lower_left": [0.796875, 0.426758],
"uv_upper_right": [0.845703, 0.501953],
"advance": [0.451613, 0.000000]
},
"91": {
"ascii_index": 91,
"xy_lower_left": [0.105946, -0.119861],
"xy_upper_right": [0.385990, 0.676312],
"uv_lower_left": [0.896484, 0.407227],
"uv_upper_right": [0.928711, 0.501953],
"advance": [0.451613, 0.000000]
},
"92": {
"ascii_index": 92,
"xy_lower_left": [0.009171, -0.119861],
"xy_upper_right": [0.442441, 0.676312],
"uv_lower_left": [0.000000, 0.307617],
"uv_upper_right": [0.050781, 0.402344],
"advance": [0.451613, 0.000000]
},
"93": {
"ascii_index": 93,
"xy_lower_left": [0.065623, -0.119861],
"xy_upper_right": [0.345667, 0.676312],
"uv_lower_left": [0.099609, 0.307617],
"uv_upper_right": [0.131836, 0.402344],
"advance": [0.451613, 0.000000]
},
"94": {
"ascii_index": 94,
"xy_lower_left": [0.017236, 0.218849],
"xy_upper_right": [0.442441, 0.595667],
"uv_lower_left": [0.199219, 0.358398],
"uv_upper_right": [0.249023, 0.402344],
"advance": [0.451613, 0.000000]
},
"95": {
"ascii_index": 95,
"xy_lower_left": [0.001107, -0.119861],
"xy_upper_right": [0.450506, 0.031151],
"uv_lower_left": [0.298828, 0.385742],
"uv_upper_right": [0.351562, 0.402344],
"advance": [0.451613, 0.000000]
},
"96": {
"ascii_index": 96,
"xy_lower_left": [0.073688, 0.452720],
"xy_upper_right": [0.329538, 0.644054],
"uv_lower_left": [0.398438, 0.380859],
"uv_upper_right": [0.427734, 0.402344],
"advance": [0.451613, 0.000000]
},
"97": {
"ascii_index": 97,
"xy_lower_left": [0.009171, -0.047280],
"xy_upper_right": [0.434377, 0.466635],
"uv_lower_left": [0.498047, 0.341797],
"uv_upper_right": [0.547852, 0.402344],
"advance": [0.451613, 0.000000]
},
"98": {
"ascii_index": 98,
"xy_lower_left": [0.025300, -0.047280],
"xy_upper_right": [0.434377, 0.595667],
"uv_lower_left": [0.597656, 0.326172],
"uv_upper_right": [0.645508, 0.402344],
"advance": [0.451613, 0.000000]
},
"99": {
"ascii_index": 99,
"xy_lower_left": [0.017236, -0.047280],
"xy_upper_right": [0.442441, 0.466635],
"uv_lower_left": [0.697266, 0.341797],
"uv_upper_right": [0.747070, 0.402344],
"advance": [0.451613, 0.000000]
},
"100": {
"ascii_index": 100,
"xy_lower_left": [0.017236, -0.047280],
"xy_upper_right": [0.434377, 0.595667],
"uv_lower_left": [0.796875, 0.326172],
"uv_upper_right": [0.845703, 0.402344],
"advance": [0.451613, 0.000000]
},
"101": {
"ascii_index": 101,
"xy_lower_left": [0.017236, -0.047280],
"xy_upper_right": [0.434377, 0.466635],
"uv_lower_left": [0.896484, 0.341797],
"uv_upper_right": [0.945312, 0.402344],
"advance": [0.451613, 0.000000]
},
"102": {
"ascii_index": 102,
"xy_lower_left": [-0.006958, -0.039216],
"xy_upper_right": [0.450506, 0.595667],
"uv_lower_left": [0.000000, 0.227539],
"uv_upper_right": [0.053711, 0.302734],
"advance": [0.451613, 0.000000]
},
"103": {
"ascii_index": 103,
"xy_lower_left": [0.017236, -0.176312],
"xy_upper_right": [0.434377, 0.466635],
"uv_lower_left": [0.099609, 0.226562],
"uv_upper_right": [0.148438, 0.302734],
"advance": [0.451613, 0.000000]
},
"104": {
"ascii_index": 104,
"xy_lower_left": [0.025300, -0.039216],
"xy_upper_right": [0.434377, 0.595667],
"uv_lower_left": [0.199219, 0.227539],
"uv_upper_right": [0.247070, 0.302734],
"advance": [0.451613, 0.000000]
},
"105": {
"ascii_index": 105,
"xy_lower_left": [0.017236, -0.039216],
"xy_upper_right": [0.466635, 0.627925],
"uv_lower_left": [0.298828, 0.223633],
"uv_upper_right": [0.351562, 0.302734],
"advance": [0.451613, 0.000000]
},
"106": {
"ascii_index": 106,
"xy_lower_left": [0.017236, -0.176312],
"xy_upper_right": [0.385990, 0.627925],
"uv_lower_left": [0.398438, 0.207031],
"uv_upper_right": [0.441406, 0.302734],
"advance": [0.451613, 0.000000]
},
"107": {
"ascii_index": 107,
"xy_lower_left": [0.025300, -0.039216],
"xy_upper_right": [0.466635, 0.595667],
"uv_lower_left": [0.498047, 0.227539],
"uv_upper_right": [0.549805, 0.302734],
"advance": [0.451613, 0.000000]
},
"108": {
"ascii_index": 108,
"xy_lower_left": [-0.023087, -0.039216],
"xy_upper_right": [0.458571, 0.595667],
"uv_lower_left": [0.597656, 0.227539],
"uv_upper_right": [0.654297, 0.302734],
"advance": [0.451613, 0.000000]
},
"109": {
"ascii_index": 109,
"xy_lower_left": [0.001107, -0.039216],
"xy_upper_right": [0.458571, 0.466635],
"uv_lower_left": [0.697266, 0.243164],
"uv_upper_right": [0.750977, 0.302734],
"advance": [0.451613, 0.000000]
},
"110": {
"ascii_index": 110,
"xy_lower_left": [0.025300, -0.039216],
"xy_upper_right": [0.434377, 0.466635],
"uv_lower_left": [0.796875, 0.243164],
"uv_upper_right": [0.844727, 0.302734],
"advance": [0.451613, 0.000000]
},
"111": {
"ascii_index": 111,
"xy_lower_left": [0.017236, -0.047280],
"xy_upper_right": [0.434377, 0.466635],
"uv_lower_left": [0.896484, 0.242188],
"uv_upper_right": [0.945312, 0.302734],
"advance": [0.451613, 0.000000]
},
"112": {
"ascii_index": 112,
"xy_lower_left": [0.025300, -0.176312],
"xy_upper_right": [0.434377, 0.466635],
"uv_lower_left": [0.000000, 0.126953],
"uv_upper_right": [0.047852, 0.203125],
"advance": [0.451613, 0.000000]
},
"113": {
"ascii_index": 113,
"xy_lower_left": [0.017236, -0.176312],
"xy_upper_right": [0.434377, 0.466635],
"uv_lower_left": [0.099609, 0.126953],
"uv_upper_right": [0.148438, 0.203125],
"advance": [0.451613, 0.000000]
},
"114": {
"ascii_index": 114,
"xy_lower_left": [0.041429, -0.039216],
"xy_upper_right": [0.450506, 0.466635],
"uv_lower_left": [0.199219, 0.143555],
"uv_upper_right": [0.247070, 0.203125],
"advance": [0.451613, 0.000000]
},
"115": {
"ascii_index": 115,
"xy_lower_left": [0.017236, -0.047280],
"xy_upper_right": [0.434377, 0.466635],
"uv_lower_left": [0.298828, 0.142578],
"uv_upper_right": [0.347656, 0.203125],
"advance": [0.451613, 0.000000]
},
"116": {
"ascii_index": 116,
"xy_lower_left": [-0.006958, -0.039216],
"xy_upper_right": [0.442441, 0.579538],
"uv_lower_left": [0.398438, 0.129883],
"uv_upper_right": [0.451172, 0.203125],
"advance": [0.451613, 0.000000]
},
"117": {
"ascii_index": 117,
"xy_lower_left": [0.025300, -0.047280],
"xy_upper_right": [0.434377, 0.458571],
"uv_lower_left": [0.498047, 0.143555],
"uv_upper_right": [0.545898, 0.203125],
"advance": [0.451613, 0.000000]
},
"118": {
"ascii_index": 118,
"xy_lower_left": [-0.006958, -0.039216],
"xy_upper_right": [0.458571, 0.458571],
"uv_lower_left": [0.597656, 0.144531],
"uv_upper_right": [0.652344, 0.203125],
"advance": [0.451613, 0.000000]
},
"119": {
"ascii_index": 119,
"xy_lower_left": [-0.023087, -0.039216],
"xy_upper_right": [0.474700, 0.458571],
"uv_lower_left": [0.697266, 0.144531],
"uv_upper_right": [0.755859, 0.203125],
"advance": [0.451613, 0.000000]
},
"120": {
"ascii_index": 120,
"xy_lower_left": [-0.006958, -0.039216],
"xy_upper_right": [0.466635, 0.458571],
"uv_lower_left": [0.796875, 0.144531],
"uv_upper_right": [0.852539, 0.203125],
"advance": [0.451613, 0.000000]
},
"121": {
"ascii_index": 121,
"xy_lower_left": [-0.006958, -0.176312],
"xy_upper_right": [0.458571, 0.458571],
"uv_lower_left": [0.896484, 0.127930],
"uv_upper_right": [0.951172, 0.203125],
"advance": [0.451613, 0.000000]
},
"122": {
"ascii_index": 122,
"xy_lower_left": [0.025300, -0.039216],
"xy_upper_right": [0.434377, 0.458571],
"uv_lower_left": [0.000000, 0.044922],
"uv_upper_right": [0.047852, 0.103516],
"advance": [0.451613, 0.000000]
},
"123": {
"ascii_index": 123,
"xy_lower_left": [0.009171, -0.119861],
"xy_upper_right": [0.426312, 0.676312],
"uv_lower_left": [0.099609, 0.008789],
"uv_upper_right": [0.148438, 0.103516],
"advance": [0.451613, 0.000000]
},
"124": {
"ascii_index": 124,
"xy_lower_left": [0.146268, -0.119861],
"xy_upper_right": [0.305345, 0.676312],
"uv_lower_left": [0.199219, 0.008789],
"uv_upper_right": [0.216797, 0.103516],
"advance": [0.451613, 0.000000]
},
"125": {
"ascii_index": 125,
"xy_lower_left": [0.025300, -0.119861],
"xy_upper_right": [0.442441, 0.676312],
"uv_lower_left": [0.298828, 0.008789],
"uv_upper_right": [0.347656, 0.103516],
"advance": [0.451613, 0.000000]
},
"126": {
"ascii_index": 126,
"xy_lower_left": [0.009171, 0.146268],
"xy_upper_right": [0.450506, 0.394054],
"uv_lower_left": [0.398438, 0.075195],
"uv_upper_right": [0.450195, 0.103516],
"advance": [0.451613, 0.000000]
}
},
"kerning": {
}
}
================================================
FILE: meta-quest/cp_from_sdk
================================================
rm -rf splatapult
cp -r ovr_openxr_mobile_sdk_59.0/XrSamples/splatapult splatapult
================================================
FILE: meta-quest/splatapult/.gitignore
================================================
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Log/OS Files
*.log
# Android Studio generated files and folders
captures/
.externalNativeBuild/
.cxx/
*.apk
output.json
# IntelliJ
*.iml
.idea/
misc.xml
deploymentTargetDropDown.xml
render.experimental.xml
# Keystore files
*.jks
*.keystore
# Google Services (e.g. APIs or Firebase)
google-services.json
# Android Profiling
*.hprof
================================================
FILE: meta-quest/splatapult/Projects/Android/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="io.github.hyperlogic.splatapult"
android:versionCode="1"
android:versionName="1.0"
android:installLocation="auto"
>
<!-- Tell the system this app requires OpenGL ES 3.1. -->
<uses-feature
android:glEsVersion="0x00030001"
android:required="true"
/>
<uses-feature
android:name="android.hardware.vr.headtracking"
android:required="true"
/>
<!-- Volume Control -->
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Enable report events to Telemetry Service -->
<uses-permission android:name="com.oculus.permission.REPORT_EVENTS" />
<uses-permission android:name="com.oculus.permission.REPORT_EVENTS_DEBUG" />
<application
android:allowBackup="false"
android:label="splatapult"
>
<meta-data
android:name="com.samsung.android.vr.application.mode"
android:value="vr_only"
/>
<meta-data android:name="com.oculus.supportedDevices" android:value="all" />
<!-- launchMode is set to singleTask because there should never be multiple copies of the app running -->
<!-- Theme.Black.NoTitleBar.Fullscreen gives solid black instead of a (bad stereoscopic) gradient on app transition -->
<!-- If targeting API level 24+, configChanges should additionally include 'density'. -->
<!-- If targeting API level 24+, android:resizeableActivity="false" should be added. -->
<activity
android:name="com.oculus.NativeActivity"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
android:launchMode="singleTask"
android:screenOrientation="landscape"
android:excludeFromRecents="false"
android:configChanges="screenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode"
>
<!-- Tell NativeActivity the name of the .so -->
<meta-data android:name="android.app.lib_name" android:value="splatapult" />
<!-- This filter lets the apk show up as a launchable icon. -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.oculus.intent.category.VR" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
================================================
FILE: meta-quest/splatapult/Projects/Android/build.bat
================================================
@rem Only edit the master copy of this file in SDK_ROOT/bin/scripts/build/perproject
@setlocal enableextensions enabledelayedexpansion
@if not exist "build.gradle" (
@echo Build script must be executed from project directory. & goto :Abort
)
@set P=..
:TryAgain
@rem @echo P = %P%
@if exist "%P%\bin\scripts\build\build.py.bat" goto :Found
@if exist "%P%\bin\scripts\build" @echo "Could not find build.py.bat" & goto :Abort
@set P=%P%\..
@goto :TryAgain
:Found
@set P=%P%\bin\scripts\build
@call %P%\build.py.bat %1 %2 %3 %4 %5
@goto :End
:Abort
:End
================================================
FILE: meta-quest/splatapult/Projects/Android/build.gradle
================================================
apply plugin: 'com.android.application'
apply from: "${rootProject.projectDir}/VrApp.gradle"
task copyFiles(type: Copy) {
copy {
from '../../../../../../shader'
into '../../assets/shader'
}
copy {
from '../../../../../../font'
into '../../assets/font'
}
copy {
from '../../../../../../texture'
into '../../assets/texture'
}
copy {
from '../../../../../../data/sh_test'
into '../../assets/data/sh_test'
}
copy {
from '../../../../../../data/livingroom'
into '../../assets/data/livingroom'
}
}
gradle.projectsEvaluated {
preBuild.dependsOn(copyFiles)
}
android {
// This is the name of the generated apk file, which will have
// -debug.apk or -release.apk appended to it.
// The filename doesn't effect the Android installation process.
// Use only letters to remain compatible with the package name.
project.archivesBaseName = "splatapult"
defaultConfig {
// Gradle replaces the manifest package with this value, which must
// be unique on a system. If you don't change it, a new app
// will replace an older one.
applicationId "com.oculus.sdk." + project.archivesBaseName
minSdkVersion 24
targetSdkVersion 25
compileSdkVersion 26
// override app plugin abiFilters for 64-bit support
externalNativeBuild {
ndk {
abiFilters 'arm64-v8a'
}
ndkBuild {
abiFilters 'arm64-v8a'
}
}
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['../../java']
jniLibs.srcDir 'libs'
assets.srcDirs = ['../../assets']
}
}
lintOptions {
disable 'ExpiredTargetSdkVersion'
}
}
================================================
FILE: meta-quest/splatapult/Projects/Android/build.py
================================================
#!/usr/bin/python
# (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
# This first bit of code is common bootstrapping code
# to determine the SDK root, and to set up the import
# path for additional python code.
# begin bootstrap
import os
import sys
def init():
root = os.path.realpath(os.path.dirname(os.path.realpath(__file__)))
os.chdir(root) # make sure we are always executing from the project directory
while os.path.isdir(os.path.join(root, "bin/scripts/build")) == False:
root = os.path.realpath(os.path.join(root, ".."))
if (
len(root) <= 5
): # Should catch both Posix and Windows root directories (e.g. '/' and 'C:\')
print("Unable to find SDK root. Exiting.")
sys.exit(1)
root = os.path.abspath(root)
os.environ["OCULUS_SDK_PATH"] = root
sys.path.append(root + "/bin/scripts/build")
init()
import ovrbuild
ovrbuild.init()
# end bootstrap
ovrbuild.build()
================================================
FILE: meta-quest/splatapult/Projects/Android/gradle.properties
================================================
org.gradle.jvmargs=-Xmx4g
================================================
FILE: meta-quest/splatapult/Projects/Android/jni/Android.mk
================================================
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := splatapult
LOCAL_CFLAGS += -Werror
# need execptions for json and radix sort.
LOCAL_CFLAGS += -fexceptions
# This should be set via an environment var
# ANDROID_VCPKG_DIR := C:/msys64/home/hyperlogic/code/vcpkg/installed/arm64-android
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/../../../../../../../src/ \
$(LOCAL_PATH)/../../../../../1stParty/OVR/Include \
$(LOCAL_PATH)/../../../../../OpenXr/Include \
$(LOCAL_PATH)/../../../../../3rdParty/khronos/openxr/OpenXR-SDK/include/ \
$(LOCAL_PATH)/../../../../../3rdParty/khronos/openxr/OpenXR-SDK/src/common/ \
$(ANDROID_VCPKG_DIR)/include \
LOCAL_SRC_PATH := ../../../../../../../src
LOCAL_SRC_FILES := $(LOCAL_SRC_PATH)/core/debugrenderer.cpp \
$(LOCAL_SRC_PATH)/core/image.cpp \
$(LOCAL_SRC_PATH)/core/log.cpp \
$(LOCAL_SRC_PATH)/core/program.cpp \
$(LOCAL_SRC_PATH)/core/texture.cpp \
$(LOCAL_SRC_PATH)/core/util.cpp \
$(LOCAL_SRC_PATH)/core/vertexbuffer.cpp \
$(LOCAL_SRC_PATH)/core/textrenderer.cpp \
$(LOCAL_SRC_PATH)/core/xrbuddy.cpp \
$(LOCAL_SRC_PATH)/app.cpp \
$(LOCAL_SRC_PATH)/android_main.cpp \
$(LOCAL_SRC_PATH)/camerasconfig.cpp \
$(LOCAL_SRC_PATH)/flycam.cpp \
$(LOCAL_SRC_PATH)/gaussiancloud.cpp \
$(LOCAL_SRC_PATH)/magiccarpet.cpp \
$(LOCAL_SRC_PATH)/ply.cpp \
$(LOCAL_SRC_PATH)/pointcloud.cpp \
$(LOCAL_SRC_PATH)/pointrenderer.cpp \
$(LOCAL_SRC_PATH)/splatrenderer.cpp \
$(LOCAL_SRC_PATH)/vrconfig.cpp \
LOCAL_LDLIBS := -lEGL -lGLESv3 -landroid -llog -lpng -lpng16 -lz
LOCAL_LDFLAGS := -u ANativeActivity_onCreate
LOCAL_LDLIBS += -L$(ANDROID_VCPKG_DIR)/lib
LOCAL_STATIC_LIBRARIES := android_native_app_glue
LOCAL_SHARED_LIBRARIES := openxr_loader
include $(BUILD_SHARED_LIBRARY)
$(call import-module,OpenXR/Projects/AndroidPrebuilt/jni)
$(call import-module,android/native_app_glue)
================================================
FILE: meta-quest/splatapult/Projects/Android/jni/Application.mk
================================================
# MAKEFILE_LIST specifies the current used Makefiles, of which this is the last
# one. I use that to obtain the Application.mk dir then import the root
# Application.mk.
ROOT_DIR := $(dir $(lastword $(MAKEFILE_LIST)))../../../../../
NDK_MODULE_PATH := $(ROOT_DIR)
# ndk-r14 introduced failure for missing dependencies. If 'false', the clean
# step will error as we currently remove prebuilt artifacts on clean.
APP_ALLOW_MISSING_DEPS=true
================================================
FILE: meta-quest/splatapult/Projects/Android/settings.gradle
================================================
rootProject.projectDir = new File(settingsDir, '../../../..')
rootProject.name = "splatapult"
include ':', \
':XrSamples:splatapult:Projects:Android'
================================================
FILE: meta-quest/splatapult/assets/.gitignore
================================================
font/
shader/
texture/
data/
================================================
FILE: meta-quest/splatapult/assets/donotedelete.txt
================================================
================================================
FILE: meta-quest/splatapult/java/com/oculus/NativeActivity.java
================================================
// Copyright (c) Facebook Technologies, LLC and its affiliates. All Rights reserved.
package com.oculus;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
/**
* When using NativeActivity, we currently need to handle loading of dependent shared libraries
* manually before a shared library that depends on them is loaded, since there is not currently a
* way to specify a shared library dependency for NativeActivity via the manifest meta-data.
*
* <p>The simplest method for doing so is to subclass NativeActivity with an empty activity that
* calls System.loadLibrary on the dependent libraries, which is unfortunate when the goal is to
* write a pure native C/C++ only Android activity.
*
* <p>A native-code only solution is to load the dependent libraries dynamically using dlopen().
* However, there are a few considerations, see:
* https://groups.google.com/forum/#!msg/android-ndk/l2E2qh17Q6I/wj6s_6HSjaYJ
*
* <p>1. Only call dlopen() if you're sure it will succeed as the bionic dynamic linker will
* remember if dlopen failed and will not re-try a dlopen on the same lib a second time.
*
* <p>2. Must remember what libraries have already been loaded to avoid infinitely looping when
* libraries have circular dependencies.
*/
public class NativeActivity extends android.app.NativeActivity {
private static final String PERMISSION_USE_SCENE = "com.oculus.permission.USE_SCENE";
private static final int REQUEST_CODE_PERMISSION_USE_SCENE = 1;
private static final String TAG = "splatapult";
static {
System.loadLibrary("openxr_loader");
System.loadLibrary("splatapult");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestScenePermissionIfNeeded();
}
private void requestScenePermissionIfNeeded() {
Log.d(TAG, "requestScenePermissionIfNeeded");
/*
if (checkSelfPermission(PERMISSION_USE_SCENE) != PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "Permission has not been granted, request " + PERMISSION_USE_SCENE);
requestPermissions(
new String[] {PERMISSION_USE_SCENE}, REQUEST_CODE_PERMISSION_USE_SCENE);
}
*/
}
}
================================================
FILE: shader/carpet_frag.glsl
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
//
// fullbright textured mesh
//
/*%%HEADER%%*/
uniform sampler2D colorTex;
in vec2 frag_uv;
out vec4 out_color;
void main()
{
vec4 texColor = texture(colorTex, frag_uv);
// premultiplied alpha blending
out_color.rgb = texColor.a * texColor.rgb;
out_color.a = texColor.a;
}
================================================
FILE: shader/carpet_vert.glsl
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
//
// fullbright textured mesh
//
/*%%HEADER%%*/
uniform mat4 modelViewProjMat;
in vec3 position;
in vec2 uv;
out vec2 frag_uv;
void main(void)
{
gl_Position = modelViewProjMat * vec4(position, 1);
frag_uv = uv;
}
================================================
FILE: shader/debugdraw_frag.glsl
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
//
// No lighting at all, solid color
//
/*%%HEADER%%*/
in vec4 frag_color;
out vec4 out_color;
void main()
{
// pre-multiplied alpha blending
out_color.rgb = frag_color.a * frag_color.rgb;
out_color.a = frag_color.a;
}
================================================
FILE: shader/debugdraw_vert.glsl
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
//
// No lighting at all, solid color
//
/*%%HEADER%%*/
uniform mat4 modelViewProjMat;
in vec3 position;
in vec4 color;
out vec4 frag_color;
void main(void)
{
gl_Position = modelViewProjMat * vec4(position, 1);
frag_color = color;
}
================================================
FILE: shader/desktop_frag.glsl
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
/*%%HEADER%%*/
/*%%DEFINES%%*/
uniform vec4 color;
uniform sampler2D colorTexture;
in vec2 frag_uv;
out vec4 out_color;
void main(void)
{
#ifdef USE_SUPERSAMPLING
// per pixel screen space partial derivatives
vec2 dx = dFdx(frag_uv) * 0.25; // horizontal offset
vec2 dy = dFdy(frag_uv) * 0.25; // vertical offset
// supersampled 2x2 ordered grid
vec4 texColor = vec4(0);
texColor += texture(colorTexture, vec2(frag_uv + dx + dy));
texColor += texture(colorTexture, vec2(frag_uv - dx + dy));
texColor += texture(colorTexture, vec2(frag_uv + dx - dy));
texColor += texture(colorTexture, vec2(frag_uv - dx - dy));
texColor *= 0.25;
#else
vec4 texColor = texture(colorTexture, frag_uv);
#endif
// premultiplied alpha blending
out_color.rgb = color.a * color.rgb * texColor.rgb;
out_color.a = color.a * texColor.a;
}
================================================
FILE: shader/desktop_vert.glsl
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
//
// fullbright textured mesh
//
/*%%HEADER%%*/
uniform mat4 modelViewProjMat;
in vec3 position;
in vec2 uv;
out vec2 frag_uv;
void main(void)
{
gl_Position = modelViewProjMat * vec4(position, 1);
frag_uv = uv;
}
================================================
FILE: shader/multi_radixsort.glsl
================================================
/**
* VkRadixSort written by Mirco Werner: https://github.com/MircoWerner/VkRadixSort
* Based on implementation of Intel's Embree: https://github.com/embree/embree/blob/v4.0.0-ploc/kernels/rthwif/builder/gpu/sort.h
*/
#version 460
//#extension GL_GOOGLE_include_directive: enable
#extension GL_KHR_shader_subgroup_basic: enable
#extension GL_KHR_shader_subgroup_arithmetic: enable
#extension GL_KHR_shader_subgroup_ballot: enable
#define WORKGROUP_SIZE 256// assert WORKGROUP_SIZE >= RADIX_SORT_BINS
#define RADIX_SORT_BINS 256
#define SUBGROUP_SIZE 32// 32 NVIDIA; 64 AMD
#define BITS 32// sorting uint32_t
layout (local_size_x = WORKGROUP_SIZE) in;
uniform uint g_num_elements;
uniform uint g_shift;
uniform uint g_num_workgroups;
uniform uint g_num_blocks_per_workgroup;
layout (std430, binding = 0) buffer elements_in {
uint g_elements_in[];
};
layout (std430, binding = 1) buffer elements_out {
uint g_elements_out[];
};
layout (std430, binding = 2) buffer indices_in {
uint g_indices_in[];
};
layout (std430, binding = 3) buffer indices_out {
uint g_indices_out[];
};
layout (std430, binding = 4) buffer histograms {
// [histogram_of_workgroup_0 | histogram_of_workgroup_1 | ... ]
uint g_histograms[];// |g_histograms| = RADIX_SORT_BINS * #WORKGROUPS = RADIX_SORT_BINS * g_num_workgroups
};
shared uint[RADIX_SORT_BINS / SUBGROUP_SIZE] sums;// subgroup reductions
shared uint[RADIX_SORT_BINS] global_offsets;// global exclusive scan (prefix sum)
struct BinFlags {
uint flags[WORKGROUP_SIZE / BITS];
};
shared BinFlags[RADIX_SORT_BINS] bin_flags;
void main() {
uint gID = gl_GlobalInvocationID.x;
uint lID = gl_LocalInvocationID.x;
uint wID = gl_WorkGroupID.x;
uint sID = gl_SubgroupID;
uint lsID = gl_SubgroupInvocationID;
uint local_histogram = 0;
uint prefix_sum = 0;
uint histogram_count = 0;
if (lID < RADIX_SORT_BINS) {
uint count = 0;
for (uint j = 0; j < g_num_workgroups; j++) {
const uint t = g_histograms[RADIX_SORT_BINS * j + lID];
local_histogram = (j == wID) ? count : local_histogram;
count += t;
}
histogram_count = count;
const uint sum = subgroupAdd(histogram_count);
prefix_sum = subgroupExclusiveAdd(histogram_count);
if (subgroupElect()) {
// one thread inside the warp/subgroup enters this section
sums[sID] = sum;
}
}
barrier();
if (lID < RADIX_SORT_BINS) {
const uint sums_prefix_sum = subgroupBroadcast(subgroupExclusiveAdd(sums[lsID]), sID);
const uint global_histogram = sums_prefix_sum + prefix_sum;
global_offsets[lID] = global_histogram + local_histogram;
}
// ==== scatter keys according to global offsets =====
const uint flags_bin = lID / BITS;
const uint flags_bit = 1 << (lID % BITS);
for (uint index = 0; index < g_num_blocks_per_workgroup; index++) {
uint elementId = wID * g_num_blocks_per_workgroup * WORKGROUP_SIZE + index * WORKGROUP_SIZE + lID;
// initialize bin flags
if (lID < RADIX_SORT_BINS) {
for (int i = 0; i < WORKGROUP_SIZE / BITS; i++) {
bin_flags[lID].flags[i] = 0U;// init all bin flags to 0
}
}
barrier();
uint element_in = 0;
uint index_in = 0;
uint binID = 0;
uint binOffset = 0;
if (elementId < g_num_elements) {
element_in = g_elements_in[elementId];
index_in = g_indices_in[elementId];
binID = (element_in >> g_shift) & uint(RADIX_SORT_BINS - 1);
// offset for group
binOffset = global_offsets[binID];
// add bit to flag
atomicAdd(bin_flags[binID].flags[flags_bin], flags_bit);
}
barrier();
if (elementId < g_num_elements) {
// calculate output index of element
uint prefix = 0;
uint count = 0;
for (uint i = 0; i < WORKGROUP_SIZE / BITS; i++) {
const uint bits = bin_flags[binID].flags[i];
const uint full_count = bitCount(bits);
const uint partial_count = bitCount(bits & (flags_bit - 1));
prefix += (i < flags_bin) ? full_count : 0U;
prefix += (i == flags_bin) ? partial_count : 0U;
count += full_count;
}
g_elements_out[binOffset + prefix] = element_in;
g_indices_out[binOffset + prefix] = index_in;
if (prefix == count - 1) {
atomicAdd(global_offsets[binID], count);
}
}
barrier();
}
}
================================================
FILE: shader/multi_radixsort_histograms.glsl
================================================
/**
* VkRadixSort written by Mirco Werner: https://github.com/MircoWerner/VkRadixSort
* Based on implementation of Intel's Embree: https://github.com/embree/embree/blob/v4.0.0-ploc/kernels/rthwif/builder/gpu/sort.h
*/
#version 460
//#extension GL_GOOGLE_include_directive: enable
#define WORKGROUP_SIZE 256 // assert WORKGROUP_SIZE >= RADIX_SORT_BINS
#define RADIX_SORT_BINS 256
uniform uint g_num_elements;
uniform uint g_shift;
//uniform uint g_num_workgroups;
uniform uint g_num_blocks_per_workgroup;
layout (local_size_x = WORKGROUP_SIZE) in;
layout (std430, binding = 0) buffer elements_in {
uint g_elements_in[];
};
layout (std430, binding = 1) buffer histograms {
// [histogram_of_workgroup_0 | histogram_of_workgroup_1 | ... ]
uint g_histograms[]; // |g_histograms| = RADIX_SORT_BINS * #WORKGROUPS
};
shared uint[RADIX_SORT_BINS] histogram;
void main() {
uint gID = gl_GlobalInvocationID.x;
uint lID = gl_LocalInvocationID.x;
uint wID = gl_WorkGroupID.x;
// initialize histogram
if (lID < RADIX_SORT_BINS) {
histogram[lID] = 0U;
}
barrier();
for (uint index = 0; index < g_num_blocks_per_workgroup; index++) {
uint elementId = wID * g_num_blocks_per_workgroup * WORKGROUP_SIZE + index * WORKGROUP_SIZE + lID;
if (elementId < g_num_elements) {
// determine the bin
const uint bin = (g_elements_in[elementId] >> g_shift) & (RADIX_SORT_BINS - 1);
// increment the histogram
atomicAdd(histogram[bin], 1U);
}
}
barrier();
if (lID < RADIX_SORT_BINS) {
g_histograms[RADIX_SORT_BINS * wID + lID] = histogram[lID];
}
}
================================================
FILE: shader/point_frag.glsl
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
//
// fullbright textured particle
//
/*%%HEADER%%*/
uniform sampler2D colorTex;
in vec2 frag_uv;
in vec4 frag_color;
out vec4 out_color;
void main()
{
vec4 texColor = texture(colorTex, frag_uv);
// premultiplied alpha blending
out_color.rgb = frag_color.a * frag_color.rgb * texColor.rgb;
out_color.a = frag_color.a * texColor.a;
}
================================================
FILE: shader/point_geom.glsl
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
/*%%HEADER%%*/
uniform float pointSize;
uniform float invAspectRatio;
layout(points) in;
layout(triangle_strip, max_vertices = 4) out;
in vec4 geom_color[];
out vec4 frag_color;
out vec2 frag_uv;
void main()
{
vec2 offset = vec2(pointSize * invAspectRatio, pointSize);
// bottom-left vertex
frag_uv = vec2(0.0, 0.0);
frag_color = geom_color[0];
gl_Position = gl_in[0].gl_Position + vec4(-offset.x, -offset.y, 0.0, 0.0);
EmitVertex();
// bottom-right vertex
frag_uv = vec2(1.0, 0.0);
frag_color = geom_color[0];
gl_Position = gl_in[0].gl_Position + vec4(offset.x, -offset.y, 0.0, 0.0);
EmitVertex();
// top-left vertex
frag_uv = vec2(0.0, 1.0);
frag_color = geom_color[0];
gl_Position = gl_in[0].gl_Position + vec4(-offset.x, offset.y, 0.0, 0.0);
EmitVertex();
// top-right vertex
frag_uv = vec2(1.0, 1.0);
frag_color = geom_color[0];
gl_Position = gl_in[0].gl_Position + vec4(offset.x, offset.y, 0.0, 0.0);
EmitVertex();
EndPrimitive();
}
================================================
FILE: shader/point_vert.glsl
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
//
// fullbright textured particle
//
/*%%HEADER%%*/
uniform float pointSize;
uniform float invAspectRatio;
uniform mat4 modelViewMat;
uniform mat4 projMat;
in vec4 position;
in vec4 color;
out vec4 geom_color;
void main(void)
{
gl_Position = projMat * modelViewMat * position;
geom_color = color;
}
================================================
FILE: shader/presort_compute.glsl
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
/*%%HEADER%%*/
layout(local_size_x = 256) in;
uniform mat4 modelViewProj;
uniform vec2 nearFar;
uniform uint keyMax;
layout(binding = 4, offset = 0) uniform atomic_uint output_count;
layout(std430, binding = 0) readonly buffer PosBuffer
{
vec4 positions[];
};
layout(std430, binding = 1) writeonly buffer OutputBuffer
{
uint quantizedZs[];
};
layout(std430, binding = 2) writeonly buffer OutputBuffer2
{
uint indices[];
};
void main()
{
uint idx = gl_GlobalInvocationID.x;
uint len = uint(positions.length());
if (idx >= len)
{
return;
}
// NOTE: alpha is encoded into the w component of the positions
vec4 p = modelViewProj * vec4(positions[idx].xyz, 1.0f);
float depth = p.w;
float xx = p.x / depth;
float yy = p.y / depth;
const float CLIP = 1.5f;
if (depth > 0.0f && xx < CLIP && xx > -CLIP && yy < CLIP && yy > -CLIP)
{
uint count = atomicCounterIncrement(output_count);
// 16.16 fixed point
//uint fixedPointZ = uint(0xffffffff) - uint(clamp(depth, 0.0f, 65535.0f) * 65536.0f);
uint fixedPointZ = keyMax - uint((depth / nearFar.y) * keyMax);
quantizedZs[count] = fixedPointZ;
indices[count] = idx;
}
}
================================================
FILE: shader/single_radixsort.glsl
================================================
/**
* VkRadixSort written by Mirco Werner: https://github.com/MircoWerner/VkRadixSort
* Based on implementation of Intel's Embree: https://github.com/embree/embree/blob/v4.0.0-ploc/kernels/rthwif/builder/gpu/sort.h
*/
#version 460
#extension GL_KHR_shader_subgroup_basic: enable
#extension GL_KHR_shader_subgroup_arithmetic: enable
#define WORKGROUP_SIZE 256// assert WORKGROUP_SIZE >= RADIX_SORT_BINS
#define RADIX_SORT_BINS 256
#define SUBGROUP_SIZE 32// 32 NVIDIA; 64 AMD
#define BITS 32// sorting uint32_t
#define ITERATIONS 4// 4 iterations, sorting 8 bits per iteration
layout (local_size_x = WORKGROUP_SIZE) in;
uniform uint g_num_elements;
layout (std430, set = 0, binding = 0) buffer elements_in {
uint g_elements_in[];
};
layout (std430, set = 0, binding = 1) buffer elements_out {
uint g_elements_out[];
};
layout (std430, set = 0, binding = 2) buffer indices_in {
uint g_indices_in[];
};
layout (std430, set = 0, binding = 3) buffer indices_out {
uint g_indices_out[];
};
shared uint[RADIX_SORT_BINS] histogram;
shared uint[RADIX_SORT_BINS / SUBGROUP_SIZE] sums;// subgroup reductions
shared uint[RADIX_SORT_BINS] local_offsets;// local exclusive scan (prefix sum) (inside subgroups)
shared uint[RADIX_SORT_BINS] global_offsets;// global exclusive scan (prefix sum)
struct BinFlags {
uint flags[WORKGROUP_SIZE / BITS];
};
shared BinFlags[RADIX_SORT_BINS] bin_flags;
#define ELEMENT_IN(index, iteration) (iteration % 2 == 0 ? g_elements_in[index] : g_elements_out[index])
#define INDEX_IN(index, iteration) (iteration % 2 == 0 ? g_indices_in[index] : g_indices_out[index])
void main() {
uint lID = gl_LocalInvocationID.x;
uint sID = gl_SubgroupID;
uint lsID = gl_SubgroupInvocationID;
for (uint iteration = 0; iteration < ITERATIONS; iteration++) {
uint shift = 8 * iteration;
// initialize histogram
if (lID < RADIX_SORT_BINS) {
histogram[lID] = 0U;
}
barrier();
for (uint ID = lID; ID < g_num_elements; ID += WORKGROUP_SIZE) {
// determine the bin
const uint bin = (ELEMENT_IN(ID, iteration) >> shift) & (RADIX_SORT_BINS - 1);
// increment the histogram
atomicAdd(histogram[bin], 1U);
}
barrier();
// subgroup reductions and subgroup prefix sums
if (lID < RADIX_SORT_BINS) {
uint histogram_count = histogram[lID];
uint sum = subgroupAdd(histogram_count);
uint prefix_sum = subgroupExclusiveAdd(histogram_count);
local_offsets[lID] = prefix_sum;
if (subgroupElect()) {
// one thread inside the warp/subgroup enters this section
sums[sID] = sum;
}
}
barrier();
// global prefix sums (offsets)
if (sID == 0) {
uint offset = 0;
for (uint i = lsID; i < RADIX_SORT_BINS; i += SUBGROUP_SIZE) {
global_offsets[i] = offset + local_offsets[i];
offset += sums[i / SUBGROUP_SIZE];
}
}
barrier();
// ==== scatter keys according to global offsets =====
const uint flags_bin = lID / BITS;
const uint flags_bit = 1 << (lID % BITS);
for (uint blockID = 0; blockID < g_num_elements; blockID += WORKGROUP_SIZE) {
barrier();
const uint ID = blockID + lID;
// initialize bin flags
if (lID < RADIX_SORT_BINS) {
for (int i = 0; i < WORKGROUP_SIZE / BITS; i++) {
bin_flags[lID].flags[i] = 0U;// init all bin flags to 0
}
}
barrier();
uint element_in = 0;
uint index_in = 0;
uint binID = 0;
uint binOffset = 0;
if (ID < g_num_elements) {
element_in = ELEMENT_IN(ID, iteration);
index_in = INDEX_IN(ID, iteration);
binID = (element_in >> shift) & uint(RADIX_SORT_BINS - 1);
// offset for group
binOffset = global_offsets[binID];
// add bit to flag
atomicAdd(bin_flags[binID].flags[flags_bin], flags_bit);
}
barrier();
if (ID < g_num_elements) {
// calculate output index of element
uint prefix = 0;
uint count = 0;
for (uint i = 0; i < WORKGROUP_SIZE / BITS; i++) {
const uint bits = bin_flags[binID].flags[i];
const uint full_count = bitCount(bits);
const uint partial_count = bitCount(bits & (flags_bit - 1));
prefix += (i < flags_bin) ? full_count : 0U;
prefix += (i == flags_bin) ? partial_count : 0U;
count += full_count;
}
if (iteration % 2 == 0) {
g_elements_out[binOffset + prefix] = element_in;
g_indices_out[binOffset + prefix] = index_in;
} else {
g_elements_in[binOffset + prefix] = element_in;
g_indices_in[binOffset + prefix] = index_in;
}
if (prefix == count - 1) {
atomicAdd(global_offsets[binID], count);
}
}
}
}
}
================================================
FILE: shader/splat_frag.glsl
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
//
// 3d gaussian splat fragment shader
//
/*%%HEADER%%*/
in vec4 frag_color; // radiance of splat
in vec4 frag_cov2inv; // inverse of the 2D screen space covariance matrix of the guassian
in vec2 frag_p; // 2D screen space center of the guassian
out vec4 out_color;
void main()
{
vec2 d = gl_FragCoord.xy - frag_p;
// TODO: Use texture for gaussian evaluation
// evaluate the gaussian
mat2 cov2Dinv = mat2(frag_cov2inv.xy, frag_cov2inv.zw);
float g = exp(-0.5f * dot(d, cov2Dinv * d));
out_color.rgb = frag_color.a * g * frag_color.rgb;
out_color.a = frag_color.a * g;
/*
// can be used to determine overdraw.
float epsilon = 1.0f / 256.0f;
out_color.rgb = frag_color.a * g * frag_color.rgb;
out_color.rgb = frag_color.rgb * 0.00000001f + vec3(epsilon, epsilon, epsilon);
out_color.a = 0.0f;
*/
if ((frag_color.a * g) <= (1.0f / 256.0f))
{
discard;
}
}
================================================
FILE: shader/splat_geom.glsl
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
/*%%HEADER%%*/
uniform vec4 viewport; // x, y, WIDTH, HEIGHT
layout(points) in;
layout(triangle_strip, max_vertices = 4) out;
in vec4 geom_color[]; // radiance of splat
in vec4 geom_cov2[]; // 2D screen space covariance matrix of the gaussian
in vec2 geom_p[]; // the 2D screen space center of the gaussian
out vec4 frag_color; // radiance of splat
out vec4 frag_cov2inv; // inverse of the 2D screen space covariance matrix of the guassian
out vec2 frag_p; // the 2D screen space center of the gaussian
// used to invert the 2D screen space covariance matrix
mat2 inverseMat2(mat2 m)
{
float det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
mat2 inv;
inv[0][0] = m[1][1] / det;
inv[0][1] = -m[0][1] / det;
inv[1][0] = -m[1][0] / det;
inv[1][1] = m[0][0] / det;
return inv;
}
void main()
{
float WIDTH = viewport.z;
float HEIGHT = viewport.w;
mat2 cov2D = mat2(geom_cov2[0].xy, geom_cov2[0].zw);
// we pass the inverse of the 2d covariance matrix to the pixel shader, to avoid doing a matrix inverse per pixel.
mat2 cov2Dinv = inverseMat2(cov2D);
vec4 cov2Dinv4 = vec4(cov2Dinv[0], cov2Dinv[1]); // cram it into a vec4
// discard splats that end up outside of a guard band
vec4 p4 = gl_in[0].gl_Position;
vec3 ndcP = p4.xyz / p4.w;
if (ndcP.z < 0.25f ||
ndcP.x > 2.0f || ndcP.x < -2.0f ||
ndcP.y > 2.0f || ndcP.y < -2.0f)
{
// discard this point
return;
}
// compute 2d extents for the splat, using covariance matrix ellipse
// see https://cookierobotics.com/007/
float k = 3.5f;
float a = cov2D[0][0];
float b = cov2D[0][1];
float c = cov2D[1][1];
float apco2 = (a + c) / 2.0f;
float amco2 = (a - c) / 2.0f;
float term = sqrt(amco2 * amco2 + b * b);
float maj = apco2 + term;
float min = apco2 - term;
float theta;
if (b == 0.0f)
{
theta = (a >= c) ? 0.0f : radians(90.0f);
}
else
{
theta = atan(maj - a, b);
}
float r1 = k * sqrt(maj);
float r2 = k * sqrt(min);
vec2 majAxis = vec2(r1 * cos(theta), r1 * sin(theta));
vec2 minAxis = vec2(r2 * cos(theta + radians(90.0f)), r2 * sin(theta + radians(90.0f)));
vec2 offsets[4];
offsets[0] = majAxis + minAxis;
offsets[1] = -majAxis + minAxis;
offsets[3] = -majAxis - minAxis;
offsets[2] = majAxis - minAxis;
vec2 offset;
float w = gl_in[0].gl_Position.w;
for (int i = 0; i < 4; i++)
{
// transform offset back into clip space, and apply it to gl_Position.
offset = offsets[i];
offset.x *= (2.0f / WIDTH) * w;
offset.y *= (2.0f / HEIGHT) * w;
gl_Position = gl_in[0].gl_Position + vec4(offset.x, offset.y, 0.0, 0.0);
frag_color = geom_color[0];
frag_cov2inv = cov2Dinv4;
frag_p = geom_p[0];
EmitVertex();
}
EndPrimitive();
}
================================================
FILE: shader/splat_peel_frag.glsl
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
//
// 3d gaussian splat fragment shader
//
/*%%HEADER%%*/
uniform sampler2D depthTex;
uniform vec4 viewport; // x, y, WIDTH, HEIGHT
in vec4 frag_color; // radiance of splat
in vec4 frag_cov2inv; // inverse of the 2D screen space covariance matrix of the guassian
in vec2 frag_p; // 2D screen space center of the guassian
out vec4 out_color;
void main()
{
vec2 d = gl_FragCoord.xy - frag_p;
vec2 uv = gl_FragCoord.xy / viewport.zw;
float depth = texture(depthTex, uv).r;
if (gl_FragCoord.z <= depth)
{
discard;
}
// TODO: Use texture for gaussian evaluation
// evaluate the gaussian
mat2 cov2Dinv = mat2(frag_cov2inv.xy, frag_cov2inv.zw);
float g = exp(-0.5f * dot(d, cov2Dinv * d));
out_color.rgb = frag_color.a * g * frag_color.rgb;
out_color.a = frag_color.a * g;
if ((frag_color.a * g) <= (10.0f / 256.0f))
{
discard;
}
/*
// can be used to determine overdraw.
float epsilon = 1.0f / 256.0f;
out_color.rgb = frag_color.a * g * frag_color.rgb;
out_color.rgb = frag_color.rgb * 0.00000001f + vec3(epsilon, epsilon, epsilon);
out_color.a = 0.0f;
*/
}
================================================
FILE: shader/splat_vert.glsl
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
//
// 3d gaussian splat vertex shader
//
/*%%HEADER%%*/
/*%%DEFINES%%*/
uniform mat4 viewMat; // used to project position into view coordinates.
uniform mat4 projMat; // used to project view coordinates into clip coordinates.
uniform vec4 projParams; // x = HEIGHT / tan(FOVY / 2), y = Z_NEAR, z = Z_FAR
uniform vec4 viewport; // x, y, WIDTH, HEIGHT
uniform vec3 eye;
in vec4 position; // center of the gaussian in object coordinates, (with alpha crammed in to w)
// spherical harmonics coeff for radiance of the splat
in vec4 r_sh0; // sh coeff for red channel (up to third-order)
#ifdef FULL_SH
in vec4 r_sh1;
in vec4 r_sh2;
in vec4 r_sh3;
#endif
in vec4 g_sh0; // sh coeff for green channel
#ifdef FULL_SH
in vec4 g_sh1;
in vec4 g_sh2;
in vec4 g_sh3;
#endif
in vec4 b_sh0; // sh coeff for blue channel
#ifdef FULL_SH
in vec4 b_sh1;
in vec4 b_sh2;
in vec4 b_sh3;
#endif
// 3x3 covariance matrix of the splat in object coordinates.
in vec3 cov3_col0;
in vec3 cov3_col1;
in vec3 cov3_col2;
out vec4 geom_color; // radiance of splat
out vec4 geom_cov2; // 2D screen space covariance matrix of the gaussian
out vec2 geom_p; // the 2D screen space center of the gaussian, (z is alpha)
vec3 ComputeRadianceFromSH(const vec3 v)
{
#ifdef FULL_SH
float b[16];
#else
float b[4];
#endif
float vx2 = v.x * v.x;
float vy2 = v.y * v.y;
float vz2 = v.z * v.z;
// zeroth order
// (/ 1.0 (* 2.0 (sqrt pi)))
b[0] = 0.28209479177387814f;
// first order
// (/ (sqrt 3.0) (* 2 (sqrt pi)))
float k1 = 0.4886025119029199f;
b[1] = -k1 * v.y;
b[2] = k1 * v.z;
b[3] = -k1 * v.x;
#ifdef FULL_SH
// second order
// (/ (sqrt 15.0) (* 2 (sqrt pi)))
float k2 = 1.0925484305920792f;
// (/ (sqrt 5.0) (* 4 (sqrt pi)))
float k3 = 0.31539156525252005f;
// (/ (sqrt 15.0) (* 4 (sqrt pi)))
float k4 = 0.5462742152960396f;
b[4] = k2 * v.y * v.x;
b[5] = -k2 * v.y * v.z;
b[6] = k3 * (3.0f * vz2 - 1.0f);
b[7] = -k2 * v.x * v.z;
b[8] = k4 * (vx2 - vy2);
// third order
// (/ (* (sqrt 2) (sqrt 35)) (* 8 (sqrt pi)))
float k5 = 0.5900435899266435f;
// (/ (sqrt 105) (* 2 (sqrt pi)))
float k6 = 2.8906114426405543f;
// (/ (* (sqrt 2) (sqrt 21)) (* 8 (sqrt pi)))
float k7 = 0.4570457994644658f;
// (/ (sqrt 7) (* 4 (sqrt pi)))
float k8 = 0.37317633259011546f;
// (/ (sqrt 105) (* 4 (sqrt pi)))
float k9 = 1.4453057213202771f;
b[9] = -k5 * v.y * (3.0f * vx2 - vy2);
b[10] = k6 * v.y * v.x * v.z;
b[11] = -k7 * v.y * (5.0f * vz2 - 1.0f);
b[12] = k8 * v.z * (5.0f * vz2 - 3.0f);
b[13] = -k7 * v.x * (5.0f * vz2 - 1.0f);
b[14] = k9 * v.z * (vx2 - vy2);
b[15] = -k5 * v.x * (vx2 - 3.0f * vy2);
float re = (b[0] * r_sh0.x + b[1] * r_sh0.y + b[2] * r_sh0.z + b[3] * r_sh0.w +
b[4] * r_sh1.x + b[5] * r_sh1.y + b[6] * r_sh1.z + b[7] * r_sh1.w +
b[8] * r_sh2.x + b[9] * r_sh2.y + b[10]* r_sh2.z + b[11]* r_sh2.w +
b[12]* r_sh3.x + b[13]* r_sh3.y + b[14]* r_sh3.z + b[15]* r_sh3.w);
float gr = (b[0] * g_sh0.x + b[1] * g_sh0.y + b[2] * g_sh0.z + b[3] * g_sh0.w +
b[4] * g_sh1.x + b[5] * g_sh1.y + b[6] * g_sh1.z + b[7] * g_sh1.w +
b[8] * g_sh2.x + b[9] * g_sh2.y + b[10]* g_sh2.z + b[11]* g_sh2.w +
b[12]* g_sh3.x + b[13]* g_sh3.y + b[14]* g_sh3.z + b[15]* g_sh3.w);
float bl = (b[0] * b_sh0.x + b[1] * b_sh0.y + b[2] * b_sh0.z + b[3] * b_sh0.w +
b[4] * b_sh1.x + b[5] * b_sh1.y + b[6] * b_sh1.z + b[7] * b_sh1.w +
b[8] * b_sh2.x + b[9] * b_sh2.y + b[10]* b_sh2.z + b[11]* b_sh2.w +
b[12]* b_sh3.x + b[13]* b_sh3.y + b[14]* b_sh3.z + b[15]* b_sh3.w);
#else
float re = (b[0] * r_sh0.x + b[1] * r_sh0.y + b[2] * r_sh0.z + b[3] * r_sh0.w);
float gr = (b[0] * g_sh0.x + b[1] * g_sh0.y + b[2] * g_sh0.z + b[3] * g_sh0.w);
float bl = (b[0] * b_sh0.x + b[1] * b_sh0.y + b[2] * b_sh0.z + b[3] * b_sh0.w);
#endif
return vec3(0.5f, 0.5f, 0.5f) + vec3(re, gr, bl);
}
#ifdef FRAMEBUFFER_SRGB
float SRGBToLinearF(float srgb)
{
if (srgb <= 0.04045f)
{
return srgb / 12.92f;
}
else
{
return pow((srgb + 0.055f) / 1.055f, 2.4f);
}
}
vec3 SRGBToLinear(const vec3 srgbColor)
{
vec3 linearColor;
for (int i = 0; i < 3; ++i) // Convert RGB, leave A unchanged
{
linearColor[i] = SRGBToLinearF(srgbColor[i]);
}
return linearColor;
}
#endif
void main(void)
{
// t is in view coordinates
float alpha = position.w;
vec4 t = viewMat * vec4(position.xyz, 1.0f);
//float X0 = viewport.x;
float X0 = viewport.x * (0.00001f * projParams.y); // one weird hack to prevent projParams from being compiled away
float Y0 = viewport.y;
float WIDTH = viewport.z;
float HEIGHT = viewport.w;
float Z_NEAR = projParams.y;
float Z_FAR = projParams.z;
// J is the jacobian of the projection and viewport transformations.
// this is an affine approximation of the real projection.
// because gaussians are closed under affine transforms.
float SX = projMat[0][0];
float SY = projMat[1][1];
float WZ = projMat[3][2];
float tzSq = t.z * t.z;
float jsx = -(SX * WIDTH) / (2.0f * t.z);
float jsy = -(SY * HEIGHT) / (2.0f * t.z);
float jtx = (SX * t.x * WIDTH) / (2.0f * tzSq);
float jty = (SY * t.y * HEIGHT) / (2.0f * tzSq);
float jtz = ((Z_FAR - Z_NEAR) * WZ) / (2.0f * tzSq);
mat3 J = mat3(vec3(jsx, 0.0f, 0.0f),
vec3(0.0f, jsy, 0.0f),
vec3(jtx, jty, jtz));
// combine the affine transforms of W (viewMat) and J (approx of viewportMat * projMat)
// using the fact that the new transformed covariance matrix V_Prime = JW * V * (JW)^T
mat3 W = mat3(viewMat);
mat3 V = mat3(cov3_col0, cov3_col1, cov3_col2);
mat3 JW = J * W;
mat3 V_prime = JW * V * transpose(JW);
// now we can 'project' the 3D covariance matrix onto the xy plane by just dropping the last column and row.
mat2 cov2D = mat2(V_prime);
// use the fact that the convolution of a gaussian with another gaussian is the sum
// of their covariance matrices to apply a low-pass filter to anti-alias the splats
cov2D[0][0] += 0.3f;
cov2D[1][1] += 0.3f;
geom_cov2 = vec4(cov2D[0], cov2D[1]); // cram it into a vec4
// geom_p is the gaussian center transformed into screen space
vec4 p4 = projMat * t;
geom_p = vec2(p4.x / p4.w, p4.y / p4.w);
geom_p.x = 0.5f * (WIDTH + (geom_p.x * WIDTH) + (2.0f * X0));
geom_p.y = 0.5f * (HEIGHT + (geom_p.y * HEIGHT) + (2.0f * Y0));
// compute radiance from sh
vec3 v = normalize(position.xyz - eye);
geom_color = vec4(ComputeRadianceFromSH(v), alpha);
#ifdef FRAMEBUFFER_SRGB
// The SIBR reference renderer uses sRGB throughout,
// i.e. the splat colors are sRGB, the gaussian and alpha-blending occurs in sRGB space.
// However, in vr our shader output must be in linear space,
// in order for openxr color conversion to work.
// So, we convert the splat color to linear,
// but the guassian and alpha-blending occur in linear space.
// This leads to results that don't quite match the SIBR reference.
geom_color.rgb = SRGBToLinear(geom_color.rgb);
#endif
// gl_Position is in clip coordinates.
gl_Position = p4;
}
================================================
FILE: shader/text_frag.glsl
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
/*%%HEADER%%*/
uniform sampler2D fontTex;
in vec2 frag_uv;
in vec4 frag_color;
out vec4 out_color;
void main()
{
vec4 texColor = texture(fontTex, frag_uv, -0.5f); // bias to increase sharpness a bit
// premultiplied alpha blending
out_color.rgb = frag_color.a * frag_color.rgb * texColor.rgb;
out_color.a = frag_color.a * texColor.a;
}
================================================
FILE: shader/text_vert.glsl
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
/*%%HEADER%%*/
uniform mat4 modelViewProjMat;
in vec3 position;
in vec2 uv;
in vec4 color;
out vec2 frag_uv;
out vec4 frag_color;
void main(void)
{
gl_Position = modelViewProjMat * vec4(position, 1.0f);
frag_uv = uv;
frag_color = color;
}
================================================
FILE: src/android_main.cpp
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
#include <android/native_window_jni.h> // for native window JNI
#include <android_native_app_glue.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES3/gl3.h>
#include <GLES3/gl3ext.h>
#include <jni.h>
#include <sys/prctl.h> // for prctl( PR_SET_NAME )
#include <sys/stat.h>
#include <sys/types.h>
#include "core/log.h"
#include "core/util.h"
#include "core/xrbuddy.h"
#include "app.h"
// AJT: ANDROID TODO: xrPerfSettingsSetPerformanceLevelEXT
// AJT: ANDROID TODO: pfnSetAndroidApplicationThreadKHR on XR_SESSION_STATE_READY
// see ovrApp::HandleSessionStateChanges in SceneModelXr.cpp
/*
static const int CPU_LEVEL = 2;
static const int GPU_LEVEL = 3;
*/
static const char* EglErrorString(const EGLint error) {
switch (error) {
case EGL_SUCCESS:
return "EGL_SUCCESS";
case EGL_NOT_INITIALIZED:
return "EGL_NOT_INITIALIZED";
case EGL_BAD_ACCESS:
return "EGL_BAD_ACCESS";
case EGL_BAD_ALLOC:
return "EGL_BAD_ALLOC";
case EGL_BAD_ATTRIBUTE:
return "EGL_BAD_ATTRIBUTE";
case EGL_BAD_CONTEXT:
return "EGL_BAD_CONTEXT";
case EGL_BAD_CONFIG:
return "EGL_BAD_CONFIG";
case EGL_BAD_CURRENT_SURFACE:
return "EGL_BAD_CURRENT_SURFACE";
case EGL_BAD_DISPLAY:
return "EGL_BAD_DISPLAY";
case EGL_BAD_SURFACE:
return "EGL_BAD_SURFACE";
case EGL_BAD_MATCH:
return "EGL_BAD_MATCH";
case EGL_BAD_PARAMETER:
return "EGL_BAD_PARAMETER";
case EGL_BAD_NATIVE_PIXMAP:
return "EGL_BAD_NATIVE_PIXMAP";
case EGL_BAD_NATIVE_WINDOW:
return "EGL_BAD_NATIVE_WINDOW";
case EGL_CONTEXT_LOST:
return "EGL_CONTEXT_LOST";
default:
return "unknown";
}
}
struct AppContext
{
AppContext() : resumed(false), sessionActive(false), assMan(nullptr), alwaysCopyAssets(true) {}
bool resumed;
bool sessionActive;
struct EGLInfo
{
EGLInfo() : majorVersion(0), minorVersion(0), display(0), config(0), context(EGL_NO_CONTEXT) {}
EGLint majorVersion;
EGLint minorVersion;
EGLDisplay display;
EGLConfig config;
EGLContext context;
EGLSurface tinySurface;
};
EGLInfo egl;
AAssetManager* assMan;
std::string externalDataPath;
bool alwaysCopyAssets;
void Clear()
{
resumed = false;
sessionActive = false;
egl.majorVersion = 0;
egl.minorVersion = 0;
egl.display = 0;
egl.config = 0;
egl.context = EGL_NO_CONTEXT;
}
bool SetupEGLContext()
{
// create the egl context
egl.display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(egl.display, &egl.majorVersion, &egl.minorVersion);
Log::D("OpenGLES majorVersion = %d, minorVersion = %d\n", egl.majorVersion, egl.minorVersion);
const int MAX_CONFIGS = 1024;
EGLConfig configs[MAX_CONFIGS];
EGLint numConfigs = 0;
if (!eglGetConfigs(egl.display, configs, MAX_CONFIGS, &numConfigs))
{
Log::E("eglGetConfigs failed: %s\n", EglErrorString(eglGetError()));
return false;
}
const EGLint configAttribs[] = {EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8, // need alpha for the multi-pass timewarp compositor
EGL_DEPTH_SIZE, 0,
EGL_STENCIL_SIZE, 0,
EGL_SAMPLES, 0,
EGL_NONE};
egl.config = 0;
for (int i = 0; i < numConfigs; i++)
{
EGLint value = 0;
eglGetConfigAttrib(egl.display, configs[i], EGL_RENDERABLE_TYPE, &value);
if ((value & EGL_OPENGL_ES3_BIT_KHR) != EGL_OPENGL_ES3_BIT_KHR) {
continue;
}
// The pbuffer config also needs to be compatible with normal window rendering
// so it can share textures with the window context.
eglGetConfigAttrib(egl.display, configs[i], EGL_SURFACE_TYPE, &value);
if ((value & (EGL_WINDOW_BIT | EGL_PBUFFER_BIT)) != (EGL_WINDOW_BIT | EGL_PBUFFER_BIT)) {
continue;
}
int j = 0;
for (; configAttribs[j] != EGL_NONE; j += 2) {
eglGetConfigAttrib(egl.display, configs[i], configAttribs[j], &value);
if (value != configAttribs[j + 1]) {
break;
}
}
if (configAttribs[j] == EGL_NONE) {
egl.config = configs[i];
break;
}
}
if (egl.config == 0)
{
Log::E("eglChooseConfig() failed: %s\n", EglErrorString(eglGetError()));
return false;
}
EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
egl.context = eglCreateContext(egl.display, egl.config, EGL_NO_CONTEXT, contextAttribs);
if (egl.context == EGL_NO_CONTEXT)
{
Log::E("eglCreateContext() failed: %s", EglErrorString(eglGetError()));
return false;
}
const EGLint surfaceAttribs[] = {EGL_WIDTH, 16, EGL_HEIGHT, 16, EGL_NONE};
egl.tinySurface = eglCreatePbufferSurface(egl.display, egl.config, surfaceAttribs);
if (egl.tinySurface == EGL_NO_SURFACE)
{
Log::E("eglCreatePbufferSurface() failed: %s", EglErrorString(eglGetError()));
eglDestroyContext(egl.display, egl.context);
egl.context = EGL_NO_CONTEXT;
return false;
}
if (eglMakeCurrent(egl.display, egl.tinySurface, egl.tinySurface, egl.context) == EGL_FALSE)
{
Log::E("eglMakeCurrent() failed: %s", EglErrorString(eglGetError()));
eglDestroySurface(egl.display, egl.tinySurface);
eglDestroyContext(egl.display, egl.context);
egl.context = EGL_NO_CONTEXT;
return false;
}
return true;
}
bool SetupAssets(android_app* app)
{
assert(app);
assert(app->activity->assetManager);
assert(app->activity->externalDataPath);
assMan = app->activity->assetManager;
externalDataPath = std::string(app->activity->externalDataPath) + "/";
// from util.h
SetRootPath(externalDataPath);
Log::D("AJT: externalDataPath = \"%s\"\n", externalDataPath.c_str());
MakeDir("texture");
UnpackAsset("texture/carpet.png");
UnpackAsset("texture/sphere.png");
MakeDir("shader");
UnpackAsset("shader/carpet_frag.glsl");
UnpackAsset("shader/carpet_vert.glsl");
UnpackAsset("shader/debugdraw_frag.glsl");
UnpackAsset("shader/debugdraw_vert.glsl");
UnpackAsset("shader/desktop_frag.glsl");
UnpackAsset("shader/desktop_vert.glsl");
UnpackAsset("shader/point_frag.glsl");
UnpackAsset("shader/point_geom.glsl");
UnpackAsset("shader/point_vert.glsl");
UnpackAsset("shader/presort_compute.glsl");
UnpackAsset("shader/splat_frag.glsl");
UnpackAsset("shader/splat_geom.glsl");
UnpackAsset("shader/splat_vert.glsl");
UnpackAsset("shader/text_frag.glsl");
UnpackAsset("shader/text_vert.glsl");
MakeDir("font");
UnpackAsset("font/JetBrainsMono-Medium.json");
UnpackAsset("font/JetBrainsMono-Medium.png");
MakeDir("data");
MakeDir("data/sh_test");
UnpackAsset("data/sh_test/cameras.json");
UnpackAsset("data/sh_test/cfg_args");
UnpackAsset("data/sh_test/input.ply");
MakeDir("data/sh_test/point_cloud");
MakeDir("data/sh_test/point_cloud/iteration_30000");
UnpackAsset("data/sh_test/point_cloud/iteration_30000/point_cloud.ply");
UnpackAsset("data/sh_test/vr.json");
MakeDir("data/livingroom");
UnpackAsset("data/livingroom/livingroom.ply");
UnpackAsset("data/livingroom/livingroom_vr.json");
return true;
}
bool MakeDir(const std::string& dirFilename)
{
std::string filename = externalDataPath + dirFilename;
if (mkdir(filename.c_str(), 0777) != 0)
{
if (errno == EEXIST)
{
Log::D("MakeDir \"%s\" already exists\n", dirFilename.c_str());
// dir already exists!
return true;
}
else
{
Log::E("mkdir failed on dir \"%s\" errno = %d\n", filename.c_str(), errno);
return false;
}
}
Log::D("MakeDir \"%s\"\n", dirFilename.c_str());
return true;
}
bool UnpackAsset(const std::string& assetFilename)
{
std::string outputFilename = externalDataPath + assetFilename;
struct stat sb;
if (stat(outputFilename.c_str(), &sb) == 0)
{
if (!alwaysCopyAssets)
{
Log::D("UnpackAsset \"%s\" already exists\n", assetFilename.c_str());
return true;
}
}
AAsset *asset = AAssetManager_open(assMan, assetFilename.c_str(), AASSET_MODE_STREAMING);
if (asset == nullptr)
{
Log::E("UnpackAsset \"%s\" AAssetManager_open failed!\n", assetFilename.c_str());
return false; // Failed to open the asset
}
// Create buffer for reading
const size_t BUFFER_SIZE = 1024;
char buffer[BUFFER_SIZE];
// Open file for writing
FILE *outFile = fopen(outputFilename.c_str(), "w");
if (outFile == nullptr)
{
Log::E("UnpackAsset \"%s\" fopen failed!\n", assetFilename.c_str());
AAsset_close(asset);
return false; // Failed to open the output file
}
// Read from assets and write to file
int bytesRead;
while ((bytesRead = AAsset_read(asset, buffer, BUFFER_SIZE)) > 0)
{
fwrite(buffer, sizeof(char), bytesRead, outFile);
}
// Close the asset and the output file
AAsset_close(asset);
fclose(outFile);
Log::D("UnpackAsset \"%s\"\n", assetFilename.c_str());
return true;
}
};
/**
* Process the next main command.
*/
static void app_handle_cmd(struct android_app* androidApp, int32_t cmd)
{
AppContext& ctx = *(AppContext*)androidApp->userData;
switch (cmd)
{
// There is no APP_CMD_CREATE. The ANativeActivity creates the
// application thread from onCreate(). The application thread
// then calls android_main().
case APP_CMD_START:
Log::D("onStart()\n");
Log::D(" APP_CMD_START\n");
break;
case APP_CMD_RESUME:
Log::D("onResume()\n");
Log::D(" APP_CMD_RESUME\n");
ctx.resumed = true;
break;
case APP_CMD_PAUSE:
Log::D("onPause()\n");
Log::D(" APP_CMD_PAUSE\n");
ctx.resumed = false;
break;
case APP_CMD_STOP:
Log::D("onStop()\n");
Log::D(" APP_CMD_STOP\n");
break;
case APP_CMD_DESTROY:
Log::D("onDestroy()\n");
Log::D(" APP_CMD_DESTROY\n");
ctx.Clear();
break;
case APP_CMD_INIT_WINDOW:
Log::D("surfaceCreated()\n");
Log::D(" APP_CMD_INIT_WINDOW\n");
break;
case APP_CMD_TERM_WINDOW:
Log::D("surfaceDestroyed()\n");
Log::D(" APP_CMD_TERM_WINDOW\n");
break;
}
}
/**
* This is the main entry point of a native application that is using
* android_native_app_glue. It runs in its own thread, with its own
* event loop for receiving input events and doing other things.
*/
void android_main(struct android_app* androidApp)
{
Log::SetAppName("splatapult");
Log::D("----------------------------------------------------------------\n");
Log::D("android_app_entry()\n");
Log::D(" android_main()\n");
JNIEnv* env;
(*androidApp->activity->vm).AttachCurrentThread(&env, nullptr);
// Note that AttachCurrentThread will reset the thread name.
prctl(PR_SET_NAME, (long)"android_main", 0, 0, 0);
AppContext ctx;
androidApp->userData = &ctx;
androidApp->onAppCmd = app_handle_cmd;
if (!ctx.SetupEGLContext())
{
Log::E("AppContext::SetupEGLContext failed!\n");
return;
}
if (!ctx.SetupAssets(androidApp))
{
Log::E("AppContext::SetupAssets failed!\n");
return;
}
MainContext mainContext;
mainContext.display = ctx.egl.display;
mainContext.config = ctx.egl.config;
mainContext.context = ctx.egl.context;
mainContext.androidApp = androidApp;
std::string dataPath = ctx.externalDataPath + "data/livingroom/livingroom.ply";
int argc = 4;
const char* argv[] = {"splataplut", "-v", "-d", dataPath.c_str()};
App app(mainContext);
App::ParseResult parseResult = app.ParseArguments(argc, argv);
switch (parseResult)
{
case App::SUCCESS_RESULT:
break;
case App::ERROR_RESULT:
Log::E("App::ParseArguments failed!\n");
return;
case App::QUIT_RESULT:
return;
}
if (!app.Init())
{
Log::E("App::Init failed!\n");
return;
}
while (androidApp->destroyRequested == 0)
{
// Read all pending events.
for (;;)
{
int events;
struct android_poll_source* source;
// If the timeout is zero, returns immediately without blocking.
// If the timeout is negative, waits indefinitely until an event appears.
int timeoutMilliseconds = 0;
if (ctx.resumed == false && ctx.sessionActive == false && androidApp->destroyRequested == 0)
{
timeoutMilliseconds = -1;
}
if (ALooper_pollAll(timeoutMilliseconds, NULL, &events, (void**)&source) < 0)
{
break;
}
// Process this event.
if (source != NULL)
{
source->process(androidApp, source);
}
}
float dt = 1.0f / 72.0f;
if (!app.Process(dt))
{
Log::E("App::Process failed!\n");
break;
}
if (!app.Render(dt, glm::ivec2(0.0f, 0.0f)))
{
Log::E("App::Render failed!\n");
return;
}
}
// TODO: DESTROY STUFF
Log::D("Finished!\n");
(*androidApp->activity->vm).DetachCurrentThread();
}
================================================
FILE: src/app.cpp
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
#include "app.h"
#ifndef __ANDROID__
#define USE_SDL
#include <GL/glew.h>
#endif
#ifdef USE_SDL
#include <SDL2/SDL.h>
#endif
#include <filesystem>
#include <thread>
#ifdef TRACY_ENABLE
#include <tracy/Tracy.hpp>
#else
#define ZoneScoped
#define ZoneScopedNC(NAME, COLOR)
#endif
#include "core/framebuffer.h"
#include "core/log.h"
#include "core/debugrenderer.h"
#include "core/inputbuddy.h"
#include "core/optionparser.h"
#include "core/textrenderer.h"
#include "core/texture.h"
#include "core/util.h"
#include "core/xrbuddy.h"
#include "camerasconfig.h"
#include "camerapathrenderer.h"
#include "flycam.h"
#include "gaussiancloud.h"
#include "magiccarpet.h"
#include "pointcloud.h"
#include "pointrenderer.h"
#include "splatrenderer.h"
#include "vrconfig.h"
enum optionIndex
{
UNKNOWN,
OPENXR,
FULLSCREEN,
DEBUG,
HELP,
FP16,
FP32,
NOSH,
};
const option::Descriptor usage[] =
{
{ UNKNOWN, 0, "", "", option::Arg::None, "USAGE: splatapult [options] FILE.ply\n\nOptions:" },
{ HELP, 0, "h", "help", option::Arg::None, " -h, --help Print usage and exit." },
{ OPENXR, 0, "v", "openxr", option::Arg::None, " -v, --openxr Launch app in vr mode, using openxr runtime." },
{ FULLSCREEN, 0, "f", "fullscren", option::Arg::None, " -f, --fullscreen Launch window in fullscreen." },
{ DEBUG, 0, "d", "debug", option::Arg::None, " -d, --debug Enable verbose debug logging." },
{ FP16, 0, "", "fp16", option::Arg::None, " --fp16 Use 16-bit half-precision floating frame buffer, to reduce color banding artifacts" },
{ FP32, 0, "", "fp32", option::Arg::None, " --fp32 Use 32-bit floating point frame buffer, to reduce color banding even more" },
{ NOSH, 0, "", "nosh", option::Arg::None, " --nosh Don't load/render full sh, this will reduce memory usage and higher performance" },
{ UNKNOWN, 0, "", "", option::Arg::None, "\nExamples:\n splataplut data/test.ply\n splatapult -v data/test.ply" },
{ 0, 0, 0, 0, 0, 0}
};
const float Z_NEAR = 0.1f;
const float Z_FAR = 1000.0f;
const float FOVY = glm::radians(45.0f);
const float MOVE_SPEED = 2.5f;
const float ROT_SPEED = 1.15f;
const glm::vec4 WHITE = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f);
const glm::vec4 BLACK = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
const int TEXT_NUM_ROWS = 25;
#include <string>
#include <filesystem>
#include <iostream>
// searches for file named configFilename, dir that contains plyFilename, it's parent and grandparent dirs.
static std::string FindConfigFile(const std::string& plyFilename, const std::string& configFilename)
{
std::filesystem::path plyPath(plyFilename);
if (!std::filesystem::exists(plyPath) || !std::filesystem::is_regular_file(plyPath))
{
Log::E("PLY file does not exist or is not a file: \"%s\"", plyFilename.c_str());
return "";
}
std::filesystem::path directory = plyPath.parent_path();
for (int i = 0; i < 3; ++i) // Check current, parent, and grandparent directories
{
std::filesystem::path configPath = directory / configFilename;
if (std::filesystem::exists(configPath) && std::filesystem::is_regular_file(configPath))
{
return configPath.string();
}
if (directory.has_parent_path())
{
directory = directory.parent_path();
}
else
{
break;
}
}
return "";
}
static std::string GetFilenameWithoutExtension(const std::string& filepath)
{
std::filesystem::path pathObj(filepath);
// Check if the path has a stem (the part of the path before the extension)
if (pathObj.has_stem())
{
return pathObj.stem().string();
}
// If there is no stem, return an empty string
return "";
}
static std::string MakeVrConfigFilename(const std::string& plyFilename)
{
std::filesystem::path plyPath(plyFilename);
std::filesystem::path directory = plyPath.parent_path();
std::string plyNoExt = GetFilenameWithoutExtension(plyFilename) + "_vr.json";
std::filesystem::path configPath = directory / plyNoExt;
return configPath.string();
}
static void Clear(glm::ivec2 windowSize, bool setViewport = true)
{
int width = windowSize.x;
int height = windowSize.y;
if (setViewport)
{
glViewport(0, 0, width, height);
}
// pre-multiplied alpha blending
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glm::vec4 clearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// NOTE: if depth buffer has less then 24 bits, it can mess up splat rendering.
glEnable(GL_DEPTH_TEST);
}
// Draw a textured quad over the entire screen.
static void RenderDesktop(glm::ivec2 windowSize, std::shared_ptr<Program> desktopProgram, uint32_t colorTexture, bool adjustAspect)
{
int width = windowSize.x;
int height = windowSize.y;
glViewport(0, 0, width, height);
glm::vec4 clearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClearColor(clearColor.x, clearColor.y, clearColor.z, clearColor.w);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glm::mat4 projMat = glm::ortho(0.0f, (float)width, 0.0f, (float)height, -10.0f, 10.0f);
if (colorTexture > 0)
{
desktopProgram->Bind();
desktopProgram->SetUniform("modelViewProjMat", projMat);
desktopProgram->SetUniform("color", glm::vec4(1.0f));
// use texture unit 0 for colorTexture
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, colorTexture);
desktopProgram->SetUniform("colorTexture", 0);
glm::vec2 xyLowerLeft(0.0f, 0.0f);
glm::vec2 xyUpperRight((float)width, (float)height);
if (adjustAspect)
{
xyLowerLeft = glm::vec2(0.0f, (height - width) / 2.0f);
xyUpperRight = glm::vec2((float)width, (height + width) / 2.0f);
}
glm::vec2 uvLowerLeft(0.0f, 0.0f);
glm::vec2 uvUpperRight(1.0f, 1.0f);
float depth = -9.0f;
glm::vec3 positions[] = {glm::vec3(xyLowerLeft, depth), glm::vec3(xyUpperRight.x, xyLowerLeft.y, depth),
glm::vec3(xyUpperRight, depth), glm::vec3(xyLowerLeft.x, xyUpperRight.y, depth)};
desktopProgram->SetAttrib("position", positions);
glm::vec2 uvs[] = {uvLowerLeft, glm::vec2(uvUpperRight.x, uvLowerLeft.y),
uvUpperRight, glm::vec2(uvLowerLeft.x, uvUpperRight.y)};
desktopProgram->SetAttrib("uv", uvs);
const size_t NUM_INDICES = 6;
uint16_t indices[NUM_INDICES] = {0, 1, 2, 0, 2, 3};
glDrawElements(GL_TRIANGLES, NUM_INDICES, GL_UNSIGNED_SHORT, indices);
}
}
static std::shared_ptr<PointCloud> LoadPointCloud(const std::string& plyFilename, bool useLinearColors)
{
auto pointCloud = std::make_shared<PointCloud>(useLinearColors);
if (!pointCloud->ImportPly(plyFilename))
{
Log::E("Error loading PointCloud!\n");
return nullptr;
}
return pointCloud;
}
static std::shared_ptr<GaussianCloud> LoadGaussianCloud(const std::string& plyFilename, const App::Options& opt)
{
GaussianCloud::Options options = {0};
#ifdef __ANDROID__
options.importFullSH = false;
options.exportFullSH = false;
#else
options.importFullSH = opt.importFullSH;
options.exportFullSH = true;
#endif
auto gaussianCloud = std::make_shared<GaussianCloud>(options);
if (!gaussianCloud->ImportPly(plyFilename))
{
Log::E("Error loading GaussianCloud!\n");
return nullptr;
}
return gaussianCloud;
}
static void PrintControls()
{
fprintf(stdout, "\
\n\
Desktop Controls\n\
--------------------\n\
* wasd - move\n\
* arrow keys - look\n\
* right mouse button - hold down for mouse look.\n\
* gamepad - if present, right stick to rotate, left stick to move, bumpers to roll\n\
* c - toggle between initial SfM point cloud (if present) and gaussian splats.\n\
* n - jump to next camera\n\
* p - jump to previous camera\n\
\n\
VR Controls\n\
---------------\n\
* c - toggle between initial SfM point cloud (if present) and gaussian splats.\n\
* left stick - move\n\
* right stick - snap turn\n\
* f - show hide floor carpet.\n\
* single grab - translate the world.\n\
* double grab - rotate and translate the world.\n\
* triple grab - (double grab while trigger is depressed) scale, rotate and translate the world.\n\
* return - save the current position and orientation/scale of the world into a vr.json file.\n\
\n");
}
App::App(MainContext& mainContextIn):
mainContext(mainContextIn)
{
cameraIndex = 0;
virtualLeftStick = glm::vec2(0.0f, 0.0f);
virtualRightStick = glm::vec2(0.0f, 0.0f);
mouseLookStick = glm::vec2(0.0f, 0.0f);
mouseLook = false;
virtualRoll = 0.0f;
virtualUp = 0.0f;
frameNum = 0;
}
App::ParseResult App::ParseArguments(int argc, const char* argv[])
{
// skip program name
if (argc > 0)
{
argc--;
argv++;
}
option::Stats stats(usage, argc, argv);
std::vector<option::Option> options(stats.options_max);
std::vector<option::Option> buffer(stats.buffer_max);
option::Parser parse(usage, argc, argv, options.data(), buffer.data());
if (parse.error())
{
return ERROR_RESULT;
}
if (options[HELP] || argc == 0)
{
option::printUsage(std::cout, usage);
PrintControls();
return QUIT_RESULT;
}
if (options[OPENXR])
{
opt.vrMode = true;
}
if (options[FULLSCREEN])
{
opt.fullscreen = true;
}
if (options[DEBUG])
{
opt.debugLogging = true;
}
if (options[FP32])
{
opt.frameBuffer = Options::FrameBuffer::Float;
}
else if (options[FP16])
{
opt.frameBuffer = Options::FrameBuffer::HalfFloat;
}
opt.importFullSH = options[NOSH] ? false : true;
bool unknownOptionFound = false;
for (option::Option* opt = options[UNKNOWN]; opt; opt = opt->next())
{
unknownOptionFound = true;
std::cout << "Unknown option: " << std::string(opt->name,opt->namelen) << "\n";
}
if (unknownOptionFound)
{
return ERROR_RESULT;
}
if (parse.nonOptionsCount() == 0)
{
std::cout << "Expected filename argument\n";
return ERROR_RESULT;
}
else
{
plyFilename = parse.nonOption(0);
}
Log::SetLevel(opt.debugLogging ? Log::Debug : Log::Warning);
std::filesystem::path plyPath(plyFilename);
if (!std::filesystem::exists(plyPath) || !std::filesystem::is_regular_file(plyPath))
{
Log::E("Invalid file \"%s\"\n", plyFilename.c_str());
return ERROR_RESULT;
}
return SUCCESS_RESULT;
}
bool App::Init()
{
bool isFramebufferSRGBEnabled = opt.vrMode;
#ifndef __ANDROID__
// AJT: ANDROID: TODO: make sure colors are accurate on android.
if (isFramebufferSRGBEnabled)
{
// necessary for proper color conversion
glEnable(GL_FRAMEBUFFER_SRGB);
}
else
{
glDisable(GL_FRAMEBUFFER_SRGB);
}
GLenum err = glewInit();
if (GLEW_OK != err)
{
Log::E("Error: %s\n", glewGetErrorString(err));
return false;
}
#endif
debugRenderer = std::make_shared<DebugRenderer>();
if (!debugRenderer->Init())
{
Log::E("DebugRenderer Init failed\n");
return false;
}
textRenderer = std::make_shared<TextRenderer>();
if (!textRenderer->Init("font/JetBrainsMono-Medium.json", "font/JetBrainsMono-Medium.png"))
{
Log::E("TextRenderer Init failed\n");
return false;
}
if (opt.vrMode)
{
xrBuddy = std::make_shared<XrBuddy>(mainContext, glm::vec2(Z_NEAR, Z_FAR));
if (!xrBuddy->Init())
{
Log::E("OpenXR Init failed\n");
return false;
}
}
std::string camerasConfigFilename = FindConfigFile(plyFilename, "cameras.json");
if (!camerasConfigFilename.empty())
{
camerasConfig = std::make_shared<CamerasConfig>();
if (!camerasConfig->ImportJson(camerasConfigFilename))
{
Log::W("Error loading cameras.json\n");
camerasConfig.reset();
}
}
else
{
Log::D("Could not find cameras.json\n");
}
if (camerasConfig)
{
cameraPathRenderer = std::make_shared<CameraPathRenderer>();
if (!cameraPathRenderer->Init(camerasConfig->GetCameraVec()))
{
Log::E("CameraPathRenderer Init failed\n");
return false;
}
}
// search for vr config file
// for example: if plyFilename is "input.ply", then search for "input_vr.json"
std::string vrConfigBaseFilename = GetFilenameWithoutExtension(plyFilename) + "_vr.json";
std::string vrConfigFilename = FindConfigFile(plyFilename, vrConfigBaseFilename);
if (!vrConfigFilename.empty())
{
vrConfig = std::make_shared<VrConfig>();
if (!vrConfig->ImportJson(vrConfigFilename))
{
Log::I("Could not load vr.json\n");
vrConfig.reset();
}
}
else
{
Log::D("Could not find %s\n", vrConfigFilename.c_str());
// Where we'd like the vr config file to exist.
vrConfigFilename = MakeVrConfigFilename(plyFilename);
}
glm::mat4 flyCamMat(1.0f);
glm::mat4 floorMat(1.0f);
if (camerasConfig)
{
flyCamMat = camerasConfig->GetCameraVec()[cameraIndex].mat;
// initialize magicCarpet from first camera and estimated floor position.
if (camerasConfig->GetNumCameras() > 0)
{
glm::vec3 floorNormal, floorPos;
camerasConfig->EstimateFloorPlane(floorNormal, floorPos);
glm::vec3 floorZ = camerasConfig->GetCameraVec()[0].mat[2];
glm::vec3 floorY = floorNormal;
glm::vec3 floorX = glm::cross(floorY, floorZ);
floorZ = glm::cross(floorX, floorY);
floorMat = glm::mat4(glm::vec4(floorX, 0.0f),
glm::vec4(floorY, 0.0f),
glm::vec4(floorZ, 0.0f),
glm::vec4(floorPos, 1.0f));
}
}
if (vrConfig)
{
floorMat = vrConfig->GetFloorMat();
if (!camerasConfig)
{
glm::vec3 pos = floorMat[3];
pos += glm::mat3(floorMat) * glm::vec3(0.0f, 1.5f, 0.0f);
glm::mat4 adjustedFloorMat = floorMat;
adjustedFloorMat[3] = glm::vec4(pos, 1.0f);
flyCamMat = adjustedFloorMat;
}
}
glm::vec3 flyCamPos, flyCamScale, floorMatUp;
glm::quat flyCamRot;
floorMatUp = glm::vec3(floorMat[1]);
Decompose(flyCamMat, &flyCamScale, &flyCamRot, &flyCamPos);
flyCam = std::make_shared<FlyCam>(floorMatUp, flyCamPos, flyCamRot, MOVE_SPEED, ROT_SPEED);
magicCarpet = std::make_shared<MagicCarpet>(floorMat, MOVE_SPEED);
if (!magicCarpet->Init(isFramebufferSRGBEnabled))
{
Log::E("Error initalizing MagicCarpet\n");
return false;
}
std::string pointCloudFilename = FindConfigFile(plyFilename, "input.ply");
if (!pointCloudFilename.empty())
{
pointCloud = LoadPointCloud(pointCloudFilename, isFramebufferSRGBEnabled);
if (!pointCloud)
{
Log::E("Error loading PointCloud\n");
return false;
}
pointRenderer = std::make_shared<PointRenderer>();
if (!pointRenderer->Init(pointCloud, isFramebufferSRGBEnabled))
{
Log::E("Error initializing point renderer!\n");
return false;
}
}
else
{
Log::D("Could not find input.ply\n");
}
gaussianCloud = LoadGaussianCloud(plyFilename, opt);
if (!gaussianCloud)
{
Log::E("Error loading GaussianCloud\n");
return false;
}
#if 0
const uint32_t SPLAT_COUNT = 25000;
glm::vec3 focalPoint = flyCam->GetCameraMat()[3];
//gaussianCloud->PruneSplats(glm::vec3(flyCam->GetCameraMat()[3]), SPLAT_COUNT);
gaussianCloud->PruneSplats(focalPoint, SPLAT_COUNT);
#endif
splatRenderer = std::make_shared<SplatRenderer>();
#if __ANDROID__
bool useRgcSortOverride = true;
#else
bool useRgcSortOverride = false;
#endif
if (!splatRenderer->Init(gaussianCloud, isFramebufferSRGBEnabled, useRgcSortOverride))
{
Log::E("Error initializing splat renderer!\n");
return false;
}
if (opt.vrMode)
{
desktopProgram = std::make_shared<Program>();
std::string defines = "#define USE_SUPERSAMPLING\n";
desktopProgram->AddMacro("DEFINES", defines);
if (!desktopProgram->LoadVertFrag("shader/desktop_vert.glsl", "shader/desktop_frag.glsl"))
{
Log::E("Error loading desktop shader!\n");
return 1;
}
xrBuddy->SetRenderCallback([this](
const glm::mat4& projMat, const glm::mat4& eyeMat,
const glm::vec4& viewport, const glm::vec2& nearFar, int viewNum)
{
Clear(glm::ivec2(0, 0), false);
glm::mat4 fullEyeMat = magicCarpet->GetCarpetMat() * eyeMat;
if (opt.drawDebug)
{
debugRenderer->Render(fullEyeMat, projMat, viewport, nearFar);
}
if (cameraPathRenderer)
{
cameraPathRenderer->SetShowCameras(opt.drawCameraFrustums);
cameraPathRenderer->SetShowPath(opt.drawCameraPath);
cameraPathRenderer->Render(fullEyeMat, projMat, viewport, nearFar);
}
if (opt.drawCarpet)
{
magicCarpet->Render(fullEyeMat, projMat, viewport, nearFar);
}
if (opt.drawPointCloud && pointRenderer)
{
pointRenderer->Render(fullEyeMat, projMat, viewport, nearFar);
}
else
{
if (viewNum == 0)
{
splatRenderer->Sort(fullEyeMat, projMat, viewport, nearFar);
}
splatRenderer->Render(fullEyeMat, projMat, viewport, nearFar);
}
});
}
if (!opt.vrMode && opt.frameBuffer != Options::FrameBuffer::Default)
{
desktopProgram = std::make_shared<Program>();
if (!desktopProgram->LoadVertFrag("shader/desktop_vert.glsl", "shader/desktop_frag.glsl"))
{
Log::E("Error loading desktop shader!\n");
return 1;
}
}
#ifdef USE_SDL
inputBuddy = std::make_shared<InputBuddy>();
inputBuddy->OnQuit([this]()
{
// forward this back to main
quitCallback();
});
inputBuddy->OnResize([this](int newWidth, int newHeight)
{
glViewport(0, 0, newWidth, newHeight);
resizeCallback(newWidth, newHeight);
});
inputBuddy->OnKey(SDLK_ESCAPE, [this](bool down, uint16_t mod)
{
quitCallback();
});
inputBuddy->OnKey(SDLK_c, [this](bool down, uint16_t mod)
{
if (down)
{
opt.drawPointCloud = !opt.drawPointCloud;
}
});
inputBuddy->OnKey(SDLK_n, [this](bool down, uint16_t mod)
{
if (down && camerasConfig)
{
cameraIndex++;
if (cameraIndex >= (int)camerasConfig->GetNumCameras())
{
cameraIndex -= (int)camerasConfig->GetNumCameras();
}
flyCam->SetCameraMat(camerasConfig->GetCameraVec()[cameraIndex].mat);
}
});
inputBuddy->OnKey(SDLK_p, [this](bool down, uint16_t mod)
{
if (down && camerasConfig)
{
cameraIndex--;
if (cameraIndex < 0)
{
cameraIndex += (int)camerasConfig->GetNumCameras();
}
flyCam->SetCameraMat(camerasConfig->GetCameraVec()[cameraIndex].mat);
}
});
inputBuddy->OnKey(SDLK_f, [this](bool down, uint16_t mod)
{
if (down)
{
opt.drawCarpet = !opt.drawCarpet;
}
});
inputBuddy->OnKey(SDLK_y, [this](bool down, uint16_t mod)
{
if (down)
{
opt.drawCameraFrustums = !opt.drawCameraFrustums;
}
});
inputBuddy->OnKey(SDLK_h, [this](bool down, uint16_t mod)
{
if (down)
{
opt.drawCameraPath = !opt.drawCameraPath;
}
});
inputBuddy->OnKey(SDLK_RETURN, [this, vrConfigFilename](bool down, uint16_t mod)
{
if (down)
{
if (!vrConfig)
{
vrConfig = std::make_shared<VrConfig>();
}
if (opt.vrMode)
{
vrConfig->SetFloorMat(magicCarpet->GetCarpetMat());
}
else
{
glm::mat4 headMat = flyCam->GetCameraMat();
glm::vec3 pos = headMat[3];
pos -= glm::mat3(headMat) * glm::vec3(0.0f, 1.5f, 0.0f);
glm::mat4 floorMat = headMat;
floorMat[3] = glm::vec4(pos, 1.0f);
vrConfig->SetFloorMat(floorMat);
}
if (vrConfig->ExportJson(vrConfigFilename))
{
Log::I("Wrote \"%s\"\n", vrConfigFilename.c_str());
}
else
{
Log::E("Writing \"%s\" failed\n", vrConfigFilename.c_str());
}
}
});
inputBuddy->OnKey(SDLK_F1, [this](bool down, uint16_t mod)
{
if (down)
{
opt.drawFps = !opt.drawFps;
}
});
inputBuddy->OnKey(SDLK_a, [this](bool down, uint16_t mod)
{
virtualLeftStick.x += down ? -1.0f : 1.0f;
});
inputBuddy->OnKey(SDLK_d, [this](bool down, uint16_t mod)
{
virtualLeftStick.x += down ? 1.0f : -1.0f;
});
inputBuddy->OnKey(SDLK_w, [this](bool down, uint16_t mod)
{
virtualLeftStick.y += down ? 1.0f : -1.0f;
});
inputBuddy->OnKey(SDLK_s, [this](bool down, uint16_t mod)
{
virtualLeftStick.y += down ? -1.0f : 1.0f;
});
inputBuddy->OnKey(SDLK_LEFT, [this](bool down, uint16_t mod)
{
virtualRightStick.x += down ? -1.0f : 1.0f;
});
inputBuddy->OnKey(SDLK_RIGHT, [this](bool down, uint16_t mod)
{
virtualRightStick.x += down ? 1.0f : -1.0f;
});
inputBuddy->OnKey(SDLK_UP, [this](bool down, uint16_t mod)
{
virtualRightStick.y += down ? 1.0f : -1.0f;
});
inputBuddy->OnKey(SDLK_DOWN, [this](bool down, uint16_t mod)
{
virtualRightStick.y += down ? -1.0f : 1.0f;
});
inputBuddy->OnKey(SDLK_q, [this](bool down, uint16_t mod)
{
virtualRoll += down ? -1.0f : 1.0f;
});
inputBuddy->OnKey(SDLK_e, [this](bool down, uint16_t mod)
{
virtualRoll += down ? 1.0f : -1.0f;
});
inputBuddy->OnKey(SDLK_t, [this](bool down, uint16_t mod)
{
virtualUp += down ? 1.0f : -1.0f;
});
inputBuddy->OnKey(SDLK_g, [this](bool down, uint16_t mod)
{
virtualUp += down ? -1.0f : 1.0f;
});
inputBuddy->OnMouseButton([this](uint8_t button, bool down, glm::ivec2 pos)
{
if (button == 3) // right button
{
if (mouseLook != down)
{
inputBuddy->SetRelativeMouseMode(down);
}
mouseLook = down;
}
});
inputBuddy->OnMouseMotion([this](glm::ivec2 pos, glm::ivec2 rel)
{
if (mouseLook)
{
const float MOUSE_SENSITIVITY = 0.001f;
mouseLookStick.x += rel.x * MOUSE_SENSITIVITY;
mouseLookStick.y -= rel.y * MOUSE_SENSITIVITY;
}
});
#endif // USE_SDL
fpsText = textRenderer->AddScreenTextWithDropShadow(glm::ivec2(0, 0), (int)TEXT_NUM_ROWS, WHITE, BLACK, "fps:");
return true;
}
void App::ProcessEvent(const SDL_Event& event)
{
#ifdef USE_SDL
inputBuddy->ProcessEvent(event);
#endif
}
void App::UpdateFps(float fps)
{
std::string text = "fps: " + std::to_string((int)fps);
textRenderer->RemoveText(fpsText);
fpsText = textRenderer->AddScreenTextWithDropShadow(glm::ivec2(0, 0), TEXT_NUM_ROWS, WHITE, BLACK, text);
//#define FIND_BEST_NUM_BLOCKS_PER_WORKGROUP
#ifdef FIND_BEST_NUM_BLOCKS_PER_WORKGROUP
Log::E("%s\n", text.c_str());
fpsVec.push_back(fps);
const uint32_t STEP_SIZE = 64;
if (fpsVec.size() == 2)
{
float dFps = fpsVec[1] - fpsVec[0];
uint32_t x = splatRenderer->numBlocksPerWorkgroup - STEP_SIZE;
Log::E(" (%.3f -> %.3f) dFps = %.3f, x = %u\n", fpsVec[0], fpsVec[1], dFps, x);
if (dFps < 0)
{
Log::E(" FPS DOWN, new x = %u\n", x - STEP_SIZE);
if (splatRenderer->numBlocksPerWorkgroup > STEP_SIZE)
{
splatRenderer->numBlocksPerWorkgroup = x - STEP_SIZE;
}
}
else
{
Log::E(" FPS UP, new x = %u\n", x + STEP_SIZE);
splatRenderer->numBlocksPerWorkgroup = x + STEP_SIZE;
}
fpsVec.clear();
}
else
{
splatRenderer->numBlocksPerWorkgroup = splatRenderer->numBlocksPerWorkgroup + STEP_SIZE;
}
#endif
}
bool App::Process(float dt)
{
if (opt.vrMode)
{
if (!xrBuddy->PollEvents())
{
Log::E("xrBuddy PollEvents failed\n");
return false;
}
if (!xrBuddy->SyncInput())
{
Log::E("xrBuddy SyncInput failed\n");
return false;
}
// copy vr input into MagicCarpet
MagicCarpet::Pose headPose, rightPose, leftPose;
if (!xrBuddy->GetActionPosition("head_pose", &headPose.pos, &headPose.posValid, &headPose.posTracked))
{
Log::W("xrBuddy GetActionPosition(head_pose) failed\n");
}
if (!xrBuddy->GetActionOrientation("head_pose", &headPose.rot, &headPose.rotValid, &headPose.rotTracked))
{
Log::W("xrBuddy GetActionOrientation(head_pose) failed\n");
}
xrBuddy->GetActionPosition("l_aim_pose", &leftPose.pos, &leftPose.posValid, &leftPose.posTracked);
xrBuddy->GetActionOrientation("l_aim_pose", &leftPose.rot, &leftPose.rotValid, &leftPose.rotTracked);
xrBuddy->GetActionPosition("r_aim_pose", &rightPose.pos, &rightPose.posValid, &rightPose.posTracked);
xrBuddy->GetActionOrientation("r_aim_pose", &rightPose.rot, &rightPose.rotValid, &rightPose.rotTracked);
glm::vec2 leftStick(0.0f, 0.0f);
glm::vec2 rightStick(0.0f, 0.0f);
bool valid = false;
bool changed = false;
xrBuddy->GetActionVec2("l_stick", &leftStick, &valid, &changed);
xrBuddy->GetActionVec2("r_stick", &rightStick, &valid, &changed);
// Convert trackpad into a "stick", for HTC Vive controllers
glm::vec2 leftTrackpadStick(0.0f, 0.0f);
bool leftTrackpadClick = false;
xrBuddy->GetActionBool("l_trackpad_click", &leftTrackpadClick, &valid, &changed);
if (leftTrackpadClick && valid)
{
xrBuddy->GetActionFloat("l_trackpad_x", &leftTrackpadStick.x, &valid, &changed);
xrBuddy->GetActionFloat("l_trackpad_y", &leftTrackpadStick.y, &valid, &changed);
}
else
{
leftTrackpadStick = glm::vec2(0.0f, 0.0f);
}
glm::vec2 rightTrackpadStick(0.0f, 0.0f);
bool rightTrackpadClick = false;
xrBuddy->GetActionBool("r_trackpad_click", &rightTrackpadClick, &valid, &changed);
if (rightTrackpadClick && valid)
{
xrBuddy->GetActionFloat("r_trackpad_x", &rightTrackpadStick.x, &valid, &changed);
xrBuddy->GetActionFloat("r_trackpad_y", &rightTrackpadStick.y, &valid, &changed);
}
else
{
rightTrackpadStick = glm::vec2(0.0f, 0.0f);
}
MagicCarpet::ButtonState buttonState;
xrBuddy->GetActionBool("l_select_click", &buttonState.leftTrigger, &valid, &changed);
xrBuddy->GetActionBool("r_select_click", &buttonState.rightTrigger, &valid, &changed);
xrBuddy->GetActionBool("l_squeeze_click", &buttonState.leftGrip, &valid, &changed);
xrBuddy->GetActionBool("r_squeeze_click", &buttonState.rightGrip, &valid, &changed);
magicCarpet->Process(headPose, leftPose, rightPose, leftStick + leftTrackpadStick,
rightStick + rightTrackpadStick, buttonState, dt);
}
#ifdef USE_SDL
InputBuddy::Joypad joypad = inputBuddy->GetJoypad();
float roll = 0.0f;
roll -= joypad.lb ? 1.0f : 0.0f;
roll += joypad.rb ? 1.0f : 0.0f;
flyCam->Process(glm::clamp(joypad.leftStick + virtualLeftStick, -1.0f, 1.0f),
glm::clamp(joypad.rightStick + virtualRightStick, -1.0f, 1.0f) + mouseLookStick / (dt > 0.0f ? dt : 1.0f),
glm::clamp(roll + virtualRoll, -1.0f, 1.0f), glm::clamp(virtualUp, -1.0f, 1.0f), dt);
mouseLookStick = glm::vec2(0.0f, 0.0f);
#endif
return true;
}
bool App::Render(float dt, const glm::ivec2& windowSize)
{
int width = windowSize.x;
int height = windowSize.y;
if (opt.vrMode)
{
if (xrBuddy->SessionReady())
{
if (!xrBuddy->RenderFrame())
{
Log::E("xrBuddy RenderFrame failed\n");
return false;
}
}
else
{
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
#ifndef __ANDROID__
// render desktop.
Clear(windowSize, true);
RenderDesktop(windowSize, desktopProgram, xrBuddy->GetColorTexture(), true);
if (opt.drawFps)
{
glm::vec4 viewport(0.0f, 0.0f, (float)width, (float)height);
glm::vec2 nearFar(Z_NEAR, Z_FAR);
glm::mat4 projMat = glm::perspective(FOVY, (float)width / (float)height, Z_NEAR, Z_FAR);
textRenderer->Render(glm::mat4(1.0f), projMat, viewport, nearFar);
}
#endif
}
else
{
// lazy init of fbo, fbo is only used for HalfFloat, Float option.
if (opt.frameBuffer != Options::FrameBuffer::Default && fboSize != windowSize)
{
fbo = std::make_shared<FrameBuffer>();
Texture::Params texParams;
texParams.minFilter = FilterType::Nearest;
texParams.magFilter = FilterType::Nearest;
texParams.sWrap = WrapType::ClampToEdge;
texParams.tWrap = WrapType::ClampToEdge;
if (opt.frameBuffer == Options::FrameBuffer::HalfFloat)
{
fboColorTex = std::make_shared<Texture>(windowSize.x, windowSize.y,
GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT,
texParams);
}
else if (opt.frameBuffer == Options::FrameBuffer::Float)
{
fboColorTex = std::make_shared<Texture>(windowSize.x, windowSize.y,
GL_RGBA32F, GL_RGBA, GL_FLOAT,
texParams);
}
else
{
Log::E("BAD opt.frameBuffer type!\n");
}
fbo->AttachColor(fboColorTex);
fboSize = windowSize;
}
if (opt.frameBuffer != Options::FrameBuffer::Default && fbo)
{
fbo->Bind();
}
Clear(windowSize, true);
glm::mat4 cameraMat = flyCam->GetCameraMat();
glm::vec4 viewport(0.0f, 0.0f, (float)width, (float)height);
glm::vec2 nearFar(Z_NEAR, Z_FAR);
glm::mat4 projMat = glm::perspective(FOVY, (float)width / (float)height, Z_NEAR, Z_FAR);
if (opt.drawDebug)
{
debugRenderer->Render(cameraMat, projMat, viewport, nearFar);
}
if (cameraPathRenderer)
{
cameraPathRenderer->SetShowCameras(opt.drawCameraFrustums);
cameraPathRenderer->SetShowPath(opt.drawCameraPath);
cameraPathRenderer->Render(cameraMat, projMat, viewport, nearFar);
}
if (opt.drawCarpet)
{
magicCarpet->Render(cameraMat, projMat, viewport, nearFar);
}
if (opt.drawPointCloud && pointRenderer)
{
pointRenderer->Render(cameraMat, projMat, viewport, nearFar);
}
else
{
splatRenderer->Sort(cameraMat, projMat, viewport, nearFar);
splatRenderer->Render(cameraMat, projMat, viewport, nearFar);
}
if (opt.drawFps)
{
textRenderer->Render(cameraMat, projMat, viewport, nearFar);
}
if (opt.frameBuffer != Options::FrameBuffer::Default && fbo)
{
// render fbo colorTexture as a full screen quad to the default fbo
glBindFramebuffer(GL_FRAMEBUFFER, 0);
Clear(windowSize, true);
RenderDesktop(windowSize, desktopProgram, fbo->GetColorTexture()->texture, false);
}
}
debugRenderer->EndFrame();
frameNum++;
return true;
}
void App::OnQuit(const VoidCallback& cb)
{
quitCallback = cb;
}
void App::OnResize(const ResizeCallback& cb)
{
resizeCallback = cb;
}
================================================
FILE: src/app.h
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
#pragma once
#include <functional>
#include <glm/glm.hpp>
#include <memory>
#include <string>
#include "maincontext.h"
class CamerasConfig;
class CameraPathRenderer;
class DebugRenderer;
class FlyCam;
struct FrameBuffer;
class GaussianCloud;
class InputBuddy;
class MagicCarpet;
class PointCloud;
class PointRenderer;
class Program;
class SplatRenderer;
class TextRenderer;
struct Texture;
class VrConfig;
class XrBuddy;
union SDL_Event;
class App
{
public:
App(MainContext& mainContextIn);
enum ParseResult
{
SUCCESS_RESULT,
ERROR_RESULT,
QUIT_RESULT
};
ParseResult ParseArguments(int argc, const char* argv[]);
bool Init();
bool IsFullscreen() const { return opt.fullscreen; }
void UpdateFps(float fps);
void ProcessEvent(const SDL_Event& event);
bool Process(float dt);
bool Render(float dt, const glm::ivec2& windowSize);
using VoidCallback = std::function<void()>;
void OnQuit(const VoidCallback& cb);
using ResizeCallback = std::function<void(int, int)>;
void OnResize(const ResizeCallback& cb);
struct Options
{
enum class FrameBuffer
{
Default,
HalfFloat,
Float
};
bool vrMode = false;
bool fullscreen = false;
FrameBuffer frameBuffer = FrameBuffer::Default;
bool drawCarpet = false;
bool drawPointCloud = false;
bool drawDebug = true;
bool debugLogging = false;
bool drawFps = true;
bool drawCameraFrustums = false;
bool drawCameraPath = false;
bool importFullSH = true;
};
protected:
MainContext& mainContext;
Options opt;
std::string plyFilename;
std::shared_ptr<DebugRenderer> debugRenderer;
std::shared_ptr<CameraPathRenderer> cameraPathRenderer;
std::shared_ptr<TextRenderer> textRenderer;
std::shared_ptr<XrBuddy> xrBuddy;
std::shared_ptr<CamerasConfig> camerasConfig;
std::shared_ptr<VrConfig> vrConfig;
int cameraIndex;
std::shared_ptr<FlyCam> flyCam;
std::shared_ptr<MagicCarpet> magicCarpet;
std::shared_ptr<PointCloud> pointCloud;
std::shared_ptr<GaussianCloud> gaussianCloud;
std::shared_ptr<PointRenderer> pointRenderer;
std::shared_ptr<SplatRenderer> splatRenderer;
std::shared_ptr<Program> desktopProgram;
std::shared_ptr<FrameBuffer> fbo;
glm::ivec2 fboSize = {0, 0};
std::shared_ptr<Texture> fboColorTex;
std::shared_ptr<InputBuddy> inputBuddy;
glm::vec2 virtualLeftStick;
glm::vec2 virtualRightStick;
glm::vec2 mouseLookStick;
bool mouseLook;
float virtualRoll;
float virtualUp;
uint32_t fpsText;
uint32_t frameNum;
VoidCallback quitCallback;
ResizeCallback resizeCallback;
std::vector<float> fpsVec;
};
================================================
FILE: src/camerapathrenderer.cpp
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
#include "camerapathrenderer.h"
#ifdef __ANDROID__
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES3/gl3.h>
#include <GLES3/gl3ext.h>
#else
#include <GL/glew.h>
#endif
#include "core/log.h"
#include "core/util.h"
#include "camerasconfig.h"
CameraPathRenderer::CameraPathRenderer()
{
}
CameraPathRenderer::~CameraPathRenderer()
{
}
bool CameraPathRenderer::Init(const std::vector<Camera>& cameraVec)
{
ddProg = std::make_shared<Program>();
if (!ddProg->LoadVertFrag("shader/debugdraw_vert.glsl", "shader/debugdraw_frag.glsl"))
{
Log::E("Error loading CameraPathRenderer shader!\n");
return false;
}
BuildCamerasVao(cameraVec);
BuildPathVao(cameraVec);
return true;
}
// viewport = (x, y, width, height)
void CameraPathRenderer::Render(const glm::mat4& cameraMat, const glm::mat4& projMat,
const glm::vec4& viewport, const glm::vec2& nearFar)
{
if (!showCameras && !showPath)
{
return;
}
GL_ERROR_CHECK("CameraPathRenderer::Render() begin");
glm::mat4 modelViewProjMat = projMat * glm::inverse(cameraMat);
ddProg->Bind();
ddProg->SetUniform("modelViewProjMat", modelViewProjMat);
if (showCameras)
{
camerasVao->Bind();
glDrawElements(GL_LINES, numCameraVerts, GL_UNSIGNED_INT, nullptr);
camerasVao->Unbind();
}
if (showPath)
{
pathVao->Bind();
glDrawElements(GL_LINES, numPathVerts, GL_UNSIGNED_INT, nullptr);
pathVao->Unbind();
}
GL_ERROR_CHECK("CameraPathRenderer::Render() draw");
}
void CameraPathRenderer::BuildCamerasVao(const std::vector<Camera>& cameraVec)
{
camerasVao = std::make_shared<VertexArrayObject>();
const uint32_t NUM_LINES = 8;
const float FRUSTUM_LEN = 0.1f;
const glm::vec4 FRUSTUM_COLOR(0.0f, 1.0f, 0.0f, 1.0f);
numCameraVerts = (uint32_t)cameraVec.size() * NUM_LINES * 2;
std::vector<glm::vec3> posVec;
posVec.reserve(numCameraVerts);
std::vector<glm::vec4> colVec;
colVec.reserve(numCameraVerts);
std::vector<uint32_t> indexVec;
indexVec.reserve(numCameraVerts);
// build lines for each frustum
for (auto& c : cameraVec)
{
float xRadius = FRUSTUM_LEN / cosf(c.fov.x / 2.0f);
float xOffset = xRadius * sinf(c.fov.x / 2.0f);
float yRadius = FRUSTUM_LEN / cosf(c.fov.y / 2.0f);
float yOffset = yRadius * sinf(c.fov.y / 2.0f);
const uint32_t NUM_FRUSTUM_VERTS = 5;
glm::vec3 verts[NUM_FRUSTUM_VERTS] = {
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(xOffset, yOffset, -FRUSTUM_LEN),
glm::vec3(-xOffset, yOffset, -FRUSTUM_LEN),
glm::vec3(-xOffset, -yOffset, -FRUSTUM_LEN),
glm::vec3(xOffset, -yOffset, -FRUSTUM_LEN)
};
for (int i = 0; i < NUM_FRUSTUM_VERTS; i++)
{
verts[i] = XformPoint(c.mat, verts[i]);
}
posVec.push_back(verts[0]); posVec.push_back(verts[1]);
posVec.push_back(verts[0]); posVec.push_back(verts[2]);
posVec.push_back(verts[0]); posVec.push_back(verts[3]);
posVec.push_back(verts[0]); posVec.push_back(verts[4]);
posVec.push_back(verts[1]); posVec.push_back(verts[2]);
posVec.push_back(verts[2]); posVec.push_back(verts[3]);
posVec.push_back(verts[3]); posVec.push_back(verts[4]);
posVec.push_back(verts[4]); posVec.push_back(verts[1]);
for (int i = 0; i < NUM_LINES; i++)
{
colVec.push_back(FRUSTUM_COLOR); colVec.push_back(FRUSTUM_COLOR);
}
}
auto posBuffer = std::make_shared<BufferObject>(GL_ARRAY_BUFFER, posVec);
auto colBuffer = std::make_shared<BufferObject>(GL_ARRAY_BUFFER, colVec);
// build element array
assert(numCameraVerts <= std::numeric_limits<uint32_t>::max());
for (uint32_t i = 0; i < numCameraVerts; i++)
{
indexVec.push_back(i);
}
auto indexBuffer = std::make_shared<BufferObject>(GL_ELEMENT_ARRAY_BUFFER, indexVec, GL_DYNAMIC_STORAGE_BIT);
// setup vertex array object with buffers
camerasVao->SetAttribBuffer(ddProg->GetAttribLoc("position"), posBuffer);
camerasVao->SetAttribBuffer(ddProg->GetAttribLoc("color"), colBuffer);
camerasVao->SetElementBuffer(indexBuffer);
}
void CameraPathRenderer::BuildPathVao(const std::vector<Camera>& cameraVec)
{
pathVao = std::make_shared<VertexArrayObject>();
const uint32_t NUM_LINES = 1;
const glm::vec4 PATH_COLOR(0.0f, 1.0f, 1.0f, 1.0f);
numPathVerts = (uint32_t)cameraVec.size() * NUM_LINES * 2;
std::vector<glm::vec3> posVec;
posVec.reserve(numPathVerts);
std::vector<glm::vec4> colVec;
colVec.reserve(numPathVerts);
std::vector<uint32_t> indexVec;
indexVec.reserve(numPathVerts);
// build lines for each path segment
if (cameraVec.size() > 1)
{
const Camera* prev = &cameraVec[0];
for (size_t i = 1; i < cameraVec.size(); i++)
{
const Camera* curr = cameraVec.data() + i;
glm::vec3 prevPos = glm::vec3(prev->mat[3]);
glm::vec3 currPos = glm::vec3(curr->mat[3]);
posVec.push_back(prevPos);
posVec.push_back(currPos);
colVec.push_back(PATH_COLOR);
colVec.push_back(PATH_COLOR);
prev = curr;
}
}
else
{
posVec.push_back(glm::vec3(0.0f, 0.0f, 0.0f));
posVec.push_back(glm::vec3(0.0f, 0.0f, 0.0f));
colVec.push_back(PATH_COLOR);
colVec.push_back(PATH_COLOR);
}
auto posBuffer = std::make_shared<BufferObject>(GL_ARRAY_BUFFER, posVec);
auto colBuffer = std::make_shared<BufferObject>(GL_ARRAY_BUFFER, colVec);
// build element array
assert(numPathVerts <= std::numeric_limits<uint32_t>::max());
for (uint32_t i = 0; i < numPathVerts; i++)
{
indexVec.push_back(i);
}
auto indexBuffer = std::make_shared<BufferObject>(GL_ELEMENT_ARRAY_BUFFER, indexVec, GL_DYNAMIC_STORAGE_BIT);
// setup vertex array object with buffers
pathVao->SetAttribBuffer(ddProg->GetAttribLoc("position"), posBuffer);
pathVao->SetAttribBuffer(ddProg->GetAttribLoc("color"), colBuffer);
pathVao->SetElementBuffer(indexBuffer);
}
================================================
FILE: src/camerapathrenderer.h
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
#pragma once
#include <glm/glm.hpp>
#include <memory>
#include <stdint.h>
#include <vector>
#include "core/program.h"
#include "core/vertexbuffer.h"
struct Camera;
class CameraPathRenderer
{
public:
CameraPathRenderer();
~CameraPathRenderer();
bool Init(const std::vector<Camera>& cameraVec);
void SetShowCameras(bool showCamerasIn) { showCameras = showCamerasIn; }
void SetShowPath(bool showPathIn) { showPath = showPathIn; }
// viewport = (x, y, width, height)
void Render(const glm::mat4& cameraMat, const glm::mat4& projMat,
const glm::vec4& viewport, const glm::vec2& nearFar);
protected:
void BuildCamerasVao(const std::vector<Camera>& cameraVec);
void BuildPathVao(const std::vector<Camera>& cameraVec);
std::shared_ptr<Program> ddProg;
std::shared_ptr<VertexArrayObject> camerasVao;
uint32_t numCameraVerts;
std::shared_ptr<VertexArrayObject> pathVao;
uint32_t numPathVerts;
bool showCameras = true;
bool showPath = true;
};
================================================
FILE: src/camerasconfig.cpp
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
#include "camerasconfig.h"
#include <fstream>
#include <nlohmann/json.hpp>
#include <iostream>
#include "core/log.h"
#include "core/util.h"
CamerasConfig::CamerasConfig()
{
}
bool CamerasConfig::ImportJson(const std::string& jsonFilename)
{
std::ifstream f(jsonFilename);
if (f.fail())
{
return false;
}
try
{
nlohmann::json data = nlohmann::json::parse(f);
for (auto&& o : data)
{
int id = o["id"].template get<int>();
nlohmann::json jPos = o["position"];
glm::vec3 pos(jPos[0].template get<float>(), jPos[1].template get<float>(), jPos[2].template get<float>());
nlohmann::json jRot = o["rotation"];
glm::mat3 rot(jRot[0][0].template get<float>(), jRot[1][0].template get<float>(), jRot[2][0].template get<float>(),
jRot[0][1].template get<float>(), jRot[1][1].template get<float>(), jRot[2][1].template get<float>(),
jRot[0][2].template get<float>(), jRot[1][2].template get<float>(), jRot[2][2].template get<float>());
int width = o["width"].template get<int>();
int height = o["height"].template get<int>();
float fx = o["fx"].template get<float>();
float fy = o["fy"].template get<float>();
glm::vec2 fov(2.0f * atanf(width / (2.0f * fx)),
2.0f * atanf(height / (2.0f * fx)));
glm::mat4 mat(glm::vec4(rot[0], 0.0f),
glm::vec4(-rot[1], 0.0f),
glm::vec4(-rot[2], 0.0f),
glm::vec4(pos, 1.0f));
// swizzle rot to make -z forward and y up.
cameraVec.emplace_back(Camera{mat, fov});
}
}
catch (const nlohmann::json::exception& e)
{
std::string s = e.what();
Log::E("CamerasConfig::ImportJson exception: %s\n", s.c_str());
return false;
}
return true;
}
void CamerasConfig::EstimateFloorPlane(glm::vec3& normalOut, glm::vec3& posOut) const
{
if (cameraVec.empty())
{
normalOut = glm::vec3(0.0f, 1.0f, 0.0f);
posOut = glm::vec3(0.0f, 0.0f, 0.0f);
}
glm::vec3 avgUp(0.0f, 0.0f, 0.0f);
float weight = 1.0f / (float)cameraVec.size();
for (auto&& c : cameraVec)
{
glm::vec3 up(c.mat[1]);
avgUp += weight * up;
}
avgUp = SafeNormalize(avgUp, glm::vec3(0.0f, 1.0f, 0.0f));
float avgDist = 0.0f;
for (auto&& c : cameraVec)
{
glm::vec3 pos(c.mat[3]);
avgDist += weight * glm::dot(pos, avgUp);
}
normalOut = avgUp;
posOut = avgUp * avgDist;
}
================================================
FILE: src/camerasconfig.h
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
#pragma once
#include <glm/glm.hpp>
#include <string>
#include <vector>
struct Camera
{
glm::mat4 mat; // inverse view matrix
glm::vec2 fov;
};
class CamerasConfig
{
public:
CamerasConfig();
bool ImportJson(const std::string& jsonFilename);
const std::vector<Camera>& GetCameraVec() const { return cameraVec; }
size_t GetNumCameras() const { return cameraVec.size(); }
void EstimateFloorPlane(glm::vec3& normalOut, glm::vec3& posOut) const;
protected:
std::vector<Camera> cameraVec;
};
================================================
FILE: src/core/binaryattribute.cpp
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
#include "binaryattribute.h"
static uint32_t propertyTypeSizeArr[(size_t)BinaryAttribute::Type::NumTypes] = {
0, // Unknown
sizeof(int8_t), // Char
sizeof(uint8_t), // UChar
sizeof(int16_t), // Short
sizeof(uint16_t), // UShort
sizeof(int32_t), // Int
sizeof(uint32_t), // UInt
sizeof(float), // Float
sizeof(double) // Double
};
BinaryAttribute::BinaryAttribute(Type typeIn, size_t offsetIn) :
type(typeIn),
size(propertyTypeSizeArr[(uint32_t)typeIn]),
offset(offsetIn)
{
;
}
================================================
FILE: src/core/binaryattribute.h
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
#pragma once
#include <cassert>
#include <cstdint>
#include <functional>
class BinaryAttribute
{
public:
enum class Type
{
Unknown,
Char,
UChar,
Short,
UShort,
Int,
UInt,
Float,
Double,
NumTypes
};
BinaryAttribute() : type(Type::Unknown), size(0), offset(0) {}
BinaryAttribute(Type typeIn, size_t offsetIn);
template <typename T>
const T* Get(const void* data) const
{
if (type == Type::Unknown)
{
return nullptr;
}
else
{
assert(size == sizeof(T));
return reinterpret_cast<const T*>(static_cast<const uint8_t*>(data) + offset);
}
}
template <typename T>
T* Get(void* data)
{
if (type == Type::Unknown)
{
return nullptr;
}
else
{
assert(size == sizeof(T));
return reinterpret_cast<T*>(static_cast<uint8_t*>(data) + offset);
}
}
template <typename T>
const T Read(const void* data) const
{
const T* ptr = Get<T>(data);
return ptr ? *ptr : 0;
}
template <typename T>
bool Write(void* data, const T& val)
{
T* ptr = Get<T>(data);
if (ptr)
{
*ptr = val;
return true;
}
else
{
return false;
}
}
template<typename T>
void ForEachMut(void* data, size_t stride, size_t count, const std::function<void(T*)>& cb)
{
assert(type != Type::Unknown);
assert(data);
uint8_t* ptr = (uint8_t*)data;
for (size_t i = 0; i < count; i++)
{
cb((T*)(ptr + offset));
ptr += stride;
}
}
template<typename T>
void ForEach(const void* data, size_t stride, size_t count, const std::function<void(const T*)>& cb) const
{
assert(type != Type::Unknown);
assert(data);
const uint8_t* ptr = (const uint8_t*)data;
for (size_t i = 0; i < count; i++)
{
cb((const T*)(ptr + offset));
ptr += stride;
}
}
Type type;
size_t size;
size_t offset;
};
================================================
FILE: src/core/debugrenderer.cpp
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
#include "debugrenderer.h"
#ifdef __ANDROID__
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES3/gl3.h>
#include <GLES3/gl3ext.h>
#else
#include <GL/glew.h>
#define GL_GLEXT_PROTOTYPES 1
#include <SDL2/SDL_opengl.h>
#include <SDL2/SDL_opengl_glext.h>
#endif
#include <memory>
#include <vector>
#include "log.h"
#include "util.h"
#include "program.h"
DebugRenderer::DebugRenderer()
{
}
bool DebugRenderer::Init()
{
ddProg = std::make_shared<Program>();
if (!ddProg->LoadVertFrag("shader/debugdraw_vert.glsl", "shader/debugdraw_frag.glsl"))
{
Log::E("Error loading DebugRenderer shader!\n");
return false;
}
return true;
}
void DebugRenderer::Line(const glm::vec3& start, const glm::vec3& end, const glm::vec3& color)
{
linePositionVec.push_back(start);
linePositionVec.push_back(end);
lineColorVec.push_back(color);
lineColorVec.push_back(color);
}
void DebugRenderer::Transform(const glm::mat4& m, float axisLen)
{
glm::vec3 x = glm::vec3(m[0]);
glm::vec3 y = glm::vec3(m[1]);
glm::vec3 z = glm::vec3(m[2]);
x = axisLen * glm::normalize(x);
y = axisLen * glm::normalize(y);
z = axisLen * glm::normalize(z);
glm::vec3 p = glm::vec3(m[3]);
Line(p, p + x, glm::vec4(1.0f, 0.0f, 0.0f, 1.0f));
Line(p, p + y, glm::vec4(0.0f, 1.0f, 0.0f, 1.0f));
Line(p, p + z, glm::vec4(0.0f, 0.0f, 1.0f, 1.0f));
}
void DebugRenderer::Render(const glm::mat4& cameraMat, const glm::mat4& projMat,
const glm::vec4& viewport, const glm::vec2& nearFar)
{
ddProg->Bind();
glm::mat4 modelViewProjMat = projMat * glm::inverse(cameraMat);
ddProg->SetUniform("modelViewProjMat", modelViewProjMat);
ddProg->SetAttrib("position", linePositionVec.data());
ddProg->SetAttrib("color", lineColorVec.data());
glDrawArrays(GL_LINES, 0, (GLsizei)linePositionVec.size());
}
void DebugRenderer::EndFrame()
{
linePositionVec.clear();
lineColorVec.clear();
}
================================================
FILE: src/core/debugrenderer.h
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
#pragma once
#include <glm/glm.hpp>
#include <memory>
#include <vector>
class Program;
class DebugRenderer
{
public:
DebugRenderer();
bool Init();
// viewport = (x, y, width, height)
void Render(const glm::mat4& cameraMat, const glm::mat4& projMat,
const glm::vec4& viewport, const glm::vec2& nearFar);
// call at end of frame.
void EndFrame();
void Line(const glm::vec3& start, const glm::vec3& end, const glm::vec3& color);
void Transform(const glm::mat4& m, float axisLen = 1.0f);
protected:
std::shared_ptr<Program> ddProg;
std::vector<glm::vec3> linePositionVec;
std::vector<glm::vec3> lineColorVec;
};
================================================
FILE: src/core/framebuffer.cpp
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
#include "framebuffer.h"
#ifdef __ANDROID__
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES3/gl3.h>
#include <GLES3/gl3ext.h>
#else
#include <GL/glew.h>
#define GL_GLEXT_PROTOTYPES 1
#include <SDL2/SDL_opengl.h>
#include <SDL2/SDL_opengl_glext.h>
#endif
#include "texture.h"
FrameBuffer::FrameBuffer()
{
glGenFramebuffers(1, &fbo);
}
FrameBuffer::~FrameBuffer()
{
glDeleteFramebuffers(1, &fbo);
fbo = 0;
}
void FrameBuffer::Bind() const
{
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
}
void FrameBuffer::AttachColor(std::shared_ptr<Texture> colorTex)
{
Bind();
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTex->texture, 0);
colorAttachment = colorTex;
}
void FrameBuffer::AttachDepth(std::shared_ptr<Texture> depthTex)
{
Bind();
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTex->texture, 0);
depthAttachment = depthTex;
}
void FrameBuffer::AttachStencil(std::shared_ptr<Texture> stencilTex)
{
Bind();
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, stencilTex->texture, 0);
stencilAttachment = stencilTex;
}
bool FrameBuffer::IsComplete() const
{
return glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
}
================================================
FILE: src/core/framebuffer.h
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
#pragma once
#include <stdint.h>
#include <memory>
struct Texture;
struct FrameBuffer
{
FrameBuffer();
~FrameBuffer();
void Bind() const;
void AttachColor(std::shared_ptr<Texture> colorTex);
void AttachDepth(std::shared_ptr<Texture> depthTex);
void AttachStencil(std::shared_ptr<Texture> stencilTex);
bool IsComplete() const;
std::shared_ptr<Texture> GetColorTexture() const { return colorAttachment; }
std::shared_ptr<Texture> GetDepthTexture() const { return depthAttachment; }
std::shared_ptr<Texture> GetStencilTexture() const { return stencilAttachment; }
uint32_t fbo;
std::shared_ptr<Texture> colorAttachment;
std::shared_ptr<Texture> depthAttachment;
std::shared_ptr<Texture> stencilAttachment;
};
================================================
FILE: src/core/image.cpp
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
#include "image.h"
//#include "util.h"
#include "log.h"
#include <string.h>
extern "C" {
#include <png.h>
}
#include "util.h"
Image::Image() : width(0), height(0), pixelFormat(PixelFormat::R), isSRGB(false)
{
}
bool Image::Load(const std::string& filenameIn)
{
std::string fullFilename = GetRootPath() + filenameIn;
const char* filename = fullFilename.c_str();
#ifdef _WIN32
FILE *fp = NULL;
fopen_s(&fp, filename, "rb");
#else
FILE *fp = fopen(filename, "rb");
#endif
if (!fp)
{
Log::E("Failed to load texture \"%s\"\n", filename);
return false;
}
unsigned char header[8];
fread(header, 1, 8, fp);
if (png_sig_cmp(header, 0, 8))
{
Log::E("Texture \"%s\" is not a valid PNG file\n", filename);
return false;
}
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr)
{
Log::E("png_create_read_struct() failed\n");
return false;
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
{
Log::E("png_create_info_struct() failed\n");
png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
return false;
}
png_init_io(png_ptr, fp);
png_set_sig_bytes(png_ptr, 8);
png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
png_byte** row_pointers = png_get_rows(png_ptr, info_ptr);
png_uint_32 w, h;
int bit_depth, color_type;
png_get_IHDR(png_ptr, info_ptr, &w, &h, &bit_depth, &color_type, NULL, NULL, NULL);
bool loaded = false;
if (bit_depth != 8)
{
Log::E("bad bit depth for texture \"%s\n", filename);
}
else
{
int pixelSize;
switch (color_type)
{
case PNG_COLOR_TYPE_GRAY:
pixelFormat = PixelFormat::R;
pixelSize = 1;
break;
case PNG_COLOR_TYPE_GA:
pixelFormat = PixelFormat::RA;
pixelSize = 2;
break;
case PNG_COLOR_TYPE_RGB:
pixelFormat = PixelFormat::RGB;
pixelSize = 3;
break;
case PNG_COLOR_TYPE_RGBA:
pixelFormat = PixelFormat::RGBA;
pixelSize = 4;
break;
default:
Log::E("unsupported pixel format %d for image \"%s\n", color_type, filename);
return false;
}
width = w;
height = h;
data.resize(width * height * pixelSize);
// copy row pointers into data vector
for (int i = 0; i < (int)height; ++i)
{
memcpy(&data[0] + i * width * pixelSize, row_pointers[height - 1 - i], width * pixelSize);
}
// pre-multiply alpha
MultiplyAlpha();
loaded = true;
}
png_destroy_info_struct(png_ptr, &info_ptr);
png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
// TODO: should probably check if the sRGB color space chunk is present
isSRGB = true;
return loaded;
}
void Image::MultiplyAlpha()
{
if (pixelFormat == PixelFormat::R || pixelFormat == PixelFormat::RGB)
{
return;
}
else if (pixelFormat == PixelFormat::RA)
{
size_t pixelSize = 2;
for (size_t i = 0; i < width * height; i++)
{
float red = (float)data[i * pixelSize] / 255.0f;
float alpha = (float)data[i * pixelSize + 1] / 255.0f;
data[i * pixelSize] = (uint8_t)((red * alpha) * 255.0f);
}
}
else if (pixelFormat == PixelFormat::RGBA)
{
size_t pixelSize = 4;
for (size_t i = 0; i < width * height; i++)
{
float red = (float)data[i * pixelSize] / 255.0f;
float green = (float)data[i * pixelSize + 1] / 255.0f;
float blue = (float)data[i * pixelSize + 2] / 255.0f;
float alpha = (float)data[i * pixelSize + 3] / 255.0f;
data[i * pixelSize] = (uint8_t)((red * alpha) * 255.0f);
data[i * pixelSize + 1] = (uint8_t)((green * alpha) * 255.0f);
data[i * pixelSize + 2] = (uint8_t)((blue * alpha) * 255.0f);
}
}
}
================================================
FILE: src/core/image.h
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
#pragma once
#include <stdint.h>
#include <string>
#include <vector>
enum class PixelFormat
{
R = 0, // intensity
RA, // intensity alpha
RGB,
RGBA
};
struct Image {
Image();
bool Load(const std::string& filename);
void MultiplyAlpha();
uint32_t width;
uint32_t height;
PixelFormat pixelFormat;
bool isSRGB;
std::vector<uint8_t> data;
};
================================================
FILE: src/core/inputbuddy.cpp
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
#include "inputbuddy.h"
#include <SDL2/SDL.h>
#include "log.h"
InputBuddy::InputBuddy()
{
int numJoySticks = SDL_NumJoysticks();
if (numJoySticks > 0)
{
SDL_Joystick* joystick = SDL_JoystickOpen(0);
Log::I("Found joystick \"%s\"\n", SDL_JoystickName(joystick));
}
else
{
Log::I("No joystick found\n");
}
}
void InputBuddy::ProcessEvent(const SDL_Event& event)
{
switch (event.type)
{
case SDL_QUIT:
quitCallback();
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
{
SDL_Keycode keycode = event.key.keysym.sym;
uint16_t mod = event.key.keysym.mod;
bool down = (event.key.type == SDL_KEYDOWN);
auto iter = keyCallbackMap.find(keycode);
if (iter != keyCallbackMap.end() && !event.key.repeat)
{
iter->second(down, mod);
}
}
break;
case SDL_JOYAXISMOTION:
UpdateJoypadAxis(event.jaxis);
break;
case SDL_JOYHATMOTION:
UpdateJoypadHat(event.jhat);
break;
case SDL_JOYBUTTONDOWN:
case SDL_JOYBUTTONUP:
UpdateJoypadButton(event.jbutton);
break;
case SDL_WINDOWEVENT:
if (event.window.event == SDL_WINDOWEVENT_RESIZED)
{
resizeCallback(event.window.data1, event.window.data2);
}
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
if (event.button.clicks == 1)
{
mouseButtonCallback(event.button.button, event.button.state == SDL_PRESSED,
glm::ivec2(event.button.x, event.button.y));
}
break;
case SDL_MOUSEMOTION:
mouseMotionCallback(glm::ivec2(event.motion.x, event.motion.y), glm::ivec2(event.motion.xrel, event.motion.yrel));
break;
}
}
void InputBuddy::OnKey(Keycode key, const KeyCallback& cb)
{
keyCallbackMap.insert(std::pair<Keycode, KeyCallback>(key, cb));
}
void InputBuddy::OnQuit(const VoidCallback& cb)
{
quitCallback = cb;
}
void InputBuddy::OnResize(const ResizeCallback& cb)
{
resizeCallback = cb;
}
void InputBuddy::OnMouseButton(const MouseButtonCallback& cb)
{
mouseButtonCallback = cb;
}
void InputBuddy::OnMouseMotion(const MouseMotionCallback& cb)
{
mouseMotionCallback = cb;
}
void InputBuddy::SetRelativeMouseMode(bool val)
{
SDL_SetRelativeMouseMode(val ? SDL_TRUE : SDL_FALSE);
}
const uint8_t LEFT_STICK_X_AXIS = 0;
const uint8_t LEFT_STICK_Y_AXIS = 1;
const uint8_t RIGHT_STICK_X_AXIS = 2;
const uint8_t RIGHT_STICK_Y_AXIS = 3;
const uint8_t LEFT_TRIGGER_AXIS = 4;
const uint8_t RIGHT_TRIGGER_AXIS = 5;
static float Deadspot(float v)
{
const float DEADSPOT = 0.15f;
return fabs(v) > DEADSPOT ? v : 0.0f;
}
void InputBuddy::UpdateJoypadAxis(const SDL_JoyAxisEvent& event)
{
// only support one joypad
if (event.which != 0)
{
return;
}
const float AXIS_MAX = (float)SDL_JOYSTICK_AXIS_MAX;
switch (event.axis)
{
case LEFT_STICK_X_AXIS:
joypad.leftStick.x = Deadspot(event.value / AXIS_MAX);
break;
case LEFT_STICK_Y_AXIS:
joypad.leftStick.y = Deadspot(-event.value / AXIS_MAX);
break;
case RIGHT_STICK_X_AXIS:
joypad.rightStick.x = Deadspot(event.value / AXIS_MAX);
break;
case RIGHT_STICK_Y_AXIS:
joypad.rightStick.y = Deadspot(-event.value / AXIS_MAX);
break;
case LEFT_TRIGGER_AXIS:
joypad.leftTrigger = ((event.value / AXIS_MAX) * 0.5f) + 0.5f; // (0, 1)
break;
case RIGHT_TRIGGER_AXIS:
joypad.rightTrigger = ((event.value / AXIS_MAX) * 0.5f) + 0.5f; // (0, 1)
break;
}
}
const uint8_t DPAD_HAT = 0;
void InputBuddy::UpdateJoypadHat(const SDL_JoyHatEvent& event)
{
// only support one joypad
if (event.which != 0)
{
return;
}
switch (event.hat)
{
case DPAD_HAT:
joypad.up = (event.value & SDL_HAT_UP) ? true : false;
joypad.right = (event.value & SDL_HAT_RIGHT) ? true : false;
joypad.down = (event.value & SDL_HAT_DOWN) ? true : false;
joypad.left = (event.value & SDL_HAT_LEFT) ? true : false;
break;
}
}
const uint8_t A_BUTTON = 0;
const uint8_t B_BUTTON = 1;
const uint8_t X_BUTTON = 2;
const uint8_t Y_BUTTON = 3;
const uint8_t LEFT_BUMPER_BUTTON = 4;
const uint8_t RIGHT_BUMPER_BUTTON = 5;
const uint8_t MENU_BUTTON = 6;
const uint8_t VIEW_BUTTON = 7;
const uint8_t LEFT_STICK_BUTTON = 8;
const uint8_t RIGHT_STICK_BUTTON = 9;
void InputBuddy::UpdateJoypadButton(const SDL_JoyButtonEvent& event)
{
// only support one joypad
if (event.which != 0)
{
return;
}
switch (event.button)
{
case A_BUTTON:
joypad.a = event.state ? true : false;
break;
case B_BUTTON:
joypad.b = event.state ? true : false;
break;
case X_BUTTON:
joypad.x = event.state ? true : false;
break;
case Y_BUTTON:
joypad.y = event.state ? true : false;
break;
case LEFT_BUMPER_BUTTON:
joypad.lb = event.state ? true : false;
break;
case RIGHT_BUMPER_BUTTON:
joypad.rb = event.state ? true : false;
break;
case MENU_BUTTON:
joypad.menu = event.state ? true : false;
break;
case VIEW_BUTTON:
joypad.view = event.state ? true : false;
break;
case LEFT_STICK_BUTTON:
joypad.ls = event.state ? true : false;
break;
case RIGHT_STICK_BUTTON:
joypad.rs = event.state ? true : false;
break;
}
}
================================================
FILE: src/core/inputbuddy.h
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
#pragma once
#include <functional>
#include <glm/glm.hpp>
#include <map>
#include <stdint.h>
#include <string.h>
union SDL_Event;
struct SDL_JoyAxisEvent;
struct SDL_JoyHatEvent;
struct SDL_JoyButtonEvent;
class InputBuddy
{
public:
using Keycode = int32_t;
InputBuddy();
using VoidCallback = std::function<void()>;
using KeyCallback = std::function<void(bool, uint16_t)>;
using ResizeCallback = std::function<void(int, int)>;
using MouseButtonCallback = std::function<void(uint8_t, bool, glm::ivec2)>; // button 1 = LEFT, 2 = MIDDLE, 3 = RIGHT
using MouseMotionCallback = std::function<void(glm::ivec2, glm::ivec2)>;
void ProcessEvent(const SDL_Event& event);
void OnKey(Keycode key, const KeyCallback& cb);
void OnQuit(const VoidCallback& cb);
void OnResize(const ResizeCallback& cb);
void OnMouseButton(const MouseButtonCallback& cb);
void OnMouseMotion(const MouseMotionCallback& cb);
void SetRelativeMouseMode(bool val);
// based on an xbox controler
struct Joypad
{
Joypad()
{
memset(this, 0, sizeof(Joypad));
}
glm::vec2 leftStick;
glm::vec2 rightStick;
float leftTrigger;
float rightTrigger;
bool down:1;
bool up:1;
bool left:1;
bool right:1;
bool view:1;
bool menu:1;
bool rs:1;
bool ls:1;
bool lb:1;
bool rb:1;
bool a:1;
bool b:1;
bool x:1;
bool y:1;
};
const Joypad& GetJoypad() const { return joypad; }
protected:
void UpdateJoypadAxis(const SDL_JoyAxisEvent& event);
void UpdateJoypadHat(const SDL_JoyHatEvent& event);
void UpdateJoypadButton(const SDL_JoyButtonEvent& event);
std::map<Keycode, KeyCallback> keyCallbackMap;
VoidCallback quitCallback;
ResizeCallback resizeCallback;
MouseButtonCallback mouseButtonCallback;
MouseMotionCallback mouseMotionCallback;
Joypad joypad;
};
================================================
FILE: src/core/log.cpp
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
#include "log.h"
#include <cstdio>
#include <cstdarg>
#ifdef __ANDROID__
#include <android/log.h>
#endif
static Log::LogLevel level = Log::Verbose;
static std::string appName = "Core";
#ifdef __ANDROID__
static int log_vprintf(int prio, const char *fmt, va_list args)
{
char buffer[4096];
int rc = vsnprintf(buffer, sizeof(buffer), fmt, args);
__android_log_write(prio, appName.c_str(), buffer);
return rc;
}
#else
static int log_vprintf(const char *fmt, va_list args)
{
char buffer[4096];
int rc = vsnprintf(buffer, sizeof(buffer), fmt, args);
fwrite(buffer, rc, 1, stdout);
fflush(stdout);
return rc;
}
#endif
int Log::printf(const char *fmt, ...)
{
#ifdef __ANDROID__
return 0;
#else
char buffer[4096];
va_list args;
va_start(args, fmt);
int rc = vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
fwrite(buffer, rc, 1, stdout);
fflush(stdout);
return rc;
#endif
}
void Log::SetLevel(LogLevel levelIn)
{
level = levelIn;
}
void Log::SetAppName(const std::string& appNameIn)
{
appName = appNameIn;
}
void Log::V(const char *fmt, ...)
{
if (level <= Log::Verbose)
{
Log::printf("[VERBOSE] ");
va_list args;
va_start(args, fmt);
#ifdef __ANDROID__
log_vprintf(ANDROID_LOG_VERBOSE, fmt, args);
#else
log_vprintf(fmt, args);
#endif
va_end(args);
}
}
void Log::D(const char *fmt, ...)
{
if (level <= Log::Debug)
{
Log::printf("[DEBUG] ");
va_list args;
va_start(args, fmt);
#ifdef __ANDROID__
log_vprintf(ANDROID_LOG_DEBUG, fmt, args);
#else
log_vprintf(fmt, args);
#endif
va_end(args);
}
}
void Log::I(const char *fmt, ...)
{
if (level <= Log::Info)
{
Log::printf("[INFO] ");
va_list args;
va_start(args, fmt);
#ifdef __ANDROID__
log_vprintf(ANDROID_LOG_INFO, fmt, args);
#else
log_vprintf(fmt, args);
#endif
va_end(args);
}
}
void Log::W(const char *fmt, ...)
{
if (level <= Log::Warning)
{
Log::printf("[WARNING] ");
va_list args;
va_start(args, fmt);
#ifdef __ANDROID__
log_vprintf(ANDROID_LOG_WARN, fmt, args);
#else
log_vprintf(fmt, args);
#endif
va_end(args);
}
}
void Log::E(const char *fmt, ...)
{
if (level <= Log::Error)
{
Log::printf("[ERROR] ");
va_list args;
va_start(args, fmt);
#ifdef __ANDROID__
log_vprintf(ANDROID_LOG_ERROR, fmt, args);
#else
log_vprintf(fmt, args);
#endif
va_end(args);
}
}
================================================
FILE: src/core/log.h
================================================
/*
Copyright (c) 2024 Anthony J. Thibault
This software is licensed under the MIT License. See LICENSE for more details.
*/
#pragma once
#include <string>
struct Log
{
enum LogLevel
{
Verbose = 0,
Debug = 1,
Info = 2,
Warning = 3,
Error = 4
};
static void SetLevel(LogLevel levelIn);
static void SetAppName(const std::string& appNameIn);
// verbose
static void V(const char *fmt, ...);
// debug
static void D(const char *fmt, ...);
// info
static void I(const char *fmt, ...);
// warning
static void W(const char *fmt, ...);
// error
static void E(const char *fmt, ...);
private:
static int printf(const char *fmt, ...);
};
================================================
FILE: src/core/optionparser.h
================================================
/*
* The Lean Mean C++ Option Parser
*
* Copyright (C) 2012-2017 Matthias S. Benkmann
*
* The "Software" in the following 2 paragraphs refers to this file containing
* the code to The Lean Mean C++ Option Parser.
* The "Software" does NOT refer to any other files which you
* may have received alongside this file (e.g. as part of a larger project that
* incorporates The Lean Mean C++ Option Parser).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this 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.
*/
/*
* NOTE: It is recommended that you read the processed HTML doxygen documentation
* rather than this source. If you don't know doxygen, it's like javadoc for C++.
* If you don't want to install doxygen you can find a copy of the processed
* documentation at
*
* http://optionparser.sourceforge.net/
*
*/
/**
* @file
*
* @brief This is the only file required to use The Lean Mean C++ Option Parser.
* Just \#include it and you're set.
*
* The Lean Mean C++ Option Parser handles the program's command line arguments
* (argc, argv).
* It supports the short and long option formats of getopt(), getopt_long()
* and getopt_long_only() but has a more convenient interface.
*
* @par Feedback:
* Send questions, bug reports, feature requests etc. to: <tt><b>optionparser-feedback(a)lists.sourceforge.net</b></tt>
*
* @par Highlights:
* <ul style="padding-left:1em;margin-left:0">
* <li> It is a header-only library. Just <code>\#include "optionparser.h"</code> and you're set.
* <li> It is freestanding. There are no dependencies whatsoever, not even the
* C or C++ standard library.
* <li> It has a usage message formatter that supports column alignment and
* line wrapping. This aids localization because it adapts to
* translated strings that are shorter or longer (even if they contain
* Asian wide characters).
* <li> Unlike getopt() and derivatives it doesn't force you to loop through
* options sequentially. Instead you can access options directly like this:
* <ul style="margin-top:.5em">
* <li> Test for presence of a switch in the argument vector:
* @code if ( options[QUIET] ) ... @endcode
* <li> Evaluate --enable-foo/--disable-foo pair where the last one used wins:
* @code if ( options[FOO].last()->type() == DISABLE ) ... @endcode
* <li> Cumulative option (-v verbose, -vv more verbose, -vvv even more verbose):
* @code int verbosity = options[VERBOSE].count(); @endcode
* <li> Iterate over all --file=<fname> arguments:
* @code for (Option* opt = options[FILE]; opt; opt = opt->next())
* fname = opt->arg; ... @endcode
* <li> If you really want to, you can still process all arguments in order:
* @code
* for (int i = 0; i < p.optionsCount(); ++i) {
* Option& opt = buffer[i];
* switch(opt.index()) {
* case HELP: ...
* case VERBOSE: ...
* case FILE: fname = opt.arg; ...
* case UNKNOWN: ...
* @endcode
* </ul>
* </ul> @n
* Despite these features the code size remains tiny.
* It is smaller than <a href="http://uclibc.org">uClibc</a>'s GNU getopt() and just a
* couple 100 bytes larger than uClibc's SUSv3 getopt(). @n
* (This does not include the usage formatter, of course. But you don't have to use that.)
*
* @par Download:
* Tarball with examples and test programs:
* <a style="font-size:larger;font-weight:bold" href="http://sourceforge.net/projects/optionparser/files/optionparser-1.7.tar.gz/download">optionparser-1.7.tar.gz</a> @n
* Just the header (this is all you really need):
* <a style="font-size:larger;font-weight:bold" href="http://optionparser.sourceforge.net/optionparser.h">optionparser.h</a>
*
* @par Changelog:
* <b>Version 1.7:</b> Work on const-correctness. @n
* <b>Version 1.6:</b> Fix for MSC compiler. @n
* <b>Version 1.5:</b> Fixed 2 warnings about potentially uninitialized variables. @n
* Added const version of Option::next(). @n
* <b>Version 1.4:</b> Fixed 2 printUsage() bugs that messed up output with small COLUMNS values. @n
* <b>Version 1.3:</b> Compatible with Microsoft Visual C++. @n
* <b>Version 1.2:</b> Added @ref option::Option::namelen "Option::namelen" and removed the extraction
* of short option characters into a special buffer. @n
* Changed @ref option::Arg::Optional "Arg::Optional" to accept arguments if they are attached
* rather than separate. This is what GNU getopt() does and how POSIX recommends
* utilities should interpret their arguments.@n
* <b>Version 1.1:</b> Optional mode with argument reordering as done by GNU getopt(), so that
* options and non-options can be mixed. See
* @ref option::Parser::parse() "Parser::parse()".
*
*
* @par Example program:
* (Note: @c option::* identifiers are links that take you to their documentation.)
* @code
* #error EXAMPLE SHORTENED FOR READABILITY. BETTER EXAMPLES ARE IN THE .TAR.GZ!
* #include <iostream>
* #include "optionparser.h"
*
* enum optionIndex { UNKNOWN, HELP, PLUS };
* const option::Descriptor usage[] =
* {
* {UNKNOWN, 0,"" , "" ,option::Arg::None, "USAGE: example [options]\n\n"
* "Options:" },
* {HELP, 0,"" , "help",option::Arg::None, " --help \tPrint usage and exit." },
* {PLUS, 0,"p", "plus",option::Arg::None, " --plus, -p \tIncrement count." },
* {UNKNOWN, 0,"" , "" ,option::Arg::None, "\nExamples:\n"
* " example --unknown -- --this_is_no_option\n"
* " example -unk --plus -ppp file1 file2\n" },
* {0,0,0,0,0,0}
* };
*
* int main(int argc, char* argv[])
* {
* argc-=(argc>0); argv+=(argc>0); // skip program name argv[0] if present
* option::Stats stats(usage, argc, argv);
* option::Option options[stats.options_max], buffer[stats.buffer_max];
* option::Parser parse(usage, argc, argv, options, buffer);
*
* if (parse.error())
* return 1;
*
* if (options[HELP] || argc == 0) {
* option::printUsage(std::cout, usage);
* return 0;
* }
*
* std::cout << "--plus count: " <<
* options[PLUS].count() << "\n";
*
* for (option::Option* opt = options[UNKNOWN]; opt; opt = opt->next())
* std::cout << "Unknown option: " << opt->name << "\n";
*
* for (int i = 0; i < parse.nonOptionsCount(); ++i)
* std::cout << "Non-option #" << i << ": " << parse.nonOption(i) << "\n";
* }
* @endcode
*
* @par Option syntax:
* @li The Lean Mean C++ Option Parser follows POSIX <code>getopt()</code> conventions and supports
* GNU-style <code>getopt_long()</code> long options as well as Perl-style single-minus
* long options (<code>getopt_long_only()</code>).
* @li short options have the format @c -X where @c X is any character that fits in a char.
* @li short options can be grouped, i.e. <code>-X -Y</code> is equivalent to @c -XY.
* @li a short option may take an argument either separate (<code>-X foo</code>) or
* attached (@c -Xfoo). You can make the parser accept the additional format @c -X=foo by
* registering @c X as a long option (in addition to being a short option) and
* enabling single-minus long options.
* @li an argument-taking short option may be grouped if it is the last in the group, e.g.
* @c -ABCXfoo or <code> -ABCX foo </code> (@c foo is the argument to the @c -X option).
* @li a lone minus character @c '-' is not treated as an option. It is customarily used where
* a file name is expected to refer to stdin or stdout.
* @li long options have the format @c --option-name.
* @li the option-name of a long option can be anything and include any characters.
* Even @c = characters will work, but don't do that.
* @li [optional] long options may be abbreviated as long as the abbreviation is unambiguous.
* You can set a minimum length for abbreviations.
* @li [optional] long options may begin with a single minus. The double minus form is always
* accepted, too.
* @li a long option may take an argument either separate (<code> --option arg </code>) or
* attached (<code> --option=arg </code>). In the attached form the equals sign is mandatory.
* @li an empty string can be passed as an attached long option argument: <code> --option-name= </code>.
* Note the distinction between an empty string as argument and no argument at all.
* @li an empty string is permitted as separate argument to both long and short options.
* @li Arguments to both short and long options may start with a @c '-' character. E.g.
* <code> -X-X </code>, <code>-X -X</code> or <code> --long-X=-X </code>. If @c -X
* and @c --long-X take an argument, that argument will be @c "-X" in all 3 cases.
* @li If using the built-in @ref option::Arg::Optional "Arg::Optional", optional arguments must
* be attached.
* @li the special option @c -- (i.e. without a name) terminates the list of
* options. Everything that follows is a non-option argument, even if it starts with
* a @c '-' character. The @c -- itself will not appear in the parse results.
* @li the first argument that doesn't start with @c '-' or @c '--' and does not belong to
* a preceding argument-taking option, will terminate the option list and is the
* first non-option argument. All following command line arguments are treated as
* non-option arguments, even if they start with @c '-' . @n
* NOTE: This behaviour is mandated by POSIX, but GNU getopt() only honours this if it is
* explicitly requested (e.g. by setting POSIXLY_CORRECT). @n
* You can enable the GNU behaviour by passing @c true as first argument to
* e.g. @ref option::Parser::parse() "Parser::parse()".
* @li Arguments that look like options (i.e. @c '-' followed by at least 1 character) but
* aren't, are NOT treated as non-option arguments. They are treated as unknown options and
* are collected into a list of unknown options for error reporting. @n
* This means that in order to pass a first non-option
* argument beginning with the minus character it is required to use the
* @c -- special option, e.g.
* @code
* program -x -- --strange-filename
* @endcode
* In this example, @c --strange-filename is a non-option argument. If the @c --
* were omitted, it would be treated as an unknown option. @n
* See @ref option::Descriptor::longopt for information on how to collect unknown options.
*
*/
#ifndef OPTIONPARSER_H_
#define OPTIONPARSER_H_
#ifdef _MSC_VER
#include <intrin.h>
#pragma intrinsic(_BitScanReverse)
#endif
/** @brief The namespace of The Lean Mean C++ Option Parser. */
namespace option
{
#ifdef _MSC_VER
struct MSC_Builtin_CLZ
{
static int builtin_clz(unsigned x)
{
unsigned long index;
_BitScanReverse(&index, x);
return 32-index; // int is always 32bit on Windows, even for target x64
}
};
#define __builtin_clz(x) MSC_Builtin_CLZ::builtin_clz(x)
#endif
class Option;
/**
* @brief Possible results when checking if an argument is valid for a certain option.
*
* In the case that no argument is provided for an option that takes an
* optional argument, return codes @c ARG_OK and @c ARG_IGNORE are equivalent.
*/
enum ArgStatus
{
//! The option does not take an argument.
ARG_NONE,
//! The argument is acceptable for the option.
ARG_OK,
//! The argument is not acceptable but that's non-fatal because the option's argument is optional.
ARG_IGNORE,
//! The argument is not acceptable and that's fatal.
ARG_ILLEGAL
};
/**
* @brief Signature of functions that check if an argument is valid for a certain type of option.
*
* Every Option has such a function assigned in its Descriptor.
* @code
* Descriptor usage[] = { {UNKNOWN, 0, "", "", Arg::None, ""}, ... };
* @endcode
*
* A CheckArg function has the following signature:
* @code ArgStatus CheckArg(const Option& option, bool msg); @endcode
*
* It is used to check if a potential argument would be acceptable for the option.
* It will even be called if there is no argument. In that case @c option.arg will be @c NULL.
*
* If @c msg is @c true and the function determines that an argument is not acceptable and
* that this is a fatal error, it should output a message to the user before
* returning @ref ARG_ILLEGAL. If @c msg is @c false the function should remain silent (or you
* will get duplicate messages).
*
* See @ref ArgStatus for the meaning of the return values.
*
* While you can provide your own functions,
* often the following pre-defined checks (which never return @ref ARG_ILLEGAL) will suffice:
*
* @li @c Arg::None @copybrief Arg::None
* @li @c Arg::Optional @copybrief Arg::Optional
*
*/
typedef ArgStatus (*CheckArg)(const Option& option, bool msg);
/**
* @brief Describes an option, its help text (usage) and how it should be parsed.
*
* The main input when constructing an option::Parser is an array of Descriptors.
* @par Example:
* @code
* enum OptionIndex {CREATE, ...};
* enum OptionType {DISABLE, ENABLE, OTHER};
*
* const option::Descriptor usage[] = {
* { CREATE, // index
* OTHER, // type
* "c", // shortopt
* "create", // longopt
* Arg::None, // check_arg
* "--create Tells the program to create something." // help
* }
* , ...
* };
* @endcode
*/
struct Descriptor
{
/**
* @brief Index of this option's linked list in the array filled in by the parser.
*
* Command line options whose Descriptors have the same index will end up in the same
* linked list in the order in which they appear on the command line. If you have
* multiple long option aliases that refer to the same option, give their descriptors
* the same @c index.
*
* If you have options that mean exactly opposite things
* (e.g. @c --enable-foo and @c --disable-foo ), you should also give them the same
* @c index, but distinguish them through different values for @ref type.
* That way they end up in the same list and you can just take the last element of the
* list and use its type. This way you get the usual behaviour where switches later
* on the command line override earlier ones without having to code it manually.
*
* @par Tip:
* Use an enum rather than plain ints for better readability, as shown in the example
* at Descriptor.
*/
const unsigned index;
/**
* @brief Used to distinguish between options with the same @ref index.
* See @ref index for details.
*
* It is recommended that you use an enum rather than a plain int to make your
* code more readable.
*/
const int type;
/**
* @brief Each char in this string will be accepted as a short option character.
*
* The string must not include the minus character @c '-' or you'll get undefined
* behaviour.
*
* If this Descriptor should not have short option characters, use the empty
* string "". NULL is not permitted here!
*
* See @ref longopt for more information.
*/
const char* const shortopt;
/**
* @brief The long option name (without the leading @c -- ).
*
* If this Descriptor should not have a long option name, use the empty
* string "". NULL is not permitted here!
*
* While @ref shortopt allows multiple short option characters, each
* Descriptor can have only a single long option name. If you have multiple
* long option names referring to the same option use separate Descriptors
* that have the same @ref index and @ref type. You may repeat
* short option characters in such an alias Descriptor but there's no need to.
*
* @par Dummy Descriptors:
* You can use dummy Descriptors with an
* empty string for both @ref shortopt and @ref longopt to add text to
* the usage that is not related to a specific option. See @ref help.
* The first dummy Descriptor will be used for unknown options (see below).
*
* @par Unknown Option Descriptor:
* The first dummy Descriptor in the list of Descriptors,
* whose @ref shortopt and @ref longopt are both the empty string, will be used
* as the Descriptor for unknown options. An unknown option is a string in
* the argument vector that is not a lone minus @c '-' but starts with a minus
* character and does not match any Descriptor's @ref shortopt or @ref longopt. @n
* Note that the dummy descriptor's @ref check_arg function @e will be called and
* its return value will be evaluated as usual. I.e. if it returns @ref ARG_ILLEGAL
* the parsing will be aborted with <code>Parser::error()==true</code>. @n
* if @c check_arg does not return @ref ARG_ILLEGAL the descriptor's
* @ref index @e will be used to pick the linked list into which
* to put the unknown option. @n
* If there is no dummy descriptor, unknown options will be dropped silently.
*
*/
const char* const longopt;
/**
* @brief For each option that matches @ref shortopt or @ref longopt this function
* will be called to check a potential argument to the option.
*
* This function will be called even if there is no potential argument. In that case
* it will be passed @c NULL as @c arg parameter. Do not confuse this with the empty
* string.
*
* See @ref CheckArg for more information.
*/
const CheckArg check_arg;
/**
* @brief The usage text associated with the options in this Descriptor.
*
* You can use option::printUsage() to format your usage message based on
* the @c help
gitextract_t4nb5sit/ ├── .github/ │ └── workflows/ │ └── build.yaml ├── .gitignore ├── .gitmodules ├── BUILD.md ├── CMakeLists.txt ├── LICENSE ├── README.md ├── data/ │ ├── test.ply │ └── test_vr.json ├── font/ │ └── JetBrainsMono-Medium.json ├── meta-quest/ │ ├── cp_from_sdk │ └── splatapult/ │ ├── .gitignore │ ├── Projects/ │ │ └── Android/ │ │ ├── AndroidManifest.xml │ │ ├── build.bat │ │ ├── build.gradle │ │ ├── build.py │ │ ├── gradle.properties │ │ ├── jni/ │ │ │ ├── Android.mk │ │ │ └── Application.mk │ │ └── settings.gradle │ ├── assets/ │ │ ├── .gitignore │ │ └── donotedelete.txt │ └── java/ │ └── com/ │ └── oculus/ │ └── NativeActivity.java ├── shader/ │ ├── carpet_frag.glsl │ ├── carpet_vert.glsl │ ├── debugdraw_frag.glsl │ ├── debugdraw_vert.glsl │ ├── desktop_frag.glsl │ ├── desktop_vert.glsl │ ├── multi_radixsort.glsl │ ├── multi_radixsort_histograms.glsl │ ├── point_frag.glsl │ ├── point_geom.glsl │ ├── point_vert.glsl │ ├── presort_compute.glsl │ ├── single_radixsort.glsl │ ├── splat_frag.glsl │ ├── splat_geom.glsl │ ├── splat_peel_frag.glsl │ ├── splat_vert.glsl │ ├── text_frag.glsl │ └── text_vert.glsl ├── src/ │ ├── android_main.cpp │ ├── app.cpp │ ├── app.h │ ├── camerapathrenderer.cpp │ ├── camerapathrenderer.h │ ├── camerasconfig.cpp │ ├── camerasconfig.h │ ├── core/ │ │ ├── binaryattribute.cpp │ │ ├── binaryattribute.h │ │ ├── debugrenderer.cpp │ │ ├── debugrenderer.h │ │ ├── framebuffer.cpp │ │ ├── framebuffer.h │ │ ├── image.cpp │ │ ├── image.h │ │ ├── inputbuddy.cpp │ │ ├── inputbuddy.h │ │ ├── log.cpp │ │ ├── log.h │ │ ├── optionparser.h │ │ ├── program.cpp │ │ ├── program.h │ │ ├── statemachine.h │ │ ├── textrenderer.cpp │ │ ├── textrenderer.h │ │ ├── texture.cpp │ │ ├── texture.h │ │ ├── util.cpp │ │ ├── util.h │ │ ├── vertexbuffer.cpp │ │ ├── vertexbuffer.h │ │ ├── xrbuddy.cpp │ │ └── xrbuddy.h │ ├── flycam.cpp │ ├── flycam.h │ ├── gaussiancloud.cpp │ ├── gaussiancloud.h │ ├── magiccarpet.cpp │ ├── magiccarpet.h │ ├── maincontext.h │ ├── ply.cpp │ ├── ply.h │ ├── pointcloud.cpp │ ├── pointcloud.h │ ├── pointrenderer.cpp │ ├── pointrenderer.h │ ├── radix_sort.hpp │ ├── sdl_main.cpp │ ├── splatrenderer.cpp │ ├── splatrenderer.h │ ├── vrconfig.cpp │ └── vrconfig.h ├── tasks.py └── vcpkg.json
SYMBOL INDEX (227 symbols across 44 files)
FILE: meta-quest/splatapult/Projects/Android/build.py
function init (line 13) | def init():
FILE: meta-quest/splatapult/java/com/oculus/NativeActivity.java
class NativeActivity (line 27) | public class NativeActivity extends android.app.NativeActivity {
method onCreate (line 37) | @Override
method requestScenePermissionIfNeeded (line 43) | private void requestScenePermissionIfNeeded() {
FILE: src/android_main.cpp
type AppContext (line 67) | struct AppContext
method AppContext (line 69) | AppContext() : resumed(false), sessionActive(false), assMan(nullptr), ...
type EGLInfo (line 73) | struct EGLInfo
method EGLInfo (line 75) | EGLInfo() : majorVersion(0), minorVersion(0), display(0), config(0),...
method Clear (line 89) | void Clear()
method SetupEGLContext (line 100) | bool SetupEGLContext()
method SetupAssets (line 186) | bool SetupAssets(android_app* app)
method MakeDir (line 242) | bool MakeDir(const std::string& dirFilename)
method UnpackAsset (line 264) | bool UnpackAsset(const std::string& assetFilename)
function app_handle_cmd (line 317) | static void app_handle_cmd(struct android_app* androidApp, int32_t cmd)
function android_main (line 365) | void android_main(struct android_app* androidApp)
FILE: src/app.cpp
type optionIndex (line 47) | enum optionIndex
function FindConfigFile (line 89) | static std::string FindConfigFile(const std::string& plyFilename, const ...
function GetFilenameWithoutExtension (line 121) | static std::string GetFilenameWithoutExtension(const std::string& filepath)
function MakeVrConfigFilename (line 135) | static std::string MakeVrConfigFilename(const std::string& plyFilename)
function Clear (line 144) | static void Clear(glm::ivec2 windowSize, bool setViewport = true)
function RenderDesktop (line 167) | static void RenderDesktop(glm::ivec2 windowSize, std::shared_ptr<Program...
function LoadPointCloud (line 214) | static std::shared_ptr<PointCloud> LoadPointCloud(const std::string& ply...
function LoadGaussianCloud (line 226) | static std::shared_ptr<GaussianCloud> LoadGaussianCloud(const std::strin...
function PrintControls (line 246) | static void PrintControls()
FILE: src/app.h
type FrameBuffer (line 19) | struct FrameBuffer
type Texture (line 28) | struct Texture
function class (line 34) | class App
FILE: src/camerapathrenderer.h
type Camera (line 16) | struct Camera
function class (line 18) | class CameraPathRenderer
FILE: src/camerasconfig.h
type Camera (line 12) | struct Camera
function class (line 18) | class CamerasConfig
FILE: src/core/binaryattribute.h
function class (line 12) | class BinaryAttribute
FILE: src/core/debugrenderer.h
function class (line 14) | class DebugRenderer
FILE: src/core/framebuffer.h
type Texture (line 11) | struct Texture
type FrameBuffer (line 13) | struct FrameBuffer
FILE: src/core/image.h
function PixelFormat (line 12) | enum class PixelFormat
FILE: src/core/inputbuddy.cpp
function Deadspot (line 115) | static float Deadspot(float v)
FILE: src/core/inputbuddy.h
type SDL_JoyAxisEvent (line 15) | struct SDL_JoyAxisEvent
type SDL_JoyHatEvent (line 16) | struct SDL_JoyHatEvent
type SDL_JoyButtonEvent (line 17) | struct SDL_JoyButtonEvent
function class (line 19) | class InputBuddy
FILE: src/core/log.cpp
function log_vprintf (line 18) | static int log_vprintf(int prio, const char *fmt, va_list args)
function log_vprintf (line 26) | static int log_vprintf(const char *fmt, va_list args)
FILE: src/core/log.h
type Log (line 10) | struct Log
FILE: src/core/optionparser.h
function namespace (line 227) | namespace option
function Option (line 853) | static Option* tag(Option* ptr)
function Option (line 858) | static Option* untag(Option* ptr)
function isTagged (line 863) | static bool isTagged(Option* ptr)
function Optional (line 923) | struct Arg
type Stats (line 950) | struct Stats
function class (line 1080) | class Parser
function optionsCount (line 1220) | int optionsCount()
function nonOptionsCount (line 1239) | int nonOptionsCount()
function error (line 1283) | bool error()
type Action (line 1291) | struct Action
function streq (line 1315) | static bool streq(const char* st1, const char* st2)
function streqabbr (line 1347) | static bool streqabbr(const char* st1, const char* st2, long long min)
function instr (line 1365) | static bool instr(char ch, const char* st)
function shift (line 1377) | static void shift(const char** args, int count)
function Action (line 1393) | struct Parser::Action
function perform (line 1442) | bool perform(Option&)
function perform (line 1482) | bool perform(Option& option)
function finished (line 1500) | bool finished(int numargs, const char** args)
function parse (line 1515) | inline void Parser::parse(bool gnu, const Descriptor usage[], int argc, ...
function add (line 1522) | inline void Stats::add(bool gnu, const Descriptor usage[], int argc, con...
function workhorse (line 1539) | inline bool Parser::workhorse(bool gnu, const Descriptor usage[], int nu...
function OStreamWriter (line 1724) | struct PrintUsageImplementation
function virtual (line 1792) | virtual void operator()(const char* str, int size)
function userstream (line 1797) | TemporaryWriter(const Temporary& u) :
function virtual (line 1815) | virtual void operator()(const char* str, int size)
function virtual (line 1836) | virtual void operator()(const char* str, int size)
function upmax (line 1851) | static void upmax(int& i1, int i2)
function indent (line 1867) | static void indent(IStringWriter& write, int& x, int want_x)
function isWideChar (line 1903) | static bool isWideChar(unsigned ch)
function class (line 1950) | class LinePartIterator
function restartTable (line 2027) | void restartTable()
function nextRow (line 2039) | bool nextRow()
function restartRow (line 2070) | void restartRow()
function next (line 2089) | bool next()
function column (line 2161) | int column()
function line (line 2170) | int line()
function length (line 2178) | int length()
function screenLength (line 2187) | int screenLength()
function class (line 2225) | class LineWrapper
FILE: src/core/program.cpp
function ExpandMacros (line 33) | static std::string ExpandMacros(std::vector<std::pair<std::string, std::...
function DumpShaderSource (line 51) | static void DumpShaderSource(const std::string& source)
function CompileShader (line 64) | static bool CompileShader(GLenum type, const std::string& source, GLint*...
FILE: src/core/program.h
function class (line 15) | class Program
type Variable (line 88) | struct Variable
FILE: src/core/statemachine.h
type TransitionStruct (line 23) | struct TransitionStruct
type StateStruct (line 30) | struct StateStruct
function AddState (line 46) | void AddState(State state, const std::string& name, const VoidCallback& ...
function AddTransition (line 53) | void AddTransition(State state, State newState, const std::string& name,...
function Process (line 58) | void Process(float dt)
FILE: src/core/textrenderer.h
type Texture (line 16) | struct Texture
function class (line 18) | class TextRenderer
FILE: src/core/texture.h
type Image (line 10) | struct Image
type class (line 12) | enum class
type class (line 22) | enum class
type Texture (line 30) | struct Texture
FILE: src/core/util.cpp
function LoadFile (line 31) | bool LoadFile(const std::string& filename, std::string& data)
function SaveFile (line 46) | bool SaveFile(const std::string& filename, const std::string& data)
function NextCodePointUTF8 (line 62) | int NextCodePointUTF8(const char *str, uint32_t *codePointOut)
function GLErrorCheck (line 97) | void GLErrorCheck(const char* message)
function SafeNormalize (line 128) | glm::vec3 SafeNormalize(const glm::vec3& v, const glm::vec3& ifZero)
function SafeMix (line 141) | glm::quat SafeMix(const glm::quat& a, const glm::quat& b, float alpha)
function MakeMat3 (line 153) | glm::mat3 MakeMat3(const glm::quat& rotation)
function MakeMat3 (line 161) | glm::mat3 MakeMat3(const glm::vec3& scale, const glm::quat& rotation)
function MakeMat3 (line 169) | glm::mat4 MakeMat3(float scale, const glm::quat& rotation)
function MakeMat4 (line 174) | glm::mat4 MakeMat4(const glm::vec3& scale, const glm::quat& rotation, co...
function MakeMat4 (line 183) | glm::mat4 MakeMat4(float scale, const glm::quat& rotation, const glm::ve...
function MakeMat4 (line 188) | glm::mat4 MakeMat4(const glm::quat& rotation, const glm::vec3& translation)
function MakeMat4 (line 193) | glm::mat4 MakeMat4(const glm::quat& rotation)
function MakeMat4 (line 198) | glm::mat4 MakeMat4(const glm::mat3& m3, const glm::vec3& translation)
function MakeMat4 (line 204) | glm::mat4 MakeMat4(const glm::mat3& m3)
function Decompose (line 209) | void Decompose(const glm::mat4& matrix, glm::vec3* scaleOut, glm::quat* ...
function Decompose (line 230) | void Decompose(const glm::mat3& matrix, glm::vec3* scaleOut, glm::quat* ...
function DecomposeSwingTwist (line 251) | void DecomposeSwingTwist(const glm::quat& rotation, const glm::vec3& twi...
function XformPoint (line 267) | glm::vec3 XformPoint(const glm::mat4& m, const glm::vec3& p)
function XformVec (line 274) | glm::vec3 XformVec(const glm::mat4& m, const glm::vec3& v)
function RandomColor (line 279) | glm::vec3 RandomColor()
function PrintMat (line 284) | void PrintMat(const glm::mat4& m4, const std::string& name)
function PrintMat (line 293) | void PrintMat(const glm::mat3& m3, const std::string& name)
function PrintMat (line 301) | void PrintMat(const glm::mat2& m2, const std::string& name)
function PrintVec (line 308) | void PrintVec(const glm::vec4& v4, const std::string& name)
function PrintVec (line 313) | void PrintVec(const glm::vec3& v3, const std::string& name)
function PrintVec (line 318) | void PrintVec(const glm::vec2& v2, const std::string& name)
function PrintQuat (line 323) | void PrintQuat(const glm::quat& q, const std::string& name)
function SetRootPath (line 345) | void SetRootPath(const std::string& rootPathIn)
function PointInsideAABB (line 350) | bool PointInsideAABB(const glm::vec3& point, const glm::vec3& aabbMin, c...
function LinearToSRGB (line 357) | float LinearToSRGB(float linear)
function SRGBToLinear (line 369) | float SRGBToLinear(float srgb)
function LinearToSRGB (line 381) | glm::vec4 LinearToSRGB(const glm::vec4& linearColor)
function SRGBToLinear (line 393) | glm::vec4 SRGBToLinear(const glm::vec4& srgbColor)
function MakeRotateAboutPointMat (line 404) | glm::mat4 MakeRotateAboutPointMat(const glm::vec3& pos, const glm::quat&...
function CreateProjection (line 420) | void CreateProjection(float* m, GraphicsAPI graphicsApi, const float tan...
function StrCpy_s (line 482) | void StrCpy_s(char* dest, size_t destsz, const char* src)
FILE: src/core/util.h
type GraphicsAPI (line 70) | enum GraphicsAPI { GRAPHICS_VULKAN, GRAPHICS_OPENGL, GRAPHICS_OPENGL_ES,...
FILE: src/core/vertexbuffer.cpp
function glBufferStorage (line 26) | static void glBufferStorage(GLenum target, GLsizeiptr size, const void* ...
FILE: src/core/vertexbuffer.h
function class (line 21) | class BufferObject
function class (line 63) | class VertexArrayObject
FILE: src/core/xrbuddy.cpp
type PathCache (line 30) | struct PathCache
method PathCache (line 32) | PathCache(XrInstance instanceIn) : instance(instanceIn) {}
method XrPath (line 33) | XrPath operator[](const std::string& key)
function CheckResult (line 53) | static bool CheckResult(XrInstance instance, XrResult result, const char...
function EnumerateExtensions (line 73) | static bool EnumerateExtensions(std::vector<XrExtensionProperties>& exte...
function ExtensionSupported (line 109) | static bool ExtensionSupported(const std::vector<XrExtensionProperties>&...
function EnumerateApiLayers (line 122) | static bool EnumerateApiLayers(std::vector<XrApiLayerProperties>& layerP...
function CreateInstance (line 156) | static bool CreateInstance(XrInstance& instance, const std::vector<const...
function GetSystemId (line 211) | static bool GetSystemId(XrInstance instance, XrSystemId& systemId)
function GetSystemProperties (line 228) | static bool GetSystemProperties(XrInstance instance, XrSystemId systemId...
function SupportsVR (line 256) | static bool SupportsVR(XrInstance instance, XrSystemId systemId)
function EnumerateViewConfigs (line 295) | static bool EnumerateViewConfigs(XrInstance instance, XrSystemId systemI...
function SetColorSpace (line 338) | static bool SetColorSpace(XrInstance instance, XrSession session, XrColo...
function CreateSession (line 360) | static bool CreateSession(XrInstance instance, XrSystemId systemId, XrSe...
function CreateActions (line 482) | static bool CreateActions(XrInstance instance, XrSystemId systemId, XrSe...
function CreateSpaces (line 727) | static bool CreateSpaces(XrInstance instance, XrSystemId systemId, XrSes...
function BeginSession (line 799) | static bool BeginSession(XrInstance instance, XrSystemId systemId, XrSes...
function EndSession (line 817) | static bool EndSession(XrInstance instance, XrSystemId systemId, XrSessi...
function CreateFrameBuffer (line 829) | static bool CreateFrameBuffer(GLuint& frameBuffer)
function CreateSwapchains (line 835) | static bool CreateSwapchains(XrInstance instance, XrSession session,
function GLuint (line 962) | static GLuint CreateDepthTexture(GLuint colorTexture, GLint width, GLint...
FILE: src/core/xrbuddy.h
function class (line 51) | class XrBuddy
FILE: src/flycam.h
function class (line 11) | class FlyCam
FILE: src/gaussiancloud.cpp
type BaseGaussianData (line 32) | struct BaseGaussianData
method BaseGaussianData (line 34) | BaseGaussianData() noexcept {}
type FullGaussianData (line 44) | struct FullGaussianData : public BaseGaussianData
method FullGaussianData (line 46) | FullGaussianData() noexcept {}
function glmToEigen (line 59) | static Eigen::Matrix3f glmToEigen(const glm::mat3& glmMat)
function eigenToGlm (line 73) | static glm::mat3 eigenToGlm(const Eigen::Matrix3f& eigenMat)
function ComputeCovMatFromRotScale (line 86) | static glm::mat3 ComputeCovMatFromRotScale(float rot[4], float scale[3])
function ComputeRotScaleFromCovMat (line 96) | static void ComputeRotScaleFromCovMat(const glm::mat3& V, glm::quat& rot...
function ComputeAlphaFromOpacity (line 119) | static float ComputeAlphaFromOpacity(float opacity)
function ComputeOpacityFromAlpha (line 124) | static float ComputeOpacityFromAlpha(float alpha)
FILE: src/gaussiancloud.h
function class (line 17) | class GaussianCloud
FILE: src/magiccarpet.h
function class (line 19) | class MagicCarpet
FILE: src/maincontext.h
type MainContext (line 31) | struct MainContext
type MainContext (line 39) | struct MainContext
type MainContext (line 49) | struct MainContext
FILE: src/ply.cpp
function CheckLine (line 22) | static bool CheckLine(std::ifstream& plyFile, const std::string& validLine)
function GetNextPlyLine (line 28) | static bool GetNextPlyLine(std::ifstream& plyFile, std::string& lineOut)
FILE: src/ply.h
function class (line 19) | class Ply
FILE: src/pointcloud.cpp
type PointData (line 18) | struct PointData
method PointData (line 20) | PointData() noexcept {}
FILE: src/pointcloud.h
function class (line 15) | class PointCloud
FILE: src/pointrenderer.cpp
function SetupAttrib (line 33) | static void SetupAttrib(int loc, const BinaryAttribute& attrib, int32_t ...
FILE: src/pointrenderer.h
function namespace (line 19) | namespace rgc::radix_sort
function class (line 24) | class PointRenderer
FILE: src/radix_sort.hpp
function glBufferStorage (line 45) | static void glBufferStorage(GLenum target, GLsizeiptr size, const void* ...
function ZeroBuffer (line 73) | static void ZeroBuffer(GLenum target, size_t size)
function __rgc_shader_injector_load_src (line 94) | inline void __rgc_shader_injector_load_src(GLuint shader, char const* src)
function __rgc_shader_injector_load_src_from_file (line 106) | inline void __rgc_shader_injector_load_src_from_file(GLuint shader, std:...
type rgc::radix_sort (line 127) | namespace rgc::radix_sort
type shader (line 131) | struct shader
method shader (line 135) | shader(GLenum type)
method src_from_txt (line 145) | void src_from_txt(char const* txt) const
method src_from_txt_file (line 150) | void src_from_txt_file(std::filesystem::path const& filename) const
method get_info_log (line 161) | std::string get_info_log() const
method compile (line 171) | void compile() const
type program (line 186) | struct program
method program (line 190) | program()
method attach_shader (line 200) | void attach_shader(GLuint shader) const
method get_info_log (line 205) | std::string get_info_log() const
method link (line 215) | void link() const
method GLint (line 227) | GLint get_uniform_location(const char* name) const
method use (line 236) | void use() const
method unuse (line 241) | static void unuse()
type sorter (line 247) | struct sorter
method GLuint (line 261) | GLuint calc_thread_blocks_num(size_t arr_len)
method T (line 267) | T round_to_power_of_2(T dim)
method resize_internal_buf (line 272) | void resize_internal_buf(size_t arr_len)
method sorter (line 301) | sorter(size_t init_arr_len)
method sort (line 340) | void sort(GLuint key_buf, GLuint val_buf, size_t arr_len)
FILE: src/sdl_main.cpp
type GlobalContext (line 30) | struct GlobalContext
function Watch (line 39) | int SDLCALL Watch(void *userdata, SDL_Event* event)
function main (line 49) | int main(int argc, char *argv[])
FILE: src/splatrenderer.cpp
function SetupAttrib (line 35) | static void SetupAttrib(int loc, const BinaryAttribute& attrib, int32_t ...
FILE: src/splatrenderer.h
function namespace (line 18) | namespace rgc::radix_sort
function class (line 23) | class SplatRenderer
FILE: src/vrconfig.h
function class (line 12) | class VrConfig
FILE: tasks.py
function clean (line 9) | def clean(c):
function build_with_config (line 12) | def build_with_config(c, config, options={}):
function build (line 24) | def build(c):
function build_debug (line 28) | def build_debug(c):
function archive (line 32) | def archive(c):
function deploy (line 36) | def deploy(c):
function all (line 45) | def all(c):
Condensed preview — 96 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (566K chars).
[
{
"path": ".github/workflows/build.yaml",
"chars": 1628,
"preview": "name: Linux/Windows Build\nrun-name: ${{ github.actor }} is building splatapult\non: [push]\njobs:\n build-linux:\n runs-"
},
{
"path": ".gitignore",
"chars": 193,
"preview": "build/\nmeta-quest/ovr_openxr_mobile_sdk_59.0/\nmeta-quest/ovr_openxr_mobile_sdk_59.0.zip\ndata/*\n!data/test.ply\n!data/test"
},
{
"path": ".gitmodules",
"chars": 76,
"preview": "[submodule \"vcpkg\"]\n\tpath = vcpkg\n\turl = https://github.com/microsoft/vcpkg\n"
},
{
"path": "BUILD.md",
"chars": 3115,
"preview": "Windows Build (vcpkg submodule)\n-----------------------\n* Install Visual Studio 2022\n* Install cmake 3.27.1\n* Ensure spl"
},
{
"path": "CMakeLists.txt",
"chars": 4251,
"preview": "# See BUILD.md for more info\n# Windows cheat sheet\n# > mkdir build\n# > cd build\n# > cmake -DSHIPPING=ON -DCMAKE_TOOLCHAI"
},
{
"path": "LICENSE",
"chars": 1072,
"preview": "MIT License\n\nCopyright 2024 Anthony J. Thibault\n\nPermission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "README.md",
"chars": 3132,
"preview": "\nSplatapult\n----------------------------------------------\n\n\n[<img src=\"https://i.yti"
},
{
"path": "data/test_vr.json",
"chars": 169,
"preview": "{\n \"floorMat\": [[-0.705639, -0.312374, -0.636, -0.573335], [-1.64858e-05, 0.897588, -0.440836, -1.78893], [0.708571, "
},
{
"path": "font/JetBrainsMono-Medium.json",
"chars": 29501,
"preview": "{\n \"texture_width\": 1024,\n \"glyph_metrics\": {\n \"32\": {\n \"ascii_index\": 32,\n \"xy_lower"
},
{
"path": "meta-quest/cp_from_sdk",
"chars": 82,
"preview": "rm -rf splatapult\ncp -r ovr_openxr_mobile_sdk_59.0/XrSamples/splatapult splatapult"
},
{
"path": "meta-quest/splatapult/.gitignore",
"chars": 430,
"preview": "# Gradle files\n.gradle/\nbuild/\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Log/OS Files\n*.log\n\n# And"
},
{
"path": "meta-quest/splatapult/Projects/Android/AndroidManifest.xml",
"chars": 2579,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n pack"
},
{
"path": "meta-quest/splatapult/Projects/Android/build.bat",
"chars": 568,
"preview": "@rem Only edit the master copy of this file in SDK_ROOT/bin/scripts/build/perproject\n\n@setlocal enableextensions enabled"
},
{
"path": "meta-quest/splatapult/Projects/Android/build.gradle",
"chars": 1712,
"preview": "\napply plugin: 'com.android.application'\napply from: \"${rootProject.projectDir}/VrApp.gradle\"\n\ntask copyFiles(type: Copy"
},
{
"path": "meta-quest/splatapult/Projects/Android/build.py",
"chars": 985,
"preview": "#!/usr/bin/python\n# (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.\n\n# This first bit of code is "
},
{
"path": "meta-quest/splatapult/Projects/Android/gradle.properties",
"chars": 26,
"preview": "org.gradle.jvmargs=-Xmx4g\n"
},
{
"path": "meta-quest/splatapult/Projects/Android/jni/Android.mk",
"chars": 1964,
"preview": "LOCAL_PATH := $(call my-dir)\ninclude $(CLEAR_VARS)\n\nLOCAL_MODULE := splatapult\n\nLOCAL_CFLAGS += -Werror\n\n# need execptio"
},
{
"path": "meta-quest/splatapult/Projects/Android/jni/Application.mk",
"chars": 441,
"preview": "# MAKEFILE_LIST specifies the current used Makefiles, of which this is the last\n# one. I use that to obtain the Applicat"
},
{
"path": "meta-quest/splatapult/Projects/Android/settings.gradle",
"chars": 155,
"preview": "rootProject.projectDir = new File(settingsDir, '../../../..')\nrootProject.name = \"splatapult\"\n\ninclude ':', \\\n ':XrSa"
},
{
"path": "meta-quest/splatapult/assets/.gitignore",
"chars": 28,
"preview": "font/\nshader/\ntexture/\ndata/"
},
{
"path": "meta-quest/splatapult/assets/donotedelete.txt",
"chars": 0,
"preview": ""
},
{
"path": "meta-quest/splatapult/java/com/oculus/NativeActivity.java",
"chars": 2223,
"preview": "// Copyright (c) Facebook Technologies, LLC and its affiliates. All Rights reserved.\npackage com.oculus;\n\nimport android"
},
{
"path": "shader/carpet_frag.glsl",
"chars": 430,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "shader/carpet_vert.glsl",
"chars": 360,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "shader/debugdraw_frag.glsl",
"chars": 368,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "shader/debugdraw_vert.glsl",
"chars": 376,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "shader/desktop_frag.glsl",
"chars": 1008,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "shader/desktop_vert.glsl",
"chars": 360,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "shader/multi_radixsort.glsl",
"chars": 4689,
"preview": "/**\n* VkRadixSort written by Mirco Werner: https://github.com/MircoWerner/VkRadixSort\n* Based on implementation of Intel"
},
{
"path": "shader/multi_radixsort_histograms.glsl",
"chars": 1676,
"preview": "/**\n* VkRadixSort written by Mirco Werner: https://github.com/MircoWerner/VkRadixSort\n* Based on implementation of Intel"
},
{
"path": "shader/point_frag.glsl",
"chars": 488,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "shader/point_geom.glsl",
"chars": 1164,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "shader/point_vert.glsl",
"chars": 446,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "shader/presort_compute.glsl",
"chars": 1366,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "shader/single_radixsort.glsl",
"chars": 5410,
"preview": "/**\n* VkRadixSort written by Mirco Werner: https://github.com/MircoWerner/VkRadixSort\n* Based on implementation of Intel"
},
{
"path": "shader/splat_frag.glsl",
"chars": 1074,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "shader/splat_geom.glsl",
"chars": 3053,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "shader/splat_peel_frag.glsl",
"chars": 1302,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "shader/splat_vert.glsl",
"chars": 7552,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "shader/text_frag.glsl",
"chars": 490,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "shader/text_vert.glsl",
"chars": 388,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/android_main.cpp",
"chars": 14987,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/app.cpp",
"chars": 33749,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/app.h",
"chars": 2955,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/camerapathrenderer.cpp",
"chars": 5877,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/camerapathrenderer.h",
"chars": 1156,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/camerasconfig.cpp",
"chars": 2809,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/camerasconfig.h",
"chars": 647,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/binaryattribute.cpp",
"chars": 664,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/binaryattribute.h",
"chars": 2378,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/debugrenderer.cpp",
"chars": 2120,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/debugrenderer.h",
"chars": 778,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/framebuffer.cpp",
"chars": 1430,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/framebuffer.h",
"chars": 902,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/image.cpp",
"chars": 4313,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/image.h",
"chars": 527,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/inputbuddy.cpp",
"chars": 5773,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/inputbuddy.h",
"chars": 2133,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/log.cpp",
"chars": 2745,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/log.h",
"chars": 746,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/optionparser.h",
"chars": 102124,
"preview": "/*\n * The Lean Mean C++ Option Parser\n *\n * Copyright (C) 2012-2017 Matthias S. Benkmann\n *\n * The \"Software\" in the fol"
},
{
"path": "src/core/program.cpp",
"chars": 12423,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/program.h",
"chars": 3047,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/statemachine.h",
"chars": 2597,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/textrenderer.cpp",
"chars": 9470,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/textrenderer.h",
"chars": 2273,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/texture.cpp",
"chars": 2955,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/texture.h",
"chars": 889,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/util.cpp",
"chars": 14307,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/util.h",
"chars": 3155,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/vertexbuffer.cpp",
"chars": 5240,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/vertexbuffer.h",
"chars": 2721,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/xrbuddy.cpp",
"chars": 66506,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/core/xrbuddy.h",
"chars": 5060,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/flycam.cpp",
"chars": 2853,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/flycam.h",
"chars": 804,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/gaussiancloud.cpp",
"chars": 23631,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/gaussiancloud.h",
"chars": 3124,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/magiccarpet.cpp",
"chars": 11463,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/magiccarpet.h",
"chars": 2591,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/maincontext.h",
"chars": 1081,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/ply.cpp",
"chars": 7254,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/ply.h",
"chars": 1206,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/pointcloud.cpp",
"chars": 8089,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/pointcloud.h",
"chars": 1262,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/pointrenderer.cpp",
"chars": 7321,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/pointrenderer.h",
"chars": 1471,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/radix_sort.hpp",
"chars": 35182,
"preview": "/*\n MIT License\n\n Copyright (c) 2021 Lorenzo Rutayisire\n\n Permission is hereby granted, free of charge, to any "
},
{
"path": "src/sdl_main.cpp",
"chars": 4716,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/splatrenderer.cpp",
"chars": 14650,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/splatrenderer.h",
"chars": 1931,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/vrconfig.cpp",
"chars": 2187,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "src/vrconfig.h",
"chars": 540,
"preview": "/*\n Copyright (c) 2024 Anthony J. Thibault\n This software is licensed under the MIT License. See LICENSE for more "
},
{
"path": "tasks.py",
"chars": 1111,
"preview": "from invoke import task\nimport os\nimport shutil\n\n\nRELEASE_NAME = \"splatapult-0.1-x64\"\n\n@task\ndef clean(c):\n c.run(\"rm"
},
{
"path": "vcpkg.json",
"chars": 193,
"preview": "{\n \"name\": \"main\",\n \"version-string\": \"latest\",\n \"dependencies\": [\n \"sdl2\",\n \"glew\",\n \"glm\",\n \"libpng\",\n "
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the hyperlogic/splatapult GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 96 files (525.8 KB), approximately 147.8k tokens, and a symbol index with 227 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.