diff --git a/.github/workflows/ci-emscripten.yml b/.github/workflows/ci-emscripten.yml new file mode 100644 index 0000000..1189204 --- /dev/null +++ b/.github/workflows/ci-emscripten.yml @@ -0,0 +1,70 @@ +name: Publish web app version to gh-pages + +on: + push: + branches: + - "*" + pull_request: + - "*" + paths: + # This action only runs on push when C++ files are changed + - "**.cpp" + - "**.h" + - "**.cmake" + - "**Lists.txt" + - "**-emscripten.yml" + workflow_dispatch: + +env: + CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm_modules + +jobs: + build: + name: Build and deploy + runs-on: macos-12 + + steps: + - name: Install dependencies + run: brew install ninja emscripten + + - uses: actions/checkout@v3 + + - uses: actions/cache@v3 + with: + path: "**/cpm_modules" + key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} + + # Setup caching of build artifacts to reduce total build time (only Linux and MacOS) + - name: ccache + uses: hendrikmuhs/ccache-action@v1.2 + + - name: Create Build Environment + run: cmake -E make_directory ${{github.workspace}}/build + + - name: Configure CMake + run: | + emcmake cmake -B ${{github.workspace}}/build -G Ninja -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + + - name: Build + run: cmake --build ${{github.workspace}}/build --parallel 4 + + - name: Archive build artifacts + uses: actions/upload-artifact@v3 + with: + name: build-artifacts + path: | + ${{github.workspace}}/build + + - name: Copy web app + working-directory: ${{github.workspace}}/build + run: | + mkdir deploy + cp Samplin\ Safari.data Samplin\ Safari.html Samplin\ Safari.js Samplin\ Safari.wasm deploy/ + mv deploy/Samplin\ Safari.html deploy/index.html + + - name: Publish + uses: peaceiris/actions-gh-pages@v3 + with: + personal_token: ${{ secrets.GITHUB_TOKEN }} + publish_branch: gh-pages + publish_dir: ${{github.workspace}}/build/deploy diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml index c3395bb..4a9ba52 100644 --- a/.github/workflows/ci-linux.yml +++ b/.github/workflows/ci-linux.yml @@ -59,9 +59,9 @@ jobs: - name: Build run: cmake --build ${{github.workspace}}/build/${{ matrix.buildtype }} --parallel 4 --config ${{ matrix.buildtype }} - - name: Checking that SamplinSafari runs + - name: Checking that Samplin Safari runs run: | - ${{github.workspace}}/build/${{ matrix.buildtype }}/SamplinSafari --help + ${{github.workspace}}/build/${{ matrix.buildtype }}/Samplin\ Safari --help - name: Archive build artifacts uses: actions/upload-artifact@v3 diff --git a/.github/workflows/ci-mac.yml b/.github/workflows/ci-mac.yml index 3771ded..5ded126 100644 --- a/.github/workflows/ci-mac.yml +++ b/.github/workflows/ci-mac.yml @@ -81,17 +81,17 @@ jobs: - name: Build run: cmake --build ${{github.workspace}}/build --parallel 4 --config ${{ matrix.buildtype }} - - name: Checking that SamplinSafari runs + - name: Checking that Samplin Safari runs if: runner.os != 'Windows' run: | - ${{github.workspace}}/build/${{ runner.os == 'macOS' && 'SamplinSafari.app/Contents/MacOS/SamplinSafari' || 'SamplinSafari' }} --help + ${{github.workspace}}/build/${{ runner.os == 'macOS' && 'Samplin\ Safari.app/Contents/MacOS/Samplin\ Safari' || 'Samplin\ Safari' }} --help - name: Creating dmg (macOS) if: runner.os == 'macOS' run: | RESULT="${{github.workspace}}/build/SamplinSafari${{ matrix.config.suffix }}.dmg" test -f $RESULT && rm $RESULT - create-dmg --window-size 500 300 --icon-size 96 --volname "SamplinSafari Installer" --app-drop-link 360 105 --icon SamplinSafari.app 130 105 $RESULT ${{github.workspace}}/build/SamplinSafari.app + create-dmg --window-size 500 300 --icon-size 96 --volname "Samplin Safari Installer" --app-drop-link 360 105 --icon Samplin\ Safari.app 130 105 $RESULT ${{github.workspace}}/build/Samplin\ Safari.app - name: Archive dmg (macOS) if: runner.os == 'macOS' diff --git a/.github/workflows/ci-windows.yml b/.github/workflows/ci-windows.yml index ff74c61..6d14f7d 100644 --- a/.github/workflows/ci-windows.yml +++ b/.github/workflows/ci-windows.yml @@ -49,13 +49,13 @@ jobs: - name: Build run: cmake --build ${{github.workspace}}/build --parallel --config ${{ matrix.buildtype }} --verbose - - name: Checking that SamplinSafari runs + - name: Checking that Samplin Safari runs run: | - ${{github.workspace}}/build/${{ matrix.buildtype }}/SamplinSafari.exe --help + "${{github.workspace}}/build/${{ matrix.buildtype }}/Samplin Safari.exe --help" - name: Archive build artifacts uses: actions/upload-artifact@v3 with: - name: SamplinSafari + name: Samplin Safari path: | - ${{github.workspace}}/build/${{ matrix.buildtype }}/SamplinSafari.exe + "${{github.workspace}}/build/${{ matrix.buildtype }}/Samplin Safari.exe" diff --git a/CMakeLists.txt b/CMakeLists.txt index 94dd946..541e444 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,37 +2,40 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/" "${CMAKE_SOURCE_DIR}/resources") +include(VersionFromGit) + string(TIMESTAMP BUILD_TIME "%Y-%m-%d %H:%M") message(STATUS "Saving build timestamp: ${BUILD_TIME}") +version_from_git( + LOG ON + TIMESTAMP "%Y-%m-%d-%H:%M:%S" + ARG_UTC) + project( SamplinSafari DESCRIPTION "A research tool to visualize and interactively inspect high-dimensional (quasi) Monte Carlo samplers." - # VERSION ${VERSION} - LANGUAGES C CXX) - -set(SAMPLINSAFARI_VERSION "${GIT_DESCRIBE}") + VERSION ${VERSION} + LANGUAGES C CXX + ) if(CMAKE_SIZEOF_VOID_P EQUAL 8) - set(SAMPLINSAFARI_VERSION "${SAMPLINSAFARI_VERSION} (64 bit)") +set(VERSION_LONG "${SEMVER} (64 bit)") elseif(CMAKE_SIZEOF_VOID_P EQUAL 4) - set(SAMPLINSAFARI_VERSION "${SAMPLINSAFARI_VERSION} (32 bit)") +set(VERSION_LONG "${SEMVER} (32 bit)") endif() +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/common.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/src/common.cpp @ONLY) + include(sanitizers) # Set ourselves as the startup project in visual studio. Not available until cmake 3.6, but doesn't break older # versions. set_property(DIRECTORY PROPERTY VS_STARTUP_PROJECT SamplinSafari) -set(USE_IWYU OFF CACHE BOOL "INCLUDE WHAT YOU USE") - include(CheckCXXCompilerFlag) -# prevent glfw from changing the directory on macOS bundles -# SET(GLFW_USE_CHDIR OFF CACHE BOOL "Prevent glfwInit from chdir to Contents/Resources" FORCE) - # ============================================================================ # Set a default build configuration (Release) # ============================================================================ @@ -50,10 +53,6 @@ if(CMAKE_GENERATOR MATCHES "Visual Studio") set_property(GLOBAL PROPERTY USE_FOLDERS ON) endif() -if(APPLE) - set(CMAKE_MACOSX_RPATH ON) -endif() - if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") # Prefer libc++ in conjunction with Clang if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") @@ -126,12 +125,6 @@ endif() # ============================================================================ include(cmake/CPM.cmake) -cpmaddpackage("gh:mitsuba-renderer/tinyformat#43816c694297a27909da0982f91315fb33d60676") -if(tinyformat_ADDED) - add_library(tinyformat INTERFACE IMPORTED) - target_include_directories(tinyformat INTERFACE "${tinyformat_SOURCE_DIR}") -endif() - cpmaddpackage("gh:wjakob/pcg32#70099eadb86d3999c38cf69d2c55f8adc1f7fe34") if(pcg32_ADDED) add_library(pcg32 INTERFACE IMPORTED) @@ -143,29 +136,28 @@ if(galois_ADDED) target_include_directories(galois++ INTERFACE "${galois_SOURCE_DIR}/include") endif() -set(NANOGUI_BACKEND "OpenGL" CACHE BOOL " " FORCE) +cpmaddpackage("gh:sgorsten/linalg@2.2") +if(linalg_ADDED) + add_library(linalg INTERFACE IMPORTED) + target_include_directories(linalg INTERFACE "${linalg_SOURCE_DIR}") +endif() + +cpmaddpackage("gh:fmtlib/fmt#7.1.3") + +cpmaddpackage("gh:samhocevar/portable-file-dialogs#7f852d88a480020d7f91957cbcefe514fc95000c") +if(portable_file_dialogs_ADDED) + add_library(portable_file_dialogs INTERFACE IMPORTED) + target_include_directories(portable_file_dialogs INTERFACE "${portable_file_dialogs_SOURCE_DIR}") +endif() + cpmaddpackage( - NAME - nanogui - GITHUB_REPOSITORY - Tom94/nanogui-1 - GIT_TAG - cf8baf7c2a02a0e8443379a6b80f6709139f84a0 + NAME hello_imgui + GITHUB_REPOSITORY pthom/hello_imgui + GIT_TAG "c57e60ad898abd79524d073f3801b80d87ce5468" OPTIONS - "NANOGUI_BACKEND OpenGL" - "NANOGUI_BUILD_EXAMPLES OFF" - "NANOGUI_BUILD_SHARED OFF" - "NANOGUI_BUILD_PYTHON OFF" - "NANOGUI_INSTALL OFF" - "EXCLUDE_FROM_ALL YES") -if(nanogui_ADDED) - add_library(nanogui::nanogui INTERFACE IMPORTED) - target_include_directories( - nanogui::nanogui SYSTEM INTERFACE ${nanogui_SOURCE_DIR}/ext/glfw/include ${nanogui_SOURCE_DIR}/ext/nanovg/src - ${nanogui_SOURCE_DIR}/include ${NANOGUI_EXTRA_INCS}) - target_link_libraries(nanogui::nanogui INTERFACE "nanogui") - target_compile_definitions(nanogui::nanogui INTERFACE "${NANOGUI_BACKEND_DEFS}") -endif() + "HELLOIMGUI_WITH_GLFW ON" + "HELLOIMGUI_WITH_TEST_ENGINE OFF" + ) # ============================================================================ @@ -211,74 +203,66 @@ add_library(samplerlib OBJECT STATIC include/sampler/Random.h include/sampler/RandomPermutation.h include/sampler/Sobol.h - src/Misc.cpp - src/Halton.cpp - src/Jittered.cpp - src/LP.cpp - src/MultiJittered.cpp - src/NRooks.cpp - src/OA.cpp - src/OAAddelmanKempthorne.cpp - src/OABose.cpp - src/OABoseBush.cpp - src/OABush.cpp - src/OACMJND.cpp - src/SOA.cpp - src/Random.cpp - src/Sobol.cpp - src/SobolMatrices.cpp) + src/sampler/Misc.cpp + src/sampler/Halton.cpp + src/sampler/Jittered.cpp + src/sampler/LP.cpp + src/sampler/MultiJittered.cpp + src/sampler/NRooks.cpp + src/sampler/OA.cpp + src/sampler/OAAddelmanKempthorne.cpp + src/sampler/OABose.cpp + src/sampler/OABoseBush.cpp + src/sampler/OABush.cpp + src/sampler/OACMJND.cpp + src/sampler/SOA.cpp + src/sampler/Random.cpp + src/sampler/Sobol.cpp + src/sampler/SobolMatrices.cpp) # Link dependencies target_link_libraries( samplerlib - PUBLIC galois++ pcg32 tinyformat -) + PUBLIC galois++ pcg32) -set_target_properties(samplerlib PROPERTIES CXX_STANDARD 17) +set_target_properties(samplerlib PROPERTIES + CXX_STANDARD 17) -# Resource file (icons etc.) -set(EXTRA_SOURCE "") -if (APPLE) - set(EXTRA_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/resources/icon.icns") -elseif(WIN32) - #set(EXTRA_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/resources/icon.rc") -endif() - -# The following lines build the main executable. -add_executable(SamplinSafari MACOSX_BUNDLE - gui/SampleViewer.cpp - gui/SampleViewer.h - gui/main.cpp - gui/Well.cpp - gui/Well.h - ${EXTRA_SOURCE} -) - -add_definitions(${NANOGUI_EXTRA_DEFS}) -target_link_libraries( - SamplinSafari - PRIVATE nanogui::nanogui - tinyformat - samplerlib) +# Now build the Samplin' Safari viewer app +string(TIMESTAMP YEAR "%Y") + +set(output_name "Samplin Safari") +set(HELLO_IMGUI_BUNDLE_IDENTIFIER_URL_PART "com.im.SamplinSafari") +set(HELLO_IMGUI_BUNDLE_IDENTIFIER_NAME_PART ${app_name}) +set(HELLO_IMGUI_ICON_DISPLAY_NAME ${output_name}) +set(HELLO_IMGUI_BUNDLE_NAME ${output_name}) +set(HELLO_IMGUI_BUNDLE_COPYRIGHT "© Wojciech Jarosz, ${YEAR}") +set(HELLO_IMGUI_BUNDLE_EXECUTABLE ${output_name}) +set(HELLO_IMGUI_BUNDLE_VERSION ${VERSION}) +set(HELLO_IMGUI_BUNDLE_SHORT_VERSION ${VERSION}) +set(HELLO_IMGUI_BUNDLE_ICON_FILE icon.icns) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/shell.emscripten.html.in ${CMAKE_CURRENT_SOURCE_DIR}/shell.emscripten.html @ONLY) + +hello_imgui_add_app(SamplinSafari + src/app.cpp + ${CMAKE_CURRENT_BINARY_DIR}/src/common.cpp + src/shader.cpp + src/export_to_file.cpp + ) set_target_properties(SamplinSafari PROPERTIES - OUTPUT_NAME "SamplinSafari" - CXX_STANDARD 17) + OUTPUT_NAME ${output_name} + CXX_STANDARD 17 + ) -if (APPLE) - # Build an application bundle on OSX - set_target_properties(SamplinSafari PROPERTIES - MACOSX_BUNDLE_BUNDLE_NAME "Samplin' Safari" - MACOSX_BUNDLE_BUNDLE_GUI_IDENTIFIER "com.im.SamplinSafari" - MACOSX_BUNDLE_ICON_FILE icon.icns - MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/resources/MacOSXBundleInfo.plist.in - MACOSX_BUNDLE_INFO_STRING "Samplin' Safari" - MACOSX_BUNDLE_COPYRIGHT "© Wojciech Jarosz") - set_source_files_properties(resources/icon.icns PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") -else() - # Insulate from a few types of ABI changes by statically linking against libgcc and libstdc++ - set_target_properties(SamplinSafari PROPERTIES LINK_FLAGS "-static-libgcc") -endif() +target_link_libraries( + SamplinSafari + PRIVATE samplerlib + linalg + fmt::fmt + portable_file_dialogs + ) if (UNIX AND NOT ${U_CMAKE_BUILD_TYPE} MATCHES DEBUG) add_custom_command(TARGET SamplinSafari POST_BUILD COMMAND strip $) @@ -294,5 +278,3 @@ if(CMAKE_GENERATOR STREQUAL "Ninja") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always") endif() endif() - -target_compile_features(samplerlib PRIVATE cxx_std_17) \ No newline at end of file diff --git a/assets/fonts/Roboto/LICENSE.txt b/assets/fonts/Roboto/LICENSE.txt new file mode 100644 index 0000000..75b5248 --- /dev/null +++ b/assets/fonts/Roboto/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/assets/fonts/Roboto/Roboto-Bold.ttf b/assets/fonts/Roboto/Roboto-Bold.ttf new file mode 100644 index 0000000..43da14d Binary files /dev/null and b/assets/fonts/Roboto/Roboto-Bold.ttf differ diff --git a/assets/fonts/Roboto/Roboto-BoldItalic.ttf b/assets/fonts/Roboto/Roboto-BoldItalic.ttf new file mode 100644 index 0000000..bcfdab4 Binary files /dev/null and b/assets/fonts/Roboto/Roboto-BoldItalic.ttf differ diff --git a/assets/fonts/Roboto/Roboto-Regular.ttf b/assets/fonts/Roboto/Roboto-Regular.ttf new file mode 100644 index 0000000..ddf4bfa Binary files /dev/null and b/assets/fonts/Roboto/Roboto-Regular.ttf differ diff --git a/assets/fonts/Roboto/Roboto-RegularItalic.ttf b/assets/fonts/Roboto/Roboto-RegularItalic.ttf new file mode 100644 index 0000000..1b5eaa3 Binary files /dev/null and b/assets/fonts/Roboto/Roboto-RegularItalic.ttf differ diff --git a/assets/fonts/fontawesome-webfont.ttf b/assets/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/assets/fonts/fontawesome-webfont.ttf differ diff --git a/resources/icon-assets/icon_128x128.png b/assets/icons/icon_128x128.png similarity index 100% rename from resources/icon-assets/icon_128x128.png rename to assets/icons/icon_128x128.png diff --git a/resources/icon-assets/icon_128x128@2x.png b/assets/icons/icon_128x128@2x.png similarity index 100% rename from resources/icon-assets/icon_128x128@2x.png rename to assets/icons/icon_128x128@2x.png diff --git a/resources/icon-assets/icon_16x16.png b/assets/icons/icon_16x16.png similarity index 100% rename from resources/icon-assets/icon_16x16.png rename to assets/icons/icon_16x16.png diff --git a/resources/icon-assets/icon_16x16@2x.png b/assets/icons/icon_16x16@2x.png similarity index 100% rename from resources/icon-assets/icon_16x16@2x.png rename to assets/icons/icon_16x16@2x.png diff --git a/resources/icon-assets/icon_256x256.png b/assets/icons/icon_256x256.png similarity index 100% rename from resources/icon-assets/icon_256x256.png rename to assets/icons/icon_256x256.png diff --git a/resources/icon-assets/icon_256x256@2x.png b/assets/icons/icon_256x256@2x.png similarity index 100% rename from resources/icon-assets/icon_256x256@2x.png rename to assets/icons/icon_256x256@2x.png diff --git a/resources/icon-assets/icon_32x32.png b/assets/icons/icon_32x32.png similarity index 100% rename from resources/icon-assets/icon_32x32.png rename to assets/icons/icon_32x32.png diff --git a/resources/icon-assets/icon_32x32@2x.png b/assets/icons/icon_32x32@2x.png similarity index 100% rename from resources/icon-assets/icon_32x32@2x.png rename to assets/icons/icon_32x32@2x.png diff --git a/resources/icon-assets/icon_512x512.png b/assets/icons/icon_512x512.png similarity index 100% rename from resources/icon-assets/icon_512x512.png rename to assets/icons/icon_512x512.png diff --git a/resources/icon-assets/icon_512x512@2x.png b/assets/icons/icon_512x512@2x.png similarity index 100% rename from resources/icon-assets/icon_512x512@2x.png rename to assets/icons/icon_512x512@2x.png diff --git a/assets/shaders/lines.frag b/assets/shaders/lines.frag new file mode 100644 index 0000000..92a36a0 --- /dev/null +++ b/assets/shaders/lines.frag @@ -0,0 +1,7 @@ +#version 100 +precision mediump float; +uniform float alpha; +void main() +{ + gl_FragColor = vec4(vec3(1.0), alpha); +} diff --git a/assets/shaders/lines.vert b/assets/shaders/lines.vert new file mode 100644 index 0000000..80b9ed0 --- /dev/null +++ b/assets/shaders/lines.vert @@ -0,0 +1,8 @@ +#version 100 +precision mediump float; +uniform mat4 mvp; +attribute vec3 position; +void main() +{ + gl_Position = mvp * vec4(position, 1.0); +} diff --git a/assets/shaders/points.frag b/assets/shaders/points.frag new file mode 100644 index 0000000..424ac03 --- /dev/null +++ b/assets/shaders/points.frag @@ -0,0 +1,17 @@ +#version 100 +precision mediump float; +uniform vec3 color; +uniform float point_size; +void main() +{ + float alpha = 1.0; + if (point_size > 3.0) + { + vec2 circCoord = 2.0 * gl_PointCoord - 1.0; + float radius2 = dot(circCoord, circCoord); + if (radius2 > 1.0) + discard; + // alpha = 1.0 - smoothstep(1.0 - 2.0 / point_size, 1.0, sqrt(radius2)); + } + gl_FragColor = vec4(color * alpha, alpha); +} diff --git a/assets/shaders/points.vert b/assets/shaders/points.vert new file mode 100644 index 0000000..799297e --- /dev/null +++ b/assets/shaders/points.vert @@ -0,0 +1,10 @@ +#version 100 +precision mediump float; +uniform mat4 mvp; +uniform float point_size; +attribute vec3 position; +void main() +{ + gl_Position = mvp * vec4(position, 1.0); + gl_PointSize = point_size; +} \ No newline at end of file diff --git a/cmake/VersionFromGit.cmake b/cmake/VersionFromGit.cmake index 45e6686..f937dc0 100644 --- a/cmake/VersionFromGit.cmake +++ b/cmake/VersionFromGit.cmake @@ -62,9 +62,10 @@ function( version_from_git ) ERROR_STRIP_TRAILING_WHITESPACE ) if( NOT git_result EQUAL 0 ) - message( FATAL_ERROR + message( WARNING "[VersionFromGit] Failed to execute Git: ${git_error}" ) + set( git_describe "" ) endif() # Get Git tag @@ -78,9 +79,10 @@ function( version_from_git ) ERROR_STRIP_TRAILING_WHITESPACE ) if( NOT git_result EQUAL 0 ) - message( FATAL_ERROR + message( WARNING "[VersionFromGit] Failed to execute Git: ${git_error}" ) + set( git_tag "" ) endif() if( git_tag MATCHES "^v(0|[1-9][0-9]*)[.](0|[1-9][0-9]*)[.](0|[1-9][0-9]*)(-[.0-9A-Za-z-]+)?([+][.0-9A-Za-z-]+)?$" ) @@ -90,9 +92,14 @@ function( version_from_git ) set( identifiers "${CMAKE_MATCH_4}" ) set( metadata "${CMAKE_MATCH_5}" ) else() - message( FATAL_ERROR + message( WARNING "[VersionFromGit] Git tag isn't valid semantic version: [${git_tag}]" ) + set( version_major "0" ) + set( version_minor "0" ) + set( version_patch "0" ) + set( identifiers "" ) + set( metadata "" ) endif() if( "${git_tag}" STREQUAL "${git_describe}" ) @@ -130,7 +137,7 @@ function( version_from_git ) # Timestamp if( DEFINED ARG_TIMESTAMP ) - string( TIMESTAMP timestamp "${ARG_TIMESTAMP}" ${ARG_UTC} ) + string( TIMESTAMP timestamp "${ARG_TIMESTAMP}" UTC ) list( APPEND metadata "${timestamp}" ) endif( DEFINED ARG_TIMESTAMP ) diff --git a/gui/SampleViewer.cpp b/gui/SampleViewer.cpp deleted file mode 100644 index 9a9a443..0000000 --- a/gui/SampleViewer.cpp +++ /dev/null @@ -1,1717 +0,0 @@ -/** \file SampleViewer.cpp - \author Wojciech Jarosz -*/ -#define _USE_MATH_DEFINES -#include "SampleViewer.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Well.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace -{ - -string pointVertexShader = - R"( - #version 330 - uniform mat4 mvp; - uniform float pointSize; - in vec3 position; - void main() - { - gl_Position = mvp * vec4(position, 1.0); - gl_PointSize = pointSize; - } -)"; - -string pointFragmentShader = - R"( - #version 330 - uniform vec3 color; - uniform float pointSize; - out vec4 out_color; - void main() - { - float alpha = 1.0; - if (pointSize > 3) - { - vec2 circCoord = 2.0 * gl_PointCoord - 1.0; - float radius2 = dot(circCoord, circCoord); - if (radius2 > 1.0) - discard; - alpha = 1.0 - smoothstep(1.0 - 2.0/pointSize, 1.0, sqrt(radius2)); - } - out_color = vec4(color, alpha); - } -)"; - -string gridVertexShader = - R"( - #version 330 - uniform mat4 mvp; - in vec3 position; - void main() - { - gl_Position = mvp * vec4(position, 1.0); - } -)"; - -string gridFragmentShader = - R"( - #version 330 - out vec4 out_color; - uniform float alpha; - void main() - { - out_color = vec4(vec3(1.0), alpha); - } -)"; - -void computeCameraMatrices(Matrix4f &model, Matrix4f &lookat, Matrix4f &view, Matrix4f &proj, const CameraParameters &c, - float window_aspect); -void drawEPSGrid(const Matrix4f &mvp, int grid_res, ofstream &file); -void writeEPSPoints(const Array2d &points, int start, int count, const Matrix4f &mvp, ofstream &file, int dim_x, - int dim_y, int dim_z); - -float mapCount2Slider(int count) -{ - return (log((float)count) / log(2.f)) / 20; -} - -int mapSlider2Count(float sliderValue) -{ - return (int)pow(2.f, round(80 * sliderValue) / 4.0f); -} - -// float mapRadius2Slider(float radius) -// { -// return sqrt((radius - 1)/32.0f); -// } -float map_slider_to_radius(float sliderValue) -{ - return sliderValue * sliderValue * 32.0f + 1.0f; -} - -void layout_2d_matrix(Matrix4f &position, int numDims, int dim_x, int dim_y) -{ - float cellSpacing = 1.f / (numDims - 1); - float cellSize = 0.96f / (numDims - 1); - - float xOffset = dim_x - (numDims - 2) / 2.0f; - float yOffset = -(dim_y - 1 - (numDims - 2) / 2.0f); - - position = Matrix4f::translate(Vector3f(cellSpacing * xOffset, cellSpacing * yOffset, 1)) * - Matrix4f::scale(Vector3f(cellSize, cellSize, 1)); -} - -} // namespace - -SampleViewer::SampleViewer() : Screen(Vector2i(800, 600), "Samplin' Safari") -{ - m_time_strings.resize(3); - - m_samplers.resize(NUM_POINT_TYPES, nullptr); - m_samplers[RANDOM] = new Random(m_num_dimensions); - m_samplers[JITTERED] = new Jittered(1, 1, 1.0f); - // m_samplers[MULTI_JITTERED] = new MultiJittered(1, 1, false, 0.0f); - m_samplers[MULTI_JITTERED_IP] = new MultiJitteredInPlace(1, 1, false, 0.0f); - // m_samplers[CORRELATED_MULTI_JITTERED] = new CorrelatedMultiJittered(1, 1, false, 0.0f); - m_samplers[CORRELATED_MULTI_JITTERED_IP] = new CorrelatedMultiJitteredInPlace(1, 1, m_num_dimensions, false, 0.0f); - m_samplers[CMJND] = new CMJNDInPlace(1, 3, MJ_STYLE, false, 0.8); - m_samplers[BOSE_OA_IP] = new BoseOAInPlace(1, MJ_STYLE, false, 0.8f, m_num_dimensions); - m_samplers[BOSE_GALOIS_OA_IP] = new BoseGaloisOAInPlace(1, MJ_STYLE, false, 0.8f, m_num_dimensions); - m_samplers[BUSH_OA_IP] = new BushOAInPlace(1, 3, MJ_STYLE, false, 0.8f, m_num_dimensions); - m_samplers[BUSH_GALOIS_OA_IP] = new BushGaloisOAInPlace(1, 3, MJ_STYLE, false, 0.8f, m_num_dimensions); - m_samplers[ADDEL_KEMP_OA_IP] = new AddelmanKempthorneOAInPlace(2, MJ_STYLE, false, 0.8f, m_num_dimensions); - m_samplers[BOSE_BUSH_OA] = new BoseBushOA(2, MJ_STYLE, false, 0.8f, m_num_dimensions); - m_samplers[BOSE_BUSH_OA_IP] = new BoseBushOAInPlace(2, MJ_STYLE, false, 0.8f, m_num_dimensions); - m_samplers[N_ROOKS_IP] = new NRooksInPlace(m_num_dimensions, 1, false, 1.0f); - m_samplers[SOBOL] = new Sobol(m_num_dimensions); - m_samplers[ZERO_TWO] = new ZeroTwo(1, m_num_dimensions, false); - m_samplers[ZERO_TWO_SHUFFLED] = new ZeroTwo(1, m_num_dimensions, true); - m_samplers[HALTON] = new Halton(m_num_dimensions); - m_samplers[HALTON_ZAREMBA] = new HaltonZaremba(m_num_dimensions); - m_samplers[HAMMERSLEY] = new Hammersley(m_num_dimensions, 1); - m_samplers[HAMMERSLEY_ZAREMBA] = new Hammersley(m_num_dimensions, 1); - m_samplers[LARCHER_PILLICHSHAMMER] = new LarcherPillichshammerGK(3, 1, false); - - m_camera[CAMERA_XY].arcball.setState({0, 0, 0, 1}); - m_camera[CAMERA_XY].persp_factor = 0.0f; - m_camera[CAMERA_XY].camera_type = CAMERA_XY; - - m_camera[CAMERA_YZ].arcball.setState(rotation_quat({0.f, -1.f, 0.f}, 0.5f * M_PI)); - m_camera[CAMERA_YZ].persp_factor = 0.0f; - m_camera[CAMERA_YZ].camera_type = CAMERA_YZ; - - m_camera[CAMERA_XZ].arcball.setState(rotation_quat({1.f, 0.f, 0.f}, 0.5f * M_PI)); - m_camera[CAMERA_XZ].persp_factor = 0.0f; - m_camera[CAMERA_XZ].camera_type = CAMERA_XZ; - - m_camera[CAMERA_2D] = m_camera[CAMERA_XY]; - m_camera[CAMERA_CURRENT] = m_camera[CAMERA_XY]; - m_camera[CAMERA_NEXT] = m_camera[CAMERA_XY]; - - // initialize the GUI elements - initialize_GUI(); - - // - // OpenGL setup - // - - // glEnable(GL_PROGRAM_POINT_SIZE); - glEnable(GL_LINE_SMOOTH); - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); - - m_render_pass = new RenderPass({this}); - m_render_pass->set_clear_color(0, Color(0.f, 0.f, 0.f, 1.f)); - - m_render_pass->set_depth_test(RenderPass::DepthTest::Less, true); - - m_point_shader = new Shader(m_render_pass, "Point shader", pointVertexShader, pointFragmentShader, - Shader::BlendMode::AlphaBlend); - - m_grid_shader = - new Shader(m_render_pass, "Grid shader", gridVertexShader, gridFragmentShader, Shader::BlendMode::AlphaBlend); - - m_point_2d_shader = new Shader(m_render_pass, "Point shader 2D", pointVertexShader, pointFragmentShader, - Shader::BlendMode::AlphaBlend); - - update_GPU_points(); - update_GPU_grids(); - set_visible(true); - resize_event(m_size); - - // Nanogui will redraw the screen for key/mouse events, but we need to manually - // invoke redraw for things like gui animations. do this in a separate thread - m_gui_refresh_thread = std::thread( - [this] - { - std::chrono::microseconds idle_quantum = std::chrono::microseconds(1000 * 1000 / 15); - std::chrono::microseconds anim_quantum = std::chrono::microseconds(1000 * 1000 / 120); - while (true) - { - if (m_join_requested) - return; - std::this_thread::sleep_for(m_gui_refresh ? anim_quantum : idle_quantum); - this->m_redraw = false; - this->redraw(); - } - }); -} - -SampleViewer::~SampleViewer() -{ - m_join_requested = true; - - for (size_t i = 0; i < m_samplers.size(); ++i) - delete m_samplers[i]; - m_gui_refresh_thread.join(); -} - -void SampleViewer::initialize_GUI() -{ - set_resize_callback( - [&](Vector2i size) - { - for (int i = 0; i < NUM_CAMERA_TYPES; ++i) - m_camera[i].arcball.setSize(size); - - if (m_help_dialog->visible()) - m_help_dialog->center(); - - perform_layout(); - }); - - auto thm = new Theme(m_nvg_context); - thm->m_standard_font_size = 14; - thm->m_button_font_size = 14; - thm->m_text_box_font_size = 14; - thm->m_window_corner_radius = 4; - thm->m_window_fill_unfocused = Color(60, 250); - thm->m_window_fill_focused = Color(65, 250); - thm->m_button_corner_radius = 2; - set_theme(thm); - auto createCollapsableSection = [this](Widget *parent, const string &title, bool collapsed = false) - { - auto group = new Widget(parent); - group->set_layout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 0, 0)); - - auto btn = - new Button(group, title, !collapsed ? m_theme->m_text_box_down_icon : m_theme->m_popup_chevron_left_icon); - btn->set_flags(Button::ToggleButton); - btn->set_pushed(!collapsed); - btn->set_fixed_size(Vector2i(180, 22)); - btn->set_font_size(16); - btn->set_icon_position(Button::IconPosition::Right); - - auto panel = new Well(group); - panel->set_layout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 5, 5)); - panel->set_visible(!collapsed); - - btn->set_change_callback( - [this, btn, panel](bool value) - { - btn->set_icon(value ? m_theme->m_text_box_down_icon : m_theme->m_popup_chevron_left_icon); - panel->set_visible(value); - this->perform_layout(this->m_nvg_context); - }); - return panel; - }; - - // create parameters dialog - { - m_parameters_dialog = new Window(this, "Sample parameters"); - m_parameters_dialog->set_theme(thm); - m_parameters_dialog->set_position(Vector2i(15, 15)); - m_parameters_dialog->set_layout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 5, 5)); - - m_help_button = new Button(m_parameters_dialog->button_panel(), "", FA_QUESTION); - m_help_button->set_flags(Button::ToggleButton); - m_help_button->set_change_callback( - [&](bool b) - { - m_help_dialog->set_visible(b); - if (b) - { - m_help_dialog->center(); - m_help_dialog->request_focus(); - } - }); - - { - auto section = createCollapsableSection(m_parameters_dialog, "Point set"); - - vector pointTypeNames(NUM_POINT_TYPES); - for (int i = 0; i < NUM_POINT_TYPES; ++i) - pointTypeNames[i] = m_samplers[i]->name(); - m_point_type_box = new ComboBox(section, pointTypeNames); - m_point_type_box->set_theme(thm); - m_point_type_box->set_fixed_height(20); - m_point_type_box->set_fixed_width(170); - m_point_type_box->set_callback( - [&](int) - { - Sampler *sampler = m_samplers[m_point_type_box->selected_index()]; - OrthogonalArray *oa = dynamic_cast(sampler); - m_strength_label->set_visible(oa); - m_strength_box->set_visible(oa); - m_strength_box->set_enabled(oa); - m_offset_type_label->set_visible(oa); - m_offset_type_box->set_visible(oa); - m_offset_type_box->set_enabled(oa); - m_jitter_slider->set_value(sampler->jitter()); - if (oa) - { - m_offset_type_box->set_selected_index(oa->offsetType()); - m_strength_box->set_value(oa->strength()); - } - this->perform_layout(); - update_GPU_points(); - update_GPU_grids(); - }); - m_point_type_box->set_tooltip("Key: Up/Down"); - - auto panel = new Widget(section); - panel->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 0)); - - auto l = new Label(panel, "Num points", "sans"); - l->set_fixed_width(100); - m_point_count_box = new IntBox(panel); - m_point_count_box->set_editable(true); - m_point_count_box->set_fixed_size(Vector2i(70, 20)); - m_point_count_box->set_alignment(TextBox::Alignment::Right); - m_point_count_box->set_spinnable(true); - m_point_count_box->set_callback( - [&](int v) - { - m_target_point_count = v; - update_GPU_points(); - update_GPU_grids(); - }); - - // Add a slider and set defaults - m_point_count_slider = new Slider(section); - m_point_count_slider->set_callback( - [&](float v) - { - m_target_point_count = mapSlider2Count(v); - update_GPU_points(); - update_GPU_grids(); - }); - m_point_count_slider->set_value(6.f / 15.f); - // m_point_count_slider->set_fixed_width(100); - m_point_count_slider->set_tooltip("Key: Left/Right"); - m_target_point_count = m_point_count = mapSlider2Count(m_point_count_slider->value()); - - panel = new Widget(section); - panel->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 0)); - - l = new Label(panel, "Dimensions", "sans"); - l->set_fixed_width(100); - m_dimension_box = new IntBox(panel); - m_dimension_box->set_editable(true); - m_dimension_box->set_fixed_size(Vector2i(70, 20)); - m_dimension_box->set_value(3); - m_dimension_box->set_alignment(TextBox::Alignment::Right); - m_dimension_box->set_default_value("3"); - m_dimension_box->set_format("[2-9]*"); - m_dimension_box->set_spinnable(true); - m_dimension_box->set_min_value(2); - // m_dimension_box->set_max_value(32); - m_dimension_box->set_value_increment(1); - m_dimension_box->set_tooltip("Key: D/d"); - m_dimension_box->set_callback( - [&](int v) - { - m_num_dimensions = v; - m_subset_axis->set_max_value(v - 1); - m_x_dimension->set_max_value(v - 1); - m_y_dimension->set_max_value(v - 1); - m_z_dimension->set_max_value(v - 1); - - update_GPU_points(); - update_GPU_grids(); - }); - - m_randomize_checkbox = new CheckBox(section, "Randomize"); - m_randomize_checkbox->set_checked(true); - m_randomize_checkbox->set_callback([&](bool) { update_GPU_points(); }); - - panel = new Widget(section); - panel->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 0)); - - l = new Label(panel, "Jitter", "sans"); - l->set_fixed_width(100); - // Add a slider for the point radius - m_jitter_slider = new Slider(panel); - m_jitter_slider->set_value(0.5f); - m_jitter_slider->set_fixed_width(70); - m_jitter_slider->set_callback( - [&](float j) - { - m_samplers[m_point_type_box->selected_index()]->setJitter(j); - update_GPU_points(); - update_GPU_grids(); - }); - - panel = new Widget(section); - panel->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 0)); - - m_strength_label = new Label(panel, "Strength", "sans"); - m_strength_label->set_fixed_width(100); - m_strength_label->set_visible(false); - m_strength_box = new IntBox(panel); - m_strength_box->set_editable(true); - m_strength_box->set_enabled(false); - m_strength_box->set_visible(false); - m_strength_box->set_fixed_size(Vector2i(70, 20)); - m_strength_box->set_value(2); - m_strength_box->set_alignment(TextBox::Alignment::Right); - m_strength_box->set_default_value("2"); - m_strength_box->set_format("[2-9]*"); - m_strength_box->set_spinnable(true); - m_strength_box->set_min_value(2); - // m_strength_box->set_max_value(32); - m_strength_box->set_value_increment(1); - // m_strength_box->set_tooltip("Key: D/d"); - m_strength_box->set_callback( - [&](int v) - { - if (OrthogonalArray *oa = - dynamic_cast(m_samplers[m_point_type_box->selected_index()])) - { - m_strength_box->set_value(oa->setStrength(v)); - } - update_GPU_points(); - update_GPU_grids(); - }); - - panel = new Widget(section); - panel->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 0)); - - m_offset_type_label = new Label(panel, "Offset type", "sans"); - m_offset_type_label->set_fixed_width(100); - m_offset_type_label->set_visible(false); - OrthogonalArray *oa = dynamic_cast(m_samplers[BOSE_OA_IP]); - m_offset_type_box = new ComboBox(panel, oa->offsetTypeNames()); - m_offset_type_box->set_visible(false); - m_offset_type_box->set_theme(thm); - m_offset_type_box->set_fixed_size(Vector2i(70, 20)); - m_offset_type_box->set_callback( - [&](int ot) - { - if (OrthogonalArray *oa = - dynamic_cast(m_samplers[m_point_type_box->selected_index()])) - { - oa->setOffsetType(ot); - } - update_GPU_points(); - update_GPU_grids(); - }); - m_offset_type_box->set_tooltip("Key: Shift+Up/Down"); - - auto epsBtn = new Button(section, "Save as EPS", FA_SAVE); - epsBtn->set_fixed_height(22); - epsBtn->set_callback( - [&] - { - try - { - string basename = file_dialog({{"", "Base filename"}}, true); - if (!basename.size()) - return; - - ofstream fileAll(basename + "_all2D.eps"); - draw_contents_2D_EPS(fileAll); - - ofstream fileXYZ(basename + "_012.eps"); - draw_contents_EPS(fileXYZ, CAMERA_CURRENT, m_x_dimension->value(), m_y_dimension->value(), - m_z_dimension->value()); - - for (int y = 0; y < m_num_dimensions; ++y) - for (int x = 0; x < y; ++x) - { - ofstream fileXY(tfm::format("%s_%d%d.eps", basename, x, y)); - draw_contents_EPS(fileXY, CAMERA_XY, x, y, 2); - } - } - catch (const std::exception &e) - { - new MessageDialog(this, MessageDialog::Type::Warning, "Error", - "An error occurred: " + std::string(e.what())); - } - }); - } - - { - auto section = createCollapsableSection(m_parameters_dialog, "Camera/view:"); - - auto panel = new Widget(section); - panel->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 2)); - - m_view_btns[CAMERA_XY] = new Button(panel, "XY"); - m_view_btns[CAMERA_XY]->set_flags(Button::RadioButton); - m_view_btns[CAMERA_XY]->set_pushed(true); - m_view_btns[CAMERA_XY]->set_callback([&] { set_view(CAMERA_XY); }); - m_view_btns[CAMERA_XY]->set_tooltip("Key: 1"); - m_view_btns[CAMERA_XY]->set_fixed_size(Vector2i(30, 22)); - - m_view_btns[CAMERA_YZ] = new Button(panel, "YZ"); - m_view_btns[CAMERA_YZ]->set_flags(Button::RadioButton); - m_view_btns[CAMERA_YZ]->set_callback([&] { set_view(CAMERA_YZ); }); - m_view_btns[CAMERA_YZ]->set_tooltip("Key: 2"); - m_view_btns[CAMERA_YZ]->set_fixed_size(Vector2i(30, 22)); - - m_view_btns[CAMERA_XZ] = new Button(panel, "XZ"); - m_view_btns[CAMERA_XZ]->set_flags(Button::RadioButton); - m_view_btns[CAMERA_XZ]->set_callback([&] { set_view(CAMERA_XZ); }); - m_view_btns[CAMERA_XZ]->set_tooltip("Key: 3"); - m_view_btns[CAMERA_XZ]->set_fixed_size(Vector2i(30, 22)); - - m_view_btns[CAMERA_CURRENT] = new Button(panel, "XYZ"); - m_view_btns[CAMERA_CURRENT]->set_flags(Button::RadioButton); - m_view_btns[CAMERA_CURRENT]->set_callback([&] { set_view(CAMERA_CURRENT); }); - m_view_btns[CAMERA_CURRENT]->set_tooltip("Key: 4"); - m_view_btns[CAMERA_CURRENT]->set_fixed_size(Vector2i(35, 22)); - - m_view_btns[CAMERA_2D] = new Button(panel, "2D"); - m_view_btns[CAMERA_2D]->set_flags(Button::RadioButton); - m_view_btns[CAMERA_2D]->set_callback([&] { set_view(CAMERA_2D); }); - m_view_btns[CAMERA_2D]->set_tooltip("Key: 0"); - m_view_btns[CAMERA_2D]->set_fixed_size(Vector2i(30, 22)); - } - - // Display options panel - { - auto displayPanel = createCollapsableSection(m_parameters_dialog, "Display options"); - - auto panel = new Widget(displayPanel); - panel->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 0)); - - new Label(panel, "Radius", "sans"); - // Add a slider for the point radius - m_point_radius_slider = new Slider(panel); - m_point_radius_slider->set_value(0.5f); - m_point_radius_slider->set_fixed_width(115); - m_point_radius_slider->set_callback([&](float v) { draw_contents(); }); - - m_scale_radius_with_points = new Button(panel, "", FA_COMPRESS); - m_scale_radius_with_points->set_flags(Button::ToggleButton); - m_scale_radius_with_points->set_pushed(true); - m_scale_radius_with_points->set_fixed_size(Vector2i(19, 19)); - m_scale_radius_with_points->set_change_callback([this](bool) { draw_contents(); }); - m_scale_radius_with_points->set_tooltip("Scale radius with number of points"); - - m_show_1d_projections = new CheckBox(displayPanel, "1D projections"); - m_show_1d_projections->set_checked(false); - m_show_1d_projections->set_tooltip("Key: P"); - m_show_1d_projections->set_callback([this](bool) { draw_contents(); }); - - m_show_point_nums = new CheckBox(displayPanel, "Point indices"); - m_show_point_nums->set_checked(false); - m_show_point_nums->set_callback([this](bool) { draw_contents(); }); - - m_show_point_coords = new CheckBox(displayPanel, "Point coords"); - m_show_point_coords->set_checked(false); - m_show_point_coords->set_callback([this](bool) { draw_contents(); }); - - m_coarse_grid_checkbox = new CheckBox(displayPanel, "Coarse grid"); - m_coarse_grid_checkbox->set_checked(false); - m_coarse_grid_checkbox->set_tooltip("Key: g"); - m_coarse_grid_checkbox->set_callback([this](bool) { update_GPU_grids(); }); - - m_fine_grid_checkbox = new CheckBox(displayPanel, "Fine grid"); - m_fine_grid_checkbox->set_tooltip("Key: G"); - m_fine_grid_checkbox->set_callback([this](bool) { update_GPU_grids(); }); - - m_bbox_grid_checkbox = new CheckBox(displayPanel, "Bounding box"); - m_bbox_grid_checkbox->set_tooltip("Key: B"); - m_bbox_grid_checkbox->set_callback([this](bool) { update_GPU_grids(); }); - } - - { - auto section = createCollapsableSection(m_parameters_dialog, "Dimension mapping", true); - // new Label(displayPanel, "Dimension mapping:", "sans-bold"); - - auto panel = new Widget(section); - panel->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 5)); - auto createDimMapping = [this, panel](IntBox *&box, const string &label, int dim) - { - auto group = new Widget(panel); - group->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 0)); - - new Label(group, label, "sans"); - box = new IntBox(group); - box->set_editable(true); - box->set_value(dim); - box->set_alignment(TextBox::Alignment::Right); - // box->set_fixed_width(30); - box->set_default_value(to_string(dim)); - box->set_spinnable(true); - box->set_min_value(0); - box->set_max_value(m_num_dimensions - 1); - box->set_value_increment(1); - box->set_callback([&](int v) { update_GPU_points(false); }); - }; - createDimMapping(m_x_dimension, "X:", 0); - createDimMapping(m_y_dimension, "Y:", 1); - createDimMapping(m_z_dimension, "Z:", 2); - } - - // point subset controls - { - auto section = createCollapsableSection(m_parameters_dialog, "Visible subset", true); - - m_subset_by_index = new CheckBox(section, "Subset by point index"); - m_subset_by_index->set_checked(false); - m_subset_by_index->set_callback( - [&](bool b) - { - m_first_draw_point_box->set_enabled(b); - m_point_draw_count_box->set_enabled(b); - m_point_draw_count_slider->set_enabled(b); - if (b) - m_subset_by_coord->set_checked(false); - update_GPU_points(false); - draw_contents(); - }); - - // Create an empty panel with a horizontal layout - auto panel = new Widget(section); - panel->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 0)); - - auto l = new Label(panel, "First point", "sans"); - l->set_fixed_width(100); - m_first_draw_point_box = new IntBox(panel); - m_first_draw_point_box->set_editable(true); - m_first_draw_point_box->set_enabled(m_subset_by_index->checked()); - m_first_draw_point_box->set_fixed_size(Vector2i(70, 20)); - m_first_draw_point_box->set_value(0); - m_first_draw_point_box->set_alignment(TextBox::Alignment::Right); - m_first_draw_point_box->set_default_value("0"); - m_first_draw_point_box->set_spinnable(true); - m_first_draw_point_box->set_min_value(0); - m_first_draw_point_box->set_max_value(m_point_count - 1); - m_first_draw_point_box->set_value_increment(1); - m_first_draw_point_box->set_callback( - [&](int v) - { - m_point_draw_count_box->set_max_value(m_point_count - m_first_draw_point_box->value()); - m_point_draw_count_box->set_value(m_point_draw_count_box->value()); - }); - - panel = new Widget(section); - panel->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 0)); - - l = new Label(panel, "Num points", "sans"); - l->set_fixed_width(100); - m_point_draw_count_box = new IntBox(panel); - m_point_draw_count_box->set_editable(true); - m_point_draw_count_box->set_enabled(m_subset_by_index->checked()); - m_point_draw_count_box->set_fixed_size(Vector2i(70, 20)); - m_point_draw_count_box->set_value(m_point_count - m_first_draw_point_box->value()); - m_point_draw_count_box->set_alignment(TextBox::Alignment::Right); - m_point_draw_count_box->set_default_value("0"); - m_point_draw_count_box->set_spinnable(true); - m_point_draw_count_box->set_min_value(0); - m_point_draw_count_box->set_max_value(m_point_count - m_first_draw_point_box->value()); - m_point_draw_count_box->set_value_increment(1); - - m_point_draw_count_slider = new Slider(section); - m_point_draw_count_slider->set_enabled(m_subset_by_index->checked()); - m_point_draw_count_slider->set_value(1.0f); - m_point_draw_count_slider->set_callback( - [&](float v) - { m_point_draw_count_box->set_value(round((m_point_count - m_first_draw_point_box->value()) * v)); }); - - m_subset_by_coord = new CheckBox(section, "Subset by coordinate"); - m_subset_by_coord->set_checked(false); - m_subset_by_coord->set_callback( - [&](bool b) - { - m_subset_axis->set_enabled(b); - m_num_subset_levels->set_enabled(b); - m_subset_level->set_enabled(b); - if (b) - m_subset_by_index->set_checked(false); - update_GPU_points(false); - draw_contents(); - }); - - panel = new Widget(section); - panel->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 0)); - - l = new Label(panel, "Subset axis", "sans"); - l->set_fixed_width(100); - m_subset_axis = new IntBox(panel); - m_subset_axis->set_editable(true); - m_subset_axis->set_enabled(m_subset_by_coord->checked()); - m_subset_axis->set_fixed_size(Vector2i(70, 20)); - m_subset_axis->set_value(0); - m_subset_axis->set_alignment(TextBox::Alignment::Right); - m_subset_axis->set_default_value("0"); - m_subset_axis->set_spinnable(true); - m_subset_axis->set_min_value(0); - m_subset_axis->set_max_value(m_num_dimensions - 1); - m_subset_axis->set_value_increment(1); - m_subset_axis->set_callback([&](int v) { update_GPU_points(false); }); - - panel = new Widget(section); - panel->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 0)); - - l = new Label(panel, "Num levels", "sans"); - l->set_fixed_width(100); - m_num_subset_levels = new IntBox(panel); - m_num_subset_levels->set_editable(true); - m_num_subset_levels->set_enabled(m_subset_by_coord->checked()); - m_num_subset_levels->set_fixed_size(Vector2i(70, 20)); - m_num_subset_levels->set_value(2); - m_num_subset_levels->set_alignment(TextBox::Alignment::Right); - m_num_subset_levels->set_default_value("2"); - m_num_subset_levels->set_spinnable(true); - m_num_subset_levels->set_min_value(1); - m_num_subset_levels->set_max_value(m_point_count); - m_num_subset_levels->set_value_increment(1); - m_num_subset_levels->set_callback( - [&](int v) - { - m_subset_level->set_max_value(v - 1); - update_GPU_points(false); - }); - - panel = new Widget(section); - panel->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 0)); - - l = new Label(panel, "Level", "sans"); - l->set_fixed_width(100); - m_subset_level = new IntBox(panel); - m_subset_level->set_editable(true); - m_subset_level->set_enabled(m_subset_by_coord->checked()); - m_subset_level->set_fixed_size(Vector2i(70, 20)); - m_subset_level->set_value(0); - m_subset_level->set_alignment(TextBox::Alignment::Right); - m_subset_level->set_default_value("0"); - m_subset_level->set_spinnable(true); - m_subset_level->set_min_value(0); - m_subset_level->set_max_value(m_num_subset_levels->value() - 1); - m_subset_level->set_value_increment(1); - m_subset_level->set_callback([&](int v) { update_GPU_points(false); }); - } - } - - // - // create help dialog - // - { - vector> helpStrings = { - {"h", "Toggle this help panel"}, - {"LMB", "Rotate camera"}, - {"Scroll", "Zoom camera"}, - {"1/2/3", "Axis-aligned orthographic projections"}, - {"4", "Perspective view"}, - {"0", "Grid of all 2D projections"}, - {"Left", "Decrease point count (+Shift: bigger increment)"}, - {"Right", "Increase point count (+Shift: bigger increment)"}, - {"Up/Down", "Cycle through samplers"}, - {"Shift+Up/Down", "Cycle through offset types (for OA samplers)"}, - {"d/D", "Decrease/Increase maximum dimension"}, - {"t/T", "Decrease/Increase the strength (for OA samplers)"}, - {"r/R", "Toggle/re-seed randomization"}, - {"g/G", "Toggle coarse/fine grid"}, - {"P", "Toggle 1D projections"}, - {"Space", "Toggle controls"}, - }; - - m_help_dialog = new Window(this, "Help"); - m_help_dialog->set_theme(thm); - m_help_dialog->set_visible(false); - GridLayout *layout = new GridLayout(Orientation::Horizontal, 2, Alignment::Middle, 15, 5); - layout->set_col_alignment({Alignment::Maximum, Alignment::Fill}); - layout->set_spacing(0, 10); - m_help_dialog->set_layout(layout); - - new Label(m_help_dialog, "key", "sans-bold"); - new Label(m_help_dialog, "Action", "sans-bold"); - - for (auto item : helpStrings) - { - new Label(m_help_dialog, item.first); - new Label(m_help_dialog, item.second); - } - - m_help_dialog->center(); - - Button *button = new Button(m_help_dialog->button_panel(), "", FA_TIMES); - button->set_callback( - [&] - { - m_help_dialog->set_visible(false); - m_help_button->set_pushed(false); - }); - } -} - -void SampleViewer::update_GPU_points(bool regenerate) -{ - // Generate the point positions - if (regenerate) - { - try - { - generate_points(); - } - catch (const std::exception &e) - { - new MessageDialog(this, MessageDialog::Type::Warning, "Error", - "An error occurred: " + std::string(e.what())); - return; - } - } - - // Upload points to GPU - - populate_point_subset(); - - m_point_shader->set_buffer("position", VariableType::Float32, {m_3d_points.size(), 3}, m_3d_points.data()); - - // Upload 2D points to GPU - // create a temporary matrix to store all the 2D projections of the points - // each 2D plot actually needs 3D points, and there are num2DPlots of them - int num2DPlots = m_num_dimensions * (m_num_dimensions - 1) / 2; - vector points2D(num2DPlots * m_subset_count); - int plot_index = 0; - for (int y = 0; y < m_num_dimensions; ++y) - for (int x = 0; x < y; ++x, ++plot_index) - for (int i = 0; i < m_subset_count; ++i) - points2D[plot_index * m_subset_count + i] = - Vector3f{m_subset_points(i, x), m_subset_points(i, y), 0.5f}; - - m_point_2d_shader->set_buffer("position", VariableType::Float32, {points2D.size(), 3}, points2D.data()); - - m_point_count_box->set_value(m_point_count); - m_point_count_slider->set_value(mapCount2Slider(m_point_count)); -} - -void SampleViewer::update_GPU_grids() -{ - vector bboxGrid, coarseGrid, fineGrid; - PointType point_type = (PointType)m_point_type_box->selected_index(); - generate_grid(bboxGrid, 1); - generate_grid(coarseGrid, m_samplers[point_type]->coarseGridRes(m_point_count)); - generate_grid(fineGrid, m_point_count); - m_coarse_line_count = coarseGrid.size(); - m_fine_line_count = fineGrid.size(); - vector positions; - positions.reserve(bboxGrid.size() + coarseGrid.size() + fineGrid.size()); - positions.insert(positions.end(), bboxGrid.begin(), bboxGrid.end()); - positions.insert(positions.end(), coarseGrid.begin(), coarseGrid.end()); - positions.insert(positions.end(), fineGrid.begin(), fineGrid.end()); - m_grid_shader->set_buffer("position", VariableType::Float32, {positions.size(), 3}, positions.data()); -} - -bool SampleViewer::mouse_motion_event(const Vector2i &p, const Vector2i &rel, int button, int modifiers) -{ - if (!Screen::mouse_motion_event(p, rel, button, modifiers)) - { - if (m_camera[CAMERA_NEXT].arcball.motion(p)) - draw_all(); - } - return true; -} - -bool SampleViewer::mouse_button_event(const Vector2i &p, int button, bool down, int modifiers) -{ - if (!Screen::mouse_button_event(p, button, down, modifiers)) - { - if (button == GLFW_MOUSE_BUTTON_1) - { - if (down) - { - m_mouse_down = true; - // on mouse down we start switching to a perspective camera - // and start recording the arcball rotation in CAMERA_NEXT - set_view(CAMERA_CURRENT); - m_camera[CAMERA_NEXT].arcball.button(p, down); - m_camera[CAMERA_NEXT].camera_type = CAMERA_CURRENT; - } - else - { - m_mouse_down = false; - m_camera[CAMERA_NEXT].arcball.button(p, down); - // since the time between mouse down and up could be shorter - // than the animation duration, we override the previous - // camera's arcball on mouse up to complete the animation - m_camera[CAMERA_PREVIOUS].arcball = m_camera[CAMERA_NEXT].arcball; - m_camera[CAMERA_PREVIOUS].camera_type = m_camera[CAMERA_NEXT].camera_type = CAMERA_CURRENT; - } - } - } - return true; -} - -bool SampleViewer::scroll_event(const Vector2i &p, const Vector2f &rel) -{ - if (!Screen::scroll_event(p, rel)) - { - m_camera[CAMERA_NEXT].zoom = max(0.001, m_camera[CAMERA_NEXT].zoom * pow(1.1, rel.y())); - draw_all(); - } - return true; -} - -void SampleViewer::draw_points(const Matrix4f &mvp, const Vector3f &color, bool show_point_nums, bool show_point_coords) -{ - // Render the point set - m_point_shader->set_uniform("mvp", mvp); - float radius = map_slider_to_radius(m_point_radius_slider->value()); - if (m_scale_radius_with_points->pushed()) - radius *= 64.0f / std::sqrt(m_point_count); - m_point_shader->set_uniform("pointSize", radius); - m_point_shader->set_uniform("color", color); - - int start = 0, count = m_point_count; - if (m_subset_by_coord->checked()) - { - count = min(count, m_subset_count); - } - else if (m_subset_by_index->checked()) - { - start = m_first_draw_point_box->value(); - count = m_point_draw_count_box->value(); - } - - m_point_shader->begin(); - m_point_shader->draw_array(Shader::PrimitiveType::Point, start, count); - m_point_shader->end(); - - if (!show_point_nums && !show_point_coords) - return; - - for (int p = start; p < start + count; ++p) - { - Vector3f point = m_3d_points[p]; - - Vector4f text_pos = mvp * Vector4f(point.x(), point.y(), point.z(), 1.0f); - Vector2f text_2d_pos((text_pos.x() / text_pos.w() + 1) / 2, (text_pos.y() / text_pos.w() + 1) / 2); - if (show_point_nums) - draw_text(Vector2i((text_2d_pos.x()) * m_size.x(), (1.f - text_2d_pos.y()) * m_size.y() - radius / 4), - tfm::format("%d", p), Color(1.0f, 1.0f, 1.0f, 0.75f), 12, 0, NVG_ALIGN_CENTER | NVG_ALIGN_BOTTOM); - if (show_point_coords) - draw_text(Vector2i((text_2d_pos.x()) * m_size.x(), (1.f - text_2d_pos.y()) * m_size.y() + radius / 4), - tfm::format("(%0.2f, %0.2f, %0.2f)", point.x() + 0.5, point.y() + 0.5, point.z() + 0.5), - Color(1.0f, 1.0f, 1.0f, 0.75f), 11, 0, NVG_ALIGN_CENTER | NVG_ALIGN_TOP); - } -} - -void SampleViewer::draw_grid(const Matrix4f &mvp, float alpha, uint32_t offset, uint32_t count) -{ - m_grid_shader->set_uniform("alpha", alpha); - - for (int axis = CAMERA_XY; axis <= CAMERA_XZ; ++axis) - { - if (m_camera[CAMERA_CURRENT].camera_type == axis || m_camera[CAMERA_CURRENT].camera_type == CAMERA_CURRENT) - { - m_grid_shader->set_uniform("mvp", Matrix4f(mvp * m_camera[axis].arcball.matrix())); - m_grid_shader->begin(); - m_grid_shader->draw_array(Shader::PrimitiveType::Line, offset, count); - m_grid_shader->end(); - } - } -} - -void SampleViewer::draw_2D_points_and_grid(const Matrix4f &mvp, int dim_x, int dim_y, int plot_index) -{ - Matrix4f pos; - layout_2d_matrix(pos, m_num_dimensions, dim_x, dim_y); - - // m_render_pass->set_depth_test(RenderPass::DepthTest::Less, true); - // Render the point set - m_point_2d_shader->set_uniform("mvp", Matrix4f(mvp * pos)); - float radius = map_slider_to_radius(m_point_radius_slider->value()) / (m_num_dimensions - 1); - if (m_scale_radius_with_points->pushed()) - radius *= 64.0f / std::sqrt(m_point_count); - m_point_2d_shader->set_uniform("pointSize", radius); - m_point_2d_shader->set_uniform("color", Vector3f(0.9f, 0.55f, 0.1f)); - - int start = 0, count = m_point_count; - if (m_subset_by_coord->checked()) - { - count = min(count, m_subset_count); - } - else if (m_subset_by_index->checked()) - { - start = m_first_draw_point_box->value(); - count = m_point_draw_count_box->value(); - } - - m_point_2d_shader->begin(); - m_point_2d_shader->draw_array(Shader::PrimitiveType::Point, m_subset_count * plot_index + start, count); - m_point_2d_shader->end(); - - // m_render_pass->set_depth_test(RenderPass::DepthTest::Less, true); - if (m_bbox_grid_checkbox->checked()) - { - m_grid_shader->set_uniform("alpha", 1.0f); - m_grid_shader->set_uniform("mvp", Matrix4f(mvp * pos * m_camera[CAMERA_2D].arcball.matrix())); - m_grid_shader->begin(); - m_grid_shader->draw_array(Shader::PrimitiveType::Line, 0, 8); - m_grid_shader->end(); - } - if (m_coarse_grid_checkbox->checked()) - { - m_grid_shader->set_uniform("alpha", 0.6f); - m_grid_shader->set_uniform("mvp", Matrix4f(mvp * pos * m_camera[CAMERA_2D].arcball.matrix())); - m_grid_shader->begin(); - m_grid_shader->draw_array(Shader::PrimitiveType::Line, 8, m_coarse_line_count); - m_grid_shader->end(); - } - if (m_fine_grid_checkbox->checked()) - { - m_grid_shader->set_uniform("alpha", 0.2f); - m_grid_shader->set_uniform("mvp", Matrix4f(mvp * pos * m_camera[CAMERA_2D].arcball.matrix())); - m_grid_shader->begin(); - m_grid_shader->draw_array(Shader::PrimitiveType::Line, 8 + m_coarse_line_count, m_fine_line_count); - m_grid_shader->end(); - } -} - -void SampleViewer::draw_contents() -{ - // update/move the camera - update_current_camera(); - - Matrix4f model, lookat, view, proj, mvp; - computeCameraMatrices(model, lookat, view, proj, m_camera[CAMERA_CURRENT], float(m_size.x()) / m_size.y()); - mvp = proj * lookat * view * model; - - m_render_pass->resize(framebuffer_size()); - m_render_pass->begin(); - - if (m_view_btns[CAMERA_2D]->pushed()) - { - Matrix4f pos; - - for (int i = 0; i < m_num_dimensions - 1; ++i) - { - layout_2d_matrix(pos, m_num_dimensions, i, m_num_dimensions - 1); - Vector4f text_pos = mvp * pos * Vector4f(0.f, -0.5f, 0.0f, 1.0f); - Vector2f text_2d_pos((text_pos.x() / text_pos.w() + 1) / 2, (text_pos.y() / text_pos.w() + 1) / 2); - draw_text(Vector2i((text_2d_pos.x()) * m_size.x(), (1.f - text_2d_pos.y()) * m_size.y() + 16), to_string(i), - Color(1.0f, 1.0f, 1.0f, 0.75f), 16, 0, NVG_ALIGN_CENTER | NVG_ALIGN_BOTTOM); - - layout_2d_matrix(pos, m_num_dimensions, 0, i + 1); - text_pos = mvp * pos * Vector4f(-0.5f, 0.f, 0.0f, 1.0f); - text_2d_pos = Vector2f((text_pos.x() / text_pos.w() + 1) / 2, (text_pos.y() / text_pos.w() + 1) / 2); - draw_text(Vector2i((text_2d_pos.x()) * m_size.x() - 4, (1.f - text_2d_pos.y()) * m_size.y()), - to_string(i + 1), Color(1.0f, 1.0f, 1.0f, 0.75f), 16, 0, NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE); - } - - int plot_index = 0; - for (int y = 0; y < m_num_dimensions; ++y) - for (int x = 0; x < y; ++x, ++plot_index) - { - // cout << "drawing plot_index: " << plot_index << endl; - draw_2D_points_and_grid(mvp, x, y, plot_index); - } - // cout << endl; - } - else - { - // m_render_pass->set_depth_test(RenderPass::DepthTest::Less, true); - draw_points(mvp, Vector3f(0.9f, 0.55f, 0.1f), m_show_point_nums->checked(), m_show_point_coords->checked()); - - if (m_show_1d_projections->checked()) - { - // smash the points against the axes and draw - Matrix4f smashX = mvp * Matrix4f::translate(Vector3f(-0.51, 0, 0)) * Matrix4f::scale(Vector3f(0, 1, 1)); - draw_points(smashX, Vector3f(0.8f, 0.3f, 0.3f)); - - Matrix4f smashY = mvp * Matrix4f::translate(Vector3f(0, -0.51, 0)) * Matrix4f::scale(Vector3f(1, 0, 1)); - draw_points(smashY, Vector3f(0.3f, 0.8f, 0.3f)); - - Matrix4f smashZ = mvp * Matrix4f::translate(Vector3f(0, 0, -0.51)) * Matrix4f::scale(Vector3f(1, 1, 0)); - draw_points(smashZ, Vector3f(0.3f, 0.3f, 0.8f)); - } - - // m_render_pass->set_depth_test(RenderPass::DepthTest::Less, true); - if (m_bbox_grid_checkbox->checked()) - draw_grid(mvp, 1.0f, 0, 8); - - if (m_coarse_grid_checkbox->checked()) - draw_grid(mvp, 0.6f, 8, m_coarse_line_count); - - if (m_fine_grid_checkbox->checked()) - draw_grid(mvp, 0.2f, 8 + m_coarse_line_count, m_fine_line_count); - - for (int i = 0; i < 3; ++i) - draw_text(m_size - Vector2i(10, (2 - i) * 14 + 20), m_time_strings[i], Color(1.0f, 1.0f, 1.0f, 0.75f), 16, - 0); - } - - m_render_pass->end(); -} - -void SampleViewer::draw_text(const Vector2i &pos, const string &text, const Color &color, int font_size, - int fixed_width, int align) const -{ - nvgFontFace(m_nvg_context, "sans"); - nvgFontSize(m_nvg_context, (float)font_size); - nvgFillColor(m_nvg_context, color); - if (fixed_width > 0) - { - nvgTextAlign(m_nvg_context, align); - nvgTextBox(m_nvg_context, (float)pos.x(), (float)pos.y(), (float)fixed_width, text.c_str(), nullptr); - } - else - { - nvgTextAlign(m_nvg_context, align); - nvgText(m_nvg_context, (float)pos.x(), (float)pos.y(), text.c_str(), nullptr); - } -} - -void SampleViewer::draw_contents_EPS(ofstream &file, CameraType camera_type, int dim_x, int dim_y, int dim_z) -{ - int size = 900; - int crop = 720; - - int pointScale = m_samplers[m_point_type_box->selected_index()]->coarseGridRes(m_point_count); - - file << "%!PS-Adobe-3.0 EPSF-3.0\n"; - file << "%%HiResBoundingBox: " << (-crop) << " " << (-crop) << " " << crop << " " << crop << "\n"; - file << "%%BoundingBox: " << (-crop) << " " << (-crop) << " " << crop << " " << crop << "\n"; - file << "%%CropBox: " << (-crop) << " " << (-crop) << " " << crop << " " << crop << "\n"; - file << "gsave " << size << " " << size << " scale\n"; - file << "/radius { " << (0.5f / pointScale) << " } def %define variable for point radius\n"; - file << "/p { radius 0 360 arc closepath fill } def %define point command\n"; - file << "/blw " << (0.01f) << " def %define variable for bounding box linewidth\n"; - file << "/clw " << (0.003f) << " def %define variable for coarse linewidth\n"; - file << "/flw " << (0.0004f) << " def %define variable for fine linewidth\n"; - file << "/pfc " - << "{0.9 0.55 0.1}" - << " def %define variable for point fill color\n"; - file << "/blc " << (0.0f) << " def %define variable for bounding box color\n"; - file << "/clc " << (0.8f) << " def %define variable for coarse line color\n"; - file << "/flc " << (0.9f) << " def %define variable for fine line color\n"; - - Matrix4f model, lookat, view, proj, mvp, mvp2; - computeCameraMatrices(model, lookat, view, proj, m_camera[camera_type], 1.0f); - mvp = proj * lookat * view * model; - int start_axis = CAMERA_XY, end_axis = CAMERA_XZ; - if (camera_type != CAMERA_CURRENT) - start_axis = end_axis = camera_type; - if (m_fine_grid_checkbox->checked()) - { - file << "% Draw fine grids \n"; - file << "flc setgray %fill color for fine grid \n"; - file << "flw setlinewidth\n"; - for (int axis = start_axis; axis <= end_axis; ++axis) - { - // this extra matrix multiply is needed to properly rotate the different grids for the XYZ view - Matrix4f mvp2 = mvp * m_camera[axis].arcball.matrix(); - drawEPSGrid(mvp2, m_point_count, file); - } - } - - if (m_coarse_grid_checkbox->checked()) - { - file << "% Draw coarse grids \n"; - file << "clc setgray %fill color for coarse grid \n"; - file << "clw setlinewidth\n"; - for (int axis = start_axis; axis <= end_axis; ++axis) - { - // this extra matrix multiply is needed to properly rotate the different grids for the XYZ view - Matrix4f mvp2 = mvp * m_camera[axis].arcball.matrix(); - PointType point_type = (PointType)m_point_type_box->selected_index(); - drawEPSGrid(mvp2, m_samplers[point_type]->coarseGridRes(m_point_count), file); - } - } - - if (m_bbox_grid_checkbox->checked()) - { - file << "% Draw bounding boxes \n"; - file << "blc setgray %fill color for bounding box \n"; - file << "blw setlinewidth\n"; - for (int axis = start_axis; axis <= end_axis; ++axis) - { - // this extra matrix multiply is needed to properly rotate the different grids for the XYZ view - Matrix4f mvp2 = mvp * m_camera[axis].arcball.matrix(); - drawEPSGrid(mvp2, 1, file); - } - } - - // Generate and render the point set - { - generate_points(); - populate_point_subset(); - file << "% Draw points \n"; - file << "pfc setrgbcolor %fill color for points\n"; - - int start = 0, count = m_point_count; - if (m_subset_by_coord->checked()) - count = min(count, m_subset_count); - else if (m_subset_by_index->checked()) - { - start = m_first_draw_point_box->value(); - count = m_point_draw_count_box->value(); - } - writeEPSPoints(m_subset_points, start, count, mvp, file, dim_x, dim_y, dim_z); - } - - file << "grestore\n"; -} - -void SampleViewer::draw_contents_2D_EPS(ofstream &file) -{ - int size = 900 * 108.0f / 720.0f; - int crop = 108; // 720; - float scale = 1.0f / (m_num_dimensions - 1); - // int pointScale = m_samplers[m_point_type_box->selected_index()]->coarseGridRes(m_point_count); - - file << "%!PS-Adobe-3.0 EPSF-3.0\n"; - file << "%%HiResBoundingBox: " << (-crop) << " " << (-crop) << " " << crop << " " << crop << "\n"; - file << "%%BoundingBox: " << (-crop) << " " << (-crop) << " " << crop << " " << crop << "\n"; - file << "%%CropBox: " << (-crop) << " " << (-crop) << " " << crop << " " << crop << "\n"; - file << "gsave " << size << " " << size << " scale\n"; - // file << "/radius { " << (0.5f/pointScale*scale) << " } def %define variable for point radius\n"; - file << "/radius { " << (0.02 * scale) << " } def %define variable for point radius\n"; - file << "/p { radius 0 360 arc closepath fill } def %define point command\n"; - file << "/blw " << (0.020f * scale) << " def %define variable for bounding box linewidth\n"; - file << "/clw " << (0.01f * scale) << " def %define variable for coarse linewidth\n"; - file << "/flw " << (0.005f * scale) << " def %define variable for fine linewidth\n"; - file << "/pfc " - << "{0.9 0.55 0.1}" - << " def %define variable for point fill color\n"; - file << "/blc " << (0.0f) << " def %define variable for bounding box color\n"; - file << "/clc " << (0.5f) << " def %define variable for coarse line color\n"; - file << "/flc " << (0.9f) << " def %define variable for fine line color\n"; - - Matrix4f model, lookat, view, proj, mvp, mvp2; - computeCameraMatrices(model, lookat, view, proj, m_camera[CAMERA_2D], 1.0f); - mvp = proj * lookat * view * model; - - // Generate and render the point set - { - generate_points(); - populate_point_subset(); - - for (int y = 0; y < m_num_dimensions; ++y) - for (int x = 0; x < y; ++x) - { - Matrix4f pos; - layout_2d_matrix(pos, m_num_dimensions, x, y); - - if (m_fine_grid_checkbox->checked()) - { - file << "% Draw fine grid \n"; - file << "flc setgray %fill color for fine grid \n"; - file << "flw setlinewidth\n"; - drawEPSGrid(mvp * pos, m_point_count, file); - } - if (m_coarse_grid_checkbox->checked()) - { - file << "% Draw coarse grids \n"; - file << "clc setgray %fill color for coarse grid \n"; - file << "clw setlinewidth\n"; - PointType point_type = (PointType)m_point_type_box->selected_index(); - drawEPSGrid(mvp * pos, m_samplers[point_type]->coarseGridRes(m_point_count), file); - } - if (m_bbox_grid_checkbox->checked()) - { - file << "% Draw bounding box \n"; - file << "blc setgray %fill color for bounding box \n"; - file << "blw setlinewidth\n"; - drawEPSGrid(mvp * pos, 1, file); - } - } - - file << "% Draw points \n"; - file << "pfc setrgbcolor %fill color for points\n"; - for (int y = 0; y < m_num_dimensions; ++y) - for (int x = 0; x < y; ++x) - { - Matrix4f pos; - layout_2d_matrix(pos, m_num_dimensions, x, y); - file << "% Draw (" << x << "," << y << ") points\n"; - - int start = 0, count = m_point_count; - if (m_subset_by_coord->checked()) - count = min(count, m_subset_count); - else if (m_subset_by_index->checked()) - { - start = m_first_draw_point_box->value(); - count = m_point_draw_count_box->value(); - } - - writeEPSPoints(m_subset_points, start, count, mvp * pos, file, x, y, 2); - } - } - - file << "grestore\n"; -} - -bool SampleViewer::keyboard_event(int key, int scancode, int action, int modifiers) -{ - if (Screen::keyboard_event(key, scancode, action, modifiers)) - return true; - - if (!action) - return false; - - switch (key) - { - case GLFW_KEY_ESCAPE: - { - this->set_visible(false); - return true; - } - case ' ': m_parameters_dialog->set_visible(!m_parameters_dialog->visible()); return true; - case 'H': - m_help_dialog->center(); - m_help_dialog->set_visible(!m_help_dialog->visible()); - m_help_button->set_pushed(m_help_dialog->visible()); - return true; - case '1': set_view(CAMERA_XY); return true; - case '2': set_view(CAMERA_YZ); return true; - case '3': set_view(CAMERA_XZ); return true; - case '4': set_view(CAMERA_CURRENT); return true; - case '0': set_view(CAMERA_2D); return true; - case 'P': - m_show_1d_projections->set_checked(!m_show_1d_projections->checked()); - draw_contents(); - return true; - case 'B': - m_bbox_grid_checkbox->set_checked(!m_bbox_grid_checkbox->checked()); - update_GPU_grids(); - return true; - case 'G': - if (modifiers & GLFW_MOD_SHIFT) - m_fine_grid_checkbox->set_checked(!m_fine_grid_checkbox->checked()); - else - m_coarse_grid_checkbox->set_checked(!m_coarse_grid_checkbox->checked()); - update_GPU_grids(); - return true; - case 'R': - if (modifiers & GLFW_MOD_SHIFT) - { - // re-randomize - PointType point_type = (PointType)m_point_type_box->selected_index(); - m_randomize_checkbox->set_checked(true); - m_samplers[point_type]->setRandomized(true); - update_GPU_points(); - draw_all(); - } - else - { - m_randomize_checkbox->set_checked(!m_randomize_checkbox->checked()); - update_GPU_points(); - } - return true; - case 'D': - m_dimension_box->set_value(m_dimension_box->value() + ((modifiers & GLFW_MOD_SHIFT) ? 1 : -1)); - m_num_dimensions = m_dimension_box->value(); - m_subset_axis->set_max_value(m_num_dimensions - 1); - m_x_dimension->set_max_value(m_num_dimensions - 1); - m_y_dimension->set_max_value(m_num_dimensions - 1); - m_z_dimension->set_max_value(m_num_dimensions - 1); - m_subset_axis->set_value(min(m_subset_axis->value(), m_num_dimensions - 1)); - m_x_dimension->set_value(min(m_x_dimension->value(), m_num_dimensions - 1)); - m_y_dimension->set_value(min(m_y_dimension->value(), m_num_dimensions - 1)); - m_z_dimension->set_value(min(m_z_dimension->value(), m_num_dimensions - 1)); - update_GPU_points(); - update_GPU_grids(); - return true; - - case 'T': - { - Sampler *sampler = m_samplers[m_point_type_box->selected_index()]; - OrthogonalArray *oa = dynamic_cast(sampler); - if (oa) - { - int t = m_strength_box->value() + ((modifiers & GLFW_MOD_SHIFT) ? 1 : -1); - t = (t < 2) ? 2 : t; - m_strength_box->set_value(oa->setStrength(t)); - } - update_GPU_points(); - update_GPU_grids(); - return true; - } - - case GLFW_KEY_UP: - case GLFW_KEY_DOWN: - { - int delta = (key == GLFW_KEY_DOWN) ? 1 : -1; - if (modifiers & GLFW_MOD_SHIFT) - { - if (OrthogonalArray *oa = dynamic_cast(m_samplers[m_point_type_box->selected_index()])) - { - m_offset_type_box->set_selected_index( - mod(m_offset_type_box->selected_index() + delta, (int)NUM_OFFSET_TYPES)); - oa->setOffsetType(m_offset_type_box->selected_index()); - - update_GPU_points(); - update_GPU_grids(); - } - } - else - { - m_point_type_box->set_selected_index(mod(m_point_type_box->selected_index() + delta, (int)NUM_POINT_TYPES)); - Sampler *sampler = m_samplers[m_point_type_box->selected_index()]; - OrthogonalArray *oa = dynamic_cast(sampler); - m_strength_label->set_visible(oa); - m_strength_box->set_visible(oa); - m_strength_box->set_enabled(oa); - m_offset_type_label->set_visible(oa); - m_offset_type_box->set_visible(oa); - m_offset_type_box->set_enabled(oa); - m_jitter_slider->set_value(sampler->jitter()); - if (oa) - { - m_offset_type_box->set_selected_index(oa->offsetType()); - m_strength_box->set_value(oa->strength()); - } - perform_layout(); - update_GPU_points(); - update_GPU_grids(); - } - return true; - } - case GLFW_KEY_LEFT: - case GLFW_KEY_RIGHT: - { - float delta = (modifiers & GLFW_MOD_SHIFT) ? 1.0f / 15 : 1.0f / 60; - delta *= (key == GLFW_KEY_RIGHT) ? 1.f : -1.f; - m_target_point_count = mapSlider2Count(std::clamp(m_point_count_slider->value() + delta, 0.0f, 1.0f)); - update_GPU_points(); - update_GPU_grids(); - return true; - } - } - - return false; -} - -void SampleViewer::set_view(CameraType view) -{ - m_animate_start_time = (float)glfwGetTime(); - m_camera[CAMERA_PREVIOUS] = m_camera[CAMERA_CURRENT]; - m_camera[CAMERA_NEXT] = m_camera[view]; - m_camera[CAMERA_NEXT].persp_factor = (view == CAMERA_CURRENT) ? 1.0f : 0.0f; - m_camera[CAMERA_NEXT].camera_type = view; - m_camera[CAMERA_CURRENT].camera_type = (view == m_camera[CAMERA_CURRENT].camera_type) ? view : CAMERA_CURRENT; - - for (int i = CAMERA_XY; i <= CAMERA_CURRENT; ++i) - m_view_btns[i]->set_pushed(false); - m_view_btns[view]->set_pushed(true); - m_gui_refresh = true; -} - -void SampleViewer::update_current_camera() -{ - CameraParameters &camera0 = m_camera[CAMERA_PREVIOUS]; - CameraParameters &camera1 = m_camera[CAMERA_NEXT]; - CameraParameters &camera = m_camera[CAMERA_CURRENT]; - - float time_now = (float)glfwGetTime(); - float time_diff = (m_animate_start_time != 0.0f) ? (time_now - m_animate_start_time) : 1.0f; - - // interpolate between the "perspectiveness" at the previous keyframe, - // and the "perspectiveness" of the currently selected camera - float t = smoothStep(0.0f, 1.0f, time_diff); - - camera.zoom = lerp(camera0.zoom, camera1.zoom, t); - camera.view_angle = lerp(camera0.view_angle, camera1.view_angle, t); - camera.persp_factor = lerp(camera0.persp_factor, camera1.persp_factor, t); - if (t >= 1.0f) - { - camera.camera_type = camera1.camera_type; - m_gui_refresh = false; - m_animate_start_time = 0.f; - } - - // if we are dragging the mouse, then just use the current arcball - // rotation, otherwise, interpolate between the previous and next camera - // orientations - if (m_mouse_down) - camera.arcball = camera1.arcball; - else - camera.arcball.setState(qslerp(camera0.arcball.state(), camera1.arcball.state(), t)); -} - -void SampleViewer::generate_points() -{ - PointType point_type = (PointType)m_point_type_box->selected_index(); - bool randomize = m_randomize_checkbox->checked(); - - float time0 = (float)glfwGetTime(); - Sampler *generator = m_samplers[point_type]; - if (generator->randomized() != randomize) - generator->setRandomized(randomize); - - generator->setDimensions(m_num_dimensions); - - int num_pts = generator->setNumSamples(m_target_point_count); - m_point_count = num_pts > 0 ? num_pts : m_target_point_count; - m_first_draw_point_box->set_max_value(m_point_count - 1); - m_first_draw_point_box->set_value(m_first_draw_point_box->value()); - m_point_draw_count_box->set_max_value(m_point_count - m_first_draw_point_box->value()); - m_point_draw_count_box->set_value(m_point_draw_count_box->value()); - - float time1 = (float)glfwGetTime(); - float time_diff1 = ((time1 - time0) * 1000.0f); - - m_points.resize(m_point_count, m_num_dimensions); - m_3d_points.resize(m_point_count); - - time1 = (float)glfwGetTime(); - for (int i = 0; i < m_point_count; ++i) - { - vector r(m_num_dimensions, 0.5f); - generator->sample(r.data(), i); - for (int j = 0; j < m_points.sizeY(); ++j) - m_points(i, j) = r[j] - 0.5f; - } - float time2 = (float)glfwGetTime(); - float time_diff2 = ((time2 - time1) * 1000.0f); - - m_time_strings[0] = tfm::format("Reset and randomize: %3.3f ms", time_diff1); - m_time_strings[1] = tfm::format("Sampling: %3.3f ms", time_diff2); - m_time_strings[2] = tfm::format("Total generation time: %3.3f ms", time_diff1 + time_diff2); -} - -void SampleViewer::populate_point_subset() -{ - // - // Populate point subsets - // - m_subset_points = m_points; - m_subset_count = m_point_count; - if (m_subset_by_coord->checked()) - { - // cout << "only accepting points where the coordinates along the " << m_subset_axis->value() - // << " axis are within: [" - // << ((m_subset_level->value() + 0.0f)/m_num_subset_levels->value()) << ", " - // << ((m_subset_level->value()+1.0f)/m_num_subset_levels->value()) << ")." << endl; - m_subset_count = 0; - for (int i = 0; i < m_points.sizeX(); ++i) - { - float v = m_points(i, m_subset_axis->value()); - // cout << v << endl; - v += 0.5f; - if (v >= (m_subset_level->value() + 0.0f) / m_num_subset_levels->value() && - v < (m_subset_level->value() + 1.0f) / m_num_subset_levels->value()) - { - // copy all dimensions (rows) of point i - for (int j = 0; j < m_subset_points.sizeY(); ++j) - m_subset_points(m_subset_count, j) = m_points(i, j); - ++m_subset_count; - } - } - } - - for (size_t i = 0; i < m_3d_points.size(); ++i) - m_3d_points[i] = - Vector3f{m_subset_points(i, m_x_dimension->value()), m_subset_points(i, m_y_dimension->value()), - m_subset_points(i, m_z_dimension->value())}; -} - -void SampleViewer::generate_grid(vector &positions, int grid_res) -{ - int fine_grid_res = 1; - int idx = 0; - positions.resize(4 * (grid_res + 1) * (fine_grid_res)); - float coarse_scale = 1.f / grid_res, fine_scale = 1.f / fine_grid_res; - // for (int z = -1; z <= 1; z+=2) - int z = 0; - for (int i = 0; i <= grid_res; ++i) - { - for (int j = 0; j < fine_grid_res; ++j) - { - positions[idx++] = Vector3f(j * fine_scale - 0.5f, i * coarse_scale - 0.5f, z * 0.5f); - positions[idx++] = Vector3f((j + 1) * fine_scale - 0.5f, i * coarse_scale - 0.5f, z * 0.5f); - positions[idx++] = Vector3f(i * coarse_scale - 0.5f, j * fine_scale - 0.5f, z * 0.5f); - positions[idx++] = Vector3f(i * coarse_scale - 0.5f, (j + 1) * fine_scale - 0.5f, z * 0.5f); - } - } -} - -namespace -{ - -void computeCameraMatrices(Matrix4f &model, Matrix4f &lookat, Matrix4f &view, Matrix4f &proj, const CameraParameters &c, - float window_aspect) -{ - model = Matrix4f::scale(Vector3f(c.zoom)); - if (c.camera_type == CAMERA_XY || c.camera_type == CAMERA_2D) - model = Matrix4f::scale(Vector3f(1, 1, 0)) * model; - if (c.camera_type == CAMERA_XZ) - model = Matrix4f::scale(Vector3f(1, 0, 1)) * model; - if (c.camera_type == CAMERA_YZ) - model = Matrix4f::scale(Vector3f(0, 1, 1)) * model; - - float fH = tan(c.view_angle / 360.0f * M_PI) * c.dnear; - float fW = fH * window_aspect; - - float oFF = c.eye.z() / c.dnear; - Matrix4f orth = Matrix4f::ortho(-fW * oFF, fW * oFF, -fH * oFF, fH * oFF, c.dnear, c.dfar); - Matrix4f frust = frustum(-fW, fW, -fH, fH, c.dnear, c.dfar); - - proj = lerp(orth, frust, c.persp_factor); - lookat = Matrix4f::look_at(c.eye, c.center, c.up); - view = c.arcball.matrix(); -} - -void drawEPSGrid(const Matrix4f &mvp, int grid_res, ofstream &file) -{ - Vector4f vA4d, vB4d; - Vector2f vA, vB; - - int fine_grid_res = 2; - float coarse_scale = 1.f / grid_res, fine_scale = 1.f / fine_grid_res; - - // draw the bounding box - // if (grid_res == 1) - { - Vector4f c004d = mvp * Vector4f(0.0f - 0.5f, 0.0f - 0.5f, 0.0f, 1.0f); - Vector4f c104d = mvp * Vector4f(1.0f - 0.5f, 0.0f - 0.5f, 0.0f, 1.0f); - Vector4f c114d = mvp * Vector4f(1.0f - 0.5f, 1.0f - 0.5f, 0.0f, 1.0f); - Vector4f c014d = mvp * Vector4f(0.0f - 0.5f, 1.0f - 0.5f, 0.0f, 1.0f); - Vector2f c00 = Vector2f(c004d.x() / c004d.w(), c004d.y() / c004d.w()); - Vector2f c10 = Vector2f(c104d.x() / c104d.w(), c104d.y() / c104d.w()); - Vector2f c11 = Vector2f(c114d.x() / c114d.w(), c114d.y() / c114d.w()); - Vector2f c01 = Vector2f(c014d.x() / c014d.w(), c014d.y() / c014d.w()); - - file << "newpath\n"; - file << c00.x() << " " << c00.y() << " moveto\n"; - file << c10.x() << " " << c10.y() << " lineto\n"; - file << c11.x() << " " << c11.y() << " lineto\n"; - file << c01.x() << " " << c01.y() << " lineto\n"; - file << "closepath\n"; - file << "stroke\n"; - } - - for (int i = 1; i <= grid_res - 1; i++) - { - // draw horizontal lines - file << "newpath\n"; - for (int j = 0; j < fine_grid_res; j++) - { - vA4d = mvp * Vector4f(j * fine_scale - 0.5f, i * coarse_scale - 0.5f, 0.0f, 1.0f); - vB4d = mvp * Vector4f((j + 1) * fine_scale - 0.5f, i * coarse_scale - 0.5f, 0.0f, 1.0f); - - vA = Vector2f(vA4d.x() / vA4d.w(), vA4d.y() / vA4d.w()); - vB = Vector2f(vB4d.x() / vB4d.w(), vB4d.y() / vB4d.w()); - - file << vA.x() << " " << vA.y(); - - if (j == 0) - file << " moveto\n"; - else - file << " lineto\n"; - - file << vB.x() << " " << vB.y(); - file << " lineto\n"; - } - file << "stroke\n"; - - // draw vertical lines - file << "newpath\n"; - for (int j = 0; j < fine_grid_res; j++) - { - vA4d = mvp * Vector4f(i * coarse_scale - 0.5f, j * fine_scale - 0.5f, 0.0f, 1.0f); - vB4d = mvp * Vector4f(i * coarse_scale - 0.5f, (j + 1) * fine_scale - 0.5f, 0.0f, 1.0f); - - vA = Vector2f(vA4d.x() / vA4d.w(), vA4d.y() / vA4d.w()); - vB = Vector2f(vB4d.x() / vB4d.w(), vB4d.y() / vB4d.w()); - - file << vA.x() << " " << vA.y(); - - if (j == 0) - file << " moveto\n"; - else - file << " lineto\n"; - - file << vB.x() << " " << vB.y(); - file << " lineto\n"; - } - file << "stroke\n"; - } -} - -void writeEPSPoints(const Array2d &points, int start, int count, const Matrix4f &mvp, ofstream &file, int dim_x, - int dim_y, int dim_z) -{ - for (int i = start; i < start + count; i++) - { - Vector4f v4d = mvp * Vector4f(points(i, dim_x), points(i, dim_y), points(i, dim_z), 1.0f); - Vector2f v2d(v4d.x() / v4d.w(), v4d.y() / v4d.w()); - file << v2d.x() << " " << v2d.y() << " p\n"; - } -} - -} // namespace diff --git a/gui/SampleViewer.h b/gui/SampleViewer.h deleted file mode 100644 index d585246..0000000 --- a/gui/SampleViewer.h +++ /dev/null @@ -1,144 +0,0 @@ -/** \file SampleViewer.h - \author Wojciech Jarosz -*/ - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "utils.h" - -using namespace nanogui; -using namespace std; - -enum PointType -{ - RANDOM = 0, - JITTERED, - // MULTI_JITTERED, - MULTI_JITTERED_IP, - // CORRELATED_MULTI_JITTERED, - // CORRELATED_MULTI_JITTERED_3D, - CORRELATED_MULTI_JITTERED_IP, - CMJND, - BOSE_OA_IP, - BOSE_GALOIS_OA_IP, - BUSH_OA_IP, - BUSH_GALOIS_OA_IP, - ADDEL_KEMP_OA_IP, - BOSE_BUSH_OA, - BOSE_BUSH_OA_IP, - N_ROOKS_IP, - SOBOL, - ZERO_TWO, - ZERO_TWO_SHUFFLED, - HALTON, - HALTON_ZAREMBA, - HAMMERSLEY, - HAMMERSLEY_ZAREMBA, - LARCHER_PILLICHSHAMMER, - NUM_POINT_TYPES -}; - -enum CameraType -{ - CAMERA_XY = 0, - CAMERA_YZ, - CAMERA_XZ, - CAMERA_2D, - CAMERA_CURRENT, - CAMERA_PREVIOUS, - CAMERA_NEXT, - NUM_CAMERA_TYPES -}; - -struct CameraParameters -{ - Arcball arcball; - float persp_factor = 0.0f; - float zoom = 1.0f, view_angle = 35.0f; - float dnear = 0.05f, dfar = 1000.0f; - Vector3f eye = Vector3f(0.0f, 0.0f, 2.0f); - Vector3f center = Vector3f(0.0f, 0.0f, 0.0f); - Vector3f up = Vector3f(0.0f, 1.0f, 0.0f); - CameraType camera_type = CAMERA_CURRENT; -}; - -class SampleViewer : public Screen -{ -public: - SampleViewer(); - ~SampleViewer(); - - bool mouse_motion_event(const Vector2i &p, const Vector2i &rel, int button, int modifiers); - bool mouse_button_event(const Vector2i &p, int button, bool down, int modifiers); - bool scroll_event(const Vector2i &p, const Vector2f &rel); - void draw_contents(); - bool keyboard_event(int key, int scancode, int action, int modifiers); - -private: - void draw_contents_EPS(ofstream &file, CameraType camera, int dimX, int dimY, int dimZ); - void draw_contents_2D_EPS(ofstream &file); - void update_GPU_points(bool regenerate = true); - void update_GPU_grids(); - void set_view(CameraType view); - void update_current_camera(); - void initialize_GUI(); - void generate_points(); - void populate_point_subset(); - void generate_grid(vector &positions, int gridRes); - void draw_text(const Vector2i &pos, const std::string &text, const Color &col = Color(1.0f, 1.0f, 1.0f, 1.0f), - int fontSize = 10, int fixedWidth = 0, int align = NVG_ALIGN_RIGHT | NVG_ALIGN_BOTTOM) const; - void draw_points(const Matrix4f &mvp, const Vector3f &color, bool showPointNums = false, - bool showPointCoords = false); - void draw_grid(const Matrix4f &mvp, float alpha, uint32_t offset, uint32_t count); - void draw_2D_points_and_grid(const Matrix4f &mvp, int dimX, int dimY, int plotIndex); - - // GUI elements - Window *m_parameters_dialog = nullptr; - Window *m_help_dialog = nullptr; - Button *m_help_button = nullptr; - Slider *m_point_count_slider, *m_point_radius_slider, *m_point_draw_count_slider, *m_jitter_slider; - IntBox *m_dimension_box, *m_x_dimension, *m_y_dimension, *m_z_dimension, *m_point_count_box, - *m_first_draw_point_box, *m_point_draw_count_box, *m_subset_axis, *m_num_subset_levels, *m_subset_level; - ComboBox *m_point_type_box; - - // Extra OA parameters - Label *m_strength_label; - IntBox *m_strength_box; - Label *m_offset_type_label; - ComboBox *m_offset_type_box; - - CheckBox *m_coarse_grid_checkbox, *m_fine_grid_checkbox, *m_bbox_grid_checkbox, *m_randomize_checkbox, - *m_subset_by_index, *m_subset_by_coord, *m_show_1d_projections, *m_show_point_nums, *m_show_point_coords; - Button *m_view_btns[CAMERA_CURRENT + 1]; - Button *m_scale_radius_with_points; - vector m_time_strings; - - /// X, Y, Z, and user-defined cameras - CameraParameters m_camera[NUM_CAMERA_TYPES]; - - float m_animate_start_time = 0.0f; - bool m_mouse_down = false; - int m_num_dimensions = 3; - - nanogui::ref m_render_pass; - nanogui::ref m_point_shader; - nanogui::ref m_grid_shader; - nanogui::ref m_point_2d_shader; - Array2d m_points, m_subset_points; - vector m_3d_points; - int m_target_point_count, m_point_count, m_subset_count = 0, m_coarse_line_count, m_fine_line_count; - - vector m_samplers; - - std::thread m_gui_refresh_thread; - std::atomic m_gui_refresh = false; - std::atomic m_join_requested = false; -}; diff --git a/gui/Well.cpp b/gui/Well.cpp deleted file mode 100644 index 0ea64b7..0000000 --- a/gui/Well.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (C) Wojciech Jarosz. All rights reserved. -// Use of this source code is governed by a BSD-style license that can -// be found in the LICENSE.txt file. -// - -#include "Well.h" -#include - -using namespace nanogui; - -Well::Well(Widget *parent, float radius, const Color &inner, const Color &outer) : - Widget(parent), m_radius(radius), m_innerColor(inner), m_outerColor(outer) -{ -} - -void Well::draw(NVGcontext *ctx) -{ - NVGpaint paint = nvgBoxGradient(ctx, m_pos.x() + 1, m_pos.y() + 1, m_size.x() - 2, m_size.y() - 2, m_radius, - m_radius + 1, m_innerColor, m_outerColor); - nvgBeginPath(ctx); - nvgRoundedRect(ctx, m_pos.x(), m_pos.y(), m_size.x(), m_size.y(), m_radius); - nvgFillPaint(ctx, paint); - nvgFill(ctx); - - Widget::draw(ctx); -} \ No newline at end of file diff --git a/gui/Well.h b/gui/Well.h deleted file mode 100644 index 8e8f003..0000000 --- a/gui/Well.h +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (C) Wojciech Jarosz. All rights reserved. -// Use of this source code is governed by a BSD-style license that can -// be found in the LICENSE.txt file. -// - -#pragma once - -#include - -/// A simple container that draws a rounded background of a specified outer and -/// inner color -NAMESPACE_BEGIN(nanogui) -class Well : public Widget -{ -public: - Well(Widget *parent, float radius = 3.0f, const Color &inner = Color(0, 32), const Color &outer = Color(0, 92)); - - /// Return the inner well color - const Color &inner_color() const - { - return m_innerColor; - } - /// Set the inner well color - void set_inner_color(const Color &innerColor) - { - m_innerColor = innerColor; - } - - /// Return the outer well color - const Color &outer_color() const - { - return m_outerColor; - } - /// Set the outer well color - void set_outer_color(const Color &outerColor) - { - m_outerColor = outerColor; - } - - void draw(NVGcontext *ctx) override; - -protected: - float m_radius; - Color m_innerColor, m_outerColor; -}; -NAMESPACE_END(nanogui) diff --git a/gui/main.cpp b/gui/main.cpp deleted file mode 100644 index 3836a80..0000000 --- a/gui/main.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/* - main.cpp -- Samplin' Safari application entry point - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE.txt file. -*/ - -#include "SampleViewer.h" -#include -#include -#include - -/* Force usage of discrete GPU on laptops */ -NANOGUI_FORCE_DISCRETE_GPU(); - -int nprocs = -1; - -int main(int argc, char **argv) -{ - vector args; - bool help = false; - bool error = false; - -#if defined(__APPLE__) - bool launched_from_finder = false; -#endif - - try - { - for (int i = 1; i < argc; ++i) - { - if (strcmp("--help", argv[i]) == 0 || strcmp("-h", argv[i]) == 0) - { - help = true; -#if defined(__APPLE__) - } - else if (strncmp("-psn", argv[i], 4) == 0) - { - launched_from_finder = true; -#endif - } - else - { - if (strncmp(argv[i], "-", 1) == 0) - { - cerr << "Invalid argument: \"" << argv[i] << "\"!" << endl; - help = true; - error = true; - } - args.push_back(argv[i]); - } - } - } - catch (const std::exception &e) - { - cout << "Error: " << e.what() << endl; - help = true; - error = true; - } - - if (help) - { - cout << "Syntax: " << argv[0] << endl; - cout << "Options:" << endl; - cout << " -h, --help Display this message" << endl; - return error ? EXIT_FAILURE : EXIT_SUCCESS; - } - - try - { - nanogui::init(); - -#if defined(__APPLE__) - if (launched_from_finder) - nanogui::chdir_to_bundle_parent(); -#endif - - { - nanogui::ref viewer = new SampleViewer(); - viewer->set_visible(true); - nanogui::mainloop(); - } - - nanogui::shutdown(); - } - catch (const std::runtime_error &e) - { - std::cerr << "Caught a fatal error: " << e.what() << endl; - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} diff --git a/gui/utils.h b/gui/utils.h deleted file mode 100644 index 8c898a2..0000000 --- a/gui/utils.h +++ /dev/null @@ -1,269 +0,0 @@ -/** \file utils.h - \author Wojciech Jarosz -*/ - -#pragma once - -#include -#include - -NAMESPACE_BEGIN(nanogui) - -// -// Missing matrix and vector operations -// - -template -Array operator*(const Matrix &a, const Array &v) -{ - Array c; - for (size_t i = 0; i < 4; ++i) - { - T accum{0}; - for (size_t j = 0; j < 4; ++j) - accum += a.m[j][i] * v[j]; - c[i] = accum; - } - return c; -} - -template -Matrix operator*(const T &a, const Matrix &b) -{ - Matrix c; - for (size_t i = 0; i < Size; ++i) - for (size_t j = 0; j < Size; ++j) - c.m[j][i] = a * b.m[j][i]; - return c; -} - -template -Matrix operator*(const Matrix &a, const T &b) -{ - Matrix c; - for (size_t i = 0; i < Size; ++i) - for (size_t j = 0; j < Size; ++j) - c.m[j][i] = a.m[j][i] * b; - return c; -} - -template -Matrix operator+(const Matrix &a, const Matrix &b) -{ - Matrix c; - for (size_t i = 0; i < Size; ++i) - for (size_t j = 0; j < Size; ++j) - c.m[j][i] = a.m[j][i] + b.m[j][i]; - return c; -} - -inline Matrix4f frustum(float left, float right, float bottom, float top, float nearVal, float farVal) -{ - Matrix4f result{0.f}; - result.m[0][0] = (2.0f * nearVal) / (right - left); - result.m[1][1] = (2.0f * nearVal) / (top - bottom); - result.m[2][0] = (right + left) / (right - left); - result.m[2][1] = (top + bottom) / (top - bottom); - result.m[2][2] = -(farVal + nearVal) / (farVal - nearVal); - result.m[2][3] = -1.0f; - result.m[3][2] = -(2.0f * farVal * nearVal) / (farVal - nearVal); - return result; -} - -// -// Quaternion math -// - -template -constexpr Array qxdir(const Array &q) -{ - return {q.w() * q.w() + q.x() * q.x() - q.y() * q.y() - q.z() * q.z(), (q.x() * q.y() + q.z() * q.w()) * 2, - (q.z() * q.x() - q.y() * q.w()) * 2}; -} -template -constexpr Array qydir(const Array &q) -{ - return {(q.x() * q.y() - q.z() * q.w()) * 2, q.w() * q.w() - q.x() * q.x() + q.y() * q.y() - q.z() * q.z(), - (q.y() * q.z() + q.x() * q.w()) * 2}; -} -template -constexpr Array qzdir(const Array &q) -{ - return {(q.z() * q.x() + q.y() * q.w()) * 2, (q.y() * q.z() - q.x() * q.w()) * 2, - q.w() * q.w() - q.x() * q.x() - q.y() * q.y() + q.z() * q.z()}; -} -template -constexpr Matrix qmat(const Array &q) -{ - auto result = Matrix::scale({1, 1, 1, 1}); - Array qdir[3] = {qxdir(q), qydir(q), qzdir(q)}; - for (size_t i = 0; i < 3; ++i) - for (size_t j = 0; j < 3; ++j) - result.m[i][j] = qdir[i][j]; - return result; -} -template -constexpr Array qmul(const Array &a, const Array &b) -{ - return {a.x() * b.w() + a.w() * b.x() + a.y() * b.z() - a.z() * b.y(), - a.y() * b.w() + a.w() * b.y() + a.z() * b.x() - a.x() * b.z(), - a.z() * b.w() + a.w() * b.z() + a.x() * b.y() - a.y() * b.x(), - a.w() * b.w() - a.x() * b.x() - a.y() * b.y() - a.z() * b.z()}; -} -template -Array rotation_quat(const Array &axis, T angle) -{ - auto t = axis * std::sin(angle / 2); - return {t[0], t[1], t[2], std::cos(angle / 2)}; -} - -template -T uangle(const Array &a, const Array &b) -{ - T d = dot(a, b); - return d > 1 ? 0 : std::acos(d < -1 ? -1 : d); -} - -template -Array slerp2(const Array &a, const Array &b, T t) -{ - T th = uangle(a, b); - return th == 0 ? a : a * (std::sin(th * (1 - t)) / std::sin(th)) + b * (std::sin(th * t) / std::sin(th)); -} - -template -Array qslerp(const Array &a, const Array &b, T t) -{ - return slerp2(a, dot(a, b) < 0 ? -b : b, t); -} - -template -constexpr Array qrot(const Array &q, const Array &v) -{ - return qxdir(q) * v.x + qydir(q) * v.y + qzdir(q) * v.z; -} - -//! Based on the arcball in nanogui 1 -struct Arcball -{ - using Quaternionf = Vector4f; - - Arcball(float speedFactor = 2.0f) : - m_active(false), m_lastPos(0, 0), m_size(0, 0), m_quat(0, 0, 0, 1), m_incr(0, 0, 0, 1), - m_speedFactor(speedFactor) - { - } - - /** - * \brief The internal rotation of the Arcball. - * - * Call \ref Arcball::matrix for drawing loops, this method will not return - * any updates while \ref mActive is ``true``. - */ - Quaternionf &state() - { - return m_quat; - } - - /// ``const`` version of \ref Arcball::state. - const Quaternionf &state() const - { - return m_quat; - } - - /// Sets the rotation of this Arcball. The Arcball will be marked as **not** active. - void setState(const Quaternionf &state) - { - m_active = false; - m_lastPos = {0, 0}; - m_quat = state; - m_incr = {0, 0, 0, 1}; - } - - void setSize(Vector2i size) - { - m_size = size; - } - - const Vector2i &size() const - { - return m_size; - } - - void button(Vector2i pos, bool pressed) - { - m_active = pressed; - m_lastPos = pos; - if (!m_active) - m_quat = normalize(qmul(m_incr, m_quat)); - m_incr = {0, 0, 0, 1}; - } - - bool motion(Vector2i pos) - { - if (!m_active) - return false; - - /* Based on the rotation controller from AntTweakBar */ - float invMinDim = 1.0f / std::min(m_size.x(), m_size.y()); - float w = (float)m_size.x(), h = (float)m_size.y(); - - float ox = (m_speedFactor * (2 * m_lastPos.x() - w) + w) - w - 1.0f; - float tx = (m_speedFactor * (2 * pos.x() - w) + w) - w - 1.0f; - float oy = (m_speedFactor * (h - 2 * m_lastPos.y()) + h) - h - 1.0f; - float ty = (m_speedFactor * (h - 2 * pos.y()) + h) - h - 1.0f; - - ox *= invMinDim; - oy *= invMinDim; - tx *= invMinDim; - ty *= invMinDim; - - Vector3f v0(ox, oy, 1.0f), v1(tx, ty, 1.0f); - if (squared_norm(v0) > 1e-4f && squared_norm(v1) > 1e-4f) - { - v0 = normalize(v0); - v1 = normalize(v1); - Vector3f axis = cross(v0, v1); - float sa = std::sqrt(dot(axis, axis)), ca = dot(v0, v1), angle = std::atan2(sa, ca); - if (tx * tx + ty * ty > 1.0f) - angle *= 1.0f + 0.2f * (std::sqrt(tx * tx + ty * ty) - 1.0f); - m_incr = rotation_quat(normalize(axis), angle); - if (!std::isfinite(norm(m_incr))) - m_incr = {0, 0, 0, 1}; - } - return true; - } - - Matrix4f matrix() const - { - return qmat(qmul(m_incr, m_quat)); - } - -private: - /// Whether or not this Arcball is currently active. - bool m_active; - - /// The last click position (which triggered the Arcball to be active / non-active). - Vector2i m_lastPos; - - /// The size of this Arcball. - Vector2i m_size; - - /** - * The current stable state. When this Arcball is active, represents the - * state of this Arcball when \ref Arcball::button was called with - * ``down = true``. - */ - Quaternionf m_quat; - - /// When active, tracks the overall update to the state. Identity when non-active. - Quaternionf m_incr; - - /** - * The speed at which this Arcball rotates. Smaller values mean it rotates - * more slowly, higher values mean it rotates more quickly. - */ - float m_speedFactor; -}; - -NAMESPACE_END(nanogui) diff --git a/include/app.h b/include/app.h new file mode 100644 index 0000000..18e90e9 --- /dev/null +++ b/include/app.h @@ -0,0 +1,164 @@ +/** \file SampleViewer.h + \author Wojciech Jarosz +*/ + +#pragma once + +#include "common.h" +#include "linalg.h" +using namespace linalg::aliases; + +// define extra conversion here before including imgui, don't do it in the imconfig.h +#define IM_VEC2_CLASS_EXTRA \ + constexpr ImVec2(const float2 &f) : x(f.x), y(f.y) \ + { \ + } \ + operator float2() const \ + { \ + return float2(x, y); \ + } \ + constexpr ImVec2(const int2 &i) : x(i.x), y(i.y) \ + { \ + } \ + operator int2() const \ + { \ + return int2((int)x, (int)y); \ + } + +#define IM_VEC4_CLASS_EXTRA \ + constexpr ImVec4(const float4 &f) : x(f.x), y(f.y), z(f.z), w(f.w) \ + { \ + } \ + operator float4() const \ + { \ + return float4(x, y, z, w); \ + } + +#include "arcball.h" +#include "hello_imgui/hello_imgui.h" +#include "misc/cpp/imgui_stdlib.h" +#include "shader.h" +#include +#include +#include +#include +#include + +using std::map; +using std::ofstream; +using std::string; +using std::vector; + +enum CameraType +{ + CAMERA_XY = 0, + CAMERA_YZ, + CAMERA_XZ, + CAMERA_CURRENT, + CAMERA_2D, + CAMERA_PREVIOUS, + CAMERA_NEXT, + NUM_CAMERA_TYPES +}; + +struct CameraParameters +{ + Arcball arcball; + float persp_factor = 0.0f; + float zoom = 1.0f, view_angle = 30.0f; + float dnear = 0.05f, dfar = 1000.0f; + float3 eye = float3{0.0f, 0.0f, 2.0f}; + float3 center = float3{0.0f, 0.0f, 0.0f}; + float3 up = float3{0.0f, 1.0f, 0.0f}; + CameraType camera_type = CAMERA_CURRENT; + + float4x4 matrix(float window_aspect) const; +}; + +enum TextAlign : int +{ + // Horizontal align + TextAlign_LEFT = 1 << 0, // Default, align text horizontally to left. + TextAlign_CENTER = 1 << 1, // Align text horizontally to center. + TextAlign_RIGHT = 1 << 2, // Align text horizontally to right. + // Vertical align + TextAlign_TOP = 1 << 3, // Align text vertically to top. + TextAlign_MIDDLE = 1 << 4, // Align text vertically to middle. + TextAlign_BOTTOM = 1 << 5, // Align text vertically to bottom. +}; + +class SampleViewer +{ +public: + SampleViewer(); + virtual ~SampleViewer(); + + void draw_scene(); + void draw_gui(); + void run() + { + HelloImGui::Run(m_params); + } + +private: + string export_XYZ_points(const string &format); + string export_points_2d(const string &format, CameraType camera, int3 dim); + string export_all_points_2d(const string &format); + + void update_GPU_points(bool regenerate = true); + void update_GPU_grids(); + void set_view(CameraType view); + void draw_editor(); + void draw_about_dialog(); + void process_hotkeys(); + void populate_point_subset(); + void generate_grid(vector &positions, int gridRes); + void draw_text(const int2 &pos, const std::string &text, const float4 &col, ImFont *font = nullptr, + int align = TextAlign_RIGHT | TextAlign_BOTTOM) const; + void draw_points(const float4x4 &mvp, const float3 &color); + void draw_grid(const float4x4 &mvp, float alpha, uint32_t offset, uint32_t count); + void draw_2D_points_and_grid(const float4x4 &mvp, int2 dims, int plotIndex); + int2 get_draw_range() const; + + /// X, Y, Z, and user-defined cameras + CameraParameters m_camera[NUM_CAMERA_TYPES]; + int m_view = CAMERA_XY; + + int m_num_dimensions = 3; + int3 m_dimension{0, 1, 2}; + Array2d m_points, m_subset_points; + vector m_3d_points; + int m_target_point_count = 256, m_point_count = 256; + int m_subset_count = 0, m_coarse_line_count, m_fine_line_count; + + vector m_samplers; + int m_sampler = 0; + bool m_randomize = false; + float m_jitter = 80.f; + float m_radius = 0.5f; + bool m_scale_radius_with_points = true; + bool m_show_1d_projections = false, m_show_point_nums = false, m_show_point_coords = false, + m_show_coarse_grid = false, m_show_fine_grid = false, m_show_bbox = false; + + Shader *m_point_shader = nullptr, *m_grid_shader = nullptr, *m_point_2d_shader = nullptr; + + int2 m_viewport_pos, m_viewport_pos_GL, m_viewport_size; + float m_animate_start_time = 0.0f; + + bool m_subset_by_index = false; + int m_first_draw_point = 0; + int m_point_draw_count = 1; + bool m_subset_by_coord = false; + int m_subset_axis = 0; + int m_num_subset_levels = 1; + int m_subset_level = 0; + + map m_regular, m_bold; // regular and bold fonts at various sizes + + float m_time1 = 0.f, m_time2 = 0.f; + float3 m_point_color = {0.9f, 0.55f, 0.1f}; + float3 m_bg_color = {0.0f, 0.0f, 0.0f}; + HelloImGui::RunnerParams m_params; + + bool m_idling_backup = false; +}; diff --git a/include/arcball.h b/include/arcball.h new file mode 100644 index 0000000..258720d --- /dev/null +++ b/include/arcball.h @@ -0,0 +1,147 @@ +/** \file arcball.h + \author Wojciech Jarosz +*/ + +#include "linalg.h" +#include + +namespace linalg +{ +template +linalg::mat ortho_matrix(T left, T right, T bottom, T top, T near_, T far_) +{ + T rl = 1 / (right - left), tb = 1 / (top - bottom), fn = 1 / (far_ - near_); + return {{2 * rl, 0, 0, 0}, + {0, 2 * tb, 0, 0}, + {0, 0, -2 * fn, 0}, + {-(right + left) * rl, -(top + bottom) * tb, -(far_ + near_) * fn, 1}}; +} + +} // namespace linalg + +//! Based on the arcball in nanogui 1 +struct Arcball +{ + using Quatf = linalg::vec; + using Vec2i = linalg::vec; + using Vec3f = linalg::vec; + using Mat44f = linalg::mat; + + Arcball(float speed_factor = 2.f) : + m_active(false), m_last_pos(0, 0), m_size(0, 0), m_quat(0, 0, 0, 1), m_incr(0, 0, 0, 1), + m_speed_factor(speed_factor) + { + } + + /** + * \brief The internal rotation of the Arcball. + * + * Call \ref Arcball::matrix for drawing loops, this method will not return + * any updates while \ref m_active is ``true``. + */ + Quatf &state() + { + return m_quat; + } + + /// ``const`` version of \ref Arcball::state. + const Quatf &state() const + { + return m_quat; + } + + /// Sets the rotation of this Arcball. The Arcball will be marked as **not** active. + void set_state(const Quatf &state) + { + m_active = false; + m_last_pos = {0, 0}; + m_quat = state; + m_incr = {0, 0, 0, 1}; + } + + void set_size(Vec2i size) + { + m_size = size; + } + + const Vec2i &size() const + { + return m_size; + } + + void button(Vec2i pos, bool pressed) + { + m_active = pressed; + m_last_pos = pos; + if (!m_active) + m_quat = linalg::normalize(linalg::qmul(m_incr, m_quat)); + m_incr = {0, 0, 0, 1}; + } + + bool motion(Vec2i pos) + { + if (!m_active) + return false; + + /* Based on the rotation controller from AntTweakBar */ + float inv_min_dim = 1.f / std::min(m_size.x, m_size.y); + float w = (float)m_size.x, h = (float)m_size.y; + + float ox = (m_speed_factor * (2 * m_last_pos.x - w) + w) - w - 1.f; + float tx = (m_speed_factor * (2 * pos.x - w) + w) - w - 1.f; + float oy = (m_speed_factor * (h - 2 * m_last_pos.y) + h) - h - 1.f; + float ty = (m_speed_factor * (h - 2 * pos.y) + h) - h - 1.f; + + ox *= inv_min_dim; + oy *= inv_min_dim; + tx *= inv_min_dim; + ty *= inv_min_dim; + + Vec3f v0{ox, oy, 1.f}, v1{tx, ty, 1.f}; + if (linalg::length2(v0) > 1e-4f && linalg::length2(v1) > 1e-4f) + { + v0 = linalg::normalize(v0); + v1 = linalg::normalize(v1); + Vec3f axis = linalg::cross(v0, v1); + float sa = std::sqrt(dot(axis, axis)), ca = linalg::dot(v0, v1), angle = std::atan2(sa, ca); + if (tx * tx + ty * ty > 1.f) + angle *= 1.f + 0.2f * (std::sqrt(tx * tx + ty * ty) - 1.f); + m_incr = linalg::rotation_quat(linalg::normalize(axis), angle); + if (!std::isfinite(linalg::length(m_incr))) + m_incr = {0, 0, 0, 1}; + } + return true; + } + + Mat44f matrix() const + { + auto m = linalg::qmat(linalg::qmul(m_incr, m_quat)); + return {{m.x, 0.f}, {m.y, 0.f}, {m.z, 0.f}, {0.f, 0.f, 0.f, 1.f}}; + } + +private: + /// Whether or not this Arcball is currently active. + bool m_active; + + /// The last click position (which triggered the Arcball to be active / non-active). + Vec2i m_last_pos; + + /// The size of this Arcball. + Vec2i m_size; + + /** + * The current stable state. When this Arcball is active, represents the + * state of this Arcball when \ref Arcball::button was called with + * ``down = true``. + */ + Quatf m_quat; + + /// When active, tracks the overall update to the state. Identity when non-active. + Quatf m_incr; + + /** + * The speed at which this Arcball rotates. Smaller values mean it rotates + * more slowly, higher values mean it rotates more quickly. + */ + float m_speed_factor; +}; \ No newline at end of file diff --git a/include/common.h b/include/common.h new file mode 100644 index 0000000..2807eed --- /dev/null +++ b/include/common.h @@ -0,0 +1,27 @@ +// +// Copyright (C) Wojciech Jarosz . All rights reserved. +// Use of this source code is governed by a BSD-style license that can +// be found in the LICENSE.txt file. +// + +#pragma once + +#if defined(_MSC_VER) +// Make MS cmath define M_PI but not the min/max macros +#define _USE_MATH_DEFINES +#define NOMINMAX +#endif + +#include + +std::string to_lower(std::string str); +std::string to_upper(std::string str); + +int version_major(); +int version_minor(); +int version_patch(); +std::string version(); +std::string git_hash(); +std::string git_describe(); +std::string build_timestamp(); +std::string backend(); \ No newline at end of file diff --git a/include/export_to_file.h b/include/export_to_file.h new file mode 100644 index 0000000..ba732c1 --- /dev/null +++ b/include/export_to_file.h @@ -0,0 +1,22 @@ +/** \file export_to_file.h + \author Wojciech Jarosz +*/ + +#pragma once + +#include "linalg.h" +#include +#include +using namespace linalg::aliases; + +std::string header_eps(const float3 &point_color, float scale, float radius); +std::string footer_eps(); +std::string draw_grid_eps(const float4x4 &mvp, int grid_res); +std::string draw_grids_eps(float4x4 mat, int fgrid_res, int cgrid_res, bool fine_grid, bool coarse_grid, bool bbox); +std::string draw_points_eps(float4x4 mat, int3 dim, const Array2d &points, int2 range); + +std::string header_svg(const float3 &point_color, float scale = 1.f); +std::string footer_svg(); +std::string draw_grid_svg(const float4x4 &mvp, int grid_res, const std::string &css_class); +std::string draw_grids_svg(float4x4 mat, int fgrid_res, int cgrid_res, bool fine_grid, bool coarse_grid, bool bbox); +std::string draw_points_svg(float4x4 mat, int3 dim, const Array2d &points, int2 range, float radius); \ No newline at end of file diff --git a/include/imgui_ext.h b/include/imgui_ext.h new file mode 100644 index 0000000..5f44ca4 --- /dev/null +++ b/include/imgui_ext.h @@ -0,0 +1,36 @@ +#include "imgui.h" + +namespace ImGui +{ + +inline bool ToggleButton(const char *label, bool *active) +{ + ImGui::PushStyleColor(ImGuiCol_Button, *active ? GetColorU32(ImGuiCol_ButtonActive) : GetColorU32(ImGuiCol_Button)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, GetColorU32(ImGuiCol_FrameBgHovered)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, GetColorU32(ImGuiCol_FrameBgActive)); + + bool ret; + if ((ret = ImGui::Button(label))) + *active = !*active; + ImGui::PopStyleColor(3); + return ret; +} + +inline void Text(const string &text) +{ + return TextUnformatted(text.c_str()); +} + +// return true when activated. +inline bool MenuItem(const string &label, const string &shortcut = "", bool selected = false, bool enabled = true) +{ + return MenuItem(label.c_str(), shortcut.c_str(), selected, enabled); +} + +// return true when activated + toggle (*p_selected) if p_selected != NULL +inline bool MenuItem(const string &label, const string &shortcut, bool *p_selected, bool enabled = true) +{ + return MenuItem(label.c_str(), shortcut.c_str(), p_selected, enabled); +} + +} // namespace ImGui \ No newline at end of file diff --git a/include/sampler/Halton.h b/include/sampler/Halton.h index e1d4ea9..a51788b 100644 --- a/include/sampler/Halton.h +++ b/include/sampler/Halton.h @@ -3,60 +3,81 @@ */ #pragma once -#include -#include -#include -#include - +#include // for pcg32 +#include // for TSamplerMinMaxDim +#include // for Halton_sampler +#include // for basic_string, string /// A Halton quasi-random number sequence. /** A wrapper for L. Gruenschloss's fast Halton sampler. */ -class Halton : public TSamplerMinMaxDim<1,256> +class Halton : public TSamplerMinMaxDim<1, 256> { public: Halton(unsigned dimensions = 2); - ~Halton() override {} + ~Halton() override + { + } void sample(float[], unsigned i) override; - unsigned dimensions() const override {return m_numDimensions;} + unsigned dimensions() const override + { + return m_numDimensions; + } void setDimensions(unsigned) override; // Init the permutation arrays using Faure-permutations. - void initFaure() {m_halton.init_faure();} + void initFaure() + { + m_halton.init_faure(); + } // Init the permutation arrays using randomized permutations. void initRandom(); /// Gets/sets whether the point set is randomized - bool randomized() const override {return m_randomized;} + bool randomized() const override + { + return m_randomized; + } void setRandomized(bool r = true) override; - std::string name() const override {return "Halton";} + std::string name() const override + { + return "Halton"; + } protected: - unsigned m_numDimensions; - bool m_randomized; - pcg32 m_rand; + unsigned m_numDimensions; + bool m_randomized; + pcg32 m_rand; Halton_sampler m_halton; }; - /// Encapsulate a Halton-Zaremba sequence quasi-random number generator. -class HaltonZaremba : public TSamplerMinMaxDim<1,(unsigned)(-1)-1> +class HaltonZaremba : public TSamplerMinMaxDim<1, (unsigned)(-1) - 1> { public: HaltonZaremba(unsigned dimensions = 2); - ~HaltonZaremba() override {} + ~HaltonZaremba() override + { + } void sample(float[], unsigned i) override; - unsigned dimensions() const override {return m_numDimensions;} + unsigned dimensions() const override + { + return m_numDimensions; + } void setDimensions(unsigned) override; - std::string name() const override {return "Halton-Zaremba";} + std::string name() const override + { + return "Halton-Zaremba"; + } + protected: unsigned m_numDimensions; }; diff --git a/include/sampler/Jittered.h b/include/sampler/Jittered.h index 954e22c..12f3b98 100644 --- a/include/sampler/Jittered.h +++ b/include/sampler/Jittered.h @@ -3,9 +3,8 @@ */ #pragma once -#include #include -#include +#include /// Encapsulate a 2D stratified or "jittered" point set. class Jittered : public TSamplerDim<2> @@ -19,13 +18,16 @@ class Jittered : public TSamplerDim<2> std::string name() const override; - int numSamples() const override {return m_resX*m_resY;} + int numSamples() const override + { + return m_resX * m_resY; + } int setNumSamples(unsigned n) override { - int sqrtVal = (n == 0) ? 1 : (int) (std::sqrt((float) n) + 0.5f); + int sqrtVal = (n == 0) ? 1 : (int)(std::sqrt((float)n) + 0.5f); m_resX = m_resY = sqrtVal; reset(); - return m_resX*m_resY; + return m_resX * m_resY; } void setNumSamples(unsigned x, unsigned y) { @@ -34,7 +36,10 @@ class Jittered : public TSamplerDim<2> reset(); } - bool randomized() const override {return m_randomize;} + bool randomized() const override + { + return m_randomize; + } void setRandomized(bool r = true) override { if ((m_randomize = r)) @@ -42,15 +47,21 @@ class Jittered : public TSamplerDim<2> } /// Gets/sets how much the points are jittered - float jitter() const override {return m_maxJit;} - float setJitter(float j = 1.0f) override {return m_maxJit = j;} + float jitter() const override + { + return m_maxJit; + } + float setJitter(float j = 1.0f) override + { + return m_maxJit = j; + } private: unsigned m_resX, m_resY; - float m_maxJit; + float m_maxJit; - float m_xScale, m_yScale; - bool m_randomize = true; - pcg32 m_rand; + float m_xScale, m_yScale; + bool m_randomize = true; + pcg32 m_rand; unsigned m_seed = 13; }; diff --git a/include/sampler/LP.h b/include/sampler/LP.h index 3a03c73..1f6a79f 100644 --- a/include/sampler/LP.h +++ b/include/sampler/LP.h @@ -3,8 +3,9 @@ */ #pragma once -#include -#include +#include // for pcg32 +#include // for TSamplerMinMaxDim +#include // for basic_string, string /// The Larcher-Pillichshammer Gruenschloss-Keller (0,3) sequence /** @@ -23,25 +24,37 @@ in P. L'Ecuyer and A. Owen (eds.), Monte Carlo and Quasi-Monte Carlo Methods 2008, Springer-Verlag, 2009. */ -class LarcherPillichshammerGK : public TSamplerMinMaxDim<1,3> +class LarcherPillichshammerGK : public TSamplerMinMaxDim<1, 3> { public: - LarcherPillichshammerGK(unsigned dimensions = 2, - unsigned numSamples = 64, - bool randomize = false); + LarcherPillichshammerGK(unsigned dimensions = 2, unsigned numSamples = 64, bool randomize = false); ~LarcherPillichshammerGK() override; void sample(float[], unsigned i) override; - unsigned dimensions() const override {return m_numDimensions;} + unsigned dimensions() const override + { + return m_numDimensions; + } + + void setDimensions(unsigned d) override + { + m_numDimensions = d; + } - std::string name() const override {return "LP-GK";} + std::string name() const override + { + return "LP-GK"; + } - int numSamples() const override {return m_numSamples;} + int numSamples() const override + { + return m_numSamples; + } int setNumSamples(unsigned n) override { m_numSamples = (n == 0) ? 1 : n; - m_inv = 1.0f / m_numSamples; + m_inv = 1.0f / m_numSamples; return m_numSamples; } @@ -63,7 +76,7 @@ class LarcherPillichshammerGK : public TSamplerMinMaxDim<1,3> protected: unsigned m_numSamples, m_numDimensions; - float m_inv; - pcg32 m_rand; + float m_inv; + pcg32 m_rand; unsigned m_scramble1, m_scramble2, m_scramble3; }; diff --git a/include/sampler/MultiJittered.h b/include/sampler/MultiJittered.h index 0d8ccbd..d128cda 100644 --- a/include/sampler/MultiJittered.h +++ b/include/sampler/MultiJittered.h @@ -3,10 +3,10 @@ */ #pragma once -#include -#include -#include -#include +#include // for sqrt +#include // for pcg32 +#include // for TSamplerDim, TSamplerMinMaxDim +#include // for basic_string, string /// A multi-jittered point set with both jittered and n-rooks stratification. /** @@ -27,44 +27,54 @@ class MultiJittered : public TSamplerDim<2> void reset() override; void sample(float[], unsigned i) override; - bool randomized() const override {return m_randomize;} - void setRandomized(bool r) override {m_randomize = r; reset();} - - std::string name() const override {return "Multi-Jittered";} + bool randomized() const override + { + return m_randomize; + } + void setRandomized(bool r) override + { + m_randomize = r; + reset(); + } + std::string name() const override + { + return "Multi-Jittered"; + } - int numSamples() const override {return m_numSamples;} + int numSamples() const override + { + return m_numSamples; + } int setNumSamples(unsigned n) override { if (n == m_numSamples) return m_numSamples; - int sqrtVal = (n == 0) ? 1 : (int) (std::sqrt((float) n) + 0.5f); + int sqrtVal = (n == 0) ? 1 : (int)(std::sqrt((float)n) + 0.5f); setNumSamples(sqrtVal, sqrtVal); return m_numSamples; } void setNumSamples(unsigned x, unsigned y) { - m_resX = x; - m_resY = y; + m_resX = x; + m_resY = y; m_numSamples = m_resX * m_resY; reset(); } protected: unsigned m_resX, m_resY, m_numSamples; - bool m_randomize; - float m_maxJit; + bool m_randomize; + float m_maxJit; float m_scale; - float** m_samples = nullptr; + float **m_samples = nullptr; - pcg32 m_rand; + pcg32 m_rand; unsigned m_seed = 13; }; - - /// An in-place version of multi-jittered point set with both jittered and n-rooks stratification. /** Produces standard multi-jittered points, but uses 2*sqrt(numSamples) permutation arrays @@ -80,44 +90,57 @@ class MultiJitteredInPlace : public TSamplerDim<2> void reset() override; void sample(float[], unsigned i) override; - bool randomized() const override {return m_randomize;} + bool randomized() const override + { + return m_randomize; + } void setRandomized(bool r) override; - float jitter() const override {return m_maxJit;} - float setJitter(float j = 1.0f) override {m_maxJit = j; reset(); return m_maxJit;} + float jitter() const override + { + return m_maxJit; + } + float setJitter(float j = 1.0f) override + { + m_maxJit = j; + reset(); + return m_maxJit; + } std::string name() const override; - int numSamples() const override {return m_numSamples;} + int numSamples() const override + { + return m_numSamples; + } int setNumSamples(unsigned n) override { if (n == m_numSamples) return m_numSamples; - int sqrtVal = (n == 0) ? 1 : (int) (std::sqrt((float) n) + 0.5f); + int sqrtVal = (n == 0) ? 1 : (int)(std::sqrt((float)n) + 0.5f); setNumSamples(sqrtVal, sqrtVal); return m_numSamples; } void setNumSamples(unsigned x, unsigned y) { - m_resX = x; - m_resY = y; + m_resX = x; + m_resY = y; m_numSamples = m_resX * m_resY; reset(); } protected: unsigned m_resX, m_resY, m_numSamples; - bool m_randomize; - float m_maxJit; + bool m_randomize; + float m_maxJit; float m_scale; - pcg32 m_rand; + pcg32 m_rand; unsigned m_seed = 13; unsigned m_permutation; }; - /// Correlated multi-jittered point sets /** Based on method described in the tech report: @@ -134,10 +157,12 @@ class CorrelatedMultiJittered : public MultiJittered void reset() override; - std::string name() const override {return "Correlated Multi-Jittered";} + std::string name() const override + { + return "Correlated Multi-Jittered"; + } }; - /// An in-place version of correlated multi-jittered point sets. /** Based on method described in the tech report: @@ -145,50 +170,74 @@ class CorrelatedMultiJittered : public MultiJittered > Andrew Kensler. "Correlated Multi-Jittered Sampling", > Pixar Technical Memo 13-01. */ -class CorrelatedMultiJitteredInPlace : public TSamplerMinMaxDim<1,1024> +class CorrelatedMultiJitteredInPlace : public TSamplerMinMaxDim<1, 1024> { public: - CorrelatedMultiJitteredInPlace(unsigned x, unsigned y, unsigned dimensions = 2, bool randomize = true, float jitter = 0.0f); + CorrelatedMultiJitteredInPlace(unsigned x, unsigned y, unsigned dimensions = 2, bool randomize = true, + float jitter = 0.0f); ~CorrelatedMultiJitteredInPlace() override; void sample(float[], unsigned i) override; - unsigned dimensions() const override {return m_numDimensions;} - void setDimensions(unsigned n) override {m_numDimensions = n;} + unsigned dimensions() const override + { + return m_numDimensions; + } + void setDimensions(unsigned n) override + { + m_numDimensions = n; + } - int numSamples() const override {return m_numSamples;} + int numSamples() const override + { + return m_numSamples; + } int setNumSamples(unsigned n) override { if (n == m_numSamples) return m_numSamples; - int sqrtVal = (n == 0) ? 1 : (int) (std::sqrt((float) n) + 0.5f); + int sqrtVal = (n == 0) ? 1 : (int)(std::sqrt((float)n) + 0.5f); setNumSamples(sqrtVal, sqrtVal); return m_numSamples; } void setNumSamples(unsigned x, unsigned y) { - m_resX = x; - m_resY = y; + m_resX = x; + m_resY = y; m_numSamples = m_resX * m_resY; } - bool randomized() const override {return m_randomize;} + bool randomized() const override + { + return m_randomize; + } void setRandomized(bool r) override { - m_randomize = r; + m_randomize = r; m_permutation = r ? m_rand.nextUInt() : 0; } - float jitter() const override {return m_maxJit;} - float setJitter(float j = 1.0f) override {m_maxJit = j; reset(); return m_maxJit;} + float jitter() const override + { + return m_maxJit; + } + float setJitter(float j = 1.0f) override + { + m_maxJit = j; + reset(); + return m_maxJit; + } - std::string name() const override {return "Correlated Multi-Jittered In-Place";} + std::string name() const override + { + return "Correlated Multi-Jittered In-Place"; + } protected: unsigned m_resX, m_resY, m_numSamples, m_numDimensions; - float m_maxJit; - bool m_randomize; - pcg32 m_rand; + float m_maxJit; + bool m_randomize; + pcg32 m_rand; unsigned m_seed = 13; unsigned m_permutation; }; \ No newline at end of file diff --git a/include/sampler/OA.h b/include/sampler/OA.h index cfc4d67..b1847fb 100644 --- a/include/sampler/OA.h +++ b/include/sampler/OA.h @@ -3,8 +3,9 @@ */ #pragma once -#include -#include +#include // for TSamplerMinMaxDim +#include // for basic_string, string +#include // for vector enum OffsetType : unsigned { @@ -24,26 +25,35 @@ enum OffsetType : unsigned class OrthogonalArray : public TSamplerMinMaxDim<2, (unsigned)-1> { public: - OrthogonalArray(unsigned t = 2, unsigned ot = CENTERED, - bool randomize = false, float jitter = 0.0f) - : m_t(t), m_ot(ot), m_randomize(randomize), - m_maxJit(jitter) + OrthogonalArray(unsigned t = 2, unsigned ot = CENTERED, bool randomize = false, float jitter = 0.0f) : + m_t(t), m_ot(ot), m_randomize(randomize), m_maxJit(jitter) { } - ~OrthogonalArray() override {} + ~OrthogonalArray() override + { + } - virtual unsigned strength() const { return m_t; } + virtual unsigned strength() const + { + return m_t; + } virtual unsigned setStrength(unsigned t) { return m_t = (t > 1) ? t : m_t; } - virtual unsigned offsetType() const { return m_ot; } - virtual unsigned setOffsetType(unsigned ot); + virtual unsigned offsetType() const + { + return m_ot; + } + virtual unsigned setOffsetType(unsigned ot); virtual std::vector offsetTypeNames() const; - bool randomized() const override { return m_randomize; } + bool randomized() const override + { + return m_randomize; + } void setRandomized(bool r) override { if ((m_randomize = r)) @@ -52,7 +62,10 @@ class OrthogonalArray : public TSamplerMinMaxDim<2, (unsigned)-1> m_seed = 0; } - float jitter() const override { return m_maxJit; } + float jitter() const override + { + return m_maxJit; + } float setJitter(float j = 1.0f) override { m_maxJit = j; @@ -60,12 +73,15 @@ class OrthogonalArray : public TSamplerMinMaxDim<2, (unsigned)-1> return m_maxJit; } - std::string name() const override { return "Abstract Orthogonal Array"; } + std::string name() const override + { + return "Abstract Orthogonal Array"; + } protected: - unsigned m_t; ///< strength - unsigned m_ot; ///< offset type - bool m_randomize; - unsigned m_seed; ///< randomization/permutation seed - float m_maxJit; ///< amount to jitter within each substratum + unsigned m_t; ///< strength + unsigned m_ot; ///< offset type + bool m_randomize; + unsigned m_seed; ///< randomization/permutation seed + float m_maxJit; ///< amount to jitter within each substratum }; \ No newline at end of file diff --git a/include/sampler/Sobol.h b/include/sampler/Sobol.h index 10554ac..7f61b3c 100644 --- a/include/sampler/Sobol.h +++ b/include/sampler/Sobol.h @@ -3,40 +3,49 @@ */ #pragma once -#include #include +#include #include - /// A Sobol quasi-random number sequence. /** A wrapper for L. Gruenschloss's fast Sobol sampler. */ -class Sobol : public TSamplerMinMaxDim<1,1024> +class Sobol : public TSamplerMinMaxDim<1, 1024> { public: Sobol(unsigned dimensions = 2); void sample(float[], unsigned i) override; - unsigned dimensions() const override {return m_numDimensions;} - void setDimensions(unsigned n) override {m_numDimensions = n;} + unsigned dimensions() const override + { + return m_numDimensions; + } + void setDimensions(unsigned n) override + { + m_numDimensions = n; + } - bool randomized() const override {return m_scrambles.size();} + bool randomized() const override + { + return m_scrambles.size(); + } void setRandomized(bool b = true) override; - std::string name() const override {return "Sobol";} + std::string name() const override + { + return "Sobol"; + } protected: - unsigned m_numDimensions; - pcg32 m_rand; + unsigned m_numDimensions; + pcg32 m_rand; std::vector m_scrambles; }; - - /// A (0,2) sequence created by padding the first two dimensions of Sobol -class ZeroTwo : public TSamplerMinMaxDim<1,1024> +class ZeroTwo : public TSamplerMinMaxDim<1, 1024> { public: ZeroTwo(unsigned n = 64, unsigned dimensions = 2, bool shuffle = false); @@ -44,26 +53,45 @@ class ZeroTwo : public TSamplerMinMaxDim<1,1024> void reset() override; void sample(float[], unsigned i) override; - unsigned dimensions() const override {return m_numDimensions;} - void setDimensions(unsigned d) override {m_numDimensions = d; reset();} + unsigned dimensions() const override + { + return m_numDimensions; + } + void setDimensions(unsigned d) override + { + m_numDimensions = d; + reset(); + } - bool randomized() const override {return m_randomize;} - void setRandomized(bool r) override {m_randomize = r; reset();} + bool randomized() const override + { + return m_randomize; + } + void setRandomized(bool r) override + { + m_randomize = r; + reset(); + } - int numSamples() const override {return m_numSamples;} + int numSamples() const override + { + return m_numSamples; + } int setNumSamples(unsigned n) override; - std::string name() const override {return m_shuffle ? "Shuffled+XORed (0,2)" : "XORed (0,2)";} + std::string name() const override + { + return m_shuffle ? "Shuffled+XORed (0,2)" : "XORed (0,2)"; + } protected: - unsigned m_numSamples, m_numDimensions; - bool m_randomize, m_shuffle; - pcg32 m_rand; + unsigned m_numSamples, m_numDimensions; + bool m_randomize, m_shuffle; + pcg32 m_rand; std::vector m_scrambles; std::vector m_permutes; }; - // Copyright (c) 2012 Leonhard Gruenschloss (leonhard@gruenschloss.org) // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -84,11 +112,10 @@ class ZeroTwo : public TSamplerMinMaxDim<1,1024> // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. - struct SobolMatrices { static const unsigned numDimensions = 1024; - static const unsigned size = 52; + static const unsigned size = 52; static const unsigned matrices[]; }; @@ -97,8 +124,7 @@ struct SobolMatrices // the point inside the sequence. The scramble parameter can be used // to permute elementary intervals, and might be chosen randomly to // generate a randomized QMC sequence. -inline float sobol(unsigned long long index, unsigned dimension, - unsigned scramble = 0U) +inline float sobol(unsigned long long index, unsigned dimension, unsigned scramble = 0U) { assert(dimension < SobolMatrices::numDimensions); diff --git a/include/shader.h b/include/shader.h new file mode 100644 index 0000000..97480d3 --- /dev/null +++ b/include/shader.h @@ -0,0 +1,263 @@ +/** + \file shader.h +*/ +#pragma once + +#include "linalg.h" +#include "traits.h" +#include +#include + +/// An abstraction for shaders that work with OpenGL, OpenGL ES, and (at some point down the road, hopefully) Metal. +/* + This is adapted from NanoGUI's Shader class. Copyright follows. + ---------- + NanoGUI was developed by Wenzel Jakob . + The widget drawing code is based on the NanoVG demo application + by Mikko Mononen. + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE.txt file. +*/ +class Shader +{ +public: + /// The type of geometry that should be rendered + enum class PrimitiveType + { + Point, + Line, + LineStrip, + Triangle, + TriangleStrip + }; + + /// Alpha blending mode + enum class BlendMode + { + None, + AlphaBlend // alpha * new_color + (1 - alpha) * old_color + }; + + /** + Initialize the shader using the source files (read from the assets directory). + + \param name + A name identifying this shader + + \param vs_filename + Filename of the vertex shader source code. + + \param fs_filename + Filename of the fragment shader source code. + */ + Shader(const std::string &name, const std::string &vs_filename, const std::string &fs_filename, + BlendMode blend_mode = BlendMode::None); + + /// Return the render pass associated with this shader + // RenderPass *render_pass() { return m_render_pass; } + + /// Return the name of this shader + const std::string &name() const + { + return m_name; + } + + /// Return the blending mode of this shader + BlendMode blend_mode() const + { + return m_blend_mode; + } + + /** + Upload a buffer (e.g. vertex positions) that will be associated with a named shader parameter. + + Note that this function should be used both for 'varying' and 'uniform' + data---the implementation takes care of routing the data to the right + endpoint. Matrices should be specified in column-major order. + + The buffer will be replaced if it is already present. + */ + void set_buffer(const std::string &name, VariableType type, size_t ndim, const size_t *shape, const void *data); + + void set_buffer(const std::string &name, VariableType type, std::initializer_list shape, const void *data) + { + set_buffer(name, type, shape.end() - shape.begin(), shape.begin(), data); + } + + // std::vectors + template + void set_buffer(const std::string &name, const std::vector> &mats) + { + size_t shape[3] = {mats.size(), M, N}; + set_buffer(name, get_type(), 3, shape, mats.data()); + } + + template + void set_buffer(const std::string &name, const std::vector> &vecs) + { + size_t shape[3] = {vecs.size(), M, 1}; + set_buffer(name, get_type(), 2, shape, vecs.data()); + } + + template + void set_buffer(const std::string &name, const std::vector &data) + { + size_t shape[3] = {data.size(), 1, 1}; + set_buffer(name, get_type(), 1, shape, data.data()); + } + + // set_uniform + template + void set_uniform(const std::string &name, const linalg::vec &value) + { + size_t shape[3] = {M, 1, 1}; + set_buffer(name, get_type(), 1, shape, &value); + } + + template + void set_uniform(const std::string &name, const linalg::mat &value) + { + size_t shape[3] = {M, N, 1}; + set_buffer(name, get_type(), 2, shape, &value); + } + + /// Upload a uniform variable (e.g. a vector or matrix) that will be associated with a named shader parameter. + template + void set_uniform(const std::string &name, const T &value) + { + size_t shape[3] = {1, 1, 1}; + size_t ndim = (size_t)-1; + const void *data; + VariableType vtype = VariableType::Invalid; + + if constexpr (std::is_scalar_v) + { + data = &value; + ndim = 0; + vtype = get_type(); + } + + if (ndim == (size_t)-1) + throw std::runtime_error("Shader::set_uniform(): invalid input array dimension!"); + + set_buffer(name, vtype, ndim, shape, data); + } + + // /** + // * \brief Associate a texture with a named shader parameter + // * + // * The association will be replaced if it is already present. + // */ + // void set_texture(const std::string &name, Texture *texture); + + /** + Begin drawing using this shader + + Note that any updates to 'uniform' and 'varying' shader parameters + *must* occur prior to this method call. + + The Python bindings also include extra \c __enter__ and \c __exit__ + aliases so that the shader can be activated via Pythons 'with' + statement. + */ + void begin(); + + /// End drawing using this shader + void end(); + + /** + Render geometry arrays, either directly or using an index array. + + \param primitive_type + What type of geometry should be rendered? + + \param offset + First index to render. Must be a multiple of 2 or 3 for lines and triangles, respectively (unless specified + using strips). + + \param offset + Number of indices to render. Must be a multiple of 2 or 3 for lines and triangles, respectively (unless + specified using strips). + + \param indexed + Render indexed geometry? In this case, an \c uint32_t valued buffer with name \c indices must have been + uploaded using \ref set(). + */ + void draw_array(PrimitiveType primitive_type, size_t offset, size_t count, bool indexed = false); + +#if defined(HELLOIMGUI_HAS_OPENGL) + uint32_t shader_handle() const + { + return m_shader_handle; + } +#elif defined(HELLOIMGUI_USE_METAL) + void *pipeline_state() const + { + return m_pipeline_state; + } +#endif + +#if defined(HELLOIMGUI_USE_GLAD) + uint32_t vertex_array_handle() const + { + return m_vertex_array_handle; + } +#endif + +protected: + enum BufferType + { + Unknown = 0, + VertexBuffer, + VertexTexture, + VertexSampler, + FragmentBuffer, + FragmentTexture, + FragmentSampler, + UniformBuffer, + IndexBuffer, + }; + + struct Buffer + { + void *buffer = nullptr; + BufferType type = Unknown; + VariableType dtype = VariableType::Invalid; + int index = 0; + size_t ndim = 0; + size_t shape[3]{0, 0, 0}; + size_t size = 0; + bool dirty = false; + + std::string to_string() const; + }; + + /// Release all resources + virtual ~Shader(); + +protected: + // RenderPass* m_render_pass; + std::string m_name; + std::unordered_map m_buffers; + BlendMode m_blend_mode; + +#if defined(HELLOIMGUI_HAS_OPENGL) + uint32_t m_shader_handle = 0; +#if defined(HELLOIMGUI_USE_GLAD) + uint32_t m_vertex_array_handle = 0; + bool m_uses_point_size = false; +#endif +#elif defined(HELLOIMGUI_USE_METAL) + void *m_pipeline_state; +#endif +}; + +#define CHK(cmd) \ + do \ + { \ + cmd; \ + (void)check_glerror(#cmd); \ + } while (0) + +bool check_glerror(const char *cmd); diff --git a/include/timer.h b/include/timer.h new file mode 100644 index 0000000..3a0a227 --- /dev/null +++ b/include/timer.h @@ -0,0 +1,49 @@ +// +// Copyright (C) Wojciech Jarosz . All rights reserved. +// Use of this source code is governed by a BSD-style license that can +// be found in the LICENSE.txt file. +// + +#pragma once + +#include + +//! Simple timer with millisecond precision +/*! + This class is convenient for collecting performance data +*/ +class Timer +{ +public: + //! Create a new timer and reset it + Timer() + { + reset(); + } + + //! Reset the timer to the current time + void reset() + { + start = std::chrono::system_clock::now(); + } + + //! Return the number of milliseconds elapsed since the timer was last reset + double elapsed() const + { + auto now = std::chrono::system_clock::now(); + auto duration = std::chrono::duration_cast(now - start); + return (double)duration.count(); + } + + //! Return the number of milliseconds elapsed since the timer was last reset and then reset it + double lap() + { + auto now = std::chrono::system_clock::now(); + auto duration = std::chrono::duration_cast(now - start); + start = now; + return (double)duration.count(); + } + +private: + std::chrono::system_clock::time_point start; +}; diff --git a/include/traits.h b/include/traits.h new file mode 100644 index 0000000..e4930f4 --- /dev/null +++ b/include/traits.h @@ -0,0 +1,110 @@ +/* + NanoGUI was developed by Wenzel Jakob . + The widget drawing code is based on the NanoVG demo application + by Mikko Mononen. + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE.txt file. +*/ + +#pragma once + +#include +#include + +/// Listing of various field types that can be used as variables in shaders +enum class VariableType +{ + Invalid = 0, + Int8, + UInt8, + Int16, + UInt16, + Int32, + UInt32, + Int64, + UInt64, + Float16, + Float32, + Float64, + Bool +}; + +/// Convert from a C++ type to an element of \ref VariableType +template +constexpr VariableType get_type() +{ + if constexpr (std::is_same_v) + return VariableType::Bool; + + if constexpr (std::is_integral_v) + { + if constexpr (sizeof(T) == 1) + return std::is_signed_v ? VariableType::Int8 : VariableType::UInt8; + else if constexpr (sizeof(T) == 2) + return std::is_signed_v ? VariableType::Int16 : VariableType::UInt16; + else if constexpr (sizeof(T) == 4) + return std::is_signed_v ? VariableType::Int32 : VariableType::UInt32; + else if constexpr (sizeof(T) == 8) + return std::is_signed_v ? VariableType::Int64 : VariableType::UInt64; + } + else if constexpr (std::is_floating_point_v) + { + if constexpr (sizeof(T) == 2) + return VariableType::Float16; + else if constexpr (sizeof(T) == 4) + return VariableType::Float32; + else if constexpr (sizeof(T) == 8) + return VariableType::Float64; + } + else + { + return VariableType::Invalid; + } +} + +/// Return the size in bytes associated with a specific variable type +inline size_t type_size(VariableType type) +{ + switch (type) + { + case VariableType::UInt8: + case VariableType::Int8: + case VariableType::Bool: return 1; + + case VariableType::UInt16: + case VariableType::Int16: + case VariableType::Float16: return 2; + + case VariableType::UInt32: + case VariableType::Int32: + case VariableType::Float32: return 4; + + case VariableType::UInt64: + case VariableType::Int64: + case VariableType::Float64: return 8; + + default: throw std::runtime_error("Unknown type!"); + } +} + +/// Return the name (e.g. "uint8") associated with a specific variable type +inline const char *type_name(VariableType type) +{ + switch (type) + { + case VariableType::Bool: return "bool"; + case VariableType::UInt8: return "uint8"; + case VariableType::Int8: return "int8"; + case VariableType::UInt16: return "uint16"; + case VariableType::Int16: return "int16"; + case VariableType::UInt32: return "uint32"; + case VariableType::Int32: return "int32"; + case VariableType::UInt64: return "uint64"; + case VariableType::Int64: return "int64"; + case VariableType::Float16: return "float16"; + case VariableType::Float32: return "float32"; + case VariableType::Float64: return "float64"; + default: return "invalid"; + } +} diff --git a/macos/Info.plist b/macos/Info.plist new file mode 100644 index 0000000..82a5d22 --- /dev/null +++ b/macos/Info.plist @@ -0,0 +1,29 @@ + + + + + CFBundleIdentifier + ${HELLO_IMGUI_BUNDLE_IDENTIFIER} + + CFBundleName + ${HELLO_IMGUI_BUNDLE_NAME} + + CFBundleDisplayName + ${HELLO_IMGUI_ICON_DISPLAY_NAME} + + CFBundleExecutable + ${HELLO_IMGUI_BUNDLE_EXECUTABLE} + + CFBundleShortVersionString + ${HELLO_IMGUI_BUNDLE_SHORT_VERSION} + + CFBundleVersion + ${HELLO_IMGUI_BUNDLE_VERSION} + + NSHumanReadableCopyright + ${HELLO_IMGUI_BUNDLE_COPYRIGHT} + + CFBundleIconFile + ${HELLO_IMGUI_BUNDLE_ICON_FILE} + + diff --git a/macos/icon.icns b/macos/icon.icns new file mode 100644 index 0000000..fd6b553 Binary files /dev/null and b/macos/icon.icns differ diff --git a/resources/MacOSXBundleInfo.plist.in b/resources/MacOSXBundleInfo.plist.in deleted file mode 100644 index c330af7..0000000 --- a/resources/MacOSXBundleInfo.plist.in +++ /dev/null @@ -1,252 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - ${MACOSX_BUNDLE_EXECUTABLE_NAME} - CFBundleGetInfoString - ${MACOSX_BUNDLE_INFO_STRING} - CFBundleIconFile - ${MACOSX_BUNDLE_ICON_FILE} - CFBundleIdentifier - ${MACOSX_BUNDLE_GUI_IDENTIFIER} - CFBundleInfoDictionaryVersion - 6.0 - CFBundleLongVersionString - ${MACOSX_BUNDLE_LONG_VERSION_STRING} - CFBundleName - ${MACOSX_BUNDLE_BUNDLE_NAME} - CFBundlePackageType - APPL - CFBundleShortVersionString - ${MACOSX_BUNDLE_SHORT_VERSION_STRING} - CFBundleSignature - ???? - CFBundleVersion - ${MACOSX_BUNDLE_BUNDLE_VERSION} - NSHumanReadableCopyright - ${MACOSX_BUNDLE_COPYRIGHT} - NSHighResolutionCapable - True - CFBundleDocumentTypes - - - CFBundleTypeExtensions - - EXR - exr - - CFBundleTypeIconFile - Document-EXR - CFBundleTypeMIMETypes - - image/exr - - CFBundleTypeOSTypes - - exr - EXR - - CFBundleTypeRole - Viewer - LSItemContentTypes - - com.ilm.openexr-image - - - - CFBundleTypeExtensions - - bmp - BMP - - CFBundleTypeIconFile - Document-BMP - CFBundleTypeMIMETypes - - image/bmp - image/x-bmp - image/x-windows-bmp - image/ms-bmp - image/x-ms-bmp - application/bmp - - CFBundleTypeOSTypes - - BMP - BMPf - - CFBundleTypeRole - Viewer - LSItemContentTypes - - com.microsoft.bmp - - - - CFBundleTypeExtensions - - gif - GIF - - CFBundleTypeIconFile - Document-GIF - CFBundleTypeMIMETypes - - image/gif - - CFBundleTypeOSTypes - - GIFf - - CFBundleTypeRole - Viewer - LSItemContentTypes - - com.compuserve.gif - - - - CFBundleTypeExtensions - - jpg - JPG - jpeg - JPEG - jpe - JPE - thm - THM - - CFBundleTypeIconFile - Document-JPG - CFBundleTypeMIMETypes - - image/jpeg - image/jpg - image/pjpeg - - CFBundleTypeOSTypes - - JPEG - ???? - - CFBundleTypeRole - Viewer - LSItemContentTypes - - public.jpeg - - - - CFBundleTypeExtensions - - pic - PIC - hdr - HDR - - CFBundleTypeIconFile - Document-PIC - CFBundleTypeRole - Viewer - LSItemContentTypes - - public.radiance - - - - CFBundleTypeExtensions - - pfm - PFM - pnm - PNM - - CFBundleTypeIconFile - Document-PFM - CFBundleTypeRole - Viewer - LSItemContentTypes - - public.pfm - - - - CFBundleTypeExtensions - - png - PNG - - CFBundleTypeIconFile - Document-PNG - CFBundleTypeMIMETypes - - image/png - application/png - application/x-png - - CFBundleTypeOSTypes - - PNGf - - CFBundleTypeRole - Viewer - LSItemContentTypes - - public.png - - - - CFBundleTypeExtensions - - tga - TGA - - CFBundleTypeIconFile - Document-TGA - CFBundleTypeOSTypes - - TPIC - - CFBundleTypeRole - Viewer - LSItemContentTypes - - com.truevision.tga-image - - - - CFBundleTypeExtensions - - psd - PSD - - CFBundleTypeIconFile - Document-PSD - CFBundleTypeMIMETypes - - image/photoshop - image/x-photoshop - image/psd - application/photoshop - application/psd - - CFBundleTypeOSTypes - - 8BPS - - CFBundleTypeRole - Viewer - LSItemContentTypes - - com.adobe.photoshop-image - - - - NSSupportsAutomaticGraphicsSwitching - - - diff --git a/shell.emscripten.html.in b/shell.emscripten.html.in new file mode 100644 index 0000000..998b037 --- /dev/null +++ b/shell.emscripten.html.in @@ -0,0 +1,251 @@ + + + + + + + @HELLO_IMGUI_ICON_DISPLAY_NAME@ + + + + + + + + + {{{ SCRIPT }}} + + + \ No newline at end of file diff --git a/src/LP.cpp b/src/LP.cpp deleted file mode 100644 index 2143a5b..0000000 --- a/src/LP.cpp +++ /dev/null @@ -1,33 +0,0 @@ -/** \file LP.cpp - \author Wojciech Jarosz -*/ - -#include -#include -#include - -LarcherPillichshammerGK::LarcherPillichshammerGK(unsigned dimension, - unsigned numSamples, - bool randomize) : - m_numSamples(numSamples), m_numDimensions(dimension), - m_inv(1.0f/m_numSamples) -{ - setRandomized(randomize); -} - -LarcherPillichshammerGK::~LarcherPillichshammerGK() -{ - -} - -void -LarcherPillichshammerGK::sample(float r[], unsigned i) -{ - r[0] = randomDigitScramble(i*m_inv, m_scramble1); - if (m_numDimensions == 1) return; - - r[1] = LarcherPillichshammerRI(i, m_scramble2); - if (m_numDimensions == 2) return; - - r[2] = GruenschlossKellerRI(i, m_scramble3); -} diff --git a/src/app.cpp b/src/app.cpp new file mode 100644 index 0000000..10c05b8 --- /dev/null +++ b/src/app.cpp @@ -0,0 +1,1426 @@ +/** \file SampleViewer.cpp + \author Wojciech Jarosz +*/ + +#include "app.h" + +using namespace linalg::ostream_overloads; + +using std::cerr; +using std::cout; +using std::endl; +using std::ofstream; +using std::to_string; + +#include "hello_imgui/hello_imgui.h" +#include "hello_imgui/hello_imgui_include_opengl.h" // cross-platform way to include OpenGL headers +#include "imgui_ext.h" +#include "imgui_internal.h" +#include "portable-file-dialogs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "export_to_file.h" +#include "timer.h" + +#include +#include +#include +#include +#include + +static bool g_show_modal = true; + +static float map_slider_to_radius(float sliderValue) +{ + return sliderValue * sliderValue * 32.0f + 2.0f; +} + +static float4x4 layout_2d_matrix(int num_dims, int2 dims) +{ + float cell_spacing = 1.f / (num_dims - 1); + float cell_size = 0.96f / (num_dims - 1); + + float2 offset = (dims - int2{0, 1} - float2{(num_dims - 2) / 2.0f}) * float2{1, -1}; + + return mul(translation_matrix(float3{offset * cell_spacing, 1}), scaling_matrix(float3{float2{cell_size}, 1})); +} + +SampleViewer::SampleViewer() +{ + m_samplers.push_back(new Random(m_num_dimensions)); + m_samplers.push_back(new Jittered(1, 1, m_jitter * 0.01f)); + m_samplers.push_back(new MultiJitteredInPlace(1, 1, false, m_jitter * 0.01f)); + m_samplers.push_back(new CorrelatedMultiJitteredInPlace(1, 1, m_num_dimensions, false, m_jitter * 0.01f)); + m_samplers.push_back(new CMJNDInPlace(1, 3, MJ_STYLE, false, m_jitter * 0.01f)); + m_samplers.push_back(new BoseOAInPlace(1, MJ_STYLE, false, m_jitter * 0.01f, m_num_dimensions)); + m_samplers.push_back(new BoseGaloisOAInPlace(1, MJ_STYLE, false, m_jitter * 0.01f, m_num_dimensions)); + m_samplers.push_back(new BushOAInPlace(1, 3, MJ_STYLE, false, m_jitter * 0.01f, m_num_dimensions)); + m_samplers.push_back(new BushGaloisOAInPlace(1, 3, MJ_STYLE, false, m_jitter * 0.01f, m_num_dimensions)); + m_samplers.push_back(new AddelmanKempthorneOAInPlace(2, MJ_STYLE, false, m_jitter * 0.01f, m_num_dimensions)); + m_samplers.push_back(new BoseBushOA(2, MJ_STYLE, false, m_jitter * 0.01f, m_num_dimensions)); + m_samplers.push_back(new BoseBushOAInPlace(2, MJ_STYLE, false, m_jitter * 0.01f, m_num_dimensions)); + m_samplers.push_back(new NRooksInPlace(m_num_dimensions, 1, false, m_jitter * 0.01f)); + m_samplers.push_back(new Sobol(m_num_dimensions)); + m_samplers.push_back(new ZeroTwo(1, m_num_dimensions, false)); + m_samplers.push_back(new ZeroTwo(1, m_num_dimensions, true)); + m_samplers.push_back(new Halton(m_num_dimensions)); + m_samplers.push_back(new HaltonZaremba(m_num_dimensions)); + m_samplers.push_back(new Hammersley(m_num_dimensions, 1)); + m_samplers.push_back(new Hammersley(m_num_dimensions, 1)); + m_samplers.push_back(new LarcherPillichshammerGK(3, 1, false)); + + m_camera[CAMERA_XY].arcball.set_state({0, 0, 0, 1}); + m_camera[CAMERA_XY].persp_factor = 0.0f; + m_camera[CAMERA_XY].camera_type = CAMERA_XY; + + m_camera[CAMERA_YZ].arcball.set_state(linalg::rotation_quat({0.f, -1.f, 0.f}, float(M_PI_2))); + m_camera[CAMERA_YZ].persp_factor = 0.0f; + m_camera[CAMERA_YZ].camera_type = CAMERA_YZ; + + m_camera[CAMERA_XZ].arcball.set_state(linalg::rotation_quat({1.f, 0.f, 0.f}, float(M_PI_2))); + m_camera[CAMERA_XZ].persp_factor = 0.0f; + m_camera[CAMERA_XZ].camera_type = CAMERA_XZ; + + m_camera[CAMERA_2D] = m_camera[CAMERA_XY]; + m_camera[CAMERA_CURRENT] = m_camera[CAMERA_XY]; + m_camera[CAMERA_NEXT] = m_camera[CAMERA_XY]; + + // set up HelloImGui parameters + m_params.appWindowParams.windowGeometry.size = {1200, 800}; + m_params.appWindowParams.windowTitle = "Samplin' Safari"; + m_params.appWindowParams.restorePreviousGeometry = false; + + // Menu bar + m_params.imGuiWindowParams.showMenuBar = true; + m_params.imGuiWindowParams.showStatusBar = true; + m_params.imGuiWindowParams.defaultImGuiWindowType = HelloImGui::DefaultImGuiWindowType::ProvideFullScreenDockSpace; + + // Setting this to true allows multiple viewports where you can drag windows outside out the main window in order to + // put their content into new native windows m_params.imGuiWindowParams.enableViewports = true; + m_params.imGuiWindowParams.enableViewports = false; + m_params.imGuiWindowParams.menuAppTitle = "File"; + + // Dockable windows + // the parameter editor + HelloImGui::DockableWindow editorWindow; + editorWindow.label = "Settings"; + editorWindow.dockSpaceName = "EditorSpace"; + editorWindow.GuiFunction = [this] { draw_editor(); }; + + // A console window named "Console" will be placed in "ConsoleSpace". It uses the HelloImGui logger gui + HelloImGui::DockableWindow consoleWindow; + consoleWindow.label = "Console"; + consoleWindow.dockSpaceName = "ConsoleSpace"; + consoleWindow.isVisible = false; + consoleWindow.rememberIsVisible = true; + consoleWindow.GuiFunction = [] { HelloImGui::LogGui(); }; + + // docking layouts + { + m_params.dockingParams.layoutName = "Settings on left"; + m_params.dockingParams.dockableWindows = {editorWindow, consoleWindow}; + + HelloImGui::DockingSplit splitEditorMain{"MainDockSpace", "EditorSpace", ImGuiDir_Left, 0.2f}; + + HelloImGui::DockingSplit splitMainConsole{"MainDockSpace", "ConsoleSpace", ImGuiDir_Down, 0.25f}; + + m_params.dockingParams.dockingSplits = {splitEditorMain, splitMainConsole}; + + HelloImGui::DockingParams alternativeLayout; + alternativeLayout.layoutName = "Settings on right"; + alternativeLayout.dockableWindows = {editorWindow, consoleWindow}; + splitEditorMain.direction = ImGuiDir_Right; + alternativeLayout.dockingSplits = {splitEditorMain, splitMainConsole}; + m_params.alternativeDockingLayouts = {alternativeLayout}; + } + + m_params.callbacks.LoadAdditionalFonts = [this]() + { + std::string roboto_r = "fonts/Roboto/Roboto-Regular.ttf"; + std::string roboto_b = "fonts/Roboto/Roboto-Bold.ttf"; + if (!HelloImGui::AssetExists(roboto_r) || !HelloImGui::AssetExists(roboto_b)) + return; + + for (auto font_size : + {14.f, 8.f, 9.f, 10.f, 11.f, 12.f, 13.f, 15.f, 16.f, 17.f, 18.f, 20.f, 22.f, 24.f, 26.f, 28.f, 30.f}) + { + m_regular[(int)font_size] = HelloImGui::LoadFontTTF_WithFontAwesomeIcons(roboto_r, font_size); + m_bold[(int)font_size] = HelloImGui::LoadFontTTF_WithFontAwesomeIcons(roboto_b, font_size); + } + }; + + m_params.callbacks.ShowMenus = []() + { + string text = ICON_FA_INFO_CIRCLE; + auto posX = (ImGui::GetCursorPosX() + ImGui::GetColumnWidth() - ImGui::CalcTextSize(text.c_str()).x - + ImGui::GetScrollX() - 2 * ImGui::GetStyle().ItemSpacing.x); + if (posX > ImGui::GetCursorPosX()) + ImGui::SetCursorPosX(posX); + if (ImGui::MenuItem(text)) + g_show_modal = true; + }; + + m_params.callbacks.ShowAppMenuItems = [this]() + { + auto save_files = [this](const string &basename, const string &ext) + { + vector saved_files; + { + HelloImGui::Log(HelloImGui::LogLevel::Info, "Saving to base filename: %s.", basename.c_str()); + + saved_files.push_back(basename + "_all2D." + ext); + ofstream fileAll(saved_files.back()); + fileAll << export_all_points_2d(ext); + + saved_files.push_back(basename + "_012." + ext); + ofstream fileXYZ(saved_files.back()); + fileXYZ << export_XYZ_points(ext); + + for (int y = 0; y < m_num_dimensions; ++y) + for (int x = 0; x < y; ++x) + { + saved_files.push_back(fmt::format("{:s}_{:d}{:d}.{}", basename, x, y, ext)); + ofstream fileXY(saved_files.back()); + fileXY << export_points_2d(ext, CAMERA_XY, {x, y, 2}); + } + } + return saved_files; + }; + + for (string ext : {"eps", "svg"}) + { +#ifndef __EMSCRIPTEN__ + if (ImGui::MenuItem(fmt::format("{} Export as {}...", ICON_FA_SAVE, to_upper(ext)))) + { + try + { + auto basename = pfd::save_file("Base filename").result(); + if (!basename.empty()) + (void)save_files(basename, ext); + } + catch (const std::exception &e) + { + fmt::print(stderr, "An error occurred while exporting to {}: {}.", ext, e.what()); + HelloImGui::Log(HelloImGui::LogLevel::Error, + fmt::format("An error occurred while exporting to {}: {}.", ext, e.what()).c_str()); + } + } +#else + if (ImGui::BeginMenu(fmt::format("{} Download as {}...", ICON_FA_SAVE, to_upper(ext)).c_str())) + { + string basename; + + if (ImGui::InputText("Base filename", &basename, ImGuiInputTextFlags_EnterReturnsTrue)) + { + try + { + ImGui::CloseCurrentPopup(); + vector saved_files = save_files(basename, ext); + for (auto &saved_file : saved_files) + emscripten_run_script(fmt::format("saveFileFromMemoryFSToDisk('{}');", saved_file).c_str()); + } + catch (const std::exception &e) + { + fmt::print(stderr, "An error occurred while exporting to {}: {}.", ext, e.what()); + HelloImGui::Log( + HelloImGui::LogLevel::Error, + fmt::format("An error occurred while exporting to {}: {}.", ext, e.what()).c_str()); + } + } + ImGui::EndMenu(); + } +#endif + } + }; + + m_params.callbacks.ShowStatus = [this]() + { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetFontSize() * 0.15f); + ImGui::Text("Reset + randomize: %3.3f ms | Sampling: %3.3f ms | Total: %3.3f ms (%3.0f pps)", m_time1, + m_time2, m_time1 + m_time2, m_point_count / (m_time1 + m_time2)); + // ImGui::SameLine(); + ImGui::SameLine(ImGui::GetIO().DisplaySize.x - 16.f * ImGui::GetFontSize()); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetFontSize() * 0.15f); + ImGui::ToggleButton(ICON_FA_TERMINAL, &m_params.dockingParams.dockableWindows[1].isVisible); + }; + + m_params.callbacks.SetupImGuiStyle = [this]() + { + // set the default theme for first startup + m_params.imGuiWindowParams.tweakedTheme.Theme = ImGuiTheme::ImGuiTheme_FromName("MaterialFlat"); + }; + m_params.callbacks.PostInit = [this]() + { + try + { +#ifndef __EMSCRIPTEN__ + glEnable(GL_PROGRAM_POINT_SIZE); + glEnable(GL_LINE_SMOOTH); + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); +#endif + + m_point_shader = + new Shader("Point shader", "shaders/points.vert", "shaders/points.frag", Shader::BlendMode::AlphaBlend); + m_grid_shader = + new Shader("Grid shader", "shaders/lines.vert", "shaders/lines.frag", Shader::BlendMode::AlphaBlend); + m_point_2d_shader = new Shader("Point shader 2D", "shaders/points.vert", "shaders/points.frag", + Shader::BlendMode::AlphaBlend); + + update_GPU_points(); + update_GPU_grids(); + + HelloImGui::Log(HelloImGui::LogLevel::Info, "Successfully initialized GL!"); + } + catch (const std::exception &e) + { + fmt::print(stderr, "Shader initialization failed!:\n\t{}.", e.what()); + HelloImGui::Log(HelloImGui::LogLevel::Error, "Shader initialization failed!:\n\t%s.", e.what()); + } + }; + m_params.callbacks.ShowGui = [this]() { draw_gui(); }; + m_params.callbacks.CustomBackground = [this]() { draw_scene(); }; + m_idling_backup = m_params.fpsIdling.enableIdling; +} + +SampleViewer::~SampleViewer() +{ + for (size_t i = 0; i < m_samplers.size(); ++i) + delete m_samplers[i]; +} + +int2 SampleViewer::get_draw_range() const +{ + int start = 0, count = m_point_count; + if (m_subset_by_coord) + count = std::min(count, m_subset_count); + else if (m_subset_by_index) + { + start = m_first_draw_point; + count = std::min(m_point_draw_count, m_point_count - m_first_draw_point); + } + return {start, count}; +} + +void SampleViewer::draw_gui() +{ + auto &io = ImGui::GetIO(); + + m_viewport_pos_GL = m_viewport_pos = {0, 0}; + m_viewport_size = io.DisplaySize; + if (auto id = m_params.dockingParams.dockSpaceIdFromName("MainDockSpace")) + { + auto central_node = ImGui::DockBuilderGetCentralNode(*id); + m_viewport_size = int2{int(central_node->Size.x), int(central_node->Size.y)}; + m_viewport_pos = int2{int(central_node->Pos.x), int(central_node->Pos.y)}; + // flip y coordinates between ImGui and OpenGL screen coordinates + m_viewport_pos_GL = + int2{int(central_node->Pos.x), int(io.DisplaySize.y - (central_node->Pos.y + central_node->Size.y))}; + } + + float radius = map_slider_to_radius(m_radius); + if (m_scale_radius_with_points) + radius *= 64.0f / std::sqrt(m_point_count); + + float4x4 mvp = m_camera[CAMERA_CURRENT].matrix(float(m_viewport_size.x) / m_viewport_size.y); + + if (m_view == CAMERA_2D) + { + for (int i = 0; i < m_num_dimensions - 1; ++i) + { + float4x4 pos = layout_2d_matrix(m_num_dimensions, int2{i, m_num_dimensions - 1}); + float4 text_pos = mul(mvp, mul(pos, float4{0.f, -0.5f, 0.0f, 1.0f})); + float2 text_2d_pos((text_pos.x / text_pos.w + 1) / 2, (text_pos.y / text_pos.w + 1) / 2); + draw_text(m_viewport_pos + + int2((text_2d_pos.x) * m_viewport_size.x, (1.f - text_2d_pos.y) * m_viewport_size.y + 16), + to_string(i), float4(1.0f, 1.0f, 1.0f, 0.75f), m_regular[16], + TextAlign_CENTER | TextAlign_BOTTOM); + + pos = layout_2d_matrix(m_num_dimensions, int2{0, i + 1}); + text_pos = mul(mvp, mul(pos, float4{-0.5f, 0.f, 0.0f, 1.0f})); + text_2d_pos = float2((text_pos.x / text_pos.w + 1) / 2, (text_pos.y / text_pos.w + 1) / 2); + draw_text(m_viewport_pos + + int2((text_2d_pos.x) * m_viewport_size.x - 4, (1.f - text_2d_pos.y) * m_viewport_size.y), + to_string(i + 1), float4(1.0f, 1.0f, 1.0f, 0.75f), m_regular[16], + TextAlign_RIGHT | TextAlign_MIDDLE); + } + } + else + { + int2 range = get_draw_range(); + + if (m_show_point_nums || m_show_point_coords) + for (int p = range.x; p < range.x + range.y; ++p) + { + float3 point = m_3d_points[p]; + + float4 text_pos = mul(mvp, float4{point.x, point.y, point.z, 1.f}); + float2 text_2d_pos((text_pos.x / text_pos.w + 1) / 2, (text_pos.y / text_pos.w + 1) / 2); + if (m_show_point_nums) + draw_text(m_viewport_pos + int2((text_2d_pos.x) * m_viewport_size.x, + (1.f - text_2d_pos.y) * m_viewport_size.y - radius / 4), + fmt::format("{:d}", p), float4(1.0f, 1.0f, 1.0f, 0.75f), m_regular[12], + TextAlign_CENTER | TextAlign_BOTTOM); + if (m_show_point_coords) + draw_text(m_viewport_pos + int2((text_2d_pos.x) * m_viewport_size.x, + (1.f - text_2d_pos.y) * m_viewport_size.y + radius / 4), + fmt::format("({:0.2f}, {:0.2f}, {:0.2f})", point.x + 0.5, point.y + 0.5, point.z + 0.5), + float4(1.0f, 1.0f, 1.0f, 0.75f), m_regular[11], TextAlign_CENTER | TextAlign_TOP); + } + } + + draw_about_dialog(); +} + +void SampleViewer::draw_about_dialog() +{ + if (g_show_modal) + { + ImGui::OpenPopup("About"); + + // I think HelloGui's way of rendering the frame multiple times before it determines window sizes is + // closing this popup before we see the app, so we need this hack to show it at startup + static int count = 0; + if (count > 0) + g_show_modal = false; + count++; + } + + // Always center this window when appearing + ImVec2 center = ImGui::GetMainViewport()->GetCenter(); + ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + ImGui::SetNextWindowFocus(); + + if (ImGui::BeginPopup("About", ImGuiWindowFlags_AlwaysAutoResize)) + { + const int col_width[2] = {11, 34}; + + ImGui::Spacing(); + + if (ImGui::BeginTable("about_table1", 2)) + { + ImGui::TableSetupColumn("one", ImGuiTableColumnFlags_WidthFixed, HelloImGui::EmSize() * col_width[0]); + ImGui::TableSetupColumn("two", ImGuiTableColumnFlags_WidthFixed, HelloImGui::EmSize() * col_width[1]); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + // right align the image + { + auto posX = (ImGui::GetCursorPosX() + ImGui::GetColumnWidth() - 128.f - ImGui::GetScrollX() - + 2 * ImGui::GetStyle().ItemSpacing.x); + if (posX > ImGui::GetCursorPosX()) + ImGui::SetCursorPosX(posX); + } + HelloImGui::ImageFromAsset("icons/icon_128x128.png"); // Display a static image + + ImGui::TableNextColumn(); + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + HelloImGui::EmSize() * col_width[1]); + + ImGui::PushFont(m_bold[30]); + ImGui::Text("Samplin' Safari"); + ImGui::PopFont(); + + ImGui::PushFont(m_bold[18]); + ImGui::Text(fmt::format("version {}", version())); + ImGui::PopFont(); + ImGui::PushFont(m_regular[10]); + ImGui::Text(fmt::format("Built using the {} backend on {}.", backend(), build_timestamp())); + ImGui::PopFont(); + + ImGui::Spacing(); + + ImGui::PushFont(m_bold[16]); + ImGui::Text("Samplin' Safari is a research tool to visualize and interactively inspect high-dimensional " + "(quasi) Monte Carlo samplers."); + ImGui::PopFont(); + + ImGui::Spacing(); + + ImGui::PopTextWrapPos(); + ImGui::EndTable(); + } + + auto right_align = [](const string &text) + { + auto posX = (ImGui::GetCursorPosX() + ImGui::GetColumnWidth() - ImGui::CalcTextSize(text.c_str()).x - + ImGui::GetScrollX() - 2 * ImGui::GetStyle().ItemSpacing.x); + if (posX > ImGui::GetCursorPosX()) + ImGui::SetCursorPosX(posX); + ImGui::Text(text); + }; + + auto add_library = [this, right_align, col_width](string name, string desc) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + ImGui::PushFont(m_bold[14]); + right_align(name); + ImGui::PopFont(); + + ImGui::TableNextColumn(); + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + HelloImGui::EmSize() * (col_width[1] - 1)); + ImGui::PushFont(m_regular[14]); + ImGui::Text(desc); + ImGui::PopFont(); + }; + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + ImGui::Text("It is freely available under a 3-clause BSD license and is developed by Wojciech Jarosz, " + "initially as part of the publication:"); + + ImGui::Spacing(); + + ImGui::Indent(HelloImGui::EmSize() * 1); + ImGui::PushFont(m_bold[14]); + ImGui::Text("Orthogonal Array Sampling for Monte Carlo Rendering"); + ImGui::PopFont(); + ImGui::Text("Wojciech Jarosz, Afnan Enayet, Andrew Kensler, Charlie Kilpatrick, Per Christensen.\n" + "In Computer Graphics Forum (Proceedings of EGSR), 38(4), July 2019."); + ImGui::Unindent(HelloImGui::EmSize() * 1); + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::Text("It additionally makes use of the following external libraries:\n\n"); + + if (ImGui::BeginTable("about_table2", 2)) + { + ImGui::TableSetupColumn("one", ImGuiTableColumnFlags_WidthFixed, HelloImGui::EmSize() * col_width[0]); + ImGui::TableSetupColumn("two", ImGuiTableColumnFlags_WidthFixed, HelloImGui::EmSize() * col_width[1]); + + add_library("Dear ImGui", "Omar Cornut's immediate-mode graphical user interface for C++"); + add_library("Hello ImGui", "Pascal Thomet's cross-platform starter-kit for Dear ImGui"); + add_library("GLFW", "Multi-platform OpenGL/windowing library on the desktop"); + add_library("NanoGUI", "Bits of code from Wenzel Jakob's BSD-licensed NanoGUI library (the shader " + "abstraction and arcball)"); + add_library("{fmt}", "A modern formatting library"); + add_library("halton/sobol", "Leonhard Gruenschloss's MIT-licensed code for Halton and Sobol sequences"); + add_library("linalg", "Sterling Orsten's public domain, single header short vector math library for C++"); + add_library("portable-file-dialogs", + "Sam Hocevar's WTFPL portable GUI dialogs library, C++11, single-header"); + add_library("pcg32", "Wenzel Jakob's tiny self-contained C++ version of Melissa O'Neill's PCG32 " + "pseudorandom number generator"); + add_library("galois++", "My small C++ library for arithmetic over general Galois fields based on " + "Art Owen's code in Statlib"); + ImGui::EndTable(); + } + + ImGui::SetKeyboardFocusHere(); + if (ImGui::Button("Dismiss", ImVec2(120, 0)) || ImGui::IsKeyPressed(ImGuiKey_Escape) || + ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_Space)) + ImGui::CloseCurrentPopup(); + ImGui::SetItemDefaultFocus(); + + ImGui::EndPopup(); + } +} + +void SampleViewer::draw_editor() +{ + auto big_header = [this](const char *label, ImGuiTreeNodeFlags flags = 0) + { + ImGui::PushFont(m_bold[16]); + bool ret = ImGui::CollapsingHeader(label, flags | ImGuiTreeNodeFlags_DefaultOpen); + ImGui::PopFont(); + return ret; + }; + + auto tooltip = [](const char *text, float wrap_width = 400.f) + { + if (ImGui::BeginItemTooltip()) + { + ImGui::PushTextWrapPos(wrap_width); + ImGui::TextWrapped("%s", text); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + }; + + // ========================================================= + if (big_header(ICON_FA_SLIDERS_H " Sampler settings")) + // ========================================================= + { + if (ImGui::BeginCombo("##", m_samplers[m_sampler]->name().c_str())) + { + for (int n = 0; n < (int)m_samplers.size(); n++) + { + Sampler *sampler = m_samplers[n]; + const bool is_selected = (m_sampler == n); + if (ImGui::Selectable(sampler->name().c_str(), is_selected)) + { + m_sampler = n; + sampler->setJitter(m_jitter * 0.01f); + sampler->setRandomized(m_randomize); + update_GPU_points(); + update_GPU_grids(); + HelloImGui::Log(HelloImGui::LogLevel::Debug, "Switching to sampler %d: %s.", m_sampler, + sampler->name().c_str()); + } + + // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + tooltip("Set the sampler used to generate the points (Key: Up/Down)"); + + if (ImGui::SliderInt("Num points", &m_target_point_count, 1, 1 << 17, "%d", + ImGuiSliderFlags_Logarithmic | ImGuiSliderFlags_AlwaysClamp)) + { + HelloImGui::Log(HelloImGui::LogLevel::Debug, "Setting target point count to %d.", m_target_point_count); + update_GPU_points(); + update_GPU_grids(); + HelloImGui::Log(HelloImGui::LogLevel::Debug, "Regenerated %d points.", m_point_count); + } + // now that the user has finished editing, sync the GUI value + if (ImGui::IsItemDeactivatedAfterEdit()) + m_target_point_count = m_point_count; + + tooltip( + "Set the target number of points to generate. For samplers that only support certain numbers of points " + "(e.g. powers of 2) this target value will be snapped to the nearest admissable value (Key: Left/Right)."); + + if (ImGui::SliderInt("Dimensions", &m_num_dimensions, 2, 10, "%d", ImGuiSliderFlags_AlwaysClamp)) + { + update_GPU_points(); + update_GPU_grids(); + } + tooltip("The number of dimensions to generate for each point (Key: D/d)."); + + if (ImGui::Checkbox("Randomize", &m_randomize)) + update_GPU_points(); + tooltip("Whether to randomize the points, or show the deterministic configuration (Key: r/R)."); + + if (ImGui::SliderFloat("Jitter", &m_jitter, 0.f, 100.f, "%3.1f%%")) + { + m_samplers[m_sampler]->setJitter(m_jitter * 0.01f); + update_GPU_points(); + update_GPU_grids(); + } + tooltip("How much the points should be jittered within their strata (Key: j/J)."); + + // add optional widgets for OA samplers + if (OrthogonalArray *oa = dynamic_cast(m_samplers[m_sampler])) + { + // Controls for the strengths of the OA + auto change_strength = [oa, this](int strength) + { + if ((unsigned)strength != oa->strength()) + { + oa->setStrength(strength); + update_GPU_points(); + update_GPU_grids(); + } + }; + int strength = oa->strength(); + if (ImGui::InputInt("Strength", &strength, 1)) + change_strength(std::max(2, strength)); + ImGui::SetItemTooltip("Key: T/t"); + + // Controls for the offset type of the OA + auto offset_names = oa->offsetTypeNames(); + auto change_offset_type = [oa, this](int offset) + { + oa->setOffsetType(offset); + m_jitter = oa->jitter(); + update_GPU_points(); + update_GPU_grids(); + }; + if (ImGui::BeginCombo("Offset type", offset_names[oa->offsetType()].c_str())) + { + for (unsigned n = 0; n < offset_names.size(); n++) + { + const bool is_selected = (oa->offsetType() == n); + if (ImGui::Selectable(offset_names[n].c_str(), is_selected)) + change_offset_type(n); + + // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + tooltip("Choose the type of offset witin each stratum (Key: Shift+Up/Down)."); + } + ImGui::Dummy({0, HelloImGui::EmSize(0.25f)}); + } + // ========================================================= + if (big_header(ICON_FA_CAMERA " Camera/view")) + // ========================================================= + { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, + ImVec2{ImGui::GetStyle().ItemSpacing.y, ImGui::GetStyle().ItemSpacing.y}); + const char *items[] = {"XY", "YZ", "XZ", "XYZ", "2D"}; + bool is_selected; + for (int i = 0; i < IM_ARRAYSIZE(items); ++i) + { + if (i > CAMERA_XY) + ImGui::SameLine(); + + is_selected = m_view == i; + if (is_selected) + ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]); + + if (ImGui::Button(items[i], float2{40.f, 0.f})) + { + set_view((CameraType)i); + } + tooltip(fmt::format("(Key: {:d}).", (i + 1) % IM_ARRAYSIZE(items)).c_str()); + ImGui::PopStyleColor(is_selected); + } + ImGui::PopStyleVar(); + ImGui::Dummy({0, HelloImGui::EmSize(0.25f)}); + } + + // ========================================================= + if (big_header(ICON_FA_EYE " Display/visibility")) + // ========================================================= + { + ImGui::ColorEdit3("Bg color", (float *)&m_bg_color); + ImGui::ColorEdit3("Point color", (float *)&m_point_color); + ImGui::SliderFloat("Radius", &m_radius, 0.f, 1.f, ""); + ImGui::SameLine(); + { + ImGui::ToggleButton(ICON_FA_COMPRESS, &m_scale_radius_with_points); + ImGui::SetItemTooltip("Scale radius with number of points"); + } + + ImGui::Checkbox("1D projections", &m_show_1d_projections); + tooltip("Also show the X-, Y-, and Z-axis projections of the 3D points (Key: p)?"); + ImGui::Checkbox("Point indices", &m_show_point_nums); + tooltip("Show the index above each point?"); + ImGui::Checkbox("Point coords", &m_show_point_coords); + tooltip("Show the XYZ coordinates below each point?"); + ImGui::Checkbox("Coarse grid", &m_show_coarse_grid); + tooltip("Show a coarse grid (Key: g)?"); + ImGui::Checkbox("Fine grid", &m_show_fine_grid); + tooltip("Show a fine grid (Key: G)?"); + ImGui::Checkbox("Bounding box", &m_show_bbox); + tooltip("Show the XY, YZ, and XZ bounding boxes (Key: b)?"); + + ImGui::Dummy({0, HelloImGui::EmSize(0.25f)}); + } + + // ========================================================= + if (big_header(ICON_FA_RANDOM " Dimension mapping")) + // ========================================================= + { + if (ImGui::SliderInt3("XYZ", &m_dimension[0], 0, m_num_dimensions - 1, "%d", ImGuiSliderFlags_AlwaysClamp)) + update_GPU_points(false); + tooltip("Set which dimensions should be used for the XYZ dimensions of the displayed 3D points."); + + ImGui::Dummy({0, HelloImGui::EmSize(0.25f)}); + } + + // ========================================================= + if (big_header(ICON_FA_FILTER " Filter points")) + // ========================================================= + { + if (ImGui::Checkbox("Filter by point index", &m_subset_by_index)) + update_GPU_points(false); + tooltip("Choose which points to show based on each point's index."); + + if (m_subset_by_index) + { + m_subset_by_coord = false; + ImGui::SliderInt("First point", &m_first_draw_point, 0, m_point_count - 1, "%d", + ImGuiSliderFlags_AlwaysClamp); + tooltip("Display points starting at this index."); + + ImGui::SliderInt("Num points##2", &m_point_draw_count, 1, m_point_count - m_first_draw_point, "%d", + ImGuiSliderFlags_AlwaysClamp); + tooltip("Display this many points from the first index."); + } + + if (ImGui::Checkbox("Filter by coordinates", &m_subset_by_coord)) + update_GPU_points(false); + tooltip("Show only points that fall within an interval along one of its dimensions."); + if (m_subset_by_coord) + { + m_subset_by_index = false; + if (ImGui::SliderInt("Axis", &m_subset_axis, 0, m_num_dimensions - 1, "%d", ImGuiSliderFlags_AlwaysClamp)) + update_GPU_points(false); + tooltip("Filter points based on this axis."); + + if (ImGui::SliderInt("Num levels", &m_num_subset_levels, 1, m_point_count, "%d", + ImGuiSliderFlags_AlwaysClamp)) + update_GPU_points(false); + tooltip("Split the unit interval along the chosen axis into this many consecutive levels (or bins)."); + + if (ImGui::SliderInt("Level", &m_subset_level, 0, m_num_subset_levels - 1, "%d", + ImGuiSliderFlags_AlwaysClamp)) + update_GPU_points(false); + tooltip("Show only points within this bin along the filtered axis."); + } + + ImGui::Dummy({0, HelloImGui::EmSize(0.25f)}); + } +} + +void SampleViewer::process_hotkeys() +{ + if (ImGui::GetIO().WantCaptureKeyboard) + return; + + Sampler *sampler = m_samplers[m_sampler]; + OrthogonalArray *oa = dynamic_cast(sampler); + auto change_strength = [oa, this](int strength) + { + if ((unsigned)strength != oa->strength()) + { + oa->setStrength(strength); + update_GPU_points(); + update_GPU_grids(); + } + }; + auto change_offset_type = [oa, this](int offset) + { + oa->setOffsetType(offset); + m_jitter = oa->jitter(); + update_GPU_points(); + update_GPU_grids(); + }; + + if ((ImGui::IsKeyPressed(ImGuiKey_UpArrow) || ImGui::IsKeyPressed(ImGuiKey_DownArrow)) && + !ImGui::IsKeyDown(ImGuiMod_Shift)) + { + int delta = ImGui::IsKeyPressed(ImGuiKey_DownArrow) ? 1 : -1; + m_sampler = mod(m_sampler + delta, (int)m_samplers.size()); + Sampler *sampler = m_samplers[m_sampler]; + sampler->setJitter(m_jitter * 0.01f); + sampler->setRandomized(m_randomize); + update_GPU_points(); + update_GPU_grids(); + } + else if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow) || ImGui::IsKeyPressed(ImGuiKey_RightArrow)) + { + m_target_point_count = + std::max(1u, ImGui::IsKeyPressed(ImGuiKey_RightArrow) ? roundUpPow2(m_target_point_count + 1) + : roundDownPow2(m_target_point_count - 1)); + update_GPU_points(); + update_GPU_grids(); + } + else if (ImGui::IsKeyPressed(ImGuiKey_D)) + { + m_num_dimensions = std::clamp(m_num_dimensions + (ImGui::IsKeyDown(ImGuiMod_Shift) ? 1 : -1), 2, 10); + m_dimension = linalg::clamp(m_dimension, int3{0}, int3{m_num_dimensions - 1}); + update_GPU_points(); + update_GPU_grids(); + } + else if (ImGui::IsKeyPressed(ImGuiKey_R)) + { + if (ImGui::IsKeyDown(ImGuiMod_Shift)) + { + m_randomize = true; + sampler->setRandomized(true); + } + else + m_randomize = !m_randomize; + update_GPU_points(); + } + else if (ImGui::IsKeyPressed(ImGuiKey_J)) + { + m_jitter = std::clamp(m_jitter + (ImGui::IsKeyDown(ImGuiMod_Shift) ? 10.f : -10.f), 0.f, 100.f); + sampler->setJitter(m_jitter * 0.01f); + update_GPU_points(); + } + else if (oa && ImGui::IsKeyPressed(ImGuiKey_T)) + change_strength(std::max(2u, oa->strength() + (ImGui::IsKeyDown(ImGuiMod_Shift) ? 1 : -1))); + else if (oa && ImGui::IsKeyDown(ImGuiMod_Shift) && + (ImGui::IsKeyPressed(ImGuiKey_UpArrow) || ImGui::IsKeyPressed(ImGuiKey_DownArrow))) + change_offset_type( + mod((int)oa->offsetType() + (ImGui::IsKeyPressed(ImGuiKey_DownArrow) ? 1 : -1), (int)NUM_OFFSET_TYPES)); + else if (ImGui::IsKeyPressed(ImGuiKey_1, false)) + set_view(CAMERA_XY); + else if (ImGui::IsKeyPressed(ImGuiKey_2, false)) + set_view(CAMERA_YZ); + else if (ImGui::IsKeyPressed(ImGuiKey_3, false)) + set_view(CAMERA_XZ); + else if (ImGui::IsKeyPressed(ImGuiKey_4, false)) + set_view(CAMERA_CURRENT); + else if (ImGui::IsKeyPressed(ImGuiKey_0, false)) + set_view(CAMERA_2D); + else if (ImGui::IsKeyPressed(ImGuiKey_P)) + m_show_1d_projections = !m_show_1d_projections; + else if (ImGui::IsKeyPressed(ImGuiKey_G)) + { + if (ImGui::IsKeyDown(ImGuiMod_Shift)) + m_show_fine_grid = !m_show_fine_grid; + else + m_show_coarse_grid = !m_show_coarse_grid; + update_GPU_grids(); + } + else if (ImGui::IsKeyPressed(ImGuiKey_B)) + { + m_show_bbox = !m_show_bbox; + update_GPU_grids(); + } +} + +void SampleViewer::update_GPU_points(bool regenerate) +{ + // + // Generate the point positions + // + if (regenerate) + { + try + { + Timer timer; + Sampler *generator = m_samplers[m_sampler]; + if (generator->randomized() != m_randomize) + generator->setRandomized(m_randomize); + + generator->setDimensions(m_num_dimensions); + + int num_pts = generator->setNumSamples(m_target_point_count); + m_point_count = num_pts > 0 ? num_pts : m_target_point_count; + + m_time1 = timer.elapsed(); + + m_points.resize(m_point_count, m_num_dimensions); + m_3d_points.resize(m_point_count); + + timer.reset(); + for (int i = 0; i < m_point_count; ++i) + { + vector r(m_num_dimensions, 0.5f); + generator->sample(r.data(), i); + for (int j = 0; j < m_points.sizeY(); ++j) + m_points(i, j) = r[j] - 0.5f; + } + m_time2 = timer.elapsed(); + } + catch (const std::exception &e) + { + fmt::print(stderr, "An error occurred while generating points: {}.", e.what()); + HelloImGui::Log(HelloImGui::LogLevel::Error, "An error occurred while generating points: %s.", e.what()); + return; + } + } + + // + // Populate point subsets + // + m_subset_points = m_points; + m_subset_count = m_point_count; + if (m_subset_by_coord) + { + m_subset_count = 0; + for (int i = 0; i < m_points.sizeX(); ++i) + { + float v = m_points(i, std::clamp(m_subset_axis, 0, m_num_dimensions - 1)); + v += 0.5f; + if (v >= (m_subset_level + 0.0f) / m_num_subset_levels && v < (m_subset_level + 1.0f) / m_num_subset_levels) + { + // copy all dimensions (rows) of point i + for (int j = 0; j < m_subset_points.sizeY(); ++j) + m_subset_points(m_subset_count, j) = m_points(i, j); + ++m_subset_count; + } + } + } + + for (size_t i = 0; i < m_3d_points.size(); ++i) + m_3d_points[i] = float3{m_subset_points(i, m_dimension.x), m_subset_points(i, m_dimension.y), + m_subset_points(i, m_dimension.z)}; + + // + // Upload points to the GPU + // + + m_point_shader->set_buffer("position", m_3d_points); + + // create a temporary matrix to store all the 2D projections of the points + // each 2D plot actually needs 3D points, and there are num2DPlots of them + int num2DPlots = m_num_dimensions * (m_num_dimensions - 1) / 2; + vector points2D(num2DPlots * m_subset_count); + int plot_index = 0; + for (int y = 0; y < m_num_dimensions; ++y) + for (int x = 0; x < y; ++x, ++plot_index) + for (int i = 0; i < m_subset_count; ++i) + points2D[plot_index * m_subset_count + i] = float3{m_subset_points(i, x), m_subset_points(i, y), 0.5f}; + + m_point_2d_shader->set_buffer("position", points2D); +} + +void SampleViewer::generate_grid(vector &positions, int grid_res) +{ + int fine_grid_res = 1; + int idx = 0; + positions.resize(4 * (grid_res + 1) * (fine_grid_res)); + float coarse_scale = 1.f / grid_res, fine_scale = 1.f / fine_grid_res; + // for (int z = -1; z <= 1; z+=2) + int z = 0; + for (int i = 0; i <= grid_res; ++i) + { + for (int j = 0; j < fine_grid_res; ++j) + { + positions[idx++] = float3(j * fine_scale - 0.5f, i * coarse_scale - 0.5f, z * 0.5f); + positions[idx++] = float3((j + 1) * fine_scale - 0.5f, i * coarse_scale - 0.5f, z * 0.5f); + positions[idx++] = float3(i * coarse_scale - 0.5f, j * fine_scale - 0.5f, z * 0.5f); + positions[idx++] = float3(i * coarse_scale - 0.5f, (j + 1) * fine_scale - 0.5f, z * 0.5f); + } + } +} + +void SampleViewer::update_GPU_grids() +{ + vector bbox_grid, coarse_grid, fine_grid; + generate_grid(bbox_grid, 1); + generate_grid(coarse_grid, m_samplers[m_sampler]->coarseGridRes(m_point_count)); + generate_grid(fine_grid, m_point_count); + m_coarse_line_count = coarse_grid.size(); + m_fine_line_count = fine_grid.size(); + vector positions; + positions.reserve(bbox_grid.size() + coarse_grid.size() + fine_grid.size()); + positions.insert(positions.end(), bbox_grid.begin(), bbox_grid.end()); + positions.insert(positions.end(), coarse_grid.begin(), coarse_grid.end()); + positions.insert(positions.end(), fine_grid.begin(), fine_grid.end()); + m_grid_shader->set_buffer("position", positions); +} + +void SampleViewer::draw_2D_points_and_grid(const float4x4 &mvp, int2 dims, int plot_index) +{ + float4x4 pos = layout_2d_matrix(m_num_dimensions, dims); + + // Render the point set + m_point_2d_shader->set_uniform("mvp", mul(mvp, pos)); + float radius = map_slider_to_radius(m_radius / (m_num_dimensions - 1)); + if (m_scale_radius_with_points) + radius *= 64.0f / std::sqrt(m_point_count); + m_point_2d_shader->set_uniform("point_size", radius); + m_point_2d_shader->set_uniform("color", m_point_color); + int2 range = get_draw_range(); + + m_point_2d_shader->begin(); + m_point_2d_shader->draw_array(Shader::PrimitiveType::Point, m_subset_count * plot_index + range.x, range.y); + m_point_2d_shader->end(); + + if (m_show_bbox) + { + m_grid_shader->set_uniform("alpha", 1.0f); + m_grid_shader->set_uniform("mvp", mul(mvp, mul(pos, m_camera[CAMERA_2D].arcball.matrix()))); + m_grid_shader->begin(); + m_grid_shader->draw_array(Shader::PrimitiveType::Line, 0, 8); + m_grid_shader->end(); + } + if (m_show_coarse_grid) + { + m_grid_shader->set_uniform("alpha", 0.6f); + m_grid_shader->set_uniform("mvp", mul(mvp, mul(pos, m_camera[CAMERA_2D].arcball.matrix()))); + m_grid_shader->begin(); + m_grid_shader->draw_array(Shader::PrimitiveType::Line, 8, m_coarse_line_count); + m_grid_shader->end(); + } + if (m_show_fine_grid) + { + m_grid_shader->set_uniform("alpha", 0.2f); + m_grid_shader->set_uniform("mvp", mul(mvp, mul(pos, m_camera[CAMERA_2D].arcball.matrix()))); + m_grid_shader->begin(); + m_grid_shader->draw_array(Shader::PrimitiveType::Line, 8 + m_coarse_line_count, m_fine_line_count); + m_grid_shader->end(); + } +} + +void SampleViewer::draw_scene() +{ + auto &io = ImGui::GetIO(); + + process_hotkeys(); + + try + { + // + // clear the scene and set up viewports + // + + // first clear the entire window with the background color + // display_size is the size of the window in pixels while accounting for dpi factor on retina screens. + // for retina displays, io.DisplaySize is the size of the window in points (logical pixels) + // but we need the size in pixels. So we scale io.DisplaySize by io.DisplayFramebufferScale + auto display_size = int2{io.DisplaySize} * int2{io.DisplayFramebufferScale}; + CHK(glViewport(0, 0, display_size.x, display_size.y)); + CHK(glClearColor(m_bg_color[0], m_bg_color[1], m_bg_color[2], 1.f)); + CHK(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); + + // now set up a new viewport for the rest of the drawing + CHK(glViewport( + m_viewport_pos_GL.x * io.DisplayFramebufferScale.x, m_viewport_pos_GL.y * io.DisplayFramebufferScale.y, + m_viewport_size.x * io.DisplayFramebufferScale.x, m_viewport_size.y * io.DisplayFramebufferScale.y)); + + // inform the arcballs of the viewport size + for (int i = 0; i < NUM_CAMERA_TYPES; ++i) + m_camera[i].arcball.set_size(m_viewport_size); + + // enable depth testing + CHK(glEnable(GL_DEPTH_TEST)); + CHK(glDepthFunc(GL_LESS)); + CHK(glDepthMask(GL_TRUE)); + + // + // process camera movement + // + if (!io.WantCaptureMouse) + { + m_camera[CAMERA_NEXT].zoom = std::max(0.001, m_camera[CAMERA_NEXT].zoom * pow(1.1, io.MouseWheel)); + if (io.MouseClicked[0]) + { + // on mouse down we start switching to a perspective camera + // and start recording the arcball rotation in CAMERA_NEXT + set_view(CAMERA_CURRENT); + m_camera[CAMERA_NEXT].arcball.button(int2{io.MousePos} - m_viewport_pos, io.MouseDown[0]); + m_camera[CAMERA_NEXT].camera_type = CAMERA_CURRENT; + } + if (io.MouseReleased[0]) + { + m_camera[CAMERA_NEXT].arcball.button(int2{io.MousePos} - m_viewport_pos, io.MouseDown[0]); + // since the time between mouse down and up could be shorter + // than the animation duration, we override the previous + // camera's arcball on mouse up to complete the animation + m_camera[CAMERA_PREVIOUS].arcball = m_camera[CAMERA_NEXT].arcball; + m_camera[CAMERA_PREVIOUS].camera_type = m_camera[CAMERA_NEXT].camera_type = CAMERA_CURRENT; + } + + m_camera[CAMERA_NEXT].arcball.motion(int2{io.MousePos} - m_viewport_pos); + } + + // + // perform animation of camera parameters, e.g. after clicking one of the view buttons + // + { + CameraParameters &camera0 = m_camera[CAMERA_PREVIOUS]; + CameraParameters &camera1 = m_camera[CAMERA_NEXT]; + CameraParameters &camera = m_camera[CAMERA_CURRENT]; + + float time_now = (float)ImGui::GetTime(); + float time_diff = (m_animate_start_time != 0.0f) ? (time_now - m_animate_start_time) : 1.0f; + + // interpolate between the "perspectiveness" at the previous keyframe, + // and the "perspectiveness" of the currently selected camera + float t = smoothStep(0.0f, 1.0f, time_diff); + + camera.zoom = lerp(camera0.zoom, camera1.zoom, t); + camera.view_angle = lerp(camera0.view_angle, camera1.view_angle, t); + camera.persp_factor = lerp(camera0.persp_factor, camera1.persp_factor, t); + if (t >= 1.0f) + { + camera.camera_type = camera1.camera_type; + // m_params.fpsIdling.fpsIdle = 9.f; // animation is done, reduce FPS + if (m_animate_start_time != 0.0f) + m_params.fpsIdling.enableIdling = m_idling_backup; + m_animate_start_time = 0.f; + } + + // if we are dragging the mouse, then just use the current arcball + // rotation, otherwise, interpolate between the previous and next camera + // orientations + if (io.MouseDown[0]) + camera.arcball = camera1.arcball; + else + camera.arcball.set_state(qslerp(camera0.arcball.state(), camera1.arcball.state(), t)); + } + + float4x4 mvp = m_camera[CAMERA_CURRENT].matrix(float(m_viewport_size.x) / m_viewport_size.y); + + // + // Now render the points and grids + // + if (m_view == CAMERA_2D) + { + int plot_index = 0; + for (int y = 0; y < m_num_dimensions; ++y) + for (int x = 0; x < y; ++x, ++plot_index) + draw_2D_points_and_grid(mvp, int2{x, y}, plot_index); + } + else + { + + if (m_show_1d_projections) + { + // smash the points against the axes and draw + float4x4 smashX = + mul(mvp, mul(translation_matrix(float3{-0.51f, 0.f, 0.f}), scaling_matrix(float3{0.f, 1.f, 1.f}))); + draw_points(smashX, {0.8f, 0.3f, 0.3f}); + + float4x4 smashY = + mul(mvp, mul(translation_matrix(float3{0.f, -0.51f, 0.f}), scaling_matrix(float3{1.f, 0.f, 1.f}))); + draw_points(smashY, {0.3f, 0.8f, 0.3f}); + + float4x4 smashZ = + mul(mvp, mul(translation_matrix(float3{0.f, 0.f, -0.51f}), scaling_matrix(float3{1.f, 1.f, 0.f}))); + draw_points(smashZ, {0.3f, 0.3f, 0.8f}); + } + + draw_points(mvp, m_point_color); + + if (m_show_bbox) + draw_grid(mvp, 1.0f, 0, 8); + + if (m_show_coarse_grid) + draw_grid(mvp, 0.6f, 8, m_coarse_line_count); + + if (m_show_fine_grid) + draw_grid(mvp, 0.2f, 8 + m_coarse_line_count, m_fine_line_count); + } + } + catch (const std::exception &e) + { + fmt::print(stderr, "OpenGL drawing failed:\n\t{}.", e.what()); + HelloImGui::Log(HelloImGui::LogLevel::Error, "OpenGL drawing failed:\n\t%s.", e.what()); + } +} + +void SampleViewer::set_view(CameraType view) +{ + if (m_view != view) + { + m_animate_start_time = (float)ImGui::GetTime(); + m_camera[CAMERA_PREVIOUS] = m_camera[CAMERA_CURRENT]; + m_camera[CAMERA_NEXT] = m_camera[view]; + m_camera[CAMERA_NEXT].persp_factor = (view == CAMERA_CURRENT) ? 1.0f : 0.0f; + m_camera[CAMERA_NEXT].camera_type = view; + m_camera[CAMERA_CURRENT].camera_type = (view == m_camera[CAMERA_CURRENT].camera_type) ? view : CAMERA_CURRENT; + m_view = view; + + // m_params.fpsIdling.fpsIdle = 0.f; // during animation, increase FPS + m_idling_backup = m_params.fpsIdling.enableIdling; + m_params.fpsIdling.enableIdling = false; + } +} + +void SampleViewer::draw_points(const float4x4 &mvp, const float3 &color) +{ + // Render the point set + m_point_shader->set_uniform("mvp", mvp); + float radius = map_slider_to_radius(m_radius); + if (m_scale_radius_with_points) + radius *= 64.0f / std::sqrt(m_point_count); + m_point_shader->set_uniform("point_size", radius); + m_point_shader->set_uniform("color", color); + + int2 range = get_draw_range(); + + m_point_shader->begin(); + m_point_shader->draw_array(Shader::PrimitiveType::Point, range.x, range.y); + m_point_shader->end(); +} + +void SampleViewer::draw_grid(const float4x4 &mvp, float alpha, uint32_t offset, uint32_t count) +{ + m_grid_shader->set_uniform("alpha", alpha); + + for (int axis = CAMERA_XY; axis <= CAMERA_XZ; ++axis) + { + if (m_camera[CAMERA_CURRENT].camera_type == axis || m_camera[CAMERA_CURRENT].camera_type == CAMERA_CURRENT) + { + m_grid_shader->set_uniform("mvp", mul(mvp, m_camera[axis].arcball.matrix())); + m_grid_shader->begin(); + m_grid_shader->draw_array(Shader::PrimitiveType::Line, offset, count); + m_grid_shader->end(); + } + } +} + +void SampleViewer::draw_text(const int2 &pos, const string &text, const float4 &color, ImFont *font, int align) const +{ + ImGui::PushFont(font); + float2 apos{pos}; + float2 size = ImGui::CalcTextSize(text.c_str()); + + if (align & TextAlign_LEFT) + apos.x = pos.x; + else if (align & TextAlign_CENTER) + apos.x -= 0.5f * size.x; + else if (align & TextAlign_RIGHT) + apos.x -= size.x; + + if (align & TextAlign_TOP) + apos.y = pos.y; + else if (align & TextAlign_MIDDLE) + apos.y -= 0.5f * size.y; + else if (align & TextAlign_BOTTOM) + apos.y -= size.y; + + ImGui::GetBackgroundDrawList()->AddText(apos, ImColor(color), text.c_str()); + ImGui::PopFont(); +} + +string SampleViewer::export_XYZ_points(const string &format) +{ + float radius = map_slider_to_radius(m_radius); + if (m_scale_radius_with_points) + radius *= 64.0f / std::sqrt(m_point_count); + + string out = (format == "eps") ? header_eps(m_point_color, 1.f, radius) : header_svg(m_point_color); + + float4x4 mvp = m_camera[CAMERA_CURRENT].matrix(1.0f); + + for (int axis = CAMERA_XY; axis <= CAMERA_XZ; ++axis) + { + if (format == "eps") + out += draw_grids_eps(mul(mvp, m_camera[axis].arcball.matrix()), m_point_count, + m_samplers[m_sampler]->coarseGridRes(m_point_count), m_show_fine_grid, + m_show_coarse_grid, m_show_bbox); + else + out += draw_grids_svg(mul(mvp, m_camera[axis].arcball.matrix()), m_point_count, + m_samplers[m_sampler]->coarseGridRes(m_point_count), m_show_fine_grid, + m_show_coarse_grid, m_show_bbox); + } + + out += (format == "eps") ? draw_points_eps(mvp, m_dimension, m_subset_points, get_draw_range()) + : draw_points_svg(mvp, m_dimension, m_subset_points, get_draw_range(), radius); + + out += (format == "eps") ? footer_eps() : footer_svg(); + return out; +} + +string SampleViewer::export_points_2d(const string &format, CameraType camera_type, int3 dim) +{ + float radius = map_slider_to_radius(m_radius); + if (m_scale_radius_with_points) + radius *= 64.0f / std::sqrt(m_point_count); + + string out = (format == "eps") ? header_eps(m_point_color, 1.f, radius) : header_svg(m_point_color); + + float4x4 mvp = m_camera[camera_type].matrix(1.0f); + + if (format == "eps") + { + out += draw_grids_eps(mvp, m_point_count, m_samplers[m_sampler]->coarseGridRes(m_point_count), m_show_fine_grid, + m_show_coarse_grid, m_show_bbox); + out += draw_points_eps(mvp, dim, m_subset_points, get_draw_range()); + } + else + { + out += draw_grids_svg(mvp, m_point_count, m_samplers[m_sampler]->coarseGridRes(m_point_count), m_show_fine_grid, + m_show_coarse_grid, m_show_bbox); + out += draw_points_svg(mvp, dim, m_subset_points, get_draw_range(), radius); + } + + out += (format == "eps") ? footer_eps() : footer_svg(); + return out; +} + +string SampleViewer::export_all_points_2d(const string &format) +{ + float scale = 1.0f / (m_num_dimensions - 1); + + float radius = map_slider_to_radius(m_radius); + if (m_scale_radius_with_points) + radius *= 64.0f / std::sqrt(m_point_count); + + string out = (format == "eps") ? header_eps(m_point_color, scale, radius) : header_svg(m_point_color, scale); + + float4x4 mvp = m_camera[CAMERA_2D].matrix(1.0f); + + for (int y = 0; y < m_num_dimensions; ++y) + for (int x = 0; x < y; ++x) + { + float4x4 pos = layout_2d_matrix(m_num_dimensions, int2{x, y}); + + if (format == "eps") + { + out += draw_grids_eps(mul(mvp, pos), m_point_count, m_samplers[m_sampler]->coarseGridRes(m_point_count), + m_show_fine_grid, m_show_coarse_grid, m_show_bbox); + out += draw_points_eps(mul(mvp, pos), {x, y, 2}, m_subset_points, get_draw_range()); + } + else + { + out += draw_grids_svg(mul(mvp, pos), m_point_count, m_samplers[m_sampler]->coarseGridRes(m_point_count), + m_show_fine_grid, m_show_coarse_grid, m_show_bbox); + out += draw_points_svg(mul(mvp, pos), {x, y, 2}, m_subset_points, get_draw_range(), radius * scale); + } + } + + out += (format == "eps") ? footer_eps() : footer_svg(); + + return out; +} + +float4x4 CameraParameters::matrix(float window_aspect) const +{ + float4x4 model = scaling_matrix(float3(zoom)); + + float fH = std::tan(view_angle / 360.0f * M_PI) * dnear; + float fW = fH * window_aspect; + + float oFF = eye.z / dnear; + float4x4 orth = linalg::ortho_matrix(-fW * oFF, fW * oFF, -fH * oFF, fH * oFF, dnear, dfar); + float4x4 frust = linalg::frustum_matrix(-fW, fW, -fH, fH, dnear, dfar); + + float4x4 proj = lerp(orth, frust, persp_factor); + float4x4 lookat = linalg::lookat_matrix(eye, center, up); + float4x4 view = arcball.matrix(); + return mul(proj, mul(lookat, mul(view, model))); +} + +int main(int argc, char **argv) +{ + vector args; + bool help = false; + bool error = false; + bool launched_from_finder = false; + + try + { + for (int i = 1; i < argc; ++i) + { + if (strcmp("--help", argv[i]) == 0 || strcmp("-h", argv[i]) == 0) + help = true; + else if (strncmp("-psn", argv[i], 4) == 0) + launched_from_finder = true; + else + { + if (strncmp(argv[i], "-", 1) == 0) + { + cerr << "Invalid argument: \"" << argv[i] << "\"!" << endl; + help = true; + error = true; + } + args.push_back(argv[i]); + } + } + (void)launched_from_finder; + } + catch (const std::exception &e) + { + cout << "Error: " << e.what() << endl; + help = true; + error = true; + } + if (help) + { + cout << "Syntax: " << argv[0] << endl; + cout << "Options:" << endl; + cout << " -h, --help Display this message" << endl; + return error ? EXIT_FAILURE : EXIT_SUCCESS; + } + try + { + SampleViewer viewer; + viewer.run(); + } + catch (const std::runtime_error &e) + { + cerr << "Caught a fatal error: " << e.what() << endl; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/src/common.cpp.in b/src/common.cpp.in new file mode 100644 index 0000000..fae820f --- /dev/null +++ b/src/common.cpp.in @@ -0,0 +1,42 @@ +// +// Copyright (C) Wojciech Jarosz . All rights reserved. +// Use of this source code is governed by a BSD-style license that can +// be found in the LICENSE.txt file. +// + +#include "common.h" +#include + +using namespace std; + +// These constants and strings are autogenerated and compiled +// into the project by cmake +// clang-format off +int version_major() { return @VERSION_MAJOR@; } +int version_minor() { return @VERSION_MINOR@; } +int version_patch() { return @VERSION_PATCH@; } +string version() { return string("@VERSION_LONG@"); } +string git_hash() { return string("@GIT_HASH@"); } +string git_describe() { return string("@GIT_DESCRIBE@"); } +string build_timestamp() { return string("@BUILD_TIME@"); } +string backend() +{ +#if defined(HELLOIMGUI_USE_GLFW) + return "GLFW"; +#elif defined(HELLOIMGUI_USE_SDL) + return "SDL"; +#endif +} +// clang-format on + +string to_lower(string str) +{ + transform(begin(str), end(str), begin(str), [](unsigned char c) { return (char)tolower(c); }); + return str; +} + +string to_upper(string str) +{ + transform(begin(str), end(str), begin(str), [](unsigned char c) { return (char)toupper(c); }); + return str; +} diff --git a/src/export_to_file.cpp b/src/export_to_file.cpp new file mode 100644 index 0000000..cfcfc3b --- /dev/null +++ b/src/export_to_file.cpp @@ -0,0 +1,293 @@ +#include "export_to_file.h" +#include +#include + +using std::ofstream; +using std::string; + +string header_eps(const float3 &point_color, float scale, float radius) +{ + string out; + + float page_size = 500.f; + + out += fmt::format("%!PS-Adobe-3.0 EPSF-3.0\n"); + out += fmt::format("%%HiResBoundingBox: {} {} {} {}\n", -page_size, -page_size, page_size, page_size); + out += fmt::format("%%BoundingBox: {} {} {} {}\n", -page_size, -page_size, page_size, page_size); + out += fmt::format("%%CropBox: {} {} {} {}\n", -page_size, -page_size, page_size, page_size); + out += fmt::format("/radius {{ {} }} def %define variable for point radius\n", radius * 0.3f * scale); + out += fmt::format("/p {{ radius 0 360 arc closepath fill }} def %define point command\n"); + out += fmt::format("/blw {} def %define variable for bounding box linewidth\n", 2.f * scale); + out += fmt::format("/clw {} def %define variable for coarse linewidth\n", 2.f * scale); + out += fmt::format("/flw {} def %define variable for fine linewidth\n", 2.f * scale); + out += fmt::format("/pfc {{ {} {} {} }} def %define variable for point fill color\n", point_color.x, point_color.y, + point_color.z); + out += fmt::format("/blc {} def %define variable for bounding box color\n", 0.0f); + out += fmt::format("/clc {} def %define variable for coarse line color\n", 0.5f); + out += fmt::format("/flc {} def %define variable for fine line color\n", 0.9f); + + return out; +} + +string footer_eps() +{ + // return "grestore\n"; + return "\n"; +} + +string draw_grid_eps(const float4x4 &mvp, int grid_res) +{ + float page_size = 500.f; + + float4 vA4d, vB4d; + float2 vA, vB; + + int fine_grid_res = 2; + float coarse_scale = 1.f / grid_res, fine_scale = 1.f / fine_grid_res; + + string out; + + // draw the outer border of the grid + { + float4 c004d = mul(mvp, float4{0.f - 0.5f, 0.f - 0.5f, 0.f, 1.f}); + float4 c104d = mul(mvp, float4{1.f - 0.5f, 0.f - 0.5f, 0.f, 1.f}); + float4 c114d = mul(mvp, float4{1.f - 0.5f, 1.f - 0.5f, 0.f, 1.f}); + float4 c014d = mul(mvp, float4{0.f - 0.5f, 1.f - 0.5f, 0.f, 1.f}); + float2 c00 = float2(c004d.x / c004d.w, c004d.y / c004d.w) * page_size; + float2 c10 = float2(c104d.x / c104d.w, c104d.y / c104d.w) * page_size; + float2 c11 = float2(c114d.x / c114d.w, c114d.y / c114d.w) * page_size; + float2 c01 = float2(c014d.x / c014d.w, c014d.y / c014d.w) * page_size; + + out += fmt::format(R"(newpath + {} {} moveto + {} {} lineto + {} {} lineto + {} {} lineto +closepath stroke +)", + c00.x, c00.y, c10.x, c10.y, c11.x, c11.y, c01.x, c01.y); + } + + // draw the inner lines of the grid + for (int i = 1; i <= grid_res - 1; i++) + { + // draw horizontal lines + out += "newpath\n"; + for (int j = 0; j < fine_grid_res; j++) + { + vA4d = mul(mvp, float4{j * fine_scale - 0.5f, i * coarse_scale - 0.5f, 0.0f, 1.0f}); + vB4d = mul(mvp, float4{(j + 1) * fine_scale - 0.5f, i * coarse_scale - 0.5f, 0.0f, 1.0f}); + + vA = float2{vA4d.x / vA4d.w, vA4d.y / vA4d.w} * page_size; + vB = float2{vB4d.x / vB4d.w, vB4d.y / vB4d.w} * page_size; + + out += fmt::format(" {} {} {:s}\n", vA.x, vA.y, (j == 0) ? "moveto" : "lineto"); + out += fmt::format(" {} {} lineto\n", vB.x, vB.y); + } + out += "stroke\n"; + + // draw vertical lines + out += "newpath\n"; + for (int j = 0; j < fine_grid_res; j++) + { + vA4d = mul(mvp, float4{i * coarse_scale - 0.5f, j * fine_scale - 0.5f, 0.0f, 1.0f}); + vB4d = mul(mvp, float4{i * coarse_scale - 0.5f, (j + 1) * fine_scale - 0.5f, 0.0f, 1.0f}); + + vA = float2{vA4d.x / vA4d.w, vA4d.y / vA4d.w} * page_size; + vB = float2{vB4d.x / vB4d.w, vB4d.y / vB4d.w} * page_size; + + out += fmt::format(" {} {} {:s}\n", vA.x, vA.y, (j == 0) ? "moveto" : "lineto"); + out += fmt::format(" {} {} lineto\n", vB.x, vB.y); + } + out += "stroke\n"; + } + return out; +} + +string draw_grids_eps(float4x4 mat, int fgrid_res, int cgrid_res, bool fine_grid, bool coarse_grid, bool bbox) +{ + string out; + if (fine_grid) + { + out += "% Draw fine grids \n"; + out += "flc setgray %fill color for fine grid \n"; + out += "flw setlinewidth\n"; + + // this extra matrix multiply is needed to properly rotate the different grids for the XYZ view + out += draw_grid_eps(mat, fgrid_res); + } + + if (coarse_grid) + { + out += "% Draw coarse grids \n"; + out += "clc setgray %fill color for coarse grid \n"; + out += "clw setlinewidth\n"; + + out += draw_grid_eps(mat, cgrid_res); + } + + if (bbox) + { + out += "% Draw bounding boxes \n"; + out += "blc setgray %fill color for bounding box \n"; + out += "blw setlinewidth\n"; + out += draw_grid_eps(mat, 1); + } + + return out; +} + +string draw_points_eps(float4x4 mat, int3 dim, const Array2d &points, int2 range) +{ + string out; + + float page_size = 500.f; + + // Render the point set + out += "% Draw points \n"; + out += "pfc setrgbcolor %fill color for points\n"; + + for (int i = range.x; i < range.x + range.y; i++) + { + auto v4d = mul(mat, float4{points(i, dim.x), points(i, dim.y), points(i, dim.z), 1.0f}); + auto v2d = float2{v4d.x / v4d.w, v4d.y / v4d.w} * page_size; + out += fmt::format("{} {} p\n", v2d.x, v2d.y); + } + + return out; +} + +string header_svg(const float3 &point_color, float scale) +{ + string out; + + scale *= 0.5f; + + float page_size = 500.f; + + out += fmt::format(R"_( + +)_", + 1000, 1000, -page_size, -page_size, 2 * page_size, 2 * page_size, int(point_color.x * 255), + int(point_color.y * 255), int(point_color.z * 255), 2.f * scale, 2.f * scale, 2.f * scale); + return out; +} + +string draw_grid_svg(const float4x4 &mvp, int grid_res, const string &css_class) +{ + float4 vA4d, vB4d; + float2 vA, vB; + + float scale = 1.f / grid_res; + float page_size = 500.f; + + string out; + + // draw the outer border of the grid + { + float4 c004d = mul(mvp, float4{0.f - 0.5f, 0.f - 0.5f, 0.f, 1.f}); + float4 c104d = mul(mvp, float4{1.f - 0.5f, 0.f - 0.5f, 0.f, 1.f}); + float4 c114d = mul(mvp, float4{1.f - 0.5f, 1.f - 0.5f, 0.f, 1.f}); + float4 c014d = mul(mvp, float4{0.f - 0.5f, 1.f - 0.5f, 0.f, 1.f}); + float2 c00 = float2{c004d.x / c004d.w, c004d.y / c004d.w} * float2{page_size, -page_size}; + float2 c10 = float2{c104d.x / c104d.w, c104d.y / c104d.w} * float2{page_size, -page_size}; + float2 c11 = float2{c114d.x / c114d.w, c114d.y / c114d.w} * float2{page_size, -page_size}; + float2 c01 = float2{c014d.x / c014d.w, c014d.y / c014d.w} * float2{page_size, -page_size}; + + out += fmt::format(R"( )", c00.x, c00.y, c10.x, c10.y, + c11.x, c11.y, c01.x, c01.y, css_class); + out += "\n"; + } + + // draw the inner lines of the grid + for (int i = 1; i <= grid_res - 1; i++) + { + // draw horizontal lines + vA4d = mul(mvp, float4{0.f - 0.5f, i * scale - 0.5f, 0.0f, 1.0f}); + vB4d = mul(mvp, float4{1.f - 0.5f, i * scale - 0.5f, 0.0f, 1.0f}); + vA = float2(vA4d.x / vA4d.w, vA4d.y / vA4d.w) * float2{page_size, -page_size}; + vB = float2(vB4d.x / vB4d.w, vB4d.y / vB4d.w) * float2{page_size, -page_size}; + out += fmt::format(R"( )", vA.x, vA.y, vB.x, vB.y, css_class); + out += "\n"; + + // draw vertical lines + vA4d = mul(mvp, float4{i * scale - 0.5f, 0.f - 0.5f, 0.0f, 1.0f}); + vB4d = mul(mvp, float4{i * scale - 0.5f, 1.f - 0.5f, 0.0f, 1.0f}); + vA = float2(vA4d.x / vA4d.w, vA4d.y / vA4d.w) * float2{page_size, -page_size}; + vB = float2(vB4d.x / vB4d.w, vB4d.y / vB4d.w) * float2{page_size, -page_size}; + out += fmt::format(R"( )", vA.x, vA.y, vB.x, vB.y, css_class); + out += "\n"; + } + return out; +} + +string draw_grids_svg(float4x4 mat, int fgrid_res, int cgrid_res, bool fine_grid, bool coarse_grid, bool bbox) +{ + string out; + if (fine_grid) + out += draw_grid_svg(mat, fgrid_res, "fine_grid"); + + if (coarse_grid) + out += draw_grid_svg(mat, cgrid_res, "coarse_grid"); + + if (bbox) + out += draw_grid_svg(mat, 1, "bbox"); + + return out; +} + +string draw_points_svg(float4x4 mat, int3 dim, const Array2d &points, int2 range, float radius) +{ + string out; + float page_size = 500.f; + + for (int i = range.x; i < range.x + range.y; i++) + { + auto v4d = mul(mat, float4{points(i, dim.x), points(i, dim.y), points(i, dim.z), 1.0f}); + auto v2d = float2{v4d.x / v4d.w, v4d.y / v4d.w} * float2{page_size, -page_size}; + out += fmt::format(" \n", v2d.x, v2d.y, radius * 0.3f); + } + + return out; +} + +string footer_svg() +{ + return ""; +} \ No newline at end of file diff --git a/src/Halton.cpp b/src/sampler/Halton.cpp similarity index 69% rename from src/Halton.cpp rename to src/sampler/Halton.cpp index a87fa2e..c820c7a 100644 --- a/src/Halton.cpp +++ b/src/sampler/Halton.cpp @@ -2,15 +2,12 @@ \author Wojciech Jarosz */ +#include // for nthPrime #include -#include -#include -#include -#include +#include // for foldedRadicalInverse +#include // for Halton_sampler - -Halton::Halton(unsigned dimensions) : - m_numDimensions(0), m_randomized(true) +Halton::Halton(unsigned dimensions) : m_numDimensions(0), m_randomized(true) { setDimensions(dimensions); setRandomized(true); @@ -44,10 +41,7 @@ void Halton::sample(float r[], unsigned i) r[d] = m_halton.sample(d, i); } - - -HaltonZaremba::HaltonZaremba(unsigned dimensions) : - m_numDimensions(0) +HaltonZaremba::HaltonZaremba(unsigned dimensions) : m_numDimensions(0) { setDimensions(dimensions); } @@ -63,5 +57,5 @@ void HaltonZaremba::setDimensions(unsigned n) void HaltonZaremba::sample(float r[], unsigned i) { for (unsigned d = 0; d < m_numDimensions; d++) - r[d] = foldedRadicalInverse(i, nthPrime(d+1)); + r[d] = foldedRadicalInverse(i, nthPrime(d + 1)); } diff --git a/src/Jittered.cpp b/src/sampler/Jittered.cpp similarity index 60% rename from src/Jittered.cpp rename to src/sampler/Jittered.cpp index d21a977..b6942c2 100644 --- a/src/Jittered.cpp +++ b/src/sampler/Jittered.cpp @@ -3,10 +3,8 @@ */ #include -#include -Jittered::Jittered(unsigned x, unsigned y, float jitter) : - m_resX(x), m_resY(y), m_maxJit(jitter) +Jittered::Jittered(unsigned x, unsigned y, float jitter) : m_resX(x), m_resY(y), m_maxJit(jitter) { reset(); } @@ -22,20 +20,21 @@ std::string Jittered::name() const void Jittered::reset() { - if (m_resX == 0) m_resX = 1; - if (m_resY == 0) m_resY = 1; - m_xScale = 1.0f/m_resX; - m_yScale = 1.0f/m_resY; + if (m_resX == 0) + m_resX = 1; + if (m_resY == 0) + m_resY = 1; + m_xScale = 1.0f / m_resX; + m_yScale = 1.0f / m_resY; // m_rand.seed(m_seed); } - void Jittered::sample(float r[], unsigned i) { if (i == 0) m_rand.seed(m_seed); float jx = 0.5f + int(m_randomize) * m_maxJit * (m_rand.nextFloat() - 0.5f); float jy = 0.5f + int(m_randomize) * m_maxJit * (m_rand.nextFloat() - 0.5f); - r[0] = ((i % m_resX) + jx) * m_xScale; - r[1] = ((i / m_resY) + jy) * m_yScale; + r[0] = ((i % m_resX) + jx) * m_xScale; + r[1] = ((i / m_resY) + jy) * m_yScale; } diff --git a/src/sampler/LP.cpp b/src/sampler/LP.cpp new file mode 100644 index 0000000..1c196c6 --- /dev/null +++ b/src/sampler/LP.cpp @@ -0,0 +1,28 @@ +/** \file LP.cpp + \author Wojciech Jarosz +*/ + +#include +#include + +LarcherPillichshammerGK::LarcherPillichshammerGK(unsigned dimension, unsigned numSamples, bool randomize) : + m_numSamples(numSamples), m_numDimensions(dimension), m_inv(1.0f / m_numSamples) +{ + setRandomized(randomize); +} + +LarcherPillichshammerGK::~LarcherPillichshammerGK() +{ +} + +void LarcherPillichshammerGK::sample(float r[], unsigned i) +{ + if (m_numDimensions > 0) + r[0] = randomDigitScramble(i * m_inv, m_scramble1); + + if (m_numDimensions > 1) + r[1] = LarcherPillichshammerRI(i, m_scramble2); + + if (m_numDimensions > 2) + r[2] = GruenschlossKellerRI(i, m_scramble3); +} diff --git a/src/Misc.cpp b/src/sampler/Misc.cpp similarity index 69% rename from src/Misc.cpp rename to src/sampler/Misc.cpp index 87c0b04..e5af5cf 100644 --- a/src/Misc.cpp +++ b/src/sampler/Misc.cpp @@ -2,16 +2,13 @@ \author Wojciech Jarosz */ -#include -#include #include -#include -#include +#include #include +#include using namespace std; - // Compute the polynomial coefficients by taking the digits // of the sample index i expressed in the base vector iToPolyCoeffs(unsigned i, unsigned base, unsigned degree) @@ -25,34 +22,33 @@ vector iToPolyCoeffs(unsigned i, unsigned base, unsigned degree) return coeffs; } - // Evaluate the polynomial with coefficients coeffs at argument location arg -unsigned polyEval(const vector & coeffs, unsigned arg) +unsigned polyEval(const vector &coeffs, unsigned arg) { unsigned ans = 0; - for (size_t l = coeffs.size(); l--; ) - ans = (ans * arg) + coeffs.at(l); // Horner's rule + for (size_t l = coeffs.size(); l--;) + ans = (ans * arg) + coeffs.at(l); // Horner's rule return ans; } // Galois field version of above -unsigned polyEval(const Galois::Field * gf, vector & coeffs, int arg) +unsigned polyEval(const Galois::Field *gf, vector &coeffs, int arg) { Galois::Element ans(gf, 0); - for (size_t l = coeffs.size(); l--; ) // Horner's rule + for (size_t l = coeffs.size(); l--;) // Horner's rule ans = (ans * arg) + coeffs.at(l); return unsigned(ans.value()); } -unsigned polyEvalExceptOne(const vector &coeffs, int base, size_t - ignoreIdx) +unsigned polyEvalExceptOne(const vector &coeffs, int base, size_t ignoreIdx) { - size_t i = 0; + size_t i = 0; unsigned res = 0; for (size_t j = 0; j < coeffs.size(); j++) { - if (j == ignoreIdx) continue; + if (j == ignoreIdx) + continue; res += coeffs[i] * unsigned(pow(base, i)); i++; } diff --git a/src/MultiJittered.cpp b/src/sampler/MultiJittered.cpp similarity index 56% rename from src/MultiJittered.cpp rename to src/sampler/MultiJittered.cpp index e536785..7125c8a 100644 --- a/src/MultiJittered.cpp +++ b/src/sampler/MultiJittered.cpp @@ -2,20 +2,18 @@ \author Wojciech Jarosz */ +#include // for permute #include -#include -#include -#include +#include // for swap using namespace std; MultiJittered::MultiJittered(unsigned x, unsigned y, bool randomize, float jitter) : - m_resX(x), m_resY(y), m_numSamples(m_resX*m_resY), m_randomize(randomize), m_maxJit(jitter) + m_resX(x), m_resY(y), m_numSamples(m_resX * m_resY), m_randomize(randomize), m_maxJit(jitter) { reset(); } - MultiJittered::~MultiJittered() { clear(); @@ -26,20 +24,22 @@ void MultiJittered::clear() if (m_samples) for (unsigned d = 0; d < dimensions(); d++) if (m_samples[d]) - delete [] m_samples[d]; - delete [] m_samples; + delete[] m_samples[d]; + delete[] m_samples; m_samples = nullptr; } void MultiJittered::reset() { - if (m_resX == 0) m_resX = 1; - if (m_resY == 0) m_resY = 1; + if (m_resX == 0) + m_resX = 1; + if (m_resY == 0) + m_resY = 1; m_scale = 1.0f / m_numSamples; clear(); - m_samples = new float * [dimensions()]; + m_samples = new float *[dimensions()]; for (unsigned d = 0; d < dimensions(); d++) m_samples[d] = new float[m_numSamples]; @@ -48,30 +48,28 @@ void MultiJittered::reset() for (unsigned i = 0; i < m_resX; i++) for (unsigned j = 0; j < m_resY; j++) { - float jitterx = 0.5f + jitter * (m_rand.nextFloat() - 0.5f); - float jittery = 0.5f + jitter * (m_rand.nextFloat() - 0.5f); - m_samples[0][j * m_resX + i] = i*m_resY + j + jitterx; - m_samples[1][j * m_resX + i] = j*m_resX + i + jittery; + float jitterx = 0.5f + jitter * (m_rand.nextFloat() - 0.5f); + float jittery = 0.5f + jitter * (m_rand.nextFloat() - 0.5f); + m_samples[0][j * m_resX + i] = i * m_resY + j + jitterx; + m_samples[1][j * m_resX + i] = j * m_resX + i + jittery; } if (m_randomize) { // shuffle x coordinates within each column of cells for (unsigned i = 0; i < m_resX; i++) - for (unsigned j = m_resY-1; j >= 1; j--) + for (unsigned j = m_resY - 1; j >= 1; j--) { unsigned k = m_rand.nextUInt(j); - std::swap(m_samples[0][j * m_resX + i], - m_samples[0][k * m_resX + i]); + std::swap(m_samples[0][j * m_resX + i], m_samples[0][k * m_resX + i]); } // shuffle y coordinates within each row of cells for (unsigned j = 0; j < m_resY; j++) - for (unsigned i = m_resX-1; i >= 1; i--) + for (unsigned i = m_resX - 1; i >= 1; i--) { unsigned k = m_rand.nextUInt(i); - std::swap(m_samples[1][j * m_resX + i], - m_samples[1][j * m_resX + k]); + std::swap(m_samples[1][j * m_resX + i], m_samples[1][j * m_resX + k]); } } } @@ -82,16 +80,15 @@ void MultiJittered::sample(float r[], unsigned i) i = 0; for (unsigned d = 0; d < dimensions(); d++) - r[d] = (m_samples[d][i])*m_scale; + r[d] = (m_samples[d][i]) * m_scale; } MultiJitteredInPlace::MultiJitteredInPlace(unsigned x, unsigned y, bool randomize, float jitter) : - m_resX(x), m_resY(y), m_numSamples(m_resX*m_resY), m_randomize(randomize), m_maxJit(jitter) + m_resX(x), m_resY(y), m_numSamples(m_resX * m_resY), m_randomize(randomize), m_maxJit(jitter) { reset(); } - MultiJitteredInPlace::~MultiJitteredInPlace() { clear(); @@ -99,7 +96,7 @@ MultiJitteredInPlace::~MultiJitteredInPlace() std::string MultiJitteredInPlace::name() const { - return tfm::format("MultiJittered In-Place"); + return "MultiJittered In-Place"; } void MultiJitteredInPlace::setRandomized(bool r) @@ -113,16 +110,15 @@ void MultiJitteredInPlace::setRandomized(bool r) void MultiJitteredInPlace::clear() { - } void MultiJitteredInPlace::reset() { - if (m_resX == 0) m_resX = 1; + if (m_resX == 0) + m_resX = 1; m_scale = 1.0f / m_numSamples; } - void MultiJitteredInPlace::sample(float r[], unsigned i) { i = i % m_numSamples; @@ -130,25 +126,23 @@ void MultiJitteredInPlace::sample(float r[], unsigned i) if (i == 0) m_rand.seed(m_seed); - int s = permute(i, m_numSamples, m_permutation * 0x51633e2d); - int x = s % m_resX; - int y = s / m_resX; - int sx = permute(x, m_resX, (y + m_permutation) * 0x68bc21eb); - int sy = permute(y, m_resY, (x + m_permutation) * 0x02e5be93); + int s = permute(i, m_numSamples, m_permutation * 0x51633e2d); + int x = s % m_resX; + int y = s / m_resX; + int sx = permute(x, m_resX, (y + m_permutation) * 0x68bc21eb); + int sy = permute(y, m_resY, (x + m_permutation) * 0x02e5be93); float jx = 0.5f + m_maxJit * (m_rand.nextFloat() - 0.5f); float jy = 0.5f + m_maxJit * (m_rand.nextFloat() - 0.5f); - r[0] = (x + (sy + jx) / m_resY) / m_resX; - r[1] = (y + (sx + jy) / m_resX) / m_resY; + r[0] = (x + (sy + jx) / m_resY) / m_resX; + r[1] = (y + (sx + jy) / m_resX) / m_resY; } - CorrelatedMultiJittered::CorrelatedMultiJittered(unsigned x, unsigned y, bool randomize, float jitter) : MultiJittered(x, y, randomize, jitter) { // empty } - CorrelatedMultiJittered::~CorrelatedMultiJittered() { clear(); @@ -156,13 +150,15 @@ CorrelatedMultiJittered::~CorrelatedMultiJittered() void CorrelatedMultiJittered::reset() { - if (m_resX == 0) m_resX = 1; - if (m_resY == 0) m_resY = 1; + if (m_resX == 0) + m_resX = 1; + if (m_resY == 0) + m_resY = 1; m_scale = 1.0f / m_numSamples; clear(); - m_samples = new float * [dimensions()]; + m_samples = new float *[dimensions()]; for (unsigned d = 0; d < dimensions(); d++) m_samples[d] = new float[m_numSamples]; @@ -171,47 +167,43 @@ void CorrelatedMultiJittered::reset() for (unsigned i = 0; i < m_resX; i++) for (unsigned j = 0; j < m_resY; j++) { - float jitterx = 0.5f + jitter * (m_rand.nextFloat() - 0.5f); - float jittery = 0.5f + jitter * (m_rand.nextFloat() - 0.5f); - m_samples[0][j * m_resX + i] = i*m_resY + j + jitterx; - m_samples[1][j * m_resX + i] = j*m_resX + i + jittery; + float jitterx = 0.5f + jitter * (m_rand.nextFloat() - 0.5f); + float jittery = 0.5f + jitter * (m_rand.nextFloat() - 0.5f); + m_samples[0][j * m_resX + i] = i * m_resY + j + jitterx; + m_samples[1][j * m_resX + i] = j * m_resX + i + jittery; } if (m_randomize) { // shuffle x coordinates within each column of cells - for (unsigned j = m_resY-1; j >= 1; j--) + for (unsigned j = m_resY - 1; j >= 1; j--) { unsigned k = m_rand.nextUInt(j); for (unsigned i = 0; i < m_resX; i++) - std::swap(m_samples[0][j * m_resX + i], - m_samples[0][k * m_resX + i]); + std::swap(m_samples[0][j * m_resX + i], m_samples[0][k * m_resX + i]); } // shuffle y coordinates within each row of cells - for (unsigned i = m_resX-1; i >= 1; i--) + for (unsigned i = m_resX - 1; i >= 1; i--) { unsigned k = m_rand.nextUInt(i); for (unsigned j = 0; j < m_resY; j++) - std::swap(m_samples[1][j * m_resX + i], - m_samples[1][j * m_resX + k]); + std::swap(m_samples[1][j * m_resX + i], m_samples[1][j * m_resX + k]); } } } CorrelatedMultiJitteredInPlace::CorrelatedMultiJitteredInPlace(unsigned x, unsigned y, unsigned dims, bool r, float j) : - m_resX(x), m_resY(y), m_numSamples(m_resX*m_resY), m_numDimensions(dims), m_maxJit(j), m_randomize(r) + m_resX(x), m_resY(y), m_numSamples(m_resX * m_resY), m_numDimensions(dims), m_maxJit(j), m_randomize(r) { setRandomized(r); } - CorrelatedMultiJitteredInPlace::~CorrelatedMultiJitteredInPlace() { // empty } - void CorrelatedMultiJitteredInPlace::sample(float r[], unsigned i) { if (i >= m_numSamples) @@ -220,14 +212,15 @@ void CorrelatedMultiJitteredInPlace::sample(float r[], unsigned i) if (i == 0) m_rand.seed(m_seed); - for (unsigned d = 0; d < dimensions(); d+=2) + for (unsigned d = 0; d < dimensions(); d += 2) { - int s = permute(i, m_numSamples, m_permutation * 0x51633e2d * (d+1)); - int sx = permute(s % m_resX, m_resX, m_permutation * 0x68bc21eb * (d+1)); - int sy = permute(s / m_resX, m_resY, m_permutation * 0x02e5be93 * (d+1)); + int s = permute(i, m_numSamples, m_permutation * 0x51633e2d * (d + 1)); + int sx = permute(s % m_resX, m_resX, m_permutation * 0x68bc21eb * (d + 1)); + int sy = permute(s / m_resX, m_resY, m_permutation * 0x02e5be93 * (d + 1)); float jx = 0.5f + m_maxJit * (m_rand.nextFloat() - 0.5f); float jy = 0.5f + m_maxJit * (m_rand.nextFloat() - 0.5f); - r[d] = (sx + (sy + jx) / m_resY) / m_resX; - r[d+1] = (s + jy) / m_numSamples; + r[d] = (sx + (sy + jx) / m_resY) / m_resX; + if (d + 1 < dimensions()) + r[d + 1] = (s + jy) / m_numSamples; } } diff --git a/src/NRooks.cpp b/src/sampler/NRooks.cpp similarity index 79% rename from src/NRooks.cpp rename to src/sampler/NRooks.cpp index f0f0fbe..1c504ad 100644 --- a/src/NRooks.cpp +++ b/src/sampler/NRooks.cpp @@ -2,10 +2,8 @@ \author Wojciech Jarosz */ -#include #include -#include -#include +#include using namespace std; @@ -21,14 +19,13 @@ NRooks::NRooks(unsigned dims, unsigned samples, bool randomize, float jitter) : NRooks::~NRooks() { - } -void -NRooks::reset() +void NRooks::reset() { - if (m_numSamples == 0) m_numSamples = 1; - m_scale = 1.0f/m_numSamples; + if (m_numSamples == 0) + m_numSamples = 1; + m_scale = 1.0f / m_numSamples; m_permutations.resize(dimensions()); // set up the random permutations for each dimension @@ -41,19 +38,16 @@ NRooks::reset() } } -void -NRooks::sample(float r[], unsigned i) +void NRooks::sample(float r[], unsigned i) { if (i >= m_numSamples) i = 0; float jitter = m_maxJit * m_randomize; for (unsigned d = 0; d < dimensions(); d++) - r[d] = (m_permutations[d][i] + - 0.5f + jitter * (m_rand.nextFloat() - 0.5f)) * m_scale; + r[d] = (m_permutations[d][i] + 0.5f + jitter * (m_rand.nextFloat() - 0.5f)) * m_scale; } - NRooksInPlace::NRooksInPlace(unsigned dim, unsigned n, bool r, float j) : m_numDimensions(dim), m_numSamples(n), m_maxJit(j), m_randomize(r) { @@ -61,7 +55,6 @@ NRooksInPlace::NRooksInPlace(unsigned dim, unsigned n, bool r, float j) : setRandomized(r); } - NRooksInPlace::~NRooksInPlace() { // empty @@ -76,7 +69,6 @@ void NRooksInPlace::reset() m_scrambles[d] = m_randomize ? m_rand.nextUInt() : 0; } - void NRooksInPlace::sample(float r[], unsigned i) { if (i >= m_numSamples) @@ -87,6 +79,5 @@ void NRooksInPlace::sample(float r[], unsigned i) float jitter = m_maxJit * m_randomize; for (unsigned d = 0; d < dimensions(); d++) - r[d] = (permute(i, m_numSamples, m_scrambles[d]) + - 0.5f + jitter * (m_rand.nextFloat() - 0.5f)) / m_numSamples; + r[d] = (permute(i, m_numSamples, m_scrambles[d]) + 0.5f + jitter * (m_rand.nextFloat() - 0.5f)) / m_numSamples; } diff --git a/src/OA.cpp b/src/sampler/OA.cpp similarity index 76% rename from src/OA.cpp rename to src/sampler/OA.cpp index 66fb5d2..4e901ae 100644 --- a/src/OA.cpp +++ b/src/sampler/OA.cpp @@ -14,5 +14,5 @@ unsigned OrthogonalArray::setOffsetType(unsigned ot) } vector OrthogonalArray::offsetTypeNames() const { - return {{"CTR", "J", "MJ", "CMJ"}}; + return {{"Centered", "Jittered", "Multi-jittered", "Correlated Multi-jittered"}}; } \ No newline at end of file diff --git a/src/OAAddelmanKempthorne.cpp b/src/sampler/OAAddelmanKempthorne.cpp similarity index 81% rename from src/OAAddelmanKempthorne.cpp rename to src/sampler/OAAddelmanKempthorne.cpp index 50d5497..79c0830 100644 --- a/src/OAAddelmanKempthorne.cpp +++ b/src/sampler/OAAddelmanKempthorne.cpp @@ -4,12 +4,10 @@ \author Wojciech Jarosz */ -#include -#include #include #include -#include -#include +#include +#include #include using namespace std; @@ -18,7 +16,7 @@ using namespace std; namespace { -int akodd(Galois::Element* kay, vector& b, vector& c, vector& k) +int akodd(Galois::Element *kay, vector &b, vector &c, vector &k) { Galois::Element num(kay->field()), den(kay->field()), four(kay->field()); @@ -43,8 +41,8 @@ int akodd(Galois::Element* kay, vector& b, vector& c, vector& k) b[0] = k[0] = c[0] = 0; for (int i = 1; i < q; i++) { - num = *kay + (p - 1); /* -1 = +(p-1) */ - den = (*kay * four) * i; + num = *kay + (p - 1); /* -1 = +(p-1) */ + den = (*kay * four) * i; b[i] = (num / den).value(); k[i] = (*kay * i).value(); c[i] = ((num * i) * i / four).value(); @@ -53,10 +51,10 @@ int akodd(Galois::Element* kay, vector& b, vector& c, vector& k) return 0; } -int akeven(Galois::Element* kay, vector& b, vector& c, vector& k) +int akeven(Galois::Element *kay, vector &b, vector &c, vector &k) { int q = kay->field()->q; - *kay = 1; + *kay = 1; if (q == 2) b[1] = c[1] = k[1] = 1; @@ -65,9 +63,9 @@ int akeven(Galois::Element* kay, vector& b, vector& c, vector& k) b[1] = c[1] = 2; b[2] = c[2] = 1; b[3] = c[3] = 3; - k[1] = 1; - k[2] = 2; - k[3] = 3; + k[1] = 1; + k[2] = 2; + k[3] = 3; } for (int i = 1; i < q; ++i) @@ -82,10 +80,9 @@ int akeven(Galois::Element* kay, vector& b, vector& c, vector& k) } // namespace -AddelmanKempthorneOAInPlace::AddelmanKempthorneOAInPlace(unsigned x, OffsetType ot, - bool randomize, float jitter, - unsigned dimensions) - : BoseGaloisOAInPlace(x, ot, randomize, jitter, dimensions) +AddelmanKempthorneOAInPlace::AddelmanKempthorneOAInPlace(unsigned x, OffsetType ot, bool randomize, float jitter, + unsigned dimensions) : + BoseGaloisOAInPlace(x, ot, randomize, jitter, dimensions) { setNumSamples(2 * x * x); reset(); @@ -98,9 +95,9 @@ string AddelmanKempthorneOAInPlace::name() const int AddelmanKempthorneOAInPlace::setNumSamples(unsigned n) { - int base = (int)round(sqrt(n * 0.5f)); - base = (base < 2) ? 2 : base; - m_s = primeGE(base); + int base = (int)round(sqrt(n * 0.5f)); + base = (base < 2) ? 2 : base; + m_s = primeGE(base); m_numSamples = 2 * m_s * m_s; m_gf.resize(m_s); reset(); @@ -119,7 +116,8 @@ void AddelmanKempthorneOAInPlace::sample(float r[], unsigned row) if (2 * row < m_numSamples) { // First q*q rows - auto Adim = [this, i, j, square](unsigned dim) { + auto Adim = [this, i, j, square](unsigned dim) + { // re-ordered the dimensions so that i is the first one // mathematically the i.value case is just a special case of the // (i+m*j) when m == 0, but putting i in the first column allows us @@ -142,13 +140,12 @@ void AddelmanKempthorneOAInPlace::sample(float r[], unsigned row) throw domain_error("Out to bounds dimension"); }; - for (unsigned dim = 0; dim < 2 * m_s + 1 && dim < dimensions(); - ++dim) + for (unsigned dim = 0; dim < 2 * m_s + 1 && dim < dimensions(); ++dim) { - int Acol = Adim(dim); - int k = (dim % 2) ? dim - 1 : (dim + 1) % (2 * m_s + 1); - int Aik = Adim(k); - int stratumJ = permute(Acol, m_s, m_seed * (dim+1)); + int Acol = Adim(dim); + int k = (dim % 2) ? dim - 1 : (dim + 1) % (2 * m_s + 1); + int Aik = Adim(k); + int stratumJ = permute(Acol, m_s, m_seed * (dim + 1)); // int sstratJ = boseLHOffset(Acol, 2*Aik, 2*m_s, // m_seed * (dim+1) * 0x68bc21eb, m_ot); LHS with 2*m_s // fine strata int sstratJ = permute(2*Aik, 2*m_s, (Acol + 1) * @@ -162,9 +159,9 @@ void AddelmanKempthorneOAInPlace::sample(float r[], unsigned row) // m_seed * (dim+1))) // sstratJ = 2*m_s - 1 - sstratJ; // antithetic mj - int sstratJ = permute(Aik, m_s, (Acol + 1) * m_seed * (dim+1) * 0x68bc21eb); + int sstratJ = permute(Aik, m_s, (Acol + 1) * m_seed * (dim + 1) * 0x68bc21eb); // randomize which quandrant the samples go into - if (permute(Aik % 2, 2, (Acol * m_s + Aik + 1) * m_seed * (dim+1))) + if (permute(Aik % 2, 2, (Acol * m_s + Aik + 1) * m_seed * (dim + 1))) sstratJ = 2 * m_s - 1 - sstratJ; // // antithetic cmj // int sstratJ = permute(Aik, m_s, m_seed * (dim+1) * @@ -172,10 +169,8 @@ void AddelmanKempthorneOAInPlace::sample(float r[], unsigned row) // // randomize whether cmj points get pushed to upper or lower half // of slice if (permute(Aik % 2, 2, m_seed * (dim+1))) // sstratJ = 2*m_s - 1 - sstratJ; - float jitterJ = 0.5f + int(m_randomize) * m_maxJit * - (m_rand.nextFloat() - 0.5f); - r[dim] = (stratumJ + (sstratJ + jitterJ) / float(2 * m_s)) / - float(m_s); + float jitterJ = 0.5f + int(m_randomize) * m_maxJit * (m_rand.nextFloat() - 0.5f); + r[dim] = (stratumJ + (sstratJ + jitterJ) / float(2 * m_s)) / float(m_s); } } else @@ -184,9 +179,9 @@ void AddelmanKempthorneOAInPlace::sample(float r[], unsigned row) for (size_t dim = 0; dim < dimensions(); ++dim) r[dim] = 0.0f; - vector b(m_s); - vector c(m_s); - vector k(m_s); + vector b(m_s); + vector c(m_s); + vector k(m_s); Galois::Element kay(&m_gf); if (m_gf.p != 2) @@ -196,7 +191,8 @@ void AddelmanKempthorneOAInPlace::sample(float r[], unsigned row) const Galois::Element ksquare = kay * square; - auto Adim = [this, &i, &j, &ksquare, &b, &c, &k](unsigned dim) { + auto Adim = [this, &i, &j, &ksquare, &b, &c, &k](unsigned dim) + { if (dim == 0) return i.value(); if (dim == 1) @@ -215,13 +211,12 @@ void AddelmanKempthorneOAInPlace::sample(float r[], unsigned row) throw domain_error("Out to bounds dimension"); }; - for (unsigned dim = 0; dim < 2 * m_s + 1 && dim < dimensions(); - ++dim) + for (unsigned dim = 0; dim < 2 * m_s + 1 && dim < dimensions(); ++dim) { - int Acol = Adim(dim); - int k = (dim % 2) ? dim - 1 : (dim + 1) % (2 * m_s + 1); - int Aik = Adim(k); - int stratumJ = permute(Acol, m_s, m_seed * (dim+1)); + int Acol = Adim(dim); + int k = (dim % 2) ? dim - 1 : (dim + 1) % (2 * m_s + 1); + int Aik = Adim(k); + int stratumJ = permute(Acol, m_s, m_seed * (dim + 1)); // int sstratJ = boseLHOffset(Acol, 2*Aik+1, 2*m_s, // m_seed * (dim+1) * 0x63bc21eb, m_ot); LHS with 2*m_s // fine strata int sstratJ = permute(2*Aik+1, 2*m_s, (Acol + 1) * @@ -235,9 +230,9 @@ void AddelmanKempthorneOAInPlace::sample(float r[], unsigned row) // m_seed * (dim+1))) // sstratJ = 2*m_s - 1 - sstratJ; // antithetic mj - int sstratJ = permute(Aik, m_s, (Acol + 1) * m_seed * (dim+1) * 0x68bc21eb); + int sstratJ = permute(Aik, m_s, (Acol + 1) * m_seed * (dim + 1) * 0x68bc21eb); // randomize which quandrant the samples go into - if (!permute(Aik % 2, 2, (Acol * m_s + Aik + 1) * m_seed * (dim+1))) + if (!permute(Aik % 2, 2, (Acol * m_s + Aik + 1) * m_seed * (dim + 1))) sstratJ = 2 * m_s - 1 - sstratJ; // // antithetic cmj // int sstratJ = permute(Aik, m_s, m_seed * (dim+1) * @@ -246,10 +241,9 @@ void AddelmanKempthorneOAInPlace::sample(float r[], unsigned row) // of slice if (!permute(Aik % 2, 2, m_seed * (dim+1))) // sstratJ = 2*m_s - 1 - sstratJ; float jitterJ = 0.5f + int(m_randomize) * m_maxJit * (m_rand.nextFloat() - 0.5f); - r[dim] = (stratumJ + (sstratJ + jitterJ) / (2 * m_s)) / m_s; + r[dim] = (stratumJ + (sstratJ + jitterJ) / (2 * m_s)) / m_s; } } } - //// \ No newline at end of file diff --git a/src/OABose.cpp b/src/sampler/OABose.cpp similarity index 64% rename from src/OABose.cpp rename to src/sampler/OABose.cpp index 4557706..8f39fd4 100644 --- a/src/OABose.cpp +++ b/src/sampler/OABose.cpp @@ -3,12 +3,11 @@ \author Wojciech Jarosz */ -#include -#include -#include #include #include -#include +#include +#include +#include using namespace std; @@ -20,24 +19,18 @@ float boseLHOffset(int sx, int sy, int s, int p, unsigned type) { switch (type) { - case CENTERED: - return s / 2.0f; - case J_STYLE: - return permute(0, s, (sy * s + sx + 1) * p); - case MJ_STYLE: - return permute(sy, s, (sx + 1) * p); + case CENTERED: return s / 2.0f; + case J_STYLE: return permute(0, s, (sy * s + sx + 1) * p); + case MJ_STYLE: return permute(sy, s, (sx + 1) * p); default: - case CMJ_STYLE: - return permute(sy, s, p); + case CMJ_STYLE: return permute(sy, s, p); } } } // namespace -BoseOA::BoseOA(unsigned x, OffsetType ot, bool randomize, float jitter, - unsigned dimensions) - : OrthogonalArray(2, ot, randomize, jitter), m_s(x), - m_numSamples(m_s * m_s), m_numDimensions(dimensions) +BoseOA::BoseOA(unsigned x, OffsetType ot, bool randomize, float jitter, unsigned dimensions) : + OrthogonalArray(2, ot, randomize, jitter), m_s(x), m_numSamples(m_s * m_s), m_numDimensions(dimensions) { reset(); } @@ -61,7 +54,7 @@ int BoseOA::setNumSamples(unsigned n) void BoseOA::setNumSamples(unsigned x, unsigned) { - m_s = primeGE(x); + m_s = primeGE(x); m_numSamples = m_s * m_s; reset(); @@ -92,7 +85,7 @@ void BoseOA::reset() clear(); - m_samples = new unsigned*[dimensions()]; + m_samples = new unsigned *[dimensions()]; for (unsigned d = 0; d < dimensions(); d++) m_samples[d] = new unsigned[m_numSamples]; @@ -120,7 +113,7 @@ void BoseOA::reset() m_samples[1][i] = (perm[1][Ai2]) * m_s; for (unsigned d = 2; d < dimensions(); ++d) { - int Aid = (Ai1 + (d - 1) * Ai2) % m_s; + int Aid = (Ai1 + (d - 1) * Ai2) % m_s; m_samples[d][i] = (perm[d][Aid]) * m_s; } } @@ -140,7 +133,7 @@ void BoseOA::reset() m_samples[1][i] = perm[1][Ai2] * m_s + Ai1; for (unsigned d = 2; d < dimensions(); ++d) { - int Aid = (Ai1 + (d - 1) * Ai2) % m_s; + int Aid = (Ai1 + (d - 1) * Ai2) % m_s; m_samples[d][i] = perm[d][Aid] * m_s + Ai2; } } @@ -170,8 +163,7 @@ void BoseOA::reset() for (unsigned j = m_s - 1; j >= 1; j--) { unsigned k = m_rand.nextUInt(j); - swap(m_samples[1][j * m_s + i], - m_samples[1][k * m_s + i]); + swap(m_samples[1][j * m_s + i], m_samples[1][k * m_s + i]); } // shuffle x coordinates within each column of cells @@ -179,8 +171,7 @@ void BoseOA::reset() for (unsigned i = m_s - 1; i >= 1; i--) { unsigned k = m_rand.nextUInt(i); - swap(m_samples[0][j * m_s + i], - m_samples[0][j * m_s + k]); + swap(m_samples[0][j * m_s + i], m_samples[0][j * m_s + k]); } // shuffle the strata in each remaining dimension d @@ -203,8 +194,7 @@ void BoseOA::reset() for (unsigned s = 0; s < m_s; s++) { for (unsigned i = pointsInStratum[s].size() - 1; i >= 1; i--) - swap(m_samples[d][pointsInStratum[s][i]], - m_samples[d][pointsInStratum[s][m_rand.nextUInt(i)]]); + swap(m_samples[d][pointsInStratum[s][i]], m_samples[d][pointsInStratum[s][m_rand.nextUInt(i)]]); } } } @@ -224,27 +214,17 @@ void BoseOA::sample(float r[], unsigned i) switch (m_ot) { case CENTERED: - case J_STYLE: - r[d] = (m_samples[d][i] / m_s + - (0.5f + jitter * (m_rand.nextFloat() - 0.5f))) / - m_s; - break; + case J_STYLE: r[d] = (m_samples[d][i] / m_s + (0.5f + jitter * (m_rand.nextFloat() - 0.5f))) / m_s; break; case MJ_STYLE: case CMJ_STYLE: - default: - r[d] = (m_samples[d][i] + - (0.5f + m_maxJit * (m_rand.nextFloat() - 0.5f))) * - m_scale; - break; + default: r[d] = (m_samples[d][i] + (0.5f + m_maxJit * (m_rand.nextFloat() - 0.5f))) * m_scale; break; } } } -BoseOAInPlace::BoseOAInPlace(unsigned x, OffsetType ot, bool randomize, - float jitter, unsigned dimensions) - : OrthogonalArray(2, ot, randomize, jitter), m_s(x), - m_numSamples(m_s * m_s), m_numDimensions(dimensions) +BoseOAInPlace::BoseOAInPlace(unsigned x, OffsetType ot, bool randomize, float jitter, unsigned dimensions) : + OrthogonalArray(2, ot, randomize, jitter), m_s(x), m_numSamples(m_s * m_s), m_numDimensions(dimensions) { reset(); } @@ -263,7 +243,7 @@ int BoseOAInPlace::setNumSamples(unsigned n) void BoseOAInPlace::setNumSamples(unsigned x, unsigned) { - m_s = primeGE(x); + m_s = primeGE(x); m_numSamples = m_s * m_s; reset(); } @@ -280,26 +260,26 @@ void BoseOAInPlace::sample(float r[], unsigned i) unsigned maxDim = min(dimensions(), m_s + 1); - int stratumX = i / m_s; - int stratumY = i % m_s; - int Ai0 = permute(stratumX, m_s, m_seed * 1); - int Ai1 = permute(stratumY, m_s, m_seed * 2); - float sstratX = boseLHOffset(Ai0, Ai1, m_s, m_seed * 1 * 0x68bc21eb, m_ot); - float sstratY = boseLHOffset(Ai1, Ai0, m_s, m_seed * 2 * 0x68bc21eb, m_ot); - float jitterX = 0.5f + int(m_randomize) * m_maxJit * (m_rand.nextFloat() - 0.5f); - float jitterY = 0.5f + int(m_randomize) * m_maxJit * (m_rand.nextFloat() - 0.5f); - r[0] = (stratumX + (sstratX + jitterX) / m_s) / m_s; - r[1] = (stratumY + (sstratY + jitterY) / m_s) / m_s; + int stratumX = i / m_s; + int stratumY = i % m_s; + int Ai0 = permute(stratumX, m_s, m_seed * 1); + int Ai1 = permute(stratumY, m_s, m_seed * 2); + float sstratX = boseLHOffset(Ai0, Ai1, m_s, m_seed * 1 * 0x68bc21eb, m_ot); + float sstratY = boseLHOffset(Ai1, Ai0, m_s, m_seed * 2 * 0x68bc21eb, m_ot); + float jitterX = 0.5f + int(m_randomize) * m_maxJit * (m_rand.nextFloat() - 0.5f); + float jitterY = 0.5f + int(m_randomize) * m_maxJit * (m_rand.nextFloat() - 0.5f); + r[0] = (stratumX + (sstratX + jitterX) / m_s) / m_s; + r[1] = (stratumY + (sstratY + jitterY) / m_s) / m_s; for (unsigned j = 2; j < maxDim; ++j) { - int Aij = (Ai0 + (j - 1) * Ai1) % m_s; - int k = (j % 2) ? j - 1 : j + 1; - int Aik = (Ai0 + (k - 1) * Ai1) % m_s; - int stratumJ = permute(Aij, m_s, m_seed * (j+1)); - float sstratJ = boseLHOffset(Aij, Aik, m_s, m_seed * (j+1) * 0x68bc21eb, m_ot); - float jitterJ = 0.5f + int(m_randomize) * m_maxJit * (m_rand.nextFloat() - 0.5f); - r[j] = (stratumJ + (sstratJ + jitterJ) / m_s) / m_s; + int Aij = (Ai0 + (j - 1) * Ai1) % m_s; + int k = (j % 2) ? j - 1 : j + 1; + int Aik = (Ai0 + (k - 1) * Ai1) % m_s; + int stratumJ = permute(Aij, m_s, m_seed * (j + 1)); + float sstratJ = boseLHOffset(Aij, Aik, m_s, m_seed * (j + 1) * 0x68bc21eb, m_ot); + float jitterJ = 0.5f + int(m_randomize) * m_maxJit * (m_rand.nextFloat() - 0.5f); + r[j] = (stratumJ + (sstratJ + jitterJ) / m_s) / m_s; } for (unsigned j = maxDim; j < dimensions(); ++j) @@ -309,10 +289,8 @@ void BoseOAInPlace::sample(float r[], unsigned i) // // -BoseGaloisOAInPlace::BoseGaloisOAInPlace(unsigned x, OffsetType ot, - bool randomize, float jitter, - unsigned dimensions) - : BoseOAInPlace(x, ot, randomize, jitter, dimensions) +BoseGaloisOAInPlace::BoseGaloisOAInPlace(unsigned x, OffsetType ot, bool randomize, float jitter, unsigned dimensions) : + BoseOAInPlace(x, ot, randomize, jitter, dimensions) { setNumSamples(x * x); reset(); @@ -325,9 +303,9 @@ string BoseGaloisOAInPlace::name() const int BoseGaloisOAInPlace::setNumSamples(unsigned n) { - int base = (int)round(sqrt(n)); - base = (base < 2) ? 2 : base; - m_s = primePowerGE(base); + int base = (int)round(sqrt(n)); + base = (base < 2) ? 2 : base; + m_s = primePowerGE(base); m_numSamples = m_s * m_s; m_gf.resize(m_s); reset(); @@ -339,30 +317,29 @@ void BoseGaloisOAInPlace::sample(float r[], unsigned i) if (i == 0) m_rand.seed(m_seed); - int stratumX = i / m_s; - int stratumY = i % m_s; + int stratumX = i / m_s; + int stratumY = i % m_s; Galois::Element Ai0(&m_gf, permute(stratumX, m_s, m_seed * 1)); Galois::Element Ai1(&m_gf, permute(stratumY, m_s, m_seed * 2)); - float sstratX = boseLHOffset(Ai0.value(), Ai1.value(), m_s, m_seed * 1 * 0x68bc21eb, m_ot); - float sstratY = boseLHOffset(Ai1.value(), Ai0.value(), m_s, m_seed * 2 * 0x68bc21eb, m_ot); - float jitterX = 0.5f + int(m_randomize) * m_maxJit * (m_rand.nextFloat() - 0.5f); - float jitterY = 0.5f + int(m_randomize) * m_maxJit * (m_rand.nextFloat() - 0.5f); - r[0] = (stratumX + (sstratX + jitterX) / m_s) / m_s; - r[1] = (stratumY + (sstratY + jitterY) / m_s) / m_s; + float sstratX = boseLHOffset(Ai0.value(), Ai1.value(), m_s, m_seed * 1 * 0x68bc21eb, m_ot); + float sstratY = boseLHOffset(Ai1.value(), Ai0.value(), m_s, m_seed * 2 * 0x68bc21eb, m_ot); + float jitterX = 0.5f + int(m_randomize) * m_maxJit * (m_rand.nextFloat() - 0.5f); + float jitterY = 0.5f + int(m_randomize) * m_maxJit * (m_rand.nextFloat() - 0.5f); + r[0] = (stratumX + (sstratX + jitterX) / m_s) / m_s; + r[1] = (stratumY + (sstratY + jitterY) / m_s) / m_s; unsigned maxDim = min(dimensions(), m_s + 1); for (unsigned j = 2; j < maxDim; ++j) { - int km1 = (j % 2) ? j - 2 : j % m_s; - int Aij = (Ai0 + (j - 1) * Ai1).value(); - int Aik = (Ai0 + km1*Ai1).value(); - int stratumJ = permute(Aij, m_s, m_seed * (j+1)); - float sstratJ = boseLHOffset(Aij, Aik, m_s, m_seed * (j+1) * 0x68bc21eb, m_ot); - float jitterJ = 0.5f + int(m_randomize) * m_maxJit * (m_rand.nextFloat() - 0.5f); - r[j] = (stratumJ + (sstratJ + jitterJ) / m_s) / m_s; + int km1 = (j % 2) ? j - 2 : j % m_s; + int Aij = (Ai0 + (j - 1) * Ai1).value(); + int Aik = (Ai0 + km1 * Ai1).value(); + int stratumJ = permute(Aij, m_s, m_seed * (j + 1)); + float sstratJ = boseLHOffset(Aij, Aik, m_s, m_seed * (j + 1) * 0x68bc21eb, m_ot); + float jitterJ = 0.5f + int(m_randomize) * m_maxJit * (m_rand.nextFloat() - 0.5f); + r[j] = (stratumJ + (sstratJ + jitterJ) / m_s) / m_s; } for (unsigned j = maxDim; j < dimensions(); ++j) r[j] = 0.5f; } - diff --git a/src/OABoseBush.cpp b/src/sampler/OABoseBush.cpp similarity index 65% rename from src/OABoseBush.cpp rename to src/sampler/OABoseBush.cpp index 55533ea..7998c1f 100644 --- a/src/OABoseBush.cpp +++ b/src/sampler/OABoseBush.cpp @@ -4,19 +4,16 @@ \author Wojciech Jarosz */ -#include #include +#include #define GF_SIZE_DEBUG #define GF_RANGE_DEBUG #include -#include using namespace std; -BoseBushOA::BoseBushOA(unsigned x, OffsetType ot, - bool randomize, float jitter, - unsigned dimensions) - : BoseGaloisOAInPlace(x, ot, randomize, jitter, dimensions) +BoseBushOA::BoseBushOA(unsigned x, OffsetType ot, bool randomize, float jitter, unsigned dimensions) : + BoseGaloisOAInPlace(x, ot, randomize, jitter, dimensions) { setNumSamples(2 * x * x); reset(); @@ -29,7 +26,7 @@ string BoseBushOA::name() const int BoseBushOA::setNumSamples(unsigned n) { - m_s = max(2, (int)pow(2.f, ceil(log2(sqrt(n / 2))))); + m_s = max(2, (int)pow(2.f, ceil(log2(sqrt(n / 2))))); m_numSamples = 2 * m_s * m_s; m_gf.resize(2 * m_s); reset(); @@ -40,12 +37,12 @@ void BoseBushOA::reset() { BoseOAInPlace::reset(); - int q = m_gf.q; + int q = m_gf.q; unsigned s = q / 2; /* number of levels in design */ Array2d A(s, q); - m_B.resize(2 * s*s, dimensions()); + m_B.resize(2 * s * s, dimensions()); int irow = 0; for (int i = 0; i < q; i++) { @@ -54,13 +51,13 @@ void BoseBushOA::reset() { const Galois::Element mul((gi * j) % s); for (unsigned k = 0; k < s; k++) - A(k,j) = (mul + k).value(); + A(k, j) = (mul + k).value(); } for (unsigned k = 0; k < s; k++) { for (unsigned j = 0; j < dimensions() && j < 2 * s; j++) - m_B(irow,j) = A(k,j); + m_B(irow, j) = A(k, j); if (dimensions() >= 2 * s + 1) m_B(irow, 2 * s) = i % s; @@ -71,28 +68,23 @@ void BoseBushOA::reset() void BoseBushOA::sample(float r[], unsigned row) { - int q = m_gf.q; + int q = m_gf.q; unsigned s = q / 2; /* number of levels in design */ for (unsigned dim = 0; dim < dimensions() && dim < 2 * s + 1; ++dim) { - int Acol = m_B(row, dim); - int stratumJ = permute(Acol, m_s, m_seed * (dim+1)); + int Acol = m_B(row, dim); + int stratumJ = permute(Acol, m_s, m_seed * (dim + 1)); float jitterJ = 0.5f + int(m_randomize) * m_maxJit * (m_rand.nextFloat() - 0.5f); - r[dim] = (stratumJ + jitterJ) / m_s; + r[dim] = (stratumJ + jitterJ) / m_s; } } - //// - - -BoseBushOAInPlace::BoseBushOAInPlace(unsigned x, OffsetType ot, - bool randomize, float jitter, - unsigned dimensions) - : BoseGaloisOAInPlace(x, ot, randomize, jitter, dimensions) +BoseBushOAInPlace::BoseBushOAInPlace(unsigned x, OffsetType ot, bool randomize, float jitter, unsigned dimensions) : + BoseGaloisOAInPlace(x, ot, randomize, jitter, dimensions) { setNumSamples(2 * x * x); reset(); @@ -105,7 +97,7 @@ string BoseBushOAInPlace::name() const int BoseBushOAInPlace::setNumSamples(unsigned n) { - m_s = max(2, (int)pow(2.f, ceil(log2(sqrt(n / 2))))); + m_s = max(2, (int)pow(2.f, ceil(log2(sqrt(n / 2))))); m_numSamples = 2 * m_s * m_s; m_gf.resize(2 * m_s); reset(); @@ -114,20 +106,19 @@ int BoseBushOAInPlace::setNumSamples(unsigned n) void BoseBushOAInPlace::sample(float r[], unsigned row) { - int q = m_gf.q; + int q = m_gf.q; unsigned s = q / 2; /* number of levels in design */ - unsigned i = row / s; + unsigned i = row / s; const Galois::Element gi(&m_gf, i); for (unsigned dim = 0; dim < dimensions() && dim < 2 * s + 1; ++dim) { - int A = (dim < 2 * s) ? (((gi * dim) % s) + (row % s)).value() - : i % s; - - int stratumJ = permute(A, m_s, m_seed * (dim+1)); + int A = (dim < 2 * s) ? (((gi * dim) % s) + (row % s)).value() : i % s; + + int stratumJ = permute(A, m_s, m_seed * (dim + 1)); float jitterJ = 0.5f + int(m_randomize) * m_maxJit * (m_rand.nextFloat() - 0.5f); - r[dim] = (stratumJ + jitterJ) / m_s; + r[dim] = (stratumJ + jitterJ) / m_s; } } \ No newline at end of file diff --git a/src/OABush.cpp b/src/sampler/OABush.cpp similarity index 63% rename from src/OABush.cpp rename to src/sampler/OABush.cpp index 25ba10e..017c2ab 100644 --- a/src/OABush.cpp +++ b/src/sampler/OABush.cpp @@ -3,10 +3,9 @@ \author Wojciech Jarosz */ -#include -#include -#include #include +#include +#include using namespace std; @@ -18,27 +17,22 @@ float bushLHOffset(int i, int N, int s, int numSS, int p, unsigned type) { switch (type) { - case CENTERED: - return numSS / 2.0f; - case J_STYLE: - return permute((i / s) % numSS, numSS, (i + 1) * p); - case MJ_STYLE: - return permute((i / s) % numSS, numSS, p); + case CENTERED: return numSS / 2.0f; + case J_STYLE: return permute((i / s) % numSS, numSS, (i + 1) * p); + case MJ_STYLE: return permute((i / s) % numSS, numSS, p); // the following is still a work in progress, seems to work for strength 3, // but not others. default: - case CMJ_STYLE: - return (permute((i / s) % s, s, p) + permute(i % s, s, p * 2) * (numSS / s)) % numSS; + case CMJ_STYLE: return (permute((i / s) % s, s, p) + permute(i % s, s, p * 2) * (numSS / s)) % numSS; } } } // namespace - -BushOAInPlace::BushOAInPlace(unsigned x, unsigned strength, OffsetType ot, - bool randomize, float jitter, unsigned dimensions) - : BoseOAInPlace(x, ot, randomize, jitter, dimensions) +BushOAInPlace::BushOAInPlace(unsigned x, unsigned strength, OffsetType ot, bool randomize, float jitter, + unsigned dimensions) : + BoseOAInPlace(x, ot, randomize, jitter, dimensions) { m_t = strength; } @@ -57,7 +51,7 @@ int BushOAInPlace::setNumSamples(unsigned n) void BushOAInPlace::setNumSamples(unsigned x, unsigned) { - m_s = primeGE(x); + m_s = primeGE(x); m_numSamples = pow(m_s, m_t); reset(); } @@ -71,32 +65,29 @@ void BushOAInPlace::sample(float r[], unsigned i) auto coeffs = iToPolyCoeffs(i, m_s, m_t); unsigned numSubStrata = m_numSamples / m_s; - unsigned add = m_ot == CMJ_STYLE ? 1 : 0; - unsigned maxDim = min(dimensions(), m_s - add); - unsigned s = m_s; + unsigned add = m_ot == CMJ_STYLE ? 1 : 0; + unsigned maxDim = min(dimensions(), m_s - add); + unsigned s = m_s; for (unsigned d = 0; d < maxDim; ++d) { - int phi = polyEval(coeffs, d + add); - int stratum = permute(phi % m_s, m_s, m_seed * (d+1)); + int phi = polyEval(coeffs, d + add); + int stratum = permute(phi % m_s, m_s, m_seed * (d + 1)); - float subStratum = bushLHOffset(i, m_numSamples, m_s, numSubStrata, m_seed * (d+1) * 0x02e5be93, m_ot); + float subStratum = bushLHOffset(i, m_numSamples, m_s, numSubStrata, m_seed * (d + 1) * 0x02e5be93, m_ot); float jitter = 0.5f + int(m_randomize) * m_maxJit * (m_rand.nextFloat() - 0.5f); - r[d] = (stratum + (subStratum + jitter) / numSubStrata) / s; + r[d] = (stratum + (subStratum + jitter) / numSubStrata) / s; } for (unsigned d = maxDim; d < dimensions(); ++d) r[d] = 0.5f; } - //// - - -BushGaloisOAInPlace::BushGaloisOAInPlace(unsigned x, unsigned strength, OffsetType ot, - bool randomize, float jitter, unsigned dimensions) - : BushOAInPlace(x, strength, ot, randomize, jitter, dimensions) +BushGaloisOAInPlace::BushGaloisOAInPlace(unsigned x, unsigned strength, OffsetType ot, bool randomize, float jitter, + unsigned dimensions) : + BushOAInPlace(x, strength, ot, randomize, jitter, dimensions) { setNumSamples(x); reset(); @@ -116,7 +107,7 @@ int BushGaloisOAInPlace::setNumSamples(unsigned n) void BushGaloisOAInPlace::setNumSamples(unsigned x, unsigned) { - m_s = primePowerGE(x); + m_s = primePowerGE(x); m_numSamples = pow(m_s, m_t); m_gf.resize(m_s); reset(); @@ -131,18 +122,18 @@ void BushGaloisOAInPlace::sample(float r[], unsigned i) auto coeffs = iToPolyCoeffs(i, m_s, m_t); unsigned numSubStrata = m_numSamples / m_s; - unsigned add = m_ot == CMJ_STYLE ? 1 : 0; - unsigned maxDim = min(dimensions(), m_s - add); - unsigned s = m_s; + unsigned add = m_ot == CMJ_STYLE ? 1 : 0; + unsigned maxDim = min(dimensions(), m_s - add); + unsigned s = m_s; for (unsigned d = 0; d < maxDim; ++d) { - int phi = polyEval(&m_gf, coeffs, d + add); - int stratum = permute(phi, m_s, m_seed * (d+1)); + int phi = polyEval(&m_gf, coeffs, d + add); + int stratum = permute(phi, m_s, m_seed * (d + 1)); - float subStratum = bushLHOffset(i, m_numSamples, m_s, numSubStrata, m_seed * (d+1) * 0x02e5be93, m_ot); + float subStratum = bushLHOffset(i, m_numSamples, m_s, numSubStrata, m_seed * (d + 1) * 0x02e5be93, m_ot); float jitter = 0.5f + int(m_randomize) * m_maxJit * (m_rand.nextFloat() - 0.5f); - r[d] = (stratum + (subStratum + jitter) / numSubStrata) / s; + r[d] = (stratum + (subStratum + jitter) / numSubStrata) / s; } for (unsigned d = maxDim; d < dimensions(); ++d) diff --git a/src/OACMJND.cpp b/src/sampler/OACMJND.cpp similarity index 100% rename from src/OACMJND.cpp rename to src/sampler/OACMJND.cpp diff --git a/src/Random.cpp b/src/sampler/Random.cpp similarity index 100% rename from src/Random.cpp rename to src/sampler/Random.cpp diff --git a/src/SOA.cpp b/src/sampler/SOA.cpp similarity index 100% rename from src/SOA.cpp rename to src/sampler/SOA.cpp diff --git a/src/Sobol.cpp b/src/sampler/Sobol.cpp similarity index 84% rename from src/Sobol.cpp rename to src/sampler/Sobol.cpp index 1e56fc5..61d547e 100644 --- a/src/Sobol.cpp +++ b/src/sampler/Sobol.cpp @@ -2,13 +2,11 @@ \author Wojciech Jarosz */ -#include -#include #include +#include +#include - -Sobol::Sobol(unsigned dimensions) : - m_numDimensions(dimensions) +Sobol::Sobol(unsigned dimensions) : m_numDimensions(dimensions) { // empty } @@ -39,17 +37,12 @@ void Sobol::setRandomized(bool b) } } - - - ZeroTwo::ZeroTwo(unsigned n, unsigned dimensions, bool shuffle) : m_numSamples(n), m_numDimensions(dimensions), m_shuffle(shuffle) { - // empty reset(); } - void ZeroTwo::reset() { m_scrambles.resize(dimensions()); @@ -57,19 +50,17 @@ void ZeroTwo::reset() for (unsigned d = 0; d < dimensions(); ++d) { m_scrambles[d] = m_randomize ? m_rand.nextUInt() : 0; - m_permutes[d] = m_shuffle ? m_rand.nextUInt() : 0; + m_permutes[d] = m_shuffle ? m_rand.nextUInt() : 0; } } - int ZeroTwo::setNumSamples(unsigned n) { return m_numSamples = (n == 0) ? 1 : n; } - void ZeroTwo::sample(float r[], unsigned i) { for (unsigned d = 0; d < dimensions(); ++d) - r[d] = sobol(permute(i, m_numSamples, m_permutes[d/2]), d % 2, m_scrambles[d]); + r[d] = sobol(permute(i, m_numSamples, m_permutes[d / 2]), d % 2, m_scrambles[d]); } diff --git a/src/SobolMatrices.cpp b/src/sampler/SobolMatrices.cpp similarity index 100% rename from src/SobolMatrices.cpp rename to src/sampler/SobolMatrices.cpp diff --git a/src/shader.cpp b/src/shader.cpp new file mode 100644 index 0000000..35ad821 --- /dev/null +++ b/src/shader.cpp @@ -0,0 +1,658 @@ +#include "shader.h" +#include "hello_imgui/hello_imgui.h" +#include "hello_imgui/hello_imgui_include_opengl.h" // cross-platform way to include OpenGL headers + +#if !defined(GL_HALF_FLOAT) +#define GL_HALF_FLOAT 0x140B +#endif + +#include + +#include + +using std::string; + +#define CHK(cmd) \ + do \ + { \ + cmd; \ + (void)check_glerror(#cmd); \ + } while (0) + +bool check_glerror(const char *cmd) +{ + GLenum err = glGetError(); + const char *msg = nullptr; + + switch (err) + { + case GL_NO_ERROR: return false; + case GL_INVALID_ENUM: msg = "invalid enumeration"; break; + case GL_INVALID_VALUE: msg = "invalid value"; break; + case GL_INVALID_OPERATION: msg = "invalid operation"; break; + case GL_INVALID_FRAMEBUFFER_OPERATION: msg = "invalid framebuffer operation"; break; + case GL_OUT_OF_MEMORY: msg = "out of memory"; break; +#ifndef __EMSCRIPTEN__ + case GL_STACK_UNDERFLOW: msg = "stack underflow"; break; + case GL_STACK_OVERFLOW: msg = "stack overflow"; break; +#endif + default: msg = "unknown error"; break; + } + + fmt::print(stderr, "OpenGL error ({}) during operation \"{}\"!\n", msg, cmd); + HelloImGui::Log(HelloImGui::LogLevel::Error, "OpenGL error (%s) during operation \"%s\"!\n", msg, cmd); + return true; +} + +static GLuint compile_gl_shader(GLenum type, const std::string &name, const std::string &shader_string) +{ + if (shader_string.empty()) + return (GLuint)0; + + GLuint id = glCreateShader(type); + const char *shader_string_const = shader_string.c_str(); + CHK(glShaderSource(id, 1, &shader_string_const, nullptr)); + CHK(glCompileShader(id)); + + GLint status; + CHK(glGetShaderiv(id, GL_COMPILE_STATUS, &status)); + + if (status != GL_TRUE) + { + const char *type_str = nullptr; + if (type == GL_VERTEX_SHADER) + type_str = "vertex shader"; + else if (type == GL_FRAGMENT_SHADER) + type_str = "fragment shader"; +#ifndef __EMSCRIPTEN__ + else if (type == GL_GEOMETRY_SHADER) + type_str = "geometry shader"; +#endif + else + type_str = "unknown shader type"; + + char error_shader[4096]; + CHK(glGetShaderInfoLog(id, sizeof(error_shader), nullptr, error_shader)); + + std::string msg = + std::string("compile_gl_shader(): unable to compile ") + type_str + " \"" + name + "\":\n\n" + error_shader; + throw std::runtime_error(msg); + } + + return id; +} + +Shader::Shader(const std::string &name, const std::string &vs_filename, const std::string &fs_filename, + BlendMode blend_mode) : + m_name(name), + m_blend_mode(blend_mode), m_shader_handle(0) +{ + string vertex_shader, fragment_shader; + { + auto load_shader_file = [](const string &filename) + { + auto shader_txt = HelloImGui::LoadAssetFileData(filename.c_str()); + if (shader_txt.data == nullptr) + throw std::runtime_error(fmt::format("Cannot load point shader from file \"{}\"", filename)); + + return shader_txt; + }; + auto vs = load_shader_file(vs_filename); + auto fs = load_shader_file(fs_filename); + + vertex_shader = string((char *)vs.data, vs.dataSize); + fragment_shader = string((char *)fs.data, fs.dataSize); + + HelloImGui::FreeAssetFileData(&vs); + HelloImGui::FreeAssetFileData(&fs); + } + + GLuint vertex_shader_handle = compile_gl_shader(GL_VERTEX_SHADER, name, vertex_shader), + fragment_shader_handle = compile_gl_shader(GL_FRAGMENT_SHADER, name, fragment_shader); + + m_shader_handle = glCreateProgram(); + + GLint status; + CHK(glAttachShader(m_shader_handle, vertex_shader_handle)); + CHK(glAttachShader(m_shader_handle, fragment_shader_handle)); + CHK(glLinkProgram(m_shader_handle)); + CHK(glDeleteShader(vertex_shader_handle)); + CHK(glDeleteShader(fragment_shader_handle)); + CHK(glGetProgramiv(m_shader_handle, GL_LINK_STATUS, &status)); + + if (status != GL_TRUE) + { + char error_shader[4096]; + CHK(glGetProgramInfoLog(m_shader_handle, sizeof(error_shader), nullptr, error_shader)); + m_shader_handle = 0; + throw std::runtime_error("Shader::Shader(name=\"" + name + "\"): unable to link shader!\n\n" + error_shader); + } + + GLint attribute_count, uniform_count; + CHK(glGetProgramiv(m_shader_handle, GL_ACTIVE_ATTRIBUTES, &attribute_count)); + CHK(glGetProgramiv(m_shader_handle, GL_ACTIVE_UNIFORMS, &uniform_count)); + + auto register_buffer = [&](BufferType type, const std::string &name, int index, GLenum gl_type) + { + if (m_buffers.find(name) != m_buffers.end()) + throw std::runtime_error("Shader::Shader(): duplicate attribute/uniform name in shader code!"); + else if (name == "indices") + throw std::runtime_error("Shader::Shader(): argument name 'indices' is reserved!"); + + Buffer &buf = m_buffers[name]; + for (int i = 0; i < 3; ++i) + buf.shape[i] = 1; + buf.ndim = 1; + buf.index = index; + buf.type = type; + + switch (gl_type) + { + case GL_FLOAT: + buf.dtype = VariableType::Float32; + buf.ndim = 0; + break; + + case GL_FLOAT_VEC2: + buf.dtype = VariableType::Float32; + buf.shape[0] = 2; + break; + + case GL_FLOAT_VEC3: + buf.dtype = VariableType::Float32; + buf.shape[0] = 3; + break; + + case GL_FLOAT_VEC4: + buf.dtype = VariableType::Float32; + buf.shape[0] = 4; + break; + + case GL_INT: + buf.dtype = VariableType::Int32; + buf.ndim = 0; + break; + + case GL_INT_VEC2: + buf.dtype = VariableType::Int32; + buf.shape[0] = 2; + break; + + case GL_INT_VEC3: + buf.dtype = VariableType::Int32; + buf.shape[0] = 3; + break; + + case GL_INT_VEC4: + buf.dtype = VariableType::Int32; + buf.shape[0] = 4; + break; + +#if defined(HELLOIMGUI_USE_GLAD) + case GL_UNSIGNED_INT: + buf.dtype = VariableType::UInt32; + buf.ndim = 0; + break; + + case GL_UNSIGNED_INT_VEC2: + buf.dtype = VariableType::UInt32; + buf.shape[0] = 2; + break; + + case GL_UNSIGNED_INT_VEC3: + buf.dtype = VariableType::UInt32; + buf.shape[0] = 3; + break; + + case GL_UNSIGNED_INT_VEC4: + buf.dtype = VariableType::UInt32; + buf.shape[0] = 4; + break; +#endif + + case GL_BOOL: + buf.dtype = VariableType::Bool; + buf.ndim = 0; + break; + + case GL_BOOL_VEC2: + buf.dtype = VariableType::Bool; + buf.shape[0] = 2; + break; + + case GL_BOOL_VEC3: + buf.dtype = VariableType::Bool; + buf.shape[0] = 3; + break; + + case GL_BOOL_VEC4: + buf.dtype = VariableType::Bool; + buf.shape[0] = 4; + break; + + case GL_FLOAT_MAT2: + buf.dtype = VariableType::Float32; + buf.shape[0] = buf.shape[1] = 2; + buf.ndim = 2; + break; + + case GL_FLOAT_MAT3: + buf.dtype = VariableType::Float32; + buf.shape[0] = buf.shape[1] = 3; + buf.ndim = 2; + break; + + case GL_FLOAT_MAT4: + buf.dtype = VariableType::Float32; + buf.shape[0] = buf.shape[1] = 4; + buf.ndim = 2; + break; + + case GL_SAMPLER_2D: + buf.dtype = VariableType::Invalid; + buf.ndim = 0; + buf.type = FragmentTexture; + break; + + default: + throw std::runtime_error("Shader::Shader(): unsupported " + "uniform/attribute type!"); + }; + + if (type == VertexBuffer) + { + for (int i = (int)buf.ndim - 1; i >= 0; --i) + { + buf.shape[i + 1] = buf.shape[i]; + } + buf.shape[0] = 0; + buf.ndim++; + } + }; + + for (int i = 0; i < attribute_count; ++i) + { + char attr_name[128]; + GLenum type = 0; + GLint size = 0; + CHK(glGetActiveAttrib(m_shader_handle, i, sizeof(attr_name), nullptr, &size, &type, attr_name)); + GLint index = glGetAttribLocation(m_shader_handle, attr_name); + register_buffer(VertexBuffer, attr_name, index, type); + } + + for (int i = 0; i < uniform_count; ++i) + { + char uniform_name[128]; + GLenum type = 0; + GLint size = 0; + CHK(glGetActiveUniform(m_shader_handle, i, sizeof(uniform_name), nullptr, &size, &type, uniform_name)); + GLint index = glGetUniformLocation(m_shader_handle, uniform_name); + register_buffer(UniformBuffer, uniform_name, index, type); + } + + Buffer &buf = m_buffers["indices"]; + buf.index = -1; + buf.ndim = 1; + buf.shape[0] = 0; + buf.shape[1] = buf.shape[2] = 1; + buf.type = IndexBuffer; + buf.dtype = VariableType::UInt32; + +#if defined(HELLOIMGUI_USE_GLAD) + CHK(glGenVertexArrays(1, &m_vertex_array_handle)); + + m_uses_point_size = vertex_shader.find("gl_PointSize") != std::string::npos; +#endif +} + +Shader::~Shader() +{ + CHK(glDeleteProgram(m_shader_handle)); +#if defined(HELLOIMGUI_USE_GLAD) + CHK(glDeleteVertexArrays(1, &m_vertex_array_handle)); +#endif +} + +void Shader::set_buffer(const std::string &name, VariableType dtype, size_t ndim, const size_t *shape, const void *data) +{ + auto it = m_buffers.find(name); + if (it == m_buffers.end()) + throw std::runtime_error("Shader::set_buffer(): could not find argument named \"" + name + "\""); + + Buffer &buf = m_buffers[name]; + + bool mismatch = ndim != buf.ndim || dtype != buf.dtype; + for (size_t i = (buf.type == UniformBuffer ? 0 : 1); i < ndim; ++i) + mismatch |= shape[i] != buf.shape[i]; + + if (mismatch) + { + Buffer arg; + arg.type = buf.type; + arg.ndim = ndim; + for (size_t i = 0; i < 3; ++i) + arg.shape[i] = i < arg.ndim ? shape[i] : 1; + arg.dtype = dtype; + throw std::runtime_error("Buffer::set_buffer(\"" + name + "\"): shape/dtype mismatch: expected " + + buf.to_string() + ", got " + arg.to_string()); + } + + size_t size = type_size(dtype); + for (size_t i = 0; i < 3; ++i) + { + buf.shape[i] = i < ndim ? shape[i] : 1; + size *= buf.shape[i]; + } + + if (buf.type == UniformBuffer) + { + if (buf.buffer && buf.size != size) + { + delete[] (uint8_t *)buf.buffer; + buf.buffer = nullptr; + } + if (!buf.buffer) + buf.buffer = new uint8_t[size]; + memcpy(buf.buffer, data, size); + } + else + { + GLuint buffer_id = 0; + if (buf.buffer) + { + buffer_id = (GLuint)((uintptr_t)buf.buffer); + } + else + { + CHK(glGenBuffers(1, &buffer_id)); + buf.buffer = (void *)((uintptr_t)buffer_id); + } + GLenum buf_type = (name == "indices") ? GL_ELEMENT_ARRAY_BUFFER : GL_ARRAY_BUFFER; + CHK(glBindBuffer(buf_type, buffer_id)); + CHK(glBufferData(buf_type, size, data, GL_DYNAMIC_DRAW)); + } + + buf.dtype = dtype; + buf.ndim = ndim; + buf.size = size; + buf.dirty = true; +} + +// void Shader::set_texture(const std::string &name, Texture *texture) +// { +// auto it = m_buffers.find(name); +// if (it == m_buffers.end()) +// throw std::runtime_error("Shader::set_texture(): could not find argument named \"" + name + "\""); +// Buffer &buf = m_buffers[name]; +// if (!(buf.type == VertexTexture || buf.type == FragmentTexture)) +// throw std::runtime_error("Shader::set_texture(): argument named \"" + name + "\" is not a texture!"); + +// buf.buffer = (void *)((uintptr_t)texture->texture_handle()); +// buf.dirty = true; +// } + +void Shader::begin() +{ + int texture_unit = 0; + + CHK(glUseProgram(m_shader_handle)); + +#if defined(HELLOIMGUI_USE_GLAD) + CHK(glBindVertexArray(m_vertex_array_handle)); +#endif + + for (auto &[key, buf] : m_buffers) + { + bool indices = key == "indices"; + if (!buf.buffer) + { + if (!indices) + fprintf(stderr, + "Shader::begin(): shader \"%s\" has an unbound " + "argument \"%s\"!\n", + m_name.c_str(), key.c_str()); + continue; + } + + GLuint buffer_id = (GLuint)((uintptr_t)buf.buffer); + GLenum gl_type = 0; + +#if defined(HELLOIMGUI_USE_GLAD) + if (!buf.dirty && buf.type != VertexTexture && buf.type != FragmentTexture) + continue; +#endif + + bool uniform_error = false; + switch (buf.type) + { + case IndexBuffer: CHK(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer_id)); break; + + case VertexBuffer: + CHK(glBindBuffer(GL_ARRAY_BUFFER, buffer_id)); + CHK(glEnableVertexAttribArray(buf.index)); + + switch (buf.dtype) + { + case VariableType::Int8: gl_type = GL_BYTE; break; + case VariableType::UInt8: gl_type = GL_UNSIGNED_BYTE; break; + case VariableType::Int16: gl_type = GL_SHORT; break; + case VariableType::UInt16: gl_type = GL_UNSIGNED_SHORT; break; + case VariableType::Int32: gl_type = GL_INT; break; + case VariableType::UInt32: gl_type = GL_UNSIGNED_INT; break; + case VariableType::Float16: gl_type = GL_HALF_FLOAT; break; + case VariableType::Float32: gl_type = GL_FLOAT; break; + default: throw std::runtime_error("Shader::begin(): unsupported vertex buffer type!"); + } + + if (buf.ndim != 2) + throw std::runtime_error("\"" + m_name + "\": vertex attribute \"" + key + + "\" has an invalid shapeension (expected ndim=2, got " + + std::to_string(buf.ndim) + ")"); + + CHK(glVertexAttribPointer(buf.index, (GLint)buf.shape[1], gl_type, GL_FALSE, 0, nullptr)); + break; + + case VertexTexture: + case FragmentTexture: + CHK(glActiveTexture(GL_TEXTURE0 + texture_unit)); + CHK(glBindTexture(GL_TEXTURE_2D, (GLuint)((uintptr_t)buf.buffer))); + if (buf.dirty) + CHK(glUniform1i(buf.index, texture_unit)); + texture_unit++; + break; + + case UniformBuffer: + if (buf.ndim > 2) + throw std::runtime_error("\"" + m_name + "\": uniform attribute \"" + key + + "\" has an invalid shape (expected ndim=0/1/2, got " + + std::to_string(buf.ndim) + ")"); + switch (buf.dtype) + { + case VariableType::Float32: + if (buf.ndim < 2) + { + const float *v = (const float *)buf.buffer; + switch (buf.shape[0]) + { + case 1: CHK(glUniform1f(buf.index, v[0])); break; + case 2: CHK(glUniform2f(buf.index, v[0], v[1])); break; + case 3: CHK(glUniform3f(buf.index, v[0], v[1], v[2])); break; + case 4: CHK(glUniform4f(buf.index, v[0], v[1], v[2], v[3])); break; + default: uniform_error = true; break; + } + } + else if (buf.ndim == 2 && buf.shape[0] == buf.shape[1]) + { + const float *v = (const float *)buf.buffer; + switch (buf.shape[0]) + { + case 2: CHK(glUniformMatrix2fv(buf.index, 1, GL_FALSE, v)); break; + case 3: CHK(glUniformMatrix3fv(buf.index, 1, GL_FALSE, v)); break; + case 4: CHK(glUniformMatrix4fv(buf.index, 1, GL_FALSE, v)); break; + default: uniform_error = true; break; + } + } + else + { + uniform_error = true; + } + break; + +#if defined(HELLOIMGUI_USE_GLES2) || defined(HELLOIMGUI_USE_GLES3) + case VariableType::UInt32: +#endif + case VariableType::Int32: + { + const int32_t *v = (const int32_t *)buf.buffer; + if (buf.ndim < 2) + { + switch (buf.shape[0]) + { + case 1: CHK(glUniform1i(buf.index, v[0])); break; + case 2: CHK(glUniform2i(buf.index, v[0], v[1])); break; + case 3: CHK(glUniform3i(buf.index, v[0], v[1], v[2])); break; + case 4: CHK(glUniform4i(buf.index, v[0], v[1], v[2], v[3])); break; + default: uniform_error = true; break; + } + } + else + { + uniform_error = true; + } + } + break; + +#if !defined(HELLOIMGUI_USE_GLES2) && !defined(HELLOIMGUI_USE_GLES3) + case VariableType::UInt32: + { + const uint32_t *v = (const uint32_t *)buf.buffer; + if (buf.ndim < 2) + { + switch (buf.shape[0]) + { + case 1: CHK(glUniform1ui(buf.index, v[0])); break; + case 2: CHK(glUniform2ui(buf.index, v[0], v[1])); break; + case 3: CHK(glUniform3ui(buf.index, v[0], v[1], v[2])); break; + case 4: CHK(glUniform4ui(buf.index, v[0], v[1], v[2], v[3])); break; + default: uniform_error = true; break; + } + } + else + { + uniform_error = true; + } + } + break; +#endif + + case VariableType::Bool: + { + const uint8_t *v = (const uint8_t *)buf.buffer; + if (buf.ndim < 2) + { + switch (buf.shape[0]) + { + case 1: CHK(glUniform1i(buf.index, v[0])); break; + case 2: CHK(glUniform2i(buf.index, v[0], v[1])); break; + case 3: CHK(glUniform3i(buf.index, v[0], v[1], v[2])); break; + case 4: CHK(glUniform4i(buf.index, v[0], v[1], v[2], v[3])); break; + default: uniform_error = true; break; + } + } + else + { + uniform_error = true; + } + } + break; + + default: uniform_error = true; break; + } + + if (uniform_error) + throw std::runtime_error("\"" + m_name + "\": uniform attribute \"" + key + + "\" has an unsupported dtype/shape configuration: " + buf.to_string()); + break; + + default: + throw std::runtime_error("\"" + m_name + "\": uniform attribute \"" + key + + "\" has an unsupported dtype/shape configuration:" + buf.to_string()); + } + + buf.dirty = false; + } + + if (m_blend_mode == BlendMode::AlphaBlend) + { + CHK(glEnable(GL_BLEND)); + CHK(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + } + +#ifndef __EMSCRIPTEN__ + if (m_uses_point_size) + CHK(glEnable(GL_PROGRAM_POINT_SIZE)); +#endif +} + +void Shader::end() +{ + if (m_blend_mode == BlendMode::AlphaBlend) + CHK(glDisable(GL_BLEND)); +#ifndef __EMSCRIPTEN__ + if (m_uses_point_size) + CHK(glDisable(GL_PROGRAM_POINT_SIZE)); + CHK(glBindVertexArray(0)); +#else + for (const auto &[key, buf] : m_buffers) + { + if (buf.type != VertexBuffer) + continue; + CHK(glDisableVertexAttribArray(buf.index)); + } +#endif + CHK(glUseProgram(0)); +} + +void Shader::draw_array(PrimitiveType primitive_type, size_t offset, size_t count, bool indexed) +{ + GLenum primitive_type_gl; + switch (primitive_type) + { + case PrimitiveType::Point: primitive_type_gl = GL_POINTS; break; + case PrimitiveType::Line: primitive_type_gl = GL_LINES; break; + case PrimitiveType::LineStrip: primitive_type_gl = GL_LINE_STRIP; break; + case PrimitiveType::Triangle: primitive_type_gl = GL_TRIANGLES; break; + case PrimitiveType::TriangleStrip: primitive_type_gl = GL_TRIANGLE_STRIP; break; + default: throw std::runtime_error("Shader::draw_array(): invalid primitive type!"); + } + + if (!indexed) + CHK(glDrawArrays(primitive_type_gl, (GLint)offset, (GLsizei)count)); + else + CHK(glDrawElements(primitive_type_gl, (GLsizei)count, GL_UNSIGNED_INT, + (const void *)(offset * sizeof(uint32_t)))); +} + +std::string Shader::Buffer::to_string() const +{ + std::string result = "Buffer[type="; + switch (type) + { + case BufferType::VertexBuffer: result += "vertex"; break; + case BufferType::FragmentBuffer: result += "fragment"; break; + case BufferType::UniformBuffer: result += "uniform"; break; + case BufferType::IndexBuffer: result += "index"; break; + default: result += "unknown"; break; + } + result += ", dtype="; + result += type_name(dtype); + result += ", shape=["; + for (size_t i = 0; i < ndim; ++i) + { + result += std::to_string(shape[i]); + if (i + 1 < ndim) + result += ", "; + } + result += "]]"; + return result; +}