├── .gitattributes
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug-task.yaml
│ └── maintenance-task.md
├── PULL_REQUEST_TEMPLATE
│ └── pull-request-task.md
└── workflows
│ └── ci.yaml
├── .gitignore
├── .rustfmt.toml
├── .vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── ACKNOWLEDGEMENTS.md
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── benchmarks
├── README.md
└── stress-test
│ ├── Cargo.toml
│ ├── README.md
│ ├── logo.png
│ ├── scripts
│ ├── run_on_device.ps1
│ └── run_on_device.sh
│ └── src
│ ├── lib.rs
│ ├── main.rs
│ └── systems
│ ├── cube.rs
│ └── mod.rs
├── cspell.json
├── docs
└── hotham_sync_loop.png
├── examples
├── common_lib
│ └── arm64-v8a
│ │ └── libopenxr_loader.so
├── complex-scene
│ ├── Cargo.toml
│ ├── README.md
│ ├── openxr_loader.dll
│ ├── scripts
│ │ ├── run_on_device.ps1
│ │ └── run_on_device.sh
│ └── src
│ │ ├── lib.rs
│ │ └── main.rs
├── crab-saber
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── README.md
│ ├── assets
│ │ ├── Chasms - Dark Matter.mp3
│ │ ├── Hit.mp3
│ │ ├── Miss.mp3
│ │ ├── NEFFEX - Tell Me That I Can't.mp3
│ │ ├── Spence - Right Here Beside You.mp3
│ │ ├── TrackTribe - Cloud Echo.mp3
│ │ └── crab_saber.glb
│ ├── openxr_loader.dll
│ ├── scripts
│ │ ├── run_on_device.ps1
│ │ └── run_on_device.sh
│ └── src
│ │ ├── components
│ │ ├── color.rs
│ │ ├── cube.rs
│ │ ├── mod.rs
│ │ └── saber.rs
│ │ ├── game_context.rs
│ │ ├── lib.rs
│ │ ├── main.rs
│ │ └── systems
│ │ ├── game.rs
│ │ ├── mod.rs
│ │ └── sabers.rs
├── custom-rendering
│ ├── Cargo.toml
│ ├── README.md
│ ├── openxr_loader.dll
│ ├── scripts
│ │ ├── run_on_device.ps1
│ │ └── run_on_device.sh
│ └── src
│ │ ├── custom_render_context.rs
│ │ ├── custom_rendering.rs
│ │ ├── hologram.rs
│ │ ├── lib.rs
│ │ ├── main.rs
│ │ ├── shaders
│ │ ├── quadric.frag
│ │ ├── quadric.glsl
│ │ └── quadric.vert
│ │ └── surface_solver.rs
├── hotham_examples.keystore
├── shared
│ ├── Cargo.toml
│ └── src
│ │ ├── lib.rs
│ │ └── navigation.rs
└── simple-scene
│ ├── Cargo.toml
│ ├── README.md
│ ├── logo.png
│ ├── openxr_loader.dll
│ ├── scripts
│ ├── run_on_device.ps1
│ └── run_on_device.sh
│ └── src
│ ├── lib.rs
│ └── main.rs
├── hotham-asset-client
├── Cargo.toml
└── src
│ ├── client.rs
│ ├── lib.rs
│ └── message.rs
├── hotham-asset-server
├── Cargo.toml
└── src
│ ├── main.rs
│ └── server.rs
├── hotham-simulator
├── .vscode
│ └── tasks.json
├── Cargo.toml
├── README.md
├── build.rs
├── build_input
│ ├── loader_interfaces.h
│ ├── openxr
│ │ ├── openxr.h
│ │ ├── openxr_platform.h
│ │ ├── openxr_platform_defines.h
│ │ ├── openxr_reflection.h
│ │ └── xr_linear.c
│ ├── viewdisplay.frag
│ ├── viewdisplay.frag.spv
│ ├── viewdisplay.vert
│ ├── viewdisplay.vert.spv
│ └── wrapper.h
├── hotham_simulator.json
├── openxr_simulator.reg
├── src
│ ├── action_state.rs
│ ├── bindings.rs
│ ├── inputs.rs
│ ├── lib.rs
│ ├── openxr_loader.rs
│ ├── shaders
│ │ ├── viewdisplay.frag.spv
│ │ └── viewdisplay.vert.spv
│ ├── simulator.rs
│ ├── space_state.rs
│ └── state.rs
├── test.ps1
└── test.sh
├── hotham
├── Cargo.toml
├── build.rs
├── data
│ ├── brdf_lut.ktx2
│ ├── brdf_lut_android.ktx2
│ ├── environment_map_diffuse.ktx2
│ ├── environment_map_diffuse_android.ktx2
│ ├── environment_map_specular.ktx2
│ └── environment_map_specular_android.ktx2
├── matrix_failed.json
└── src
│ ├── asset_importer
│ ├── mod.rs
│ └── scene.rs
│ ├── components
│ ├── animation_controller.rs
│ ├── animation_target.rs
│ ├── global_transform.rs
│ ├── grabbable.rs
│ ├── hand.rs
│ ├── hmd.rs
│ ├── info.rs
│ ├── joint.rs
│ ├── local_transform.rs
│ ├── mesh.rs
│ ├── mod.rs
│ ├── panel.rs
│ ├── parent.rs
│ ├── physics
│ │ ├── additional_mass.rs
│ │ ├── collider.rs
│ │ ├── impulse.rs
│ │ ├── mod.rs
│ │ ├── rigid_body.rs
│ │ └── teleport.rs
│ ├── pointer.rs
│ ├── root.rs
│ ├── skin.rs
│ ├── sound_emitter.rs
│ ├── stage.rs
│ ├── ui_panel.rs
│ └── visible.rs
│ ├── contexts
│ ├── audio_context.rs
│ ├── gui_context.rs
│ ├── haptic_context.rs
│ ├── input_context.rs
│ ├── mod.rs
│ ├── physics_context.rs
│ ├── render_context.rs
│ ├── vulkan_context.rs
│ └── xr_context
│ │ ├── input.rs
│ │ ├── mod.rs
│ │ └── time.rs
│ ├── engine.rs
│ ├── hotham_error.rs
│ ├── lib.rs
│ ├── rendering
│ ├── buffer.rs
│ ├── camera.rs
│ ├── descriptors.rs
│ ├── frame.rs
│ ├── image.rs
│ ├── legacy_buffer.rs
│ ├── light.rs
│ ├── material.rs
│ ├── memory.rs
│ ├── mesh_data.rs
│ ├── mod.rs
│ ├── primitive.rs
│ ├── resources.rs
│ ├── scene_data.rs
│ ├── swapchain.rs
│ ├── texture.rs
│ └── vertex.rs
│ ├── shaders
│ ├── brdf.glsl
│ ├── common.glsl
│ ├── culling.comp
│ ├── gui.frag
│ ├── gui.vert
│ ├── lights.glsl
│ ├── pbr.frag
│ ├── pbr.glsl
│ └── pbr.vert
│ ├── systems
│ ├── animation.rs
│ ├── audio.rs
│ ├── debug.rs
│ ├── draw_gui.rs
│ ├── grabbing.rs
│ ├── hands.rs
│ ├── haptics.rs
│ ├── mod.rs
│ ├── physics.rs
│ ├── pointers.rs
│ ├── rendering.rs
│ ├── skinning.rs
│ └── update_global_transform.rs
│ ├── util.rs
│ └── workers
│ └── mod.rs
├── logo.jpg
├── openxr_loader.dll
├── shell.nix
└── test_assets
├── Quartet 14 - Clip.mp3
├── asteroid.glb
├── box_no_material.glb
├── box_with_colliders.glb
├── cube.glb
├── culling_stress_test.glb
├── damaged_helmet.glb
├── damaged_helmet_squished.glb
├── ferris-the-crab
├── source
│ └── ferris.glb
└── textures
│ └── ferris_eyes_0.png
├── floor.glb
├── gymnopedie.mp3
├── hologram_templates.glb
├── ibl_test.glb
├── ibl_test_squished.glb
├── ice_crash.mp3
├── left_hand.glb
├── left_hand_skinned_matrices.json
├── normal_tangent_mirror_test.glb
├── normal_tangent_test.glb
├── render_Diffuse.jpg
├── render_Diffuse_known_good.jpg
├── render_Full.jpg
├── render_Full_known_good.jpg
├── render_No_IBL.jpg
├── render_No_IBL_known_good.jpg
├── render_Normals.jpg
├── render_Normals_known_good.jpg
├── render_Spotlight.jpg
├── render_Spotlight_known_good.jpg
├── render_draw_gui.jpg
├── render_draw_gui_known_good.jpg
├── render_normal_tangent_test_IBL.jpg
├── render_normal_tangent_test_IBL_and_spotlight.jpg
├── render_normal_tangent_test_IBL_and_spotlight_known_good.jpg
├── render_normal_tangent_test_IBL_known_good.jpg
├── render_normal_tangent_test_Normals.jpg
├── render_normal_tangent_test_Normals_known_good.jpg
├── render_normal_tangent_test_Spotlight1.jpg
├── render_normal_tangent_test_Spotlight1_known_good.jpg
├── render_normal_tangent_test_Spotlight2.jpg
├── render_normal_tangent_test_Spotlight2_known_good.jpg
├── renderdoc_performance_counters.json
├── right_hand.glb
├── right_hand_skinned_matrices.json
├── right_here_beside_you_clip.mp3
├── sphere.glb
└── tell_me_that_i_cant_clip.mp3
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.glb filter=lfs diff=lfs merge=lfs -text
2 | *.so filter=lfs diff=lfs merge=lfs -text
3 | *.ktx2 filter=lfs diff=lfs merge=lfs -text
4 | *.mp3 filter=lfs diff=lfs merge=lfs -text
5 | *.jpg filter=lfs diff=lfs merge=lfs -text
6 | *.png filter=lfs diff=lfs merge=lfs -text
7 | test_assets/Quartet[[:space:]]14[[:space:]]-[[:space:]]Clip.mp3 filter=lfs diff=lfs merge=lfs -text
8 | test_assets/culling_stress_test.glb filter=lfs diff=lfs merge=lfs -text
9 | hotham/data/brdf_lut.ktx2 filter=lfs diff=lfs merge=lfs -text
10 | hotham/data/environment_map_diffuse.ktx2 filter=lfs diff=lfs merge=lfs -text
11 | hotham/data/environment_map_specular.ktx2 filter=lfs diff=lfs merge=lfs -text
12 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [leetvr]
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-task.yaml:
--------------------------------------------------------------------------------
1 | name: Hotham Bug Report
2 | description: File a bug report
3 | title: "[Bug]: "
4 | labels: ["bug"]
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | Thanks for taking the time to fill out this bug report!
10 | - type: textarea
11 | id: what-happened
12 | attributes:
13 | label: What happened?
14 | description: Also tell us, what did you expect to happen?
15 | placeholder: Tell us what you saw!
16 | value: "A bug happened!"
17 | validations:
18 | required: true
19 | - type: dropdown
20 | id: version
21 | attributes:
22 | label: Version
23 | description: What version of our software are you running?
24 | options:
25 | - 0.1.1 (Default)
26 | - latest
27 | validations:
28 | required: true
29 | - type: dropdown
30 | id: hotham-component
31 | attributes:
32 | label: In which Hotham component are you seeing the problem on?
33 | multiple: true
34 | options:
35 | - Core
36 | - Example
37 | - Simulator
38 | - Debug
39 | - Other
40 | - type: dropdown
41 | id: vr-system
42 | attributes:
43 | label: What VR System are you seeing the problem on?
44 | multiple: false
45 | options:
46 | - Quest-2
47 | - Quest-1
48 | - Other
49 | - type: dropdown
50 | id: operating-system
51 | attributes:
52 | label: What OS are you seeing the problem on?
53 | multiple: true
54 | options:
55 | - Linux
56 | - MacOS
57 | - Windows
58 | - type: textarea
59 | id: logs
60 | attributes:
61 | label: Relevant log output
62 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
63 | render: shell
64 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/maintenance-task.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Maintenance Task
3 | about: Something needs maintaining!
4 | labels: maintenance
5 | ---
6 |
7 | ## Description
8 | Description goes here! Include why this might be helpful
9 |
10 | ## TODO
11 | - [ ]
12 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE/pull-request-task.md:
--------------------------------------------------------------------------------
1 | ## What is this related to?
2 |
3 | ## What changed?
4 |
5 | ## What is the overall impact?
6 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | pull_request:
3 | branches: [main]
4 |
5 | name: CI
6 |
7 | env:
8 | CARGO_TERM_COLOR: always
9 | RUST_TOOLCHAIN: stable
10 | TOOLCHAIN_PROFILE: minimal
11 |
12 | jobs:
13 | lints:
14 | name: Lint
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: Install ALSA dev
18 | run: |
19 | sudo apt-get update
20 | sudo apt-get install libasound2-dev
21 | - name: Checkout sources
22 | uses: actions/checkout@v2
23 | - name: Install toolchain
24 | uses: actions-rs/toolchain@v1
25 | with:
26 | profile: ${{ env.TOOLCHAIN_PROFILE }}
27 | toolchain: ${{ env.RUST_TOOLCHAIN }}
28 | target: aarch64-linux-android
29 | override: true
30 | components: rustfmt, clippy
31 | - name: Install Android NDK
32 | run: |
33 | sudo apt-get install curl unzip
34 | curl https://dl.google.com/android/repository/android-ndk-r22b-linux-x86_64.zip > /tmp/ndk.zip
35 | sudo unzip -d /ndk /tmp/ndk.zip
36 | echo /ndk/android-ndk-r22b/toolchains/llvm/prebuilt/linux-x86_64/bin >> $GITHUB_PATH
37 | - name: Cache
38 | uses: Swatinem/rust-cache@v1
39 | - name: Run cargo fmt
40 | uses: actions-rs/cargo@v1
41 | with:
42 | command: fmt
43 | args: --all -- --check
44 | - name: Run cargo clippy
45 | uses: actions-rs/cargo@v1
46 | with:
47 | command: clippy
48 | args: --tests -- -D warnings
49 | - name: Run cargo clippy (android)
50 | uses: actions-rs/cargo@v1
51 | with:
52 | command: check
53 | args: --target aarch64-linux-android
54 |
55 | spellcheck:
56 | name: CSpell
57 | runs-on: ubuntu-latest
58 | steps:
59 | - uses: actions/checkout@v3
60 | - uses: streetsidesoftware/cspell-action@v2
61 | with:
62 | files: |
63 | "**/*.md"
64 |
65 | test:
66 | name: Test
67 | runs-on: ubuntu-latest
68 | steps:
69 | - name: Install ALSA dev
70 | run: |
71 | sudo apt-get update
72 | sudo apt-get install libasound2-dev
73 | - name: Checkout sources
74 | uses: actions/checkout@v2
75 | - name: Install toolchain
76 | uses: actions-rs/toolchain@v1
77 | with:
78 | profile: ${{ env.TOOLCHAIN_PROFILE }}
79 | toolchain: ${{ env.RUST_TOOLCHAIN }}
80 | override: true
81 | - name: Cache
82 | uses: Swatinem/rust-cache@v1
83 | - name: Run cargo test --no-run
84 | uses: actions-rs/cargo@v1
85 | with:
86 | command: test
87 | args: '--no-run'
88 | - name: Run cargo test
89 | uses: actions-rs/cargo@v1
90 | env:
91 | RUST_TEST_THREADS: 1
92 | with:
93 | command: test
94 | args: '-p hotham --lib'
95 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | */target
2 | Cargo.lock
3 | target
4 |
5 | # Ignore test assets
6 | sponza*
7 |
8 | # Note that we NEVER care about uncompressed glTF stuff
9 | *.gltf
10 | *.bin
11 | *.jpg
12 | *.png
13 | *.ktx2
14 | *.ktx
15 |
16 | # Ignore logs
17 | *.log
18 |
19 | # Don't ignore images from rendering tests
20 | !render_*.jpg
21 |
22 | # Ignore compiled shaders
23 | *.spv
24 |
--------------------------------------------------------------------------------
/.rustfmt.toml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leetvr/hotham/cab13bfcc2dcce1828d0520c03950a28668f5330/.rustfmt.toml
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "raczzalan.webgl-glsl-editor"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "(Windows) Launch",
9 | "type": "cppvsdbg",
10 | "request": "launch",
11 | "program": "enter program name, for example ${workspaceFolder}/a.exe",
12 | "args": [],
13 | "stopAtEntry": false,
14 | "cwd": "${fileDirname}",
15 | "environment": [],
16 | "console": "externalTerminal"
17 | }
18 | ]
19 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
3 | "[glsl]": {
4 | "editor.formatOnSave": false
5 | },
6 | "files.insertFinalNewline": true,
7 | "files.trimFinalNewlines": true,
8 | "files.trimTrailingWhitespace": true,
9 | // Uncomment this to tell rust-analyzer to treat the code as being compiled for android.
10 | // "rust-analyzer.cargo.target": "aarch64-linux-android",
11 | "webgl-glsl-editor.format.placeSpaceAfterKeywords": true
12 | }
13 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "type": "cargo",
6 | "command": "test",
7 | "args": [
8 | "-p",
9 | "hotham",
10 | "--",
11 | "--test-threads",
12 | "1",
13 | "--nocapture"
14 | ],
15 | "env": {
16 | "RUST_BACKTRACE": "1"
17 | },
18 | "problemMatcher": [
19 | "$rustc"
20 | ],
21 | "label": "Run Hotham test suite"
22 | },
23 | {
24 | "type": "cargo",
25 | "command": "test",
26 | "args": [
27 | "-p",
28 | "hotham",
29 | "--",
30 | "systems::grabbing::tests::test_grabbing_system",
31 | "--nocapture"
32 | ],
33 | "env": {
34 | "RUST_BACKTRACE": "1"
35 | },
36 | "problemMatcher": [
37 | "$rustc"
38 | ],
39 | "label": "Run a specific test",
40 | "group": {
41 | "kind": "test",
42 | "isDefault": false
43 | }
44 | },
45 | {
46 | "type": "cargo",
47 | "command": "run",
48 | "args": [
49 | "--bin",
50 | "hotham_stress_test_desktop",
51 | "--release"
52 | ],
53 | "env": {
54 | "RUST_BACKTRACE": "1"
55 | },
56 | "problemMatcher": [
57 | "$rustc"
58 | ],
59 | "label": "Run Hotham stress test on the simulator",
60 | "group": {
61 | "kind": "test",
62 | "isDefault": false
63 | }
64 | },
65 | {
66 | "type": "cargo",
67 | "command": "run",
68 | "args": [
69 | "--bin",
70 | "hotham_simple_scene_example",
71 | "--release"
72 | ],
73 | "env": {
74 | "RUST_BACKTRACE": "1"
75 | },
76 | "problemMatcher": [
77 | "$rustc"
78 | ],
79 | "label": "Run Hotham simple scene on the simulator",
80 | "group": {
81 | "kind": "test",
82 | "isDefault": false,
83 | }
84 | },
85 | {
86 | "type": "cargo",
87 | "command": "run",
88 | "args": [
89 | "--bin",
90 | "hotham_asset_server",
91 | ],
92 | "env": {
93 | "RUST_BACKTRACE": "1"
94 | },
95 | "problemMatcher": [
96 | "$rustc"
97 | ],
98 | "label": "Run Hotham simple scene on the simulator",
99 | "group": {
100 | "kind": "test",
101 | "isDefault": true,
102 | }
103 | },
104 | ]
105 | }
106 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## UNRELEASED
8 | ### Changed
9 | - Fixed default hand glTF files so offsets are not required when applied to grip pose - @rasmusgo [#271](https://github.com/leetvr/hotham/pull/271)
10 |
11 | ## [0.2] - 2022-05-10
12 | ### Added
13 | - Developers can now add their own application name, version and OpenXR layers to the `Engine` - @jmgao [#197](https://github.com/leetvr/hotham/pull/197)
14 |
15 | ### Changed
16 | - `Panel`s are now independent from `egui`, thanks to @jmgao's fantastic work. `Panel` can now have variable sizes and developers are able to add their own custom content. Existing functionality has been moved to `UiPanel`. - @jmgao [#188](https://github.com/leetvr/hotham/issues/188).
17 |
18 | ### Fixed
19 | - Hotham and Hotham Simulator now no longer have hard dependencies on Vulkan Validation layers. [#182](https://github.com/leetvr/hotham/issues/182)
20 | - Hotham Simulator no longer segfaults on close - @jmgao [#185](https://github.com/leetvr/hotham/issues/185)
21 | - Android debug build is no longer broken - @jmgao [#186](https://github.com/leetvr/hotham/issues/186)
22 | - Fixed ANR on volume up / down - @jmgao [#190](https://github.com/leetvr/hotham/issues/190)
23 | - Bump to `ndk-glue` - @jmgao [#201](https://github.com/leetvr/hotham/issues/201)
24 |
25 | ### Maintenance
26 | - Significant CI improvements, including `clippy` - @davidkuhta [#160](https://github.com/leetvr/hotham/issues/160)
27 |
28 |
29 | ## 0.1 - 2022-03-01
30 | Initial release!
31 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to contribute to Hotham
2 | First of all, thank you so much for your interest and support in Hotham. As a small, open source project, our success is only possible because of people like **you**.
3 |
4 | ## Filing bugs 🐛
5 | Bug reports are some of the most helpful things you can add. Did the example project fail to run on your headset? Did the engine crash when trying to run your game? Did the model importer panic when importing a model? Please, file and issue and **let us know**!
6 |
7 | ## General questions 💬
8 | The best place to get a quick question answered is in [the Hotham discord](https://discord.gg/SZEZUX6ZsQ). There you can speak with the Hotham core team, as well as other developers who are also working on their own projects with Hotham.
9 |
10 | ## Contributing code 🔨
11 | Fixed a bug? Got a suggestion for a great feature? Open a pull request! While we strive to keep the scope of the project small (making VR applications for mobile headsets), we're also open to new ideas. if you're thinking of contributing a large feature, it might be a good idea to get in touch first to see if that would be a good fit for the future road-map, or whether it's something that's planned for later on.
12 |
13 | ## Sponsoring 💰
14 | If you're light on time, why not [contribute some of your hard earned cash money for the project](https://github.com/sponsors/leetvr)? Your funds help enable more developers to work full time on the project.
15 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = [
3 | "benchmarks/stress-test",
4 | "examples/complex-scene",
5 | "examples/crab-saber",
6 | "examples/custom-rendering",
7 | "examples/shared",
8 | "examples/simple-scene",
9 | "hotham-asset-client",
10 | "hotham-asset-server",
11 | "hotham-simulator",
12 | "hotham",
13 | ]
14 |
15 | # Make performance critical packages compile with optimizations
16 | [profile.dev.package.rapier3d]
17 | opt-level = 3
18 |
19 | [profile.dev.package.parry3d]
20 | opt-level = 3
21 |
22 | [profile.dev.package.nalgebra]
23 | opt-level = 3
24 |
25 | [profile.dev.package.glam]
26 | opt-level = 3
27 |
28 | [profile.dev.package.gltf]
29 | opt-level = 3
30 |
31 | [profile.dev.package.gltf-json]
32 | opt-level = 3
33 |
34 | [profile.dev.package.serde]
35 | opt-level = 3
36 |
37 | [profile.dev.package.image]
38 | opt-level = 3
39 |
40 | [profile.dev.package.png]
41 | opt-level = 3
42 |
43 | [profile.dev.package.bytemuck]
44 | opt-level = 3
45 |
46 | [profile.dev.package.symphonia]
47 | opt-level = 3
48 |
49 | [profile.dev.package.symphonia-core]
50 | opt-level = 3
51 |
52 | [profile.dev.package.symphonia-bundle-mp3]
53 | opt-level = 3
54 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Let Eyes Equals Two Pty Ltd
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/leetvr/hotham/actions/workflows/ci.yaml)
2 | [](https://docs.rs/hotham/)
3 | [](https://crates.io/crates/hotham)
4 | [](https://discord.gg/SZEZUX6ZsQ)
5 | [](LICENSE-MIT)
6 | [](LICENSE-APACHE)
7 |
8 | 
9 |
10 | # Introduction
11 | G'day, and welcome to Hotham! 👋
12 |
13 | Hotham is an attempt to create a lightweight, high performance game engine for standalone VR headsets, like the Oculus Quest 2.
14 |
15 | It's primarily aimed at small (1-5 person) teams of mostly technical folk who are looking to create VR games, but find existing tools cumbersome to work with. You can learn more about the project [in the FAQ](https://github.com/leetvr/hotham/wiki/FAQ) or by checking out [our talk at the Rust Gamedev Meetup](https://youtu.be/adt63Gqt6yA?t=717).
16 |
17 | # Getting started
18 | Hotham is a complex project with many moving parts! Have no fear - we've written an easy to follow [Getting Started guide](https://github.com/leetvr/hotham/wiki/Getting-started) that will have you running our example application in no time. Head on over to [getting started](https://github.com/leetvr/hotham/wiki/Getting-started) to.. get.. started.
19 |
20 | # Sponsoring
21 | Hotham's development is only possible thanks to the support of the community. It's currently being developed on full time by [@kanerogers](https://github.com/kanerogers) If you'd like to help make VR development in Rust possible, please [consider becoming a donor](https://github.com/sponsors/leetvr). 💗
22 |
23 | ## Featured sponsors
24 | These generous people contribute $50 or more each month to support Hotham ❤️
25 |
26 |
27 |
28 |
29 |
30 | # Progress 🔨
31 | - [x] Vulkan renderer
32 | - [x] OpenXR integration
33 | - [x] Android (eg. Oculus Quest) support
34 | - [x] Simple Windows-based OpenXR simulator
35 | - [x] Import of [glTF](https://www.khronos.org/gltf/) models
36 | - [x] Support for skinned models
37 | - [x] Support for animations
38 | - [x] Hand/controller presence
39 | - [x] Object grabbing support
40 | - [x] Physics (eg. collision detection) support
41 | - [x] [Basic PBR support](https://github.com/leetvr/hotham/issues/65)
42 | - [x] [Simple debug UI](https://github.com/leetvr/hotham/issues/96)
43 | - [x] [Sound support](https://github.com/leetvr/hotham/issues/52)
44 | - [x] [Text/UI support](https://github.com/leetvr/hotham/issues/93)
45 |
46 | # Current Demo
47 | 
48 |
49 | # Future goals ✨
50 | - [ ] Visual editor
51 | - [ ] Fast reloading
52 | - [ ] OpenXR record/playback for integration testing
53 | - [ ] Better debugging support
54 | - [ ] Better performance profiling support
55 |
56 | ## License
57 |
58 | Licensed under either of
59 |
60 | * Apache License, Version 2.0
61 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
62 | * MIT license
63 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
64 |
65 | at your option.
66 |
67 | ## Contribution
68 |
69 | Unless you explicitly state otherwise, any contribution intentionally submitted
70 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
71 | dual licensed as above, without any additional terms or conditions.
72 |
73 | ## Disclaimer
74 | 1. Trademarks are the property of their respective owners
75 |
--------------------------------------------------------------------------------
/benchmarks/README.md:
--------------------------------------------------------------------------------
1 | # Benchmarks
2 |
3 | ## Many objects
4 | ### Methodology
5 | - Each second, add a cube (in this case, the default Blender cube, exported to glTF)
6 | - At the moment of crash, record the number of cubes and current FPS
7 |
8 | ### Hotham 0.2
9 | - **Result**: Crash - `Result::unwrap()` on an `Err` value: ERROR_OUT_OF_POOL_MEMORY', hotham\src\gltf_loader.rs:448:14`
10 | - **Max cubes**: 98
11 | - **FPS at time of crash**: 72
12 | - **OVR stats**: `FPS=72/72,Prd=28ms,Tear=0,Early=0,Stale=1,VSnc=0,Lat=-1,Fov=0,CPU4/GPU=2/3,1171/490MHz,OC=FF,TA=0/0/0,SP=N/N/N,Mem=1353MHz,Free=3274MB,PLS=0,Temp=28.0C/0.0C,TW=2.39ms,App=1.73ms,GD=0.27ms,CPU&GPU=4.08ms,LCnt=1,GPU%=0.35,CPU%=0.12(W0.14),DSF=1.00`
13 |
14 | ## Many vertices
15 | ### Methodology
16 | - Start with a mesh with two triangles.
17 | - Each second, increase the number of vertices
18 | - Record FPS vs Vertices
19 | - At the moment of crash, record the number of vertices and last FPS
20 |
21 | ### Hotham 0.2
22 | - **Result**: Crash - segfault
23 | - **Max vertices**: 1,040,400 vertices,
24 | - **FPS at time of crash**: 16
25 | - **OVR stats at time of crash**: `FPS=16/72,Prd=83ms,Tear=2,Early=2,Stale=56,VSnc=0,Lat=-1,Fov=0,CPU4/GPU=4/3,1478/490MHz,OC=FF,TA=0/0/0,SP=N/N/N,Mem=1353MHz,Free=3200MB,PLS=0,Temp=31.0C/0.0C,TW=8.91ms,App=11.80ms,GD=0.00ms,CPU&GPU=58.12ms,LCnt=1,GPU%=1.00,CPU%=0.02(W0.05),DSF=1.00`
26 | - **Highest vertex count at 72 FPS**: 144,400
27 |
28 | ## Sponza
29 | ### Methodology
30 | - Download the [new Sponza scene](https://www.intel.com/content/www/us/en/developer/topic-technology/graphics-research/samples.html)
31 | - Export the glTF file into a GLB file with Blender
32 | - Place the glTF file in the `test_assets` directory.
33 | - Record the FPS if the scene loads
34 |
35 | ### Hotham 0.2
36 | - **Result**: Crash - `ERROR_OUT_OF_POOL_MEMORY`
37 | - **Note**: Was unable to run in simulator so did not attempt to run in headset. Will require further investigation to load the GLB from the device's internal storage as it is too large to either include in the binary or the APK.
--------------------------------------------------------------------------------
/benchmarks/stress-test/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | edition = "2018"
3 | license = "MIT OR Apache-2.0"
4 | name = "hotham-stress-test"
5 | version = "0.2.0"
6 |
7 | [lib]
8 | crate-type = ["lib", "cdylib"]
9 |
10 | [[bin]]
11 | name = "hotham_stress_test_desktop"
12 | path = "src/main.rs"
13 |
14 | [dependencies]
15 | hotham = {path = "../../hotham"}
16 |
17 | [target.'cfg(target_os = "android")'.dependencies]
18 | ndk-glue = "0.6"
19 |
20 | [package.metadata.android]
21 | apk_label = "Hotham Stress Test"
22 | fullscreen = true
23 | runtime_libs = "../../examples/common_lib"
24 | target_sdk_version = 29
25 |
26 | [package.metadata.android.application]
27 | debuggable = true
28 | label = "Hotham Stress Test"
29 | theme = "@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen"
30 |
31 | [package.metadata.android.application.activity]
32 | config_changes = "screenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode"
33 | launch_mode = "singleTask"
34 | orientation = "landscape"
35 |
36 | [[package.metadata.android.uses_permission]]
37 | name = "android.permission.INTERNET"
38 |
39 | [[package.metadata.android.uses_permission]]
40 | name = "android.permission.ACCESS_NETWORK_STATE"
41 |
42 | [[package.metadata.android.application.meta_data]]
43 | name = "com.oculus.supportedDevices"
44 | value = "quest|quest2"
45 |
46 | [[package.metadata.android.application.meta_data]]
47 | name = "com.oculus.intent.category.VR"
48 | value = "vr_only"
49 |
50 | [[package.metadata.android.application.activity.intent_filter]]
51 | actions = ["android.intent.action.MAIN"]
52 | categories = ["com.oculus.intent.category.VR", "android.intent.category.LAUNCHER"]
53 |
54 | [[package.metadata.android.application.activity.meta_data]]
55 | name = "com.oculus.vr.focusaware"
56 | value = "true"
57 |
58 | [[package.metadata.android.uses_feature]]
59 | name = "android.hardware.vulkan.level"
60 | required = true
61 | version = 1
62 |
63 | [[package.metadata.android.uses_feature]]
64 | name = "android.hardware.vr.headtracking"
65 | required = true
66 | version = 1
67 |
68 | # !! IMPORTANT !!
69 | #
70 | # When creating your own apps, make sure to generate your own keystore, rather than using our example one!
71 | # You can use `keytool` like so:
72 | # keytool -genkey -v -keystore my-release-key.keystore -keyalg RSA -keysize 2048 -validity 10000
73 | #
74 | # For more information on key signing and why it's so important, check out this article:
75 | # https://developer.android.com/studio/publish/app-signing
76 | #
77 | # !! IMPORTANT !!
78 | [package.metadata.android.signing.release]
79 | path = "../../examples/hotham_examples.keystore"
80 | keystore_password = "chomsky-vigilant-spa"
81 |
--------------------------------------------------------------------------------
/benchmarks/stress-test/README.md:
--------------------------------------------------------------------------------
1 | 
2 | # Stress Test
3 | This project contains a series of tests designed to *torture* Hotham. It is unfair, unkind and it most certainly does not care what you think of it.
4 |
5 | ## How to run the tests
6 | Switching between the various tests is accomplished by changing the `test` variable in `src/lib.rs` to some variant of the `StressTest` enum:
7 |
8 | ```rust
9 | /// Used to select which test to run
10 | pub enum StressTest {
11 | /// Generate one cube per second
12 | ManyCubes,
13 | /// Create a dynamic mesh and increase the number of vertices each second
14 | ManyVertices,
15 | /// Load the New Sponza scene into the engine
16 | Sponza,
17 | }
18 | ```
19 |
20 | For more information on these tests, and their results, consult the `README.md` file in the parent directory.
21 |
--------------------------------------------------------------------------------
/benchmarks/stress-test/logo.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:4817a1d3abf7c0ca25fb8cd0b93deeec532fe67303c5aba09f507f62f0373521
3 | size 124631
4 |
--------------------------------------------------------------------------------
/benchmarks/stress-test/scripts/run_on_device.ps1:
--------------------------------------------------------------------------------
1 | ${env:RUST_BACKTRACE} = 1
2 | adb shell am force-stop rust.hotham_stress_test
3 |
4 | Set-Location $PSScriptRoot\..
5 | cargo apk run --release
6 |
7 | if ($?) {
8 | $processId = $null
9 | foreach ($i in 1..5) {
10 | $processId = adb shell pidof rust.hotham_stress_test
11 | if ($processId) { break }
12 | Write-Output "Waiting for process to start, sleeping..."
13 | Start-Sleep -Seconds 1
14 | }
15 | if ($processId) {
16 | Write-Output "Found PID of " $processId
17 | adb logcat --pid=$processId
18 | } else {
19 | Write-Error "Failed to find PID of rust.hotham_stress_test"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/benchmarks/stress-test/scripts/run_on_device.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -eux
3 |
4 | adb shell am force-stop rust.hotham_stress_test
5 |
6 | scriptdir=$(dirname -- "$(realpath -- "$0")")
7 | cd $scriptdir/..
8 |
9 | cargo apk run --release
10 |
11 | # Wait for the app to start
12 | for i in 1 2 3 4 5; do
13 | adb shell pidof rust.hotham_stress_test && break
14 | sleep 1
15 | done
16 |
17 | adb logcat --pid="$(adb shell pidof rust.hotham_stress_test)"
18 |
--------------------------------------------------------------------------------
/benchmarks/stress-test/src/main.rs:
--------------------------------------------------------------------------------
1 | use hotham::HothamResult;
2 |
3 | fn main() -> HothamResult<()> {
4 | hotham_stress_test::real_main()
5 | }
6 |
--------------------------------------------------------------------------------
/benchmarks/stress-test/src/systems/cube.rs:
--------------------------------------------------------------------------------
1 | use hotham::{
2 | asset_importer::{add_model_to_world, Models},
3 | components::LocalTransform,
4 | glam::Vec3,
5 | hecs::World,
6 | };
7 |
8 | pub fn setup_cubes(world: &mut World, resolution: usize, models: &Models) {
9 | let step = 2. / resolution as f32;
10 | let scale_factor = 3.;
11 | let scale = Vec3::splat(step / scale_factor);
12 | let half_resolution = resolution as f32 / 2.;
13 | let x_offset = half_resolution / scale_factor;
14 |
15 | for floor in 0..resolution {
16 | for row in 0..resolution {
17 | for column in 0..resolution {
18 | let c = add_model_to_world("Cube", models, world, None).unwrap();
19 | let mut t = world.get::<&mut LocalTransform>(c).unwrap();
20 | t.scale = scale;
21 | t.translation.y = floor as f32 / scale_factor;
22 | t.translation.x = column as f32 / scale_factor - x_offset;
23 | t.translation.z = row as f32 / scale_factor - half_resolution - 2.0;
24 | }
25 | }
26 | }
27 | }
28 |
29 | // Different ways of arranging items
30 | // fn circle(u: f32, v: f32, _t: f32) -> Vector3 {
31 | // let x = (PI * u).sin();
32 | // let y = v;
33 | // let z = (PI * u).cos();
34 | // [x, y, z].into()
35 | // }
36 |
37 | // fn sphere(u: f32, v: f32, _t: f32) -> Vector3 {
38 | // let r = 1.;
39 | // let s = r * (0.5 * PI * v).cos();
40 |
41 | // let x = s * (PI * u).sin();
42 | // let y = r * (PI * 0.5 * v).sin();
43 | // let z = s * (PI * u).cos();
44 | // [x, y, z].into()
45 | // }
46 |
47 | // fn _torus(u: f32, v: f32, _t: f32) -> Vector3 {
48 | // let r1 = 0.75;
49 | // let r2 = 0.25;
50 | // let s = r1 + r2 * (PI * v).cos();
51 |
52 | // let x = s * (PI * u).sin();
53 | // let y = r2 * (PI * v).sin();
54 | // let z = s * (PI * u).cos();
55 | // [x, y, z].into()
56 | // }
57 |
--------------------------------------------------------------------------------
/benchmarks/stress-test/src/systems/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod cube;
2 | pub use cube::setup_cubes;
3 |
--------------------------------------------------------------------------------
/cspell.json:
--------------------------------------------------------------------------------
1 | // cSpell Settings
2 | {
3 | // Version of the setting file. Always 0.2
4 | "version": "0.2",
5 | // language - current active spelling language
6 | "language": "en",
7 | // words - list of words to be always considered correct
8 | "words": [
9 | "aarch",
10 | "ALSA",
11 | "bindgen",
12 | "brdf",
13 | "brdflut",
14 | "Brickner",
15 | "Carmack",
16 | "cdylib",
17 | "clippy",
18 | "consts",
19 | "davidkuhta",
20 | "debuggable",
21 | "DONT",
22 | "dylib",
23 | "egui",
24 | "emath",
25 | "epaint",
26 | "Fovf",
27 | "framebuffer",
28 | "framebuffers",
29 | "fullscreen",
30 | "Gamedev",
31 | "glsl",
32 | "gltf",
33 | "grabbable",
34 | "haptics",
35 | "Hasher",
36 | "hecs",
37 | "Hemsley",
38 | "hotham",
39 | "intrinsics",
40 | "itertools",
41 | "izip",
42 | "jmgao",
43 | "kanerogers",
44 | "KHRONOS",
45 | "Lambertian",
46 | "lerp",
47 | "Lhand",
48 | "libasound",
49 | "libktx",
50 | "libovr",
51 | "linvel",
52 | "Luckey",
53 | "memoffset",
54 | "metalness",
55 | "microfacet",
56 | "mipmap",
57 | "mipmaps",
58 | "MSAA",
59 | "multibody",
60 | "multisample",
61 | "multisampling",
62 | "multiview",
63 | "nalgebra",
64 | "nanos",
65 | "NEFFEX",
66 | "NONBLOCKING",
67 | "nonoverlapping",
68 | "oddio",
69 | "openxr",
70 | "openxrs",
71 | "Photometria",
72 | "prefiltered",
73 | "Quaternionf",
74 | "Ralith",
75 | "rasmusgo",
76 | "rasterization",
77 | "rasterizer",
78 | "renderdoc",
79 | "renderpass",
80 | "renderpasses",
81 | "repr",
82 | "rezural",
83 | "rgba",
84 | "rustc",
85 | "rustfmt",
86 | "Sascha",
87 | "serde",
88 | "shaderc",
89 | "SIGGRAPH",
90 | "sinoth",
91 | "slerp",
92 | "socio",
93 | "spatialized",
94 | "spatializer",
95 | "spirv",
96 | "Sponza",
97 | "Squisher",
98 | "srgb",
99 | "struct",
100 | "subaction",
101 | "subpass",
102 | "subpasses",
103 | "subresource",
104 | "supercompression",
105 | "swapchain",
106 | "swapchains",
107 | "Swatinem",
108 | "thiserror",
109 | "thumbstick",
110 | "tonemap",
111 | "toolchains",
112 | "tungstenite",
113 | "Uninit",
114 | "unmap",
115 | "unnormalized",
116 | "viewports",
117 | "vulkan",
118 | "wbrickner",
119 | "Willems",
120 | "winit"
121 | ],
122 | "ignoreWords": [
123 | "Adreno",
124 | "basisu",
125 | "bvec3",
126 | "Ldot",
127 | "Ndot",
128 | "outcol",
129 | "Posef",
130 | "Reitz",
131 | "SFLOAT",
132 | "UNORM",
133 | "Vdot",
134 | "viewdisplay",
135 | "writeonly",
136 | "xyzw"
137 | ],
138 | "flagWords": [
139 | "centre"
140 | ],
141 | "ignorePaths": [
142 | "**/*.bin",
143 | "**/*.glb",
144 | "**/*.ktx2",
145 | "**/*.mp3",
146 | "**/*.so",
147 | "**/node_modules/**",
148 | "**/target/**",
149 | "cspell.json"
150 | ],
151 | "enableFiletypes": [
152 | "glsl"
153 | ]
154 | }
155 |
--------------------------------------------------------------------------------
/docs/hotham_sync_loop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leetvr/hotham/cab13bfcc2dcce1828d0520c03950a28668f5330/docs/hotham_sync_loop.png
--------------------------------------------------------------------------------
/examples/common_lib/arm64-v8a/libopenxr_loader.so:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:36ffaa34b35733229a56fd47d434b0b3ceb423e71857918799c29018c8209c36
3 | size 8706760
4 |
--------------------------------------------------------------------------------
/examples/complex-scene/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | edition = "2018"
3 | license = "MIT OR Apache-2.0"
4 | name = "complex-scene-example"
5 | version = "0.2.0"
6 |
7 | [lib]
8 | crate-type = ["lib", "cdylib"]
9 |
10 | [[bin]]
11 | name = "hotham_complex_scene_example"
12 | path = "src/main.rs"
13 |
14 | [dependencies]
15 | hotham = {path = "../../hotham"}
16 | hotham-examples = {path = "../shared"}
17 |
18 | [target.'cfg(target_os = "android")'.dependencies]
19 | ndk-glue = "0.6"
20 |
21 | [package.metadata.android]
22 | apk_label = "Hotham Complex Scene Example"
23 | fullscreen = true
24 | runtime_libs = "../common_lib"
25 | target_sdk_version = 29
26 |
27 | [package.metadata.android.application]
28 | debuggable = true
29 | label = "Hotham Complex Scene Example"
30 | theme = "@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen"
31 |
32 | [package.metadata.android.application.activity]
33 | config_changes = "screenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode"
34 | launch_mode = "singleTask"
35 | orientation = "landscape"
36 |
37 | [[package.metadata.android.uses_permission]]
38 | name = "android.permission.INTERNET"
39 |
40 | [[package.metadata.android.uses_permission]]
41 | name = "android.permission.ACCESS_NETWORK_STATE"
42 |
43 | [[package.metadata.android.application.meta_data]]
44 | name = "com.oculus.supportedDevices"
45 | value = "quest|quest2"
46 |
47 | [[package.metadata.android.application.meta_data]]
48 | name = "com.oculus.intent.category.VR"
49 | value = "vr_only"
50 |
51 | [[package.metadata.android.application.activity.intent_filter]]
52 | actions = ["android.intent.action.MAIN"]
53 | categories = ["com.oculus.intent.category.VR", "android.intent.category.LAUNCHER"]
54 |
55 | [[package.metadata.android.application.activity.meta_data]]
56 | name = "com.oculus.vr.focusaware"
57 | value = "true"
58 |
59 | [[package.metadata.android.uses_feature]]
60 | name = "android.hardware.vulkan.level"
61 | required = true
62 | version = 1
63 |
64 | [[package.metadata.android.uses_feature]]
65 | name = "android.hardware.vr.headtracking"
66 | required = true
67 | version = 1
68 |
69 | # !! IMPORTANT !!
70 | #
71 | # When creating your own apps, make sure to generate your own keystore, rather than using our example one!
72 | # You can use `keytool` like so:
73 | # keytool -genkey -v -keystore my-release-key.keystore -keyalg RSA -keysize 2048 -validity 10000
74 | #
75 | # For more information on key signing and why it's so important, check out this article:
76 | # https://developer.android.com/studio/publish/app-signing
77 | #
78 | # !! IMPORTANT !!
79 | [package.metadata.android.signing.release]
80 | path = "../hotham_examples.keystore"
81 | keystore_password = "chomsky-vigilant-spa"
82 |
--------------------------------------------------------------------------------
/examples/complex-scene/README.md:
--------------------------------------------------------------------------------
1 | # Complex Scene
2 | This scene is using some of Hotham's more complex features.
3 |
--------------------------------------------------------------------------------
/examples/complex-scene/openxr_loader.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leetvr/hotham/cab13bfcc2dcce1828d0520c03950a28668f5330/examples/complex-scene/openxr_loader.dll
--------------------------------------------------------------------------------
/examples/complex-scene/scripts/run_on_device.ps1:
--------------------------------------------------------------------------------
1 | adb shell am force-stop rust.complex_scene_example
2 |
3 | Set-Location $PSScriptRoot\..
4 | cargo apk run --release
5 |
6 | if ($?) {
7 | Start-Sleep -Seconds 2
8 | $processIdStr = (adb shell pidof rust.complex_scene_example) | Out-String
9 | Write-Output $processIdStr
10 | $processId = $processIdStr -as [int]
11 | Write-Output $processId
12 | adb logcat --pid=$processId
13 | }
14 |
--------------------------------------------------------------------------------
/examples/complex-scene/scripts/run_on_device.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -eux
3 |
4 | adb shell am force-stop rust.complex_scene_example
5 |
6 | scriptdir=$(dirname -- "$(realpath -- "$0")")
7 | cd $scriptdir/..
8 |
9 | cargo apk run --release
10 |
11 | adb logcat --pid="$(adb shell pidof rust.complex_scene_example)"
12 |
--------------------------------------------------------------------------------
/examples/complex-scene/src/lib.rs:
--------------------------------------------------------------------------------
1 | use hotham::{
2 | asset_importer::{self, add_model_to_world},
3 | components::{
4 | hand::Handedness,
5 | physics::{BodyType, SharedShape},
6 | Collider, Grabbable, LocalTransform, RigidBody,
7 | },
8 | glam,
9 | hecs::World,
10 | na,
11 | systems::{
12 | animation_system, debug::debug_system, grabbing_system, hands::add_hand, hands_system,
13 | physics_system, rendering::rendering_system, skinning::skinning_system,
14 | update_global_transform_system,
15 | },
16 | xr, Engine, HothamResult, TickData,
17 | };
18 | use hotham_examples::navigation::{navigation_system, State};
19 |
20 | #[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))]
21 | pub fn main() {
22 | println!("[HOTHAM_COMPLEX_SCENE] MAIN!");
23 | real_main().expect("Error running app!");
24 | println!("[HOTHAM_COMPLEX_SCENE] FINISHED! Goodbye!");
25 | }
26 |
27 | pub fn real_main() -> HothamResult<()> {
28 | let mut engine = Engine::new();
29 | let mut state = Default::default();
30 | init(&mut engine)?;
31 |
32 | while let Ok(tick_data) = engine.update() {
33 | tick(tick_data, &mut engine, &mut state);
34 | engine.finish()?;
35 | }
36 |
37 | Ok(())
38 | }
39 |
40 | fn tick(tick_data: TickData, engine: &mut Engine, state: &mut State) {
41 | if tick_data.current_state == xr::SessionState::FOCUSED {
42 | hands_system(engine);
43 | grabbing_system(engine);
44 | physics_system(engine);
45 | animation_system(engine);
46 | navigation_system(engine, state);
47 | update_global_transform_system(engine);
48 | skinning_system(engine);
49 | debug_system(engine);
50 | }
51 |
52 | rendering_system(engine, tick_data.swapchain_image_index);
53 | }
54 |
55 | fn init(engine: &mut Engine) -> Result<(), hotham::HothamError> {
56 | if option_env!("HOTHAM_ASSET_SERVER_ADDRESS").is_some() {
57 | #[cfg(target_os = "android")]
58 | engine.watch_assets(vec!["test_assets/damaged_helmet_squished.glb".into()]);
59 | #[cfg(not(target_os = "android"))]
60 | engine.watch_assets(vec!["test_assets/damaged_helmet.glb".into()]);
61 | }
62 | let render_context = &mut engine.render_context;
63 | let vulkan_context = &mut engine.vulkan_context;
64 | let world = &mut engine.world;
65 |
66 | let mut glb_buffers: Vec<&[u8]> = vec![
67 | include_bytes!("../../../test_assets/floor.glb"),
68 | include_bytes!("../../../test_assets/left_hand.glb"),
69 | include_bytes!("../../../test_assets/right_hand.glb"),
70 | ];
71 |
72 | #[cfg(target_os = "android")]
73 | glb_buffers.push(include_bytes!(
74 | "../../../test_assets/damaged_helmet_squished.glb"
75 | ));
76 |
77 | #[cfg(not(target_os = "android"))]
78 | glb_buffers.push(include_bytes!("../../../test_assets/damaged_helmet.glb"));
79 |
80 | let models =
81 | asset_importer::load_models_from_glb(&glb_buffers, vulkan_context, render_context)?;
82 | add_helmet(&models, world, [-1., 1.4, -1.].into());
83 | add_helmet(&models, world, [1., 1.4, -1.].into());
84 | add_hand(&models, Handedness::Left, world);
85 | add_hand(&models, Handedness::Right, world);
86 | add_floor(&models, world);
87 |
88 | Ok(())
89 | }
90 |
91 | fn add_floor(models: &std::collections::HashMap, world: &mut World) {
92 | let entity = add_model_to_world("Floor", models, world, None).expect("Could not find Floor");
93 | let collider = Collider::new(SharedShape::halfspace(na::Vector3::y_axis()));
94 | let rigid_body = RigidBody {
95 | body_type: BodyType::Fixed,
96 | ..Default::default()
97 | };
98 | world.insert(entity, (collider, rigid_body)).unwrap();
99 | }
100 |
101 | fn add_helmet(
102 | models: &std::collections::HashMap,
103 | world: &mut World,
104 | translation: glam::Vec3,
105 | ) {
106 | let helmet = add_model_to_world("Damaged Helmet", models, world, None)
107 | .expect("Could not find Damaged Helmet");
108 |
109 | {
110 | let mut local_transform = world.get::<&mut LocalTransform>(helmet).unwrap();
111 | local_transform.translation = translation;
112 | local_transform.scale = [0.5, 0.5, 0.5].into();
113 | }
114 |
115 | let collider = Collider::new(SharedShape::ball(0.35));
116 |
117 | world.insert(helmet, (collider, Grabbable {})).unwrap();
118 | }
119 |
--------------------------------------------------------------------------------
/examples/complex-scene/src/main.rs:
--------------------------------------------------------------------------------
1 | use hotham::HothamResult;
2 |
3 | fn main() -> HothamResult<()> {
4 | complex_scene_example::real_main()
5 | }
6 |
--------------------------------------------------------------------------------
/examples/crab-saber/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
--------------------------------------------------------------------------------
/examples/crab-saber/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | edition = "2018"
3 | license = "MIT OR Apache-2.0"
4 | name = "crab-saber"
5 | version = "0.2.0"
6 |
7 | [lib]
8 | crate-type = ["lib", "cdylib"]
9 |
10 | [[bin]]
11 | name = "hotham_crab_saber"
12 | path = "src/main.rs"
13 |
14 | [dependencies]
15 | hotham = {path = "../../hotham"}
16 | rand = "0.8.0"
17 |
18 | [dev-dependencies]
19 | approx = "0.5"
20 |
21 | [target.'cfg(target_os = "android")'.dependencies]
22 | ndk-glue = "0.6"
23 |
24 | [package.metadata.android]
25 | apk_label = "Crab Saber"
26 | fullscreen = true
27 | runtime_libs = "../common_lib"
28 | target_sdk_version = 29
29 |
30 | [[package.metadata.android.uses_permission]]
31 | name = "android.permission.INTERNET"
32 |
33 | [[package.metadata.android.uses_permission]]
34 | name = "android.permission.ACCESS_NETWORK_STATE"
35 |
36 | [package.metadata.android.application]
37 | debuggable = false
38 | label = "Crab Saber"
39 | theme = "@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen"
40 |
41 | [package.metadata.android.application.activity]
42 | config_changes = "screenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode"
43 | launch_mode = "singleTask"
44 | orientation = "landscape"
45 |
46 | [[package.metadata.android.application.meta_data]]
47 | name = "com.oculus.supportedDevices"
48 | value = "quest|quest2"
49 |
50 | [[package.metadata.android.application.meta_data]]
51 | name = "com.oculus.intent.category.VR"
52 | value = "vr_only"
53 |
54 | [[package.metadata.android.application.activity.intent_filter]]
55 | actions = ["android.intent.action.MAIN"]
56 | categories = ["com.oculus.intent.category.VR", "android.intent.category.LAUNCHER"]
57 |
58 | [[package.metadata.android.application.activity.meta_data]]
59 | name = "com.oculus.vr.focusaware"
60 | value = "true"
61 |
62 | [[package.metadata.android.uses_feature]]
63 | name = "android.hardware.vulkan.level"
64 | required = true
65 | version = 1
66 |
67 | [[package.metadata.android.uses_feature]]
68 | name = "android.hardware.vr.headtracking"
69 | required = true
70 | version = 1
71 |
72 | # !! IMPORTANT !!
73 | #
74 | # When creating your own apps, make sure to generate your own keystore, rather than using our example one!
75 | # You can use `keytool` like so:
76 | # keytool -genkey -v -keystore my-release-key.keystore -keyalg RSA -keysize 2048 -validity 10000
77 | #
78 | # For more information on key signing and why it's so important, check out this article:
79 | # https://developer.android.com/studio/publish/app-signing
80 | #
81 | # !! IMPORTANT !!
82 | [package.metadata.android.signing.release]
83 | path = "../hotham_examples.keystore"
84 | keystore_password = "chomsky-vigilant-spa"
85 |
--------------------------------------------------------------------------------
/examples/crab-saber/README.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 | This example is a clone of the popular VR game, [Beat Saber](https://beatsaber.com/). We're not lawyers, but this is provided as an example *only*: trying to upload this game to an app store is probably not a good idea.
3 |
4 | # Running the example
5 | ## Pre-requisites
6 | 1. [Git LFS](https://git-lfs.github.com/) needs to be enabled for this repo. You'll have a _real_ bad time if you don't.
7 | 1. Install [Rust](https://www.rust-lang.org/tools/install), [cargo apk](https://crates.io/crates/cargo-apk) and the [Android SDK and Android NDK](https://developer.android.com/studio). _Note: only the Android NDK and SDK are required - if Android Studio is not your cup of tea, you can certainly install those separately_
8 | 1. An Oculus developer account is required & [developer mode](https://developer.oculus.com/documentation/native/android/mobile-device-setup/) needs to be enabled.
9 |
10 | With all that done, you should now be able to see your device from ADB:
11 |
12 | ````
13 | user@host:~/hotham$ adb devices
14 | List of devices attached
15 | 1PASH9B12E0092 device
16 | ````
17 |
18 | Then run `run_on_device.ps1` (Windows) or `run_on_device.sh` (Linux) and you're good to go.
19 |
20 | # Troubleshooting
21 | This is definitely _cutting edge_ software, so don't be surprised if it breaks. If you run into any trouble, your friends at the [Hotham discord](https://discord.gg/SZEZUX6ZsQ) can give you a hand!
22 |
23 | # License Information
24 | This example uses a modified version of the asset [Beat Sabers](https://sketchfab.com/3d-models/beat-sabers-e7c6358273d44faea03fa77d9792fd6a), by "Spark", under the [CC4.0 license](https://creativecommons.org/licenses/by/4.0/).
25 |
--------------------------------------------------------------------------------
/examples/crab-saber/assets/Chasms - Dark Matter.mp3:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:3300a21ee310faa72fb1b437247618df5a2644b8b034321433826394a405a1b5
3 | size 7046829
4 |
--------------------------------------------------------------------------------
/examples/crab-saber/assets/Hit.mp3:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:c5185f3662887e7205e36a98d085ffbfc60df2241dcc8eabb27a05f942d44f29
3 | size 39773
4 |
--------------------------------------------------------------------------------
/examples/crab-saber/assets/Miss.mp3:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:9ad8da06bbd7ee1abbdbe5e6c4bef87a79a9d4fb1f710a97f6aef8b4363dbce4
3 | size 31422
4 |
--------------------------------------------------------------------------------
/examples/crab-saber/assets/NEFFEX - Tell Me That I Can't.mp3:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:0c5052949e3bce202cabb5a7ffffb3e8f5f91698f7112206debdf023b2217d5c
3 | size 7065637
4 |
--------------------------------------------------------------------------------
/examples/crab-saber/assets/Spence - Right Here Beside You.mp3:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:c58c6f8cba332b5ad7f0d8fa7a40ba8bfe6d58fa1cb2edd2637e03376d997e0f
3 | size 6977865
4 |
--------------------------------------------------------------------------------
/examples/crab-saber/assets/TrackTribe - Cloud Echo.mp3:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:79fca3fd88fbabde1969ab15435d1a0627ad31c471b60bbf82ae788076196506
3 | size 6490943
4 |
--------------------------------------------------------------------------------
/examples/crab-saber/assets/crab_saber.glb:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:7ef784fb740e2583eceacfb885ac17b58fb47c6f209b3d962b321026d71ede37
3 | size 89840
4 |
--------------------------------------------------------------------------------
/examples/crab-saber/openxr_loader.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leetvr/hotham/cab13bfcc2dcce1828d0520c03950a28668f5330/examples/crab-saber/openxr_loader.dll
--------------------------------------------------------------------------------
/examples/crab-saber/scripts/run_on_device.ps1:
--------------------------------------------------------------------------------
1 | adb shell am force-stop rust.crab_saber
2 |
3 | Set-Location $PSScriptRoot\..
4 | cargo apk run --release
5 |
6 | if ($?) {
7 | $processId = $null
8 | foreach ($i in 1..5) {
9 | $processId = adb shell pidof rust.crab_saber
10 | if ($processId) { break }
11 | Write-Output "Waiting for process to start, sleeping..."
12 | Start-Sleep -Seconds 1
13 | }
14 | if ($processId) {
15 | Write-Output "Found PID of " $processId
16 | adb logcat --pid=$processId
17 | } else {
18 | Write-Error "Failed to find PID of rust.crab_saber"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/crab-saber/scripts/run_on_device.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -eux
3 |
4 | adb shell am force-stop rust.crab_saber
5 |
6 | scriptdir=$(dirname -- "$(realpath -- "$0")")
7 | cd $scriptdir/..
8 |
9 | cargo apk run --release --package crab-saber
10 |
11 | # Wait for the app to start
12 | for i in 1 2 3 4 5; do
13 | adb shell pidof rust.crab_saber && break
14 | sleep 1
15 | done
16 |
17 | adb logcat --pid="$(adb shell pidof rust.crab_saber)"
18 |
--------------------------------------------------------------------------------
/examples/crab-saber/src/components/color.rs:
--------------------------------------------------------------------------------
1 | #[derive(Debug, Clone, PartialEq, Eq, Copy)]
2 | pub enum Color {
3 | Red,
4 | Blue,
5 | }
6 |
--------------------------------------------------------------------------------
/examples/crab-saber/src/components/cube.rs:
--------------------------------------------------------------------------------
1 | #[derive(Debug, Clone)]
2 | pub struct Cube {}
3 |
--------------------------------------------------------------------------------
/examples/crab-saber/src/components/mod.rs:
--------------------------------------------------------------------------------
1 | mod color;
2 | mod cube;
3 | mod saber;
4 |
5 | pub use color::Color;
6 | pub use cube::Cube;
7 | pub use saber::Saber;
8 |
--------------------------------------------------------------------------------
/examples/crab-saber/src/components/saber.rs:
--------------------------------------------------------------------------------
1 | pub struct Saber {}
2 |
--------------------------------------------------------------------------------
/examples/crab-saber/src/lib.rs:
--------------------------------------------------------------------------------
1 | mod components;
2 | mod game_context;
3 | mod systems;
4 |
5 | use hotham::{
6 | components::Visible,
7 | hecs::{Entity, World},
8 | systems::{
9 | audio_system, draw_gui_system, haptics_system, physics_system, pointers_system,
10 | rendering_system, update_global_transform_system,
11 | },
12 | xr::{self, SessionState},
13 | Engine, HothamResult, TickData,
14 | };
15 |
16 | use game_context::{GameContext, GameState};
17 | use systems::{game::game_system, sabers_system};
18 |
19 | #[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))]
20 | pub fn main() {
21 | println!("[CRAB_SABER] MAIN!");
22 | real_main().expect("[CRAB_SABER] ERROR IN MAIN!");
23 | }
24 |
25 | pub fn real_main() -> HothamResult<()> {
26 | let mut engine = Engine::new();
27 | let mut game_context = init(&mut engine);
28 |
29 | while let Ok(tick_data) = engine.update() {
30 | tick(tick_data, &mut engine, &mut game_context);
31 | engine.finish()?;
32 | }
33 |
34 | Ok(())
35 | }
36 |
37 | fn tick(tick_data: TickData, engine: &mut Engine, game_context: &mut GameContext) {
38 | handle_state_change(&tick_data, engine, game_context);
39 |
40 | // Simulation tasks - these are only necessary in the focussed state.
41 | if tick_data.current_state == xr::SessionState::FOCUSED {
42 | // Sync world with input contexts
43 | sabers_system(engine);
44 | pointers_system(engine);
45 |
46 | // Update physics simulation
47 | physics_system(engine);
48 |
49 | // Update game simulation
50 | game_system(engine, game_context);
51 |
52 | // Update world
53 | update_global_transform_system(engine);
54 |
55 | // Sync world with output contexts
56 | haptics_system(engine);
57 | audio_system(engine);
58 | draw_gui_system(engine);
59 | }
60 |
61 | // Draw objects
62 | rendering_system(engine, tick_data.swapchain_image_index);
63 | }
64 |
65 | fn handle_state_change(tick_data: &TickData, engine: &mut Engine, game_context: &mut GameContext) {
66 | let world = &mut engine.world;
67 | let audio_context = &mut engine.audio_context;
68 | match (tick_data.previous_state, tick_data.current_state) {
69 | (SessionState::VISIBLE, SessionState::FOCUSED) => {
70 | audio_context.resume_music_track();
71 | match game_context.state {
72 | GameState::Init => {}
73 | GameState::MainMenu | GameState::GameOver => {
74 | show(world, game_context.pointer);
75 | }
76 | GameState::Playing(_) => {
77 | show(world, game_context.blue_saber);
78 | show(world, game_context.red_saber);
79 | }
80 | }
81 | }
82 | (SessionState::FOCUSED, SessionState::VISIBLE) => {
83 | audio_context.pause_music_track();
84 | match game_context.state {
85 | GameState::Init => {}
86 | GameState::MainMenu | GameState::GameOver => {
87 | hide(world, game_context.pointer);
88 | }
89 | GameState::Playing(_) => {
90 | hide(world, game_context.blue_saber);
91 | hide(world, game_context.red_saber);
92 | }
93 | }
94 | }
95 | _ => {}
96 | }
97 | }
98 |
99 | fn init(engine: &mut Engine) -> GameContext {
100 | let mut game_context = GameContext::new(engine);
101 | game_context.add_songs(&mut engine.audio_context);
102 | game_context.add_sound_effects(&mut engine.audio_context);
103 | game_context
104 | }
105 |
106 | fn hide(world: &mut World, entity: Entity) {
107 | if world.remove_one::(entity).is_err() {
108 | println!("[STATE_CHANGE] Tried to make {entity:?} hidden but it had no Visible component")
109 | }
110 | }
111 |
112 | fn show(world: &mut World, entity: Entity) {
113 | world.insert_one(entity, Visible {}).unwrap();
114 | }
115 |
--------------------------------------------------------------------------------
/examples/crab-saber/src/main.rs:
--------------------------------------------------------------------------------
1 | use crab_saber::real_main;
2 | use hotham::HothamResult;
3 |
4 | fn main() -> HothamResult<()> {
5 | real_main()
6 | }
7 |
--------------------------------------------------------------------------------
/examples/crab-saber/src/systems/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod game;
2 | pub mod sabers;
3 | pub use game::game_system;
4 | pub use sabers::sabers_system;
5 |
--------------------------------------------------------------------------------
/examples/crab-saber/src/systems/sabers.rs:
--------------------------------------------------------------------------------
1 | use hotham::{
2 | asset_importer::{add_model_to_world, Models},
3 | components::{
4 | physics::{BodyType, SharedShape},
5 | stage, Collider, LocalTransform, RigidBody,
6 | },
7 | contexts::InputContext,
8 | glam::Affine3A,
9 | hecs::{Entity, With, World},
10 | systems::pointers::{POSITION_OFFSET, ROTATION_OFFSET},
11 | Engine,
12 | };
13 |
14 | use crate::components::{Color, Saber};
15 |
16 | const SABER_HEIGHT: f32 = 0.8;
17 | const SABER_HALF_HEIGHT: f32 = SABER_HEIGHT / 2.;
18 | const SABER_WIDTH: f32 = 0.02;
19 | const SABER_HALF_WIDTH: f32 = SABER_WIDTH / 2.;
20 |
21 | /// Sync the transform of the player's sabers with the pose of their controllers in OpenXR
22 | pub fn sabers_system(engine: &mut Engine) {
23 | sabers_system_inner(&mut engine.world, &engine.input_context)
24 | }
25 |
26 | fn sabers_system_inner(world: &mut World, input_context: &InputContext) {
27 | // Get the isometry of the stage
28 | let global_from_stage = stage::get_global_from_stage(world);
29 |
30 | // Create a transform from local space to grip space.
31 | let grip_from_local = Affine3A::from_rotation_translation(ROTATION_OFFSET, POSITION_OFFSET);
32 |
33 | for (_, (color, local_transform)) in
34 | world.query_mut::>()
35 | {
36 | // Get our the space and path of the hand.
37 | let stage_from_grip = match color {
38 | Color::Red => input_context.left.stage_from_grip(),
39 | Color::Blue => input_context.right.stage_from_grip(),
40 | };
41 |
42 | // Apply transform
43 | let global_from_local = global_from_stage * stage_from_grip * grip_from_local;
44 | local_transform.update_from_affine(&global_from_local);
45 | }
46 | }
47 |
48 | pub fn add_saber(color: Color, models: &Models, world: &mut World) -> Entity {
49 | let model_name = match color {
50 | Color::Blue => "Blue Saber",
51 | Color::Red => "Red Saber",
52 | };
53 | let saber = add_model_to_world(model_name, models, world, None).unwrap();
54 | add_saber_physics(world, saber);
55 | world.insert(saber, (Saber {}, color)).unwrap();
56 | saber
57 | }
58 |
59 | fn add_saber_physics(world: &mut World, saber: Entity) {
60 | // Give it a collider and rigid-body
61 | let collider = Collider {
62 | shape: SharedShape::cylinder(SABER_HALF_HEIGHT, SABER_HALF_WIDTH),
63 | sensor: true,
64 | ..Default::default()
65 | };
66 | let rigid_body = RigidBody {
67 | body_type: BodyType::KinematicPositionBased,
68 | ..Default::default()
69 | };
70 |
71 | // Add the components to the entity.
72 | world.insert(saber, (collider, rigid_body)).unwrap();
73 | }
74 |
75 | #[cfg(test)]
76 | mod tests {
77 | use super::*;
78 |
79 | #[test]
80 | fn test_sabers() {
81 | use hotham::components::{GlobalTransform, LocalTransform};
82 |
83 | let mut world = World::new();
84 | let input_context = InputContext::testing();
85 | let saber = world.spawn((
86 | Color::Red,
87 | Saber {},
88 | LocalTransform::default(),
89 | GlobalTransform::default(),
90 | ));
91 | sabers_system_inner(&mut world, &input_context);
92 |
93 | let local_transform = world.get::<&LocalTransform>(saber).unwrap();
94 | approx::assert_relative_eq!(
95 | local_transform.translation,
96 | [-0.2, 1.3258567, -0.47001815].into()
97 | );
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/examples/custom-rendering/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | edition = "2018"
3 | license = "MIT OR Apache-2.0"
4 | name = "custom-rendering-example"
5 | version = "0.2.0"
6 |
7 | [lib]
8 | crate-type = ["lib", "cdylib"]
9 |
10 | [[bin]]
11 | name = "hotham_custom_rendering_example"
12 | path = "src/main.rs"
13 |
14 | [dependencies]
15 | hotham = {path = "../../hotham"}
16 | hotham-examples = {path = "../shared"}
17 |
18 | [target.'cfg(target_os = "android")'.dependencies]
19 | ndk-glue = "0.6"
20 |
21 | [package.metadata.android]
22 | apk_label = "Hotham Custom Rendering Example"
23 | fullscreen = true
24 | runtime_libs = "../common_lib"
25 | target_sdk_version = 29
26 |
27 | [package.metadata.android.application]
28 | debuggable = true
29 | label = "Hotham Custom Rendering Example"
30 | theme = "@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen"
31 |
32 | [package.metadata.android.application.activity]
33 | config_changes = "screenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode"
34 | launch_mode = "singleTask"
35 | orientation = "landscape"
36 |
37 | [[package.metadata.android.uses_permission]]
38 | name = "android.permission.INTERNET"
39 |
40 | [[package.metadata.android.uses_permission]]
41 | name = "android.permission.access_network_state"
42 |
43 | [[package.metadata.android.application.meta_data]]
44 | name = "com.oculus.supportedDevices"
45 | value = "quest|quest2"
46 |
47 | [[package.metadata.android.application.meta_data]]
48 | name = "com.oculus.intent.category.VR"
49 | value = "vr_only"
50 |
51 | [[package.metadata.android.application.activity.intent_filter]]
52 | actions = ["android.intent.action.MAIN"]
53 | categories = ["com.oculus.intent.category.VR", "android.intent.category.LAUNCHER"]
54 |
55 | [[package.metadata.android.application.activity.meta_data]]
56 | name = "com.oculus.vr.focusaware"
57 | value = "true"
58 |
59 | [[package.metadata.android.uses_feature]]
60 | name = "android.hardware.vulkan.level"
61 | required = true
62 | version = 1
63 |
64 | [[package.metadata.android.uses_feature]]
65 | name = "android.hardware.vr.headtracking"
66 | required = true
67 | version = 1
68 |
69 | # !! IMPORTANT !!
70 | #
71 | # When creating your own apps, make sure to generate your own keystore, rather than using our example one!
72 | # You can use `keytool` like so:
73 | # keytool -genkey -v -keystore my-release-key.keystore -keyalg RSA -keysize 2048 -validity 10000
74 | #
75 | # For more information on key signing and why it's so important, check out this article:
76 | # https://developer.android.com/studio/publish/app-signing
77 | #
78 | # !! IMPORTANT !!
79 | [package.metadata.android.signing.release]
80 | path = "../hotham_examples.keystore"
81 | keystore_password = "chomsky-vigilant-spa"
82 |
--------------------------------------------------------------------------------
/examples/custom-rendering/README.md:
--------------------------------------------------------------------------------
1 | # Custom Rendering
2 | This example shows how to perform custom rendering with hotham.
3 |
--------------------------------------------------------------------------------
/examples/custom-rendering/openxr_loader.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leetvr/hotham/cab13bfcc2dcce1828d0520c03950a28668f5330/examples/custom-rendering/openxr_loader.dll
--------------------------------------------------------------------------------
/examples/custom-rendering/scripts/run_on_device.ps1:
--------------------------------------------------------------------------------
1 | adb shell am force-stop rust.custom_rendering_example
2 |
3 | Set-Location $PSScriptRoot\..
4 | cargo apk run --release
5 |
6 | if ($?) {
7 | Start-Sleep -Seconds 2
8 | $processIdStr = (adb shell pidof rust.custom_rendering_example) | Out-String
9 | Write-Output $processIdStr
10 | $processId = $processIdStr -as [int]
11 | Write-Output $processId
12 | adb logcat --pid=$processId
13 | }
14 |
--------------------------------------------------------------------------------
/examples/custom-rendering/scripts/run_on_device.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -eux
3 |
4 | adb shell am force-stop rust.custom_rendering_example
5 |
6 | scriptdir=$(dirname -- "$(realpath -- "$0")")
7 | cd $scriptdir/..
8 |
9 | cargo apk run --release
10 |
11 | # Wait for the app to start
12 | for i in 1 2 3 4 5; do
13 | adb shell pidof rust.custom_rendering_example && break
14 | sleep 1
15 | done
16 |
17 | adb logcat --pid="$(adb shell pidof rust.custom_rendering_example)"
18 |
--------------------------------------------------------------------------------
/examples/custom-rendering/src/hologram.rs:
--------------------------------------------------------------------------------
1 | use hotham::{glam::Mat4, id_arena::Id, rendering::mesh_data::MeshData};
2 |
3 | /// The Hologram component is used together with custom rendering to render quadric surfaces.
4 | #[derive(Debug, Clone, Copy)]
5 | pub struct Hologram {
6 | pub mesh_data_handle: Id,
7 | pub hologram_data: HologramData,
8 | }
9 |
10 | #[derive(Debug, Clone, Copy)]
11 | pub struct HologramData {
12 | pub surface_q_in_local: Mat4,
13 | pub bounds_q_in_local: Mat4,
14 | pub uv_from_local: Mat4,
15 | }
16 |
--------------------------------------------------------------------------------
/examples/custom-rendering/src/main.rs:
--------------------------------------------------------------------------------
1 | use hotham::HothamResult;
2 |
3 | fn main() -> HothamResult<()> {
4 | custom_rendering_example::real_main()
5 | }
6 |
--------------------------------------------------------------------------------
/examples/custom-rendering/src/shaders/quadric.glsl:
--------------------------------------------------------------------------------
1 | struct QuadricData {
2 | mat4 gosFromLocal;
3 | mat4 surfaceQ;
4 | mat4 boundsQ;
5 | mat4 uvFromGos;
6 | };
7 |
8 | layout (std430, set = 1, binding = 0) readonly buffer QuadricDataBuffer {
9 | QuadricData data[];
10 | } quadricDataBuffer;
11 |
--------------------------------------------------------------------------------
/examples/custom-rendering/src/shaders/quadric.vert:
--------------------------------------------------------------------------------
1 | #version 460
2 |
3 | #include "../../../../hotham/src/shaders/common.glsl"
4 |
5 | layout (location = 0) in vec3 inPos;
6 |
7 | layout (location = 0) out vec4 outGosPos;
8 | layout (location = 1) out flat uint outInstanceIndex;
9 |
10 | #include "quadric.glsl"
11 |
12 | out gl_PerVertex {
13 | vec4 gl_Position;
14 | };
15 |
16 | void main() {
17 | QuadricData d = quadricDataBuffer.data[gl_InstanceIndex];
18 | outInstanceIndex = gl_InstanceIndex;
19 | outGosPos = d.gosFromLocal * vec4(inPos, 1.0);
20 | gl_Position = sceneData.viewProjection[gl_ViewIndex] * outGosPos;
21 | }
22 |
--------------------------------------------------------------------------------
/examples/custom-rendering/src/surface_solver.rs:
--------------------------------------------------------------------------------
1 | use hotham::{
2 | components::LocalTransform,
3 | glam::{Mat4, Vec3},
4 | hecs::{Entity, World},
5 | na::{ArrayStorage, Matrix, U1, U10, U3},
6 | util::na_vector_from_glam,
7 | Engine,
8 | };
9 |
10 | use crate::hologram::Hologram;
11 |
12 | type Matrix10x10 = Matrix>;
13 | type Matrix10x3 = Matrix>;
14 | type Vector10 = Matrix>;
15 | type RowVector10 = Matrix>;
16 |
17 | pub struct ControlPoints {
18 | pub entities: Vec,
19 | }
20 |
21 | pub struct HologramBackside {
22 | pub entity: Entity,
23 | }
24 |
25 | pub fn surface_solver_system(engine: &mut Engine) {
26 | let world = &mut engine.world;
27 | surface_solver_system_inner(world);
28 | }
29 |
30 | fn surface_solver_system_inner(world: &mut World) {
31 | for (_, (hologram, control_points, local_transform)) in world
32 | .query::<(&mut Hologram, &mut ControlPoints, &mut LocalTransform)>()
33 | .iter()
34 | {
35 | let local_from_global = local_transform.to_affine().inverse();
36 |
37 | #[allow(non_snake_case)]
38 | let mut AtA: Matrix10x10 = Default::default();
39 | #[allow(non_snake_case)]
40 | let mut BtB: Matrix10x10 = Default::default();
41 | #[allow(non_snake_case)]
42 | let mut BtN: Vector10 = Default::default();
43 |
44 | for e in &control_points.entities {
45 | let t = world.get::<&LocalTransform>(*e).unwrap();
46 | let global_from_control = t.to_affine();
47 | let local_from_control = local_from_global * global_from_control;
48 | let point_in_local = local_from_control.transform_point3(Vec3::ZERO);
49 | let arrow_in_local = local_from_control.transform_vector3(Vec3::Y);
50 | let p = na_vector_from_glam(point_in_local);
51 | let d = na_vector_from_glam(arrow_in_local);
52 |
53 | let (x, y, z) = (p.x, p.y, p.z);
54 | let a_row: RowVector10 =
55 | [x * x, y * y, z * z, x * y, x * z, y * z, x, y, z, 1.0].into();
56 | AtA += a_row.transpose() * a_row;
57 | let b_rows_t = Matrix10x3::from_columns(&[
58 | [2.0 * x, 0.0, 0.0, y, z, 0.0, 1.0, 0.0, 0.0, 0.].into(),
59 | [0.0, 2.0 * y, 0.0, x, 0.0, z, 0.0, 1.0, 0.0, 0.].into(),
60 | [0.0, 0.0, 2.0 * z, 0.0, x, y, 0.0, 0.0, 1.0, 0.].into(),
61 | ]);
62 | BtB += b_rows_t * b_rows_t.transpose();
63 | BtN += b_rows_t * d;
64 | }
65 | // let eigen_decomposition = AtA.symmetric_eigen();
66 | // eigen_decomposition
67 | let eps = 1.0e-6;
68 | let svd = AtA.svd(false, true);
69 | let rank = svd.rank(eps).min(9);
70 | let v_t = svd.v_t.unwrap();
71 | let nullspace = v_t.rows(rank, 10 - rank);
72 | let svd_subspace = (nullspace * BtB * nullspace.transpose()).svd(true, true);
73 | let solution_in_subspace = svd_subspace.pseudo_inverse(eps).unwrap() * nullspace * BtN;
74 | let q = nullspace.transpose() * solution_in_subspace;
75 | hologram.hologram_data.surface_q_in_local = Mat4::from_cols_array_2d(&[
76 | [2.0 * q[0], q[3], q[4], q[6]],
77 | [q[3], 2.0 * q[1], q[5], q[7]],
78 | [q[4], q[5], 2.0 * q[2], q[8]],
79 | [q[6], q[7], q[8], 2.0 * q[9]],
80 | ]);
81 | }
82 |
83 | for (_, (target, source)) in world.query::<(&mut Hologram, &HologramBackside)>().iter() {
84 | target.hologram_data.surface_q_in_local = world
85 | .get::<&Hologram>(source.entity)
86 | .unwrap()
87 | .hologram_data
88 | .surface_q_in_local
89 | * -1.0;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/examples/hotham_examples.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leetvr/hotham/cab13bfcc2dcce1828d0520c03950a28668f5330/examples/hotham_examples.keystore
--------------------------------------------------------------------------------
/examples/shared/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | edition = "2018"
3 | license = "MIT OR Apache-2.0"
4 | name = "hotham-examples"
5 | version = "0.2.0"
6 |
7 | [dependencies]
8 | hotham = {path = "../../hotham"}
9 |
--------------------------------------------------------------------------------
/examples/shared/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod navigation;
2 |
--------------------------------------------------------------------------------
/examples/simple-scene/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | edition = "2018"
3 | license = "MIT OR Apache-2.0"
4 | name = "simple-scene-example"
5 | version = "0.2.0"
6 |
7 | [lib]
8 | crate-type = ["lib", "cdylib"]
9 |
10 | [[bin]]
11 | name = "hotham_simple_scene_example"
12 | path = "src/main.rs"
13 |
14 | [dependencies]
15 | hotham = {path = "../../hotham"}
16 |
17 | [target.'cfg(target_os = "android")'.dependencies]
18 | ndk-glue = "0.6"
19 |
20 | [package.metadata.android]
21 | apk_label = "Hotham Simple Scene Example"
22 | fullscreen = true
23 | runtime_libs = "../common_lib"
24 | target_sdk_version = 29
25 |
26 | [package.metadata.android.application]
27 | debuggable = true
28 | label = "Hotham Simple Scene Example"
29 | theme = "@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen"
30 |
31 | [package.metadata.android.application.activity]
32 | config_changes = "screenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode"
33 | launch_mode = "singleTask"
34 | orientation = "landscape"
35 |
36 | [[package.metadata.android.uses_permission]]
37 | name = "android.permission.INTERNET"
38 |
39 | [[package.metadata.android.uses_permission]]
40 | name = "android.permission.ACCESS_NETWORK_STATE"
41 |
42 | [[package.metadata.android.application.meta_data]]
43 | name = "com.oculus.supportedDevices"
44 | value = "quest|quest2"
45 |
46 | [[package.metadata.android.application.meta_data]]
47 | name = "com.oculus.intent.category.VR"
48 | value = "vr_only"
49 |
50 | [[package.metadata.android.application.activity.intent_filter]]
51 | actions = ["android.intent.action.MAIN"]
52 | categories = ["com.oculus.intent.category.VR", "android.intent.category.LAUNCHER"]
53 |
54 | [[package.metadata.android.application.activity.meta_data]]
55 | name = "com.oculus.vr.focusaware"
56 | value = "true"
57 |
58 | [[package.metadata.android.uses_feature]]
59 | name = "android.hardware.vulkan.level"
60 | required = true
61 | version = 1
62 |
63 | [[package.metadata.android.uses_feature]]
64 | name = "android.hardware.vr.headtracking"
65 | required = true
66 | version = 1
67 |
68 | # !! IMPORTANT !!
69 | #
70 | # When creating your own apps, make sure to generate your own keystore, rather than using our example one!
71 | # You can use `keytool` like so:
72 | # keytool -genkey -v -keystore my-release-key.keystore -keyalg RSA -keysize 2048 -validity 10000
73 | #
74 | # For more information on key signing and why it's so important, check out this article:
75 | # https://developer.android.com/studio/publish/app-signing
76 | #
77 | # !! IMPORTANT !!
78 | [package.metadata.android.signing.release]
79 | path = "../hotham_examples.keystore"
80 | keystore_password = "chomsky-vigilant-spa"
81 |
--------------------------------------------------------------------------------
/examples/simple-scene/README.md:
--------------------------------------------------------------------------------
1 | 
2 | # Asteroid
3 | Asteroid is a simple game designed to show off Hotham's capabilities.
4 |
5 | The premise of the game is simple: defend an asteroid and its refining facility from alien invasion.
6 |
--------------------------------------------------------------------------------
/examples/simple-scene/logo.png:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:0b8879e7a996b0a7727bab4367878bd9d18aaaf6f8a2c343bd5622a406f4c559
3 | size 583949
4 |
--------------------------------------------------------------------------------
/examples/simple-scene/openxr_loader.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leetvr/hotham/cab13bfcc2dcce1828d0520c03950a28668f5330/examples/simple-scene/openxr_loader.dll
--------------------------------------------------------------------------------
/examples/simple-scene/scripts/run_on_device.ps1:
--------------------------------------------------------------------------------
1 | adb shell am force-stop rust.simple_scene_example
2 |
3 | Set-Location $PSScriptRoot\..
4 | cargo apk run --release
5 |
6 | if ($?) {
7 | $processId = $null
8 | foreach ($i in 1..5) {
9 | $processId = adb shell pidof rust.simple_scene_example
10 | if ($processId) { break }
11 | Write-Output "Waiting for process to start, sleeping..."
12 | Start-Sleep -Seconds 1
13 | }
14 | if ($processId) {
15 | Write-Output "Found PID of " $processId
16 | adb logcat --pid=$processId
17 | } else {
18 | Write-Error "Failed to find PID of rust.simple_scene_example"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/simple-scene/scripts/run_on_device.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -eux
3 |
4 | adb shell am force-stop rust.simple_scene_example
5 |
6 | scriptdir=$(dirname -- "$(realpath -- "$0")")
7 | cd $scriptdir/..
8 |
9 | cargo apk run --release
10 |
11 | # Wait for the app to start
12 | for i in 1 2 3 4 5; do
13 | adb shell pidof rust.simple_scene_example && break
14 | sleep 1
15 | done
16 |
17 | adb logcat --pid="$(adb shell pidof rust.simple_scene_example)"
18 |
--------------------------------------------------------------------------------
/examples/simple-scene/src/lib.rs:
--------------------------------------------------------------------------------
1 | use hotham::{
2 | asset_importer::{self, add_model_to_world},
3 | components::{
4 | hand::Handedness,
5 | physics::{BodyType, SharedShape},
6 | Collider, LocalTransform, RigidBody,
7 | },
8 | hecs::World,
9 | na,
10 | systems::{
11 | animation_system, debug::debug_system, grabbing_system, hands::add_hand, hands_system,
12 | physics_system, rendering::rendering_system, skinning::skinning_system,
13 | update_global_transform_system,
14 | },
15 | xr, Engine, HothamResult, TickData,
16 | };
17 |
18 | #[derive(Clone, Debug, Default)]
19 | /// Most Hotham applications will want to keep track of some sort of state.
20 | /// However, this _simple_ scene doesn't have any, so this is just left here to let you know that
21 | /// this is something you'd probably want to do!
22 | struct State {}
23 |
24 | #[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))]
25 | pub fn main() {
26 | println!("[HOTHAM_SIMPLE_SCENE] MAIN!");
27 | real_main().expect("Error running app!");
28 | println!("[HOTHAM_SIMPLE_SCENE] FINISHED! Goodbye!");
29 | }
30 |
31 | pub fn real_main() -> HothamResult<()> {
32 | let mut engine = Engine::new();
33 | let mut state = Default::default();
34 | init(&mut engine)?;
35 |
36 | while let Ok(tick_data) = engine.update() {
37 | tick(tick_data, &mut engine, &mut state);
38 | engine.finish()?;
39 | }
40 |
41 | Ok(())
42 | }
43 |
44 | fn tick(tick_data: TickData, engine: &mut Engine, _state: &mut State) {
45 | if tick_data.current_state == xr::SessionState::FOCUSED {
46 | hands_system(engine);
47 | grabbing_system(engine);
48 | physics_system(engine);
49 | animation_system(engine);
50 | update_global_transform_system(engine);
51 | skinning_system(engine);
52 | debug_system(engine);
53 | }
54 |
55 | rendering_system(engine, tick_data.swapchain_image_index);
56 | }
57 |
58 | fn init(engine: &mut Engine) -> Result<(), hotham::HothamError> {
59 | let render_context = &mut engine.render_context;
60 | let vulkan_context = &mut engine.vulkan_context;
61 | let world = &mut engine.world;
62 |
63 | let mut glb_buffers: Vec<&[u8]> = vec![
64 | include_bytes!("../../../test_assets/floor.glb"),
65 | include_bytes!("../../../test_assets/left_hand.glb"),
66 | include_bytes!("../../../test_assets/right_hand.glb"),
67 | ];
68 | let models =
69 | asset_importer::load_models_from_glb(&glb_buffers, vulkan_context, render_context)?;
70 | add_floor(&models, world);
71 | add_hand(&models, Handedness::Left, world);
72 | add_hand(&models, Handedness::Right, world);
73 |
74 | #[cfg(target_os = "android")]
75 | glb_buffers.push(include_bytes!(
76 | "../../../test_assets/damaged_helmet_squished.glb"
77 | ));
78 |
79 | #[cfg(not(target_os = "android"))]
80 | glb_buffers.push(include_bytes!("../../../test_assets/damaged_helmet.glb"));
81 |
82 | let models =
83 | asset_importer::load_models_from_glb(&glb_buffers, vulkan_context, render_context)?;
84 | add_helmet(&models, world);
85 | add_model_to_world("Cube", &models, world, None);
86 |
87 | // Update global transforms from local transforms before physics_system gets confused
88 | update_global_transform_system(engine);
89 |
90 | Ok(())
91 | }
92 |
93 | fn add_floor(models: &std::collections::HashMap, world: &mut World) {
94 | let entity = add_model_to_world("Floor", models, world, None).expect("Could not find Floor");
95 | let collider = Collider::new(SharedShape::halfspace(na::Vector3::y_axis()));
96 | let rigid_body = RigidBody {
97 | body_type: BodyType::Fixed,
98 | ..Default::default()
99 | };
100 | world.insert(entity, (collider, rigid_body)).unwrap();
101 | }
102 |
103 | fn add_helmet(models: &std::collections::HashMap, world: &mut World) {
104 | let helmet = add_model_to_world("Damaged Helmet", models, world, None)
105 | .expect("Could not find Damaged Helmet");
106 |
107 | {
108 | let mut local_transform = world.get::<&mut LocalTransform>(helmet).unwrap();
109 | local_transform.translation.z = -1.;
110 | local_transform.translation.y = 1.4;
111 | local_transform.scale = [0.5, 0.5, 0.5].into();
112 | }
113 |
114 | let collider = Collider::new(SharedShape::ball(0.35));
115 |
116 | world
117 | .insert(helmet, (collider, RigidBody::default()))
118 | .unwrap();
119 | }
120 |
--------------------------------------------------------------------------------
/examples/simple-scene/src/main.rs:
--------------------------------------------------------------------------------
1 | use hotham::HothamResult;
2 |
3 | fn main() -> HothamResult<()> {
4 | simple_scene_example::real_main()
5 | }
6 |
--------------------------------------------------------------------------------
/hotham-asset-client/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | edition = "2021"
3 | name = "hotham-asset-client"
4 | version = "0.1.0"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | anyhow = "1.0"
10 | futures-util = {version = "0.3.11", default-features = false}
11 | quinn = {version = "0.8.5", features = ["tls-rustls"]}
12 | rustls = {version = "0.20.3", features = ["dangerous_configuration", "quic"]}
13 | tokio = {version = "1.0.1", default-features = false, features = ["sync"]}
14 |
15 | [dev-dependencies]
16 | tokio = {version = "1.0.1", default-features = false, features = ["rt"]}
17 |
--------------------------------------------------------------------------------
/hotham-asset-client/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod client;
2 | pub mod message;
3 | use std::sync::Arc;
4 |
5 | pub use client::watch;
6 |
7 | #[derive(Debug, Clone)]
8 | pub struct AssetUpdatedMessage {
9 | pub asset_id: String,
10 | pub asset_data: Arc>,
11 | }
12 |
--------------------------------------------------------------------------------
/hotham-asset-client/src/message.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 |
3 | #[non_exhaustive]
4 | #[derive(Debug, Clone, PartialEq, Eq)]
5 | pub enum MessageType {
6 | GetAsset,
7 | WatchAsset,
8 | AssetUpdated,
9 | OK,
10 | Error,
11 | Asset,
12 | _Invalid,
13 | }
14 |
15 | #[derive(Debug, Clone, PartialEq, Eq)]
16 | pub enum Message<'a> {
17 | GetAsset(&'a str),
18 | WatchAsset(&'a str),
19 | AssetUpdated(&'a str),
20 | OK,
21 | Error(String),
22 | Asset(Vec),
23 | }
24 |
25 | impl<'a> Message<'a> {
26 | pub fn parse(message_type: MessageType, buffer: &'a [u8]) -> Result {
27 | let message = match message_type {
28 | MessageType::GetAsset => Message::GetAsset(std::str::from_utf8(buffer)?),
29 | MessageType::WatchAsset => Message::WatchAsset(std::str::from_utf8(buffer)?),
30 | MessageType::AssetUpdated => Message::AssetUpdated(std::str::from_utf8(buffer)?),
31 | MessageType::OK => Message::OK,
32 | MessageType::Error => Message::Error(std::str::from_utf8(buffer)?.into()),
33 | MessageType::Asset => Message::Asset(buffer.to_vec()),
34 | _ => anyhow::bail!("Invalid message type"),
35 | };
36 |
37 | Ok(message)
38 | }
39 |
40 | pub async fn read(recv: &mut quinn::RecvStream, buffer: &'a mut [u8]) -> Result> {
41 | // Read 9 bytes
42 | recv.read_exact(&mut buffer[0..9]).await?;
43 | // Byte 0 is the tag:
44 | let message_type: MessageType = unsafe { std::mem::transmute(buffer[0]) };
45 | // Bytes 1..9 is the message length as big endian u64
46 | let message_length = u64::from_be_bytes(buffer[1..9].try_into()?);
47 |
48 | let message_buffer = &mut buffer[0..message_length as _];
49 | recv.read_exact(message_buffer).await?;
50 | Message::parse(message_type, message_buffer)
51 | }
52 |
53 | pub async fn write_all(&'a self, stream: &mut quinn::SendStream) -> Result<()> {
54 | // Would it be easier to just put this all into another buffer?
55 | // Yes
56 | // Would it be as fun?
57 | // No.
58 |
59 | // Byte 0 is the tag:
60 | stream.write_all(&[self.get_type() as _]).await?;
61 | // Bytes 1..9 is the message length as big endian u64
62 | stream.write_all(&(self.len() as u64).to_be_bytes()).await?;
63 | stream.write_all(self.buf()).await?;
64 |
65 | Ok(())
66 | }
67 |
68 | pub fn get_type(&self) -> MessageType {
69 | match self {
70 | Message::GetAsset(_) => MessageType::GetAsset,
71 | Message::WatchAsset(_) => MessageType::WatchAsset,
72 | Message::AssetUpdated(_) => MessageType::AssetUpdated,
73 | Message::OK => MessageType::OK,
74 | Message::Error(_) => MessageType::Error,
75 | Message::Asset(_) => MessageType::Asset,
76 | }
77 | }
78 |
79 | pub fn len(&self) -> usize {
80 | self.buf().len()
81 | }
82 |
83 | pub fn is_empty(&self) -> bool {
84 | self.buf().is_empty()
85 | }
86 |
87 | pub fn buf(&self) -> &'_ [u8] {
88 | match self {
89 | Message::GetAsset(s) => s.as_bytes(),
90 | Message::WatchAsset(s) => s.as_bytes(),
91 | Message::AssetUpdated(s) => s.as_bytes(),
92 | Message::OK => &[],
93 | Message::Error(s) => s.as_bytes(),
94 | Message::Asset(b) => b,
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/hotham-asset-server/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | edition = "2021"
3 | name = "hotham-asset-server"
4 | version = "0.1.0"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | anyhow = "1.0"
10 | futures-util = {version = "0.3.11", default-features = false, features = ["io"]}
11 | hotham-asset-client = {path = "../hotham-asset-client"}
12 | notify-debouncer-mini = {version = "*", default-features = false}
13 | quinn = {version = "0.8.5", features = ["tls-rustls"]}
14 | rcgen = "0.10.0"
15 | rustls = {version = "0.20.3", features = ["dangerous_configuration", "quic"]}
16 | tokio = {version = "1.21.2", features = ["full"]}
17 |
--------------------------------------------------------------------------------
/hotham-asset-server/src/main.rs:
--------------------------------------------------------------------------------
1 | mod server;
2 |
3 | use std::{collections::HashMap, sync::Arc, time::SystemTime};
4 | use tokio::sync::Mutex;
5 |
6 | use anyhow::Result;
7 | use futures_util::StreamExt;
8 | /// A simple server that serves assets to localhost or remote targets. It's great and has no flaws.
9 | // TODO:
10 | // 1. Accept connections
11 | // 2. Send a file on "GET"
12 | // 3. Watch for file updates
13 | // 4. Send a "file updated" message back to the client on update
14 | // 5. GOTO 2
15 | use server::{handle_connection, make_server_endpoint, watch_files};
16 |
17 | pub type WatchList = Arc>>>;
18 |
19 | #[tokio::main]
20 | async fn main() -> Result<()> {
21 | let addr = "0.0.0.0:5000".parse().unwrap();
22 | let (mut incoming, _server_cert) = make_server_endpoint(addr).unwrap();
23 |
24 | loop {
25 | let incoming_conn = incoming.next().await.unwrap();
26 | let new_conn = incoming_conn.await.unwrap();
27 | let watch_list = WatchList::default();
28 |
29 | let watcher_watch_list = watch_list.clone();
30 | let watcher_connection = new_conn.connection.clone();
31 | tokio::spawn(watch_files(watcher_connection, watcher_watch_list));
32 | tokio::spawn(handle_connection(new_conn, watch_list.clone()));
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/hotham-simulator/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "label": "Run tests",
8 | "type": "shell",
9 | "command": "./test.sh",
10 | "group": {
11 | "kind": "test",
12 | "isDefault": true
13 | },
14 | "presentation": {
15 | "reveal": "always",
16 | "panel": "shared"
17 | }
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/hotham-simulator/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | build = "build.rs"
3 | edition = "2018"
4 | license = "MIT OR Apache-2.0"
5 | name = "hotham-simulator"
6 | version = "0.2.0"
7 |
8 | [lib]
9 | crate-type = ["cdylib"]
10 |
11 | [dependencies]
12 | ash = "0.33.2"
13 | ash-window = "0.7"
14 | glam = "0.23"
15 | lazy_static = "1.4.0"
16 | openxr-sys = "0.9"
17 | rand = "0.8"
18 | vk-shader-macros = "0.2.8"
19 | winit = "0.26"
20 |
21 | [build-dependencies]
22 | bindgen = "*"
23 |
--------------------------------------------------------------------------------
/hotham-simulator/README.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 | To make VR development a little bit less painful (_because who really wants to keep taking their headset on and off all the time_), Hotham comes with a handy-dandy OpenXR simulator.
3 |
4 | To get started with the simulator, follow the instructions over [here](https://github.com/leetvr/hotham/wiki/Adding-the-Hotham-Simulator-to-your-development-environment).
5 |
--------------------------------------------------------------------------------
/hotham-simulator/build.rs:
--------------------------------------------------------------------------------
1 | // Dummy function
2 | #[cfg(target_os = "android")]
3 | fn main() {}
4 |
5 | #[cfg(not(target_os = "android"))]
6 | fn main() {
7 | println!("cargo:rerun-if-changed=build_input");
8 | // use std::{fs, path::Path, str};
9 | // The bindgen::Builder is the main entry point
10 | // to bindgen, and lets you build up options for
11 | // the resulting bindings.
12 |
13 | // Annoying as requires complicated clang setup on windows. Headers are unlikely to change any time soon.
14 | // let bindings = bindgen::Builder::default()
15 | // // The input header we would like to generate
16 | // // bindings for.
17 | // .header("build_input/wrapper.h")
18 | // // Finish the builder and generate the bindings.
19 | // .generate()
20 | // // Unwrap the Result and panic on failure.
21 | // .expect("Unable to generate bindings");
22 |
23 | // bindings
24 | // .write_to_file("src/bindings.rs")
25 | // .expect("Couldn't write bindings!");
26 | }
27 |
--------------------------------------------------------------------------------
/hotham-simulator/build_input/openxr/openxr_platform_defines.h:
--------------------------------------------------------------------------------
1 | /*
2 | ** Copyright (c) 2017-2021, The Khronos Group Inc.
3 | **
4 | ** SPDX-License-Identifier: Apache-2.0 OR MIT
5 | */
6 |
7 | #ifndef OPENXR_PLATFORM_DEFINES_H_
8 | #define OPENXR_PLATFORM_DEFINES_H_ 1
9 |
10 | #ifdef __cplusplus
11 | extern "C" {
12 | #endif
13 |
14 | /* Platform-specific calling convention macros.
15 | *
16 | * Platforms should define these so that OpenXR clients call OpenXR functions
17 | * with the same calling conventions that the OpenXR implementation expects.
18 | *
19 | * XRAPI_ATTR - Placed before the return type in function declarations.
20 | * Useful for C++11 and GCC/Clang-style function attribute syntax.
21 | * XRAPI_CALL - Placed after the return type in function declarations.
22 | * Useful for MSVC-style calling convention syntax.
23 | * XRAPI_PTR - Placed between the '(' and '*' in function pointer types.
24 | *
25 | * Function declaration: XRAPI_ATTR void XRAPI_CALL xrFunction(void);
26 | * Function pointer type: typedef void (XRAPI_PTR *PFN_xrFunction)(void);
27 | */
28 | #if defined(_WIN32)
29 | #define XRAPI_ATTR
30 | // On Windows, functions use the stdcall convention
31 | #define XRAPI_CALL __stdcall
32 | #define XRAPI_PTR XRAPI_CALL
33 | #elif defined(__ANDROID__) && defined(__ARM_ARCH) && __ARM_ARCH < 7
34 | #error "API not supported for the 'armeabi' NDK ABI"
35 | #elif defined(__ANDROID__) && defined(__ARM_ARCH) && __ARM_ARCH >= 7 && defined(__ARM_32BIT_STATE)
36 | // On Android 32-bit ARM targets, functions use the "hardfloat"
37 | // calling convention, i.e. float parameters are passed in registers. This
38 | // is true even if the rest of the application passes floats on the stack,
39 | // as it does by default when compiling for the armeabi-v7a NDK ABI.
40 | #define XRAPI_ATTR __attribute__((pcs("aapcs-vfp")))
41 | #define XRAPI_CALL
42 | #define XRAPI_PTR XRAPI_ATTR
43 | #else
44 | // On other platforms, use the default calling convention
45 | #define XRAPI_ATTR
46 | #define XRAPI_CALL
47 | #define XRAPI_PTR
48 | #endif
49 |
50 | #include
51 |
52 | #if !defined(XR_NO_STDINT_H)
53 | #if defined(_MSC_VER) && (_MSC_VER < 1600)
54 | typedef signed __int8 int8_t;
55 | typedef unsigned __int8 uint8_t;
56 | typedef signed __int16 int16_t;
57 | typedef unsigned __int16 uint16_t;
58 | typedef signed __int32 int32_t;
59 | typedef unsigned __int32 uint32_t;
60 | typedef signed __int64 int64_t;
61 | typedef unsigned __int64 uint64_t;
62 | #else
63 | #include
64 | #endif
65 | #endif // !defined( XR_NO_STDINT_H )
66 |
67 | // XR_PTR_SIZE (in bytes)
68 | #if (defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__) ) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__))
69 | #define XR_PTR_SIZE 8
70 | #else
71 | #define XR_PTR_SIZE 4
72 | #endif
73 |
74 | // Needed so we can use clang __has_feature portably.
75 | #if !defined(XR_COMPILER_HAS_FEATURE)
76 | #if defined(__clang__)
77 | #define XR_COMPILER_HAS_FEATURE(x) __has_feature(x)
78 | #else
79 | #define XR_COMPILER_HAS_FEATURE(x) 0
80 | #endif
81 | #endif
82 |
83 | // Identifies if the current compiler has C++11 support enabled.
84 | // Does not by itself identify if any given C++11 feature is present.
85 | #if !defined(XR_CPP11_ENABLED) && defined(__cplusplus)
86 | #if defined(__GNUC__) && defined(__GXX_EXPERIMENTAL_CXX0X__)
87 | #define XR_CPP11_ENABLED 1
88 | #elif defined(_MSC_VER) && (_MSC_VER >= 1600)
89 | #define XR_CPP11_ENABLED 1
90 | #elif (__cplusplus >= 201103L) // 201103 is the first C++11 version.
91 | #define XR_CPP11_ENABLED 1
92 | #endif
93 | #endif
94 |
95 | // Identifies if the current compiler supports C++11 nullptr.
96 | #if !defined(XR_CPP_NULLPTR_SUPPORTED)
97 | #if defined(XR_CPP11_ENABLED) && \
98 | ((defined(__clang__) && XR_COMPILER_HAS_FEATURE(cxx_nullptr)) || \
99 | (defined(__GNUC__) && (((__GNUC__ * 1000) + __GNUC_MINOR__) >= 4006)) || \
100 | (defined(_MSC_VER) && (_MSC_VER >= 1600)) || \
101 | (defined(__EDG_VERSION__) && (__EDG_VERSION__ >= 403)))
102 | #define XR_CPP_NULLPTR_SUPPORTED 1
103 | #endif
104 | #endif
105 |
106 | #ifdef __cplusplus
107 | }
108 | #endif
109 |
110 | #endif
111 |
--------------------------------------------------------------------------------
/hotham-simulator/build_input/viewdisplay.frag:
--------------------------------------------------------------------------------
1 | #version 450
2 | #extension GL_EXT_multiview : require
3 |
4 | layout (binding = 0) uniform sampler2DArray samplerView;
5 |
6 | layout (location = 0) in vec2 inUV;
7 | layout (location = 0) out vec4 outColor;
8 |
9 | layout (constant_id = 0) const float VIEW_LAYER = 0.0f;
10 |
11 | void main()
12 | {
13 | const float alpha = 0.0;
14 |
15 | vec2 p1 = vec2(2.0 * inUV - 1.0);
16 | vec2 p2 = p1 / (1.0 - alpha * length(p1));
17 | p2 = (p2 + 1.0) * 0.5;
18 |
19 | bool inside = ((p2.x >= 0.0) && (p2.x <= 1.0) && (p2.y >= 0.0 ) && (p2.y <= 1.0));
20 | outColor = inside ? texture(samplerView, vec3(p2, VIEW_LAYER)) : vec4(0.0);
21 | }
--------------------------------------------------------------------------------
/hotham-simulator/build_input/viewdisplay.frag.spv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leetvr/hotham/cab13bfcc2dcce1828d0520c03950a28668f5330/hotham-simulator/build_input/viewdisplay.frag.spv
--------------------------------------------------------------------------------
/hotham-simulator/build_input/viewdisplay.vert:
--------------------------------------------------------------------------------
1 | #version 450
2 |
3 | layout (location = 0) out vec2 outUV;
4 |
5 | out gl_PerVertex
6 | {
7 | vec4 gl_Position;
8 | };
9 |
10 | void main()
11 | {
12 | outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
13 | gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f);
14 | }
--------------------------------------------------------------------------------
/hotham-simulator/build_input/viewdisplay.vert.spv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leetvr/hotham/cab13bfcc2dcce1828d0520c03950a28668f5330/hotham-simulator/build_input/viewdisplay.vert.spv
--------------------------------------------------------------------------------
/hotham-simulator/build_input/wrapper.h:
--------------------------------------------------------------------------------
1 | #include "loader_interfaces.h"
--------------------------------------------------------------------------------
/hotham-simulator/hotham_simulator.json:
--------------------------------------------------------------------------------
1 | {
2 | "file_format_version": "1.0.0",
3 | "runtime": {
4 | "api_version": "1.0",
5 | "name": "Hotham Simulator",
6 | "library_path": "..\\target\\debug\\hotham_simulator.dll"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/hotham-simulator/openxr_simulator.reg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leetvr/hotham/cab13bfcc2dcce1828d0520c03950a28668f5330/hotham-simulator/openxr_simulator.reg
--------------------------------------------------------------------------------
/hotham-simulator/src/action_state.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 |
3 | use openxr_sys::{Action, Path, FALSE};
4 |
5 | #[derive(Debug, Clone, Default)]
6 | /// Stores Action state, allowing the simulator to simulate input for an application.
7 | // A bit yuck to use u64 instead of Action, but it doesn't support Hash.. but whatever.
8 | pub struct ActionState {
9 | boolean_actions: HashMap,
10 | bindings: HashMap,
11 | }
12 | impl ActionState {
13 | pub(crate) fn get_boolean(&self, action: Action) -> openxr_sys::Bool32 {
14 | self.boolean_actions
15 | .get(&action.into_raw())
16 | .map(|p| (*p).into())
17 | .unwrap_or(FALSE)
18 | }
19 |
20 | pub(crate) fn add_binding(&mut self, path: Path, action: Action) {
21 | self.bindings.insert(path, action.into_raw());
22 | }
23 |
24 | /// Resets all action state.
25 | pub(crate) fn clear(&mut self) {
26 | // Set all the booleans to false.
27 | self.boolean_actions.values_mut().for_each(|v| *v = false);
28 | }
29 |
30 | pub(crate) fn set_boolean(&mut self, path: &Path, value: bool) {
31 | let action = self.bindings.get(path).unwrap();
32 | self.boolean_actions.insert(*action, value);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/hotham-simulator/src/inputs.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashSet;
2 |
3 | use winit::event::{ElementState, KeyboardInput, VirtualKeyCode};
4 |
5 | #[derive(Debug, Clone, Default)]
6 | pub struct Inputs {
7 | pub pressed: HashSet,
8 | }
9 |
10 | impl Inputs {
11 | pub fn process_event(&mut self, keyboard_input: KeyboardInput) {
12 | if let Some(key) = keyboard_input.virtual_keycode {
13 | let _ = match keyboard_input.state {
14 | ElementState::Pressed => self.pressed.insert(key),
15 | ElementState::Released => self.pressed.remove(&key),
16 | };
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/hotham-simulator/src/openxr_loader.rs:
--------------------------------------------------------------------------------
1 | #![allow(non_upper_case_globals)]
2 | #![allow(non_camel_case_types)]
3 | #![allow(non_snake_case)]
4 | #![allow(deref_nullptr)]
5 | include!("./bindings.rs");
6 |
--------------------------------------------------------------------------------
/hotham-simulator/src/shaders/viewdisplay.frag.spv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leetvr/hotham/cab13bfcc2dcce1828d0520c03950a28668f5330/hotham-simulator/src/shaders/viewdisplay.frag.spv
--------------------------------------------------------------------------------
/hotham-simulator/src/shaders/viewdisplay.vert.spv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leetvr/hotham/cab13bfcc2dcce1828d0520c03950a28668f5330/hotham-simulator/src/shaders/viewdisplay.vert.spv
--------------------------------------------------------------------------------
/hotham-simulator/src/space_state.rs:
--------------------------------------------------------------------------------
1 | use core::fmt::Debug;
2 | use openxr_sys::{Quaternionf, Vector3f};
3 |
4 | #[derive(Clone)]
5 | pub struct SpaceState {
6 | pub name: String,
7 | pub position: Vector3f,
8 | pub orientation: Quaternionf,
9 | }
10 |
11 | impl SpaceState {
12 | pub fn new(name: &str) -> Self {
13 | Self {
14 | name: name.to_string(),
15 | position: Default::default(),
16 | orientation: Quaternionf::IDENTITY,
17 | }
18 | }
19 | }
20 |
21 | impl Debug for SpaceState {
22 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 | f.debug_struct("SpaceState")
24 | .field("name", &self.name)
25 | .field(
26 | "position",
27 | &format!(
28 | "x: {}, y: {}, z: {}",
29 | self.position.x, self.position.y, self.position.z
30 | ),
31 | )
32 | .field(
33 | "orientation",
34 | &format!(
35 | "x: {}, y: {}, z: {}, w: {}",
36 | self.orientation.x, self.orientation.y, self.orientation.z, self.orientation.w
37 | ),
38 | )
39 | .finish()
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/hotham-simulator/test.ps1:
--------------------------------------------------------------------------------
1 | Write-Output "All your OpenXR are belong to crab"
2 | ${Env:RUST_BACKTRACE} = 3
3 | cargo build
4 |
5 | try {
6 | Push-Location -Path "C:\Users\kanem\Development\OpenXR-SDK-Source\build\src\tests\hello_xr\Debug"
7 | .\hello_xr.exe -g Vulkan2
8 | }
9 | catch {
10 | Write-Warning "Problem!"
11 | }
12 | finally {
13 | Pop-Location
14 | }
--------------------------------------------------------------------------------
/hotham-simulator/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | echo "All your OpenXR are belong to crab"
4 | cargo build
5 | cd ../openxrs/openxr
6 | cargo run --example vulkan
--------------------------------------------------------------------------------
/hotham/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | build = "build.rs"
3 | description = "A framework for creating incredible standalone VR experiences"
4 | edition = "2021"
5 | keywords = ["vr", "gamedev", "openxr", "vulkan"]
6 | license = "MIT OR Apache-2.0"
7 | name = "hotham"
8 | readme = "../README.md"
9 | repository = "https://github.com/leetvr/hotham/"
10 | version = "0.2.0"
11 |
12 | [dependencies]
13 | anyhow = "1.0"
14 | ash = "0.33.2"
15 | bitflags = "1.3"
16 | cpal = "0.15.2"
17 | ctrlc = {version = "3", features = ["termination"]}
18 | egui = "0.15"
19 | generational-arena = "0.2.8"
20 | glam = {features = ["mint", "serde", "approx"], version = "0.23"}
21 | gltf = {version = "1.0", features = ["KHR_lights_punctual", "KHR_materials_unlit", "names", "utils"], default-features = false}
22 | half = "2.1.0"
23 | hecs = "0.10.1"
24 | hotham-asset-client = {path = "../hotham-asset-client"}
25 | id-arena = "2.2.1"
26 | image = {version = "0.24.3", default-features = false, features = ["jpeg", "png"]}
27 | itertools = "0.10.0"
28 | # ktx2 = "0.3"
29 | ktx2 = {git = "https://github.com/BVE-Reborn/ktx2"}
30 | memoffset = "0.8.0"
31 | mint = "0.5.6"
32 | notify-debouncer-mini = "0.2.1"
33 | oddio = "0.5"
34 | openxr = {features = ["loaded", "mint"], version = "0.17"}
35 | rapier3d = "0.17"
36 | ruzstd = "0.3"
37 | serde = {version = "1.0", features = ["derive"]}
38 | symphonia = {version = "0.5", default-features = false, features = ["mp3"]}
39 | thiserror = "1.0"
40 | tokio = {version = "1.0.1", default-features = false, features = ["rt"]}
41 | vk-shader-macros = "0.2.8"
42 |
43 | [target.'cfg(not(any(target_os = "macos", target_os = "ios")))'.dev-dependencies]
44 | renderdoc = "0.10"
45 |
46 | [dev-dependencies]
47 | approx = "0.5"
48 | serde_json = "1.0"
49 |
50 | [target.'cfg(target_os = "android")'.dependencies]
51 | jni = "0.19.0"
52 | ndk = "0.6"
53 | ndk-glue = "0.6"
54 |
55 | [target.'cfg(target_os = "windows")'.dependencies]
56 | windows = {version = "0.48", features = ["Win32_Foundation", "Win32_System_Performance"]}
57 |
58 | [target.'cfg(not(target_os = "windows"))'.dependencies]
59 | libc = "0.2"
60 |
--------------------------------------------------------------------------------
/hotham/build.rs:
--------------------------------------------------------------------------------
1 | pub fn main() {
2 | // On Android, we must ensure that we're dynamically linking against the C++ standard library.
3 | // For more details, see https://github.com/rust-windowing/android-ndk-rs/issues/167
4 | use std::env::var;
5 | if var("TARGET")
6 | .map(|target| target == "aarch64-linux-android")
7 | .unwrap_or(false)
8 | {
9 | println!("cargo:rustc-link-lib=dylib=c++");
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/hotham/data/brdf_lut.ktx2:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:73fc7f8aa9f277891ed1c60d236e276057ab6f88795e3def8378ebb08af46764
3 | size 70600
4 |
--------------------------------------------------------------------------------
/hotham/data/brdf_lut_android.ktx2:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:73fc7f8aa9f277891ed1c60d236e276057ab6f88795e3def8378ebb08af46764
3 | size 70600
4 |
--------------------------------------------------------------------------------
/hotham/data/environment_map_diffuse.ktx2:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:a39e0376673774c83dffe3363e73ee9fe788293649377ceaed2290357e8727d1
3 | size 196872
4 |
--------------------------------------------------------------------------------
/hotham/data/environment_map_diffuse_android.ktx2:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:0deee512ab4c020651d16627bb9aebe76b7eac2dd670fb16a2bfa1344b181cc3
3 | size 8816
4 |
--------------------------------------------------------------------------------
/hotham/data/environment_map_specular.ktx2:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:33b1302bc3cb41d28f704df1395911ed5e96a639d7126fc2d9b0e76e1b0da6b9
3 | size 16777680
4 |
--------------------------------------------------------------------------------
/hotham/data/environment_map_specular_android.ktx2:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:8e08416a52eab7bcf43a2ac7894fce159aecbb2957a1e79f5464af5857a238ea
3 | size 524752
4 |
--------------------------------------------------------------------------------
/hotham/src/asset_importer/scene.rs:
--------------------------------------------------------------------------------
1 | use crate::rendering::light::Light;
2 |
3 | use super::Models;
4 |
5 | /// Representation of a glTF Scene
6 | pub struct Scene {
7 | /// The models in the scene
8 | pub models: Models,
9 | /// The lights in the scene
10 | pub lights: Vec,
11 | }
12 |
--------------------------------------------------------------------------------
/hotham/src/components/animation_controller.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 |
3 | use gltf::animation::util::ReadOutputs;
4 | use itertools::Itertools;
5 |
6 | use crate::{asset_importer::ImportContext, components::AnimationTarget};
7 | use glam::Quat;
8 |
9 | #[derive(Debug, Clone, PartialEq, Default)]
10 |
11 | /// Component that controls how an `AnimationTarget` should be animated.
12 | /// Added by `gltf_loader` to the root node if its children contain animation data.
13 | pub struct AnimationController {
14 | /// The amount to blend from
15 | pub blend_from: usize,
16 | /// The amount to blend to
17 | pub blend_to: usize,
18 | /// The total blend amount
19 | pub blend_amount: f32,
20 | /// The targets to apply this animation to
21 | pub targets: Vec,
22 | }
23 |
24 | impl AnimationController {
25 | pub(crate) fn load(
26 | animations: gltf::iter::Animations,
27 | import_context: &mut ImportContext,
28 | ) -> AnimationController {
29 | let node_entity_map = &import_context.node_entity_map;
30 | let buffer = &import_context.buffer;
31 |
32 | let mut targets = HashMap::new();
33 |
34 | for channel in animations.flat_map(|a| a.channels()) {
35 | let target = *node_entity_map
36 | .get(&channel.target().node().index())
37 | .unwrap();
38 |
39 | let animation_target = targets.entry(target).or_insert(AnimationTarget {
40 | target,
41 | rotations: Vec::new(),
42 | scales: Vec::new(),
43 | translations: Vec::new(),
44 | });
45 |
46 | let reader = channel.reader(|_| Some(buffer));
47 | match reader.read_outputs() {
48 | Some(ReadOutputs::Translations(translation_data)) => {
49 | for t in translation_data {
50 | animation_target
51 | .translations
52 | .push([t[0], t[1], t[2]].into());
53 | }
54 | }
55 | Some(ReadOutputs::Rotations(rotation_data)) => {
56 | for r in rotation_data.into_f32() {
57 | animation_target
58 | .rotations
59 | .push(Quat::from_xyzw(r[0], r[1], r[2], r[3]));
60 | }
61 | }
62 | Some(ReadOutputs::Scales(scale_data)) => {
63 | for s in scale_data {
64 | animation_target.scales.push([s[0], s[1], s[2]].into());
65 | }
66 | }
67 | _ => {}
68 | }
69 | }
70 |
71 | AnimationController {
72 | blend_from: 0,
73 | blend_to: 1,
74 | blend_amount: 0.,
75 | targets: targets.drain().map(|n| n.1).collect_vec(),
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/hotham/src/components/animation_target.rs:
--------------------------------------------------------------------------------
1 | use glam::{Quat, Vec3};
2 | use hecs::Entity;
3 |
4 | /// A component that allows an entity to be animated.
5 | /// Usually added by `gltf_loader` if the node contains animation data.
6 | #[derive(Debug, Clone, PartialEq)]
7 | pub struct AnimationTarget {
8 | /// The entity that is affected by this animation
9 | pub target: Entity,
10 | /// Rotations for this animation
11 | pub rotations: Vec,
12 | /// Scales for this animation
13 | pub scales: Vec,
14 | /// Translations for this animation
15 | pub translations: Vec,
16 | }
17 |
--------------------------------------------------------------------------------
/hotham/src/components/global_transform.rs:
--------------------------------------------------------------------------------
1 | use crate::util;
2 | use glam::{Affine3A, Quat, Vec3};
3 |
4 | use super::LocalTransform;
5 |
6 | /// Component used to represent the global transform of the entity in the renderer.
7 | /// This is the transformation from local to global space.
8 | #[derive(Debug, Clone, Copy, PartialEq)]
9 | pub struct GlobalTransform(pub Affine3A);
10 |
11 | impl Default for GlobalTransform {
12 | fn default() -> Self {
13 | Self(Affine3A::IDENTITY)
14 | }
15 | }
16 |
17 | impl GlobalTransform {
18 | /// Convenience function to convert the [`GlobalTransform`] into a [`rapier3d::na::Isometry3`]
19 | pub fn to_isometry(&self) -> rapier3d::na::Isometry3 {
20 | util::isometry_from_affine(&self.0)
21 | }
22 |
23 | /// Convenience function to decompose the [`GlobalTransform`] into its components
24 | pub fn to_scale_rotation_translation(&self) -> (Vec3, Quat, Vec3) {
25 | self.0.to_scale_rotation_translation()
26 | }
27 | }
28 |
29 | impl From for GlobalTransform {
30 | fn from(l: LocalTransform) -> Self {
31 | GlobalTransform(l.to_affine())
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/hotham/src/components/grabbable.rs:
--------------------------------------------------------------------------------
1 | #[derive(Debug, Clone, Copy)]
2 | pub struct Grabbable {}
3 |
4 | #[derive(Debug, Clone, Copy)]
5 | pub struct Grabbed;
6 |
7 | #[derive(Debug, Clone, Copy)]
8 | pub struct Released;
9 |
--------------------------------------------------------------------------------
/hotham/src/components/hand.rs:
--------------------------------------------------------------------------------
1 | use glam::Affine3A;
2 | use hecs::Entity;
3 |
4 | /// A component that represents the "side" or "handedness" that an entity is on
5 | /// Used by components such as `Hand` and `Pointer` to identify which controller they should map to
6 | #[derive(Debug, PartialEq, Clone, Copy, Eq, PartialOrd, Ord)]
7 | pub enum Handedness {
8 | /// Left hand side
9 | Left,
10 | /// Right hand side
11 | Right,
12 | }
13 |
14 | #[derive(Clone)]
15 | pub struct GrabbedEntity {
16 | pub entity: Entity,
17 | pub grip_from_local: Affine3A,
18 | }
19 |
20 | /// A component that's added to an entity to represent a "hand" presence.
21 | /// Used to give the player a feeling of immersion by allowing them to grab objects in the world
22 | /// Requires `hands_system`
23 | #[derive(Clone)]
24 | pub struct Hand {
25 | /// How much has this hand been gripped?
26 | pub grip_value: f32,
27 | /// Did the grip button go from not pressed to pressed this frame?
28 | pub grip_button_just_pressed: bool,
29 | /// Which side is this hand on?
30 | pub handedness: Handedness,
31 | /// Have we grabbed something?
32 | pub grabbed_entity: Option,
33 | }
34 |
35 | impl Hand {
36 | /// Shortcut helper to create a Left hand
37 | pub fn left() -> Hand {
38 | Hand {
39 | grip_value: 0.0,
40 | grip_button_just_pressed: false,
41 | handedness: Handedness::Left,
42 | grabbed_entity: None,
43 | }
44 | }
45 |
46 | /// Shortcut helper to create a right hand
47 | pub fn right() -> Hand {
48 | Hand {
49 | grip_value: 0.0,
50 | grip_button_just_pressed: false,
51 | handedness: Handedness::Right,
52 | grabbed_entity: None,
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/hotham/src/components/hmd.rs:
--------------------------------------------------------------------------------
1 | /// A marker component used to indicate the player's headset, or Head Mounted Display in the game simulation.
2 | ///
3 | /// The entity marked with this component will have its [`super::LocalTransform`] is updated each frame by the
4 | /// engine with the pose of the player's headset in the real world (ie. stage space). Since this entity is
5 | /// parented to the [`super::Stage`] entity, querying for the HMD's [`super::GlobalTransform`] will then give
6 | /// the pose of the HMD in the virtual world (ie. global space).
7 | ///
8 | /// This is very important when incorporating the user's position in the real world into the game simulation,
9 | /// ie. player controllers. Future versions of Hotham may add more functionality to make this even easier.
10 | pub struct HMD {}
11 |
--------------------------------------------------------------------------------
/hotham/src/components/info.rs:
--------------------------------------------------------------------------------
1 | /// Component that adds some information about the entity
2 | /// Useful for debugging - added by default by `gltf_loader`
3 | #[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
4 | pub struct Info {
5 | /// A helpful name
6 | pub name: String,
7 | /// Node ID from the original glTF file
8 | pub node_id: usize,
9 | }
10 |
--------------------------------------------------------------------------------
/hotham/src/components/joint.rs:
--------------------------------------------------------------------------------
1 | use glam::Affine3A;
2 | use hecs::Entity;
3 |
4 | /// A component that adds a "skinned joint" to an entity.
5 | /// For more detail, check out the [glTF spec](https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skins-overview)
6 | /// Automatically added by `gltf_loader` for nodes that contain skin data
7 | #[derive(Debug, Clone, Copy, PartialEq)]
8 | pub struct Joint {
9 | /// Pointer to the root of the skeleton
10 | pub skeleton_root: Entity,
11 | /// Inverse bind matrix used to apply the skin in the vertex shader
12 | pub inverse_bind_matrix: Affine3A,
13 | }
14 |
--------------------------------------------------------------------------------
/hotham/src/components/local_transform.rs:
--------------------------------------------------------------------------------
1 | use glam::{Affine3A, Quat, Vec3};
2 | use gltf::scene::Transform as TransformData;
3 | use serde::{Deserialize, Serialize};
4 |
5 | use crate::util::{decompose_isometry, isometry_from_affine};
6 |
7 | /// The component's position in global space (ie. the game simulation), relative to its parent.
8 | ///
9 | /// There are two ways to set an entity's position in Hotham:
10 | ///
11 | /// 1. **Game controlled** - this entity will have its rigid body position set by the **game** simulation
12 | /// 1. **Physics controlled** - this entity will have its position set by the **physics** simulation
13 | ///
14 | /// If an entity has a [`super::RigidBody`] component with a `body_type` of [`super::physics::BodyType::Dynamic`], then you
15 | /// are indicating that you want this entity's position in the game simulation (ie. its global position)
16 | /// to be entirely controlled by the physics simulation.
17 | ///
18 | /// Otherwise, you can just modify [`LocalTransform`] and your entity will have its position in the game
19 | /// simulation AND the physics simulation (if it has a [`super::RigidBody`] and/or [`super::Collider`]) set relative to its [`super::Parent`].
20 | ///
21 | /// If the entity doesn't have a [`super::Parent`], then the [`super::GlobalTransform`] is just whatever you've set here.
22 | #[derive(Clone, PartialEq, Debug, Copy, Deserialize, Serialize)]
23 | pub struct LocalTransform {
24 | /// The translation of the entity
25 | pub translation: Vec3,
26 | /// The rotation of the entity
27 | pub rotation: Quat,
28 | /// The non-uniform scale of the entity
29 | pub scale: Vec3,
30 | }
31 |
32 | impl Default for LocalTransform {
33 | fn default() -> Self {
34 | Self {
35 | translation: Vec3::ZERO,
36 | rotation: Quat::IDENTITY,
37 | scale: Vec3::ONE,
38 | }
39 | }
40 | }
41 |
42 | impl LocalTransform {
43 | pub(crate) fn load(transform_data: TransformData) -> LocalTransform {
44 | let (t, r, s) = transform_data.decomposed();
45 | let translation = t.into();
46 | let rotation = Quat::from_xyzw(r[0], r[1], r[2], r[3]);
47 | let scale = s.into();
48 |
49 | LocalTransform {
50 | scale,
51 | rotation,
52 | translation,
53 | }
54 | }
55 |
56 | pub fn from_rotation_translation(rotation: Quat, translation: Vec3) -> Self {
57 | LocalTransform {
58 | rotation,
59 | translation,
60 | ..Default::default()
61 | }
62 | }
63 |
64 | /// Convenience function to convert the [`LocalTransform`] into a [`rapier3d::na::Isometry3`]
65 | pub fn to_isometry(&self) -> rapier3d::na::Isometry3 {
66 | isometry_from_affine(&self.to_affine())
67 | }
68 |
69 | /// Update the translation and rotation from a [`rapier3d::na::Isometry3`]
70 | pub fn update_from_isometry(&mut self, isometry: &rapier3d::na::Isometry3) {
71 | (self.rotation, self.translation) = decompose_isometry(isometry);
72 | }
73 |
74 | /// Update the scale, rotation and rotation from a [`glam::Affine3A`]
75 | pub fn update_from_affine(&mut self, transform: &glam::Affine3A) {
76 | (self.scale, self.rotation, self.translation) = transform.to_scale_rotation_translation();
77 | }
78 |
79 | /// Update ONLY rotation and rotation from a [`glam::Affine3A`]
80 | pub fn update_rotation_translation_from_affine(&mut self, transform: &glam::Affine3A) {
81 | (_, self.rotation, self.translation) = transform.to_scale_rotation_translation();
82 | }
83 |
84 | /// Convenience function to convert the [`LocalTransform`] into a [`glam::Affine3A`]
85 | pub fn to_affine(&self) -> Affine3A {
86 | Affine3A::from_scale_rotation_translation(self.scale, self.rotation, self.translation)
87 | }
88 | }
89 |
90 | impl From for Affine3A {
91 | fn from(l: LocalTransform) -> Self {
92 | l.to_affine()
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/hotham/src/components/mesh.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | asset_importer::ImportContext, contexts::RenderContext, id_arena::Id,
3 | rendering::mesh_data::MeshData,
4 | };
5 |
6 | /// A handle to MeshData stored on the GPU.
7 | #[derive(Debug, Clone)]
8 | pub struct Mesh {
9 | pub handle: Id,
10 | }
11 |
12 | impl Mesh {
13 | /// Create a new mesh handle from mesh data.
14 | pub fn new(mesh_data: MeshData, render_context: &mut RenderContext) -> Self {
15 | let handle = render_context.resources.mesh_data.alloc(mesh_data);
16 | Mesh { handle }
17 | }
18 |
19 | /// Takes mesh data from a glTF file, uploads it to the GPU and inserts it into mesh_map
20 | pub(crate) fn load(gltf_mesh_data: gltf::Mesh, import_context: &mut ImportContext) {
21 | let index = gltf_mesh_data.index();
22 | let mesh_data = MeshData::load(gltf_mesh_data, import_context);
23 |
24 | import_context
25 | .mesh_map
26 | .insert(index, Mesh::new(mesh_data, import_context.render_context));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/hotham/src/components/mod.rs:
--------------------------------------------------------------------------------
1 | #![allow(missing_docs)]
2 | pub mod animation_controller;
3 | pub mod animation_target;
4 | pub mod global_transform;
5 | pub mod grabbable;
6 | pub mod hand;
7 | pub mod hmd;
8 | pub mod info;
9 | pub mod joint;
10 | pub mod local_transform;
11 | pub mod mesh;
12 | pub mod panel;
13 | pub mod parent;
14 | pub mod physics;
15 | pub mod pointer;
16 | pub mod root;
17 | pub mod skin;
18 | pub mod sound_emitter;
19 | pub mod stage;
20 | pub mod ui_panel;
21 | pub mod visible;
22 |
23 | pub use animation_controller::AnimationController;
24 | pub use animation_target::AnimationTarget;
25 | pub use global_transform::GlobalTransform;
26 | pub use grabbable::*;
27 | pub use hand::Hand;
28 | pub use hmd::HMD;
29 | pub use info::Info;
30 | pub use joint::Joint;
31 | pub use local_transform::LocalTransform;
32 | pub use mesh::Mesh;
33 | pub use panel::Panel;
34 | pub use parent::Parent;
35 | pub use physics::collider::Collider;
36 | pub use physics::RigidBody;
37 | pub use pointer::Pointer;
38 | pub use root::Root;
39 | pub use skin::Skin;
40 | pub use sound_emitter::SoundEmitter;
41 | pub use stage::Stage;
42 | pub use ui_panel::UIPanel;
43 | pub use visible::Visible;
44 |
--------------------------------------------------------------------------------
/hotham/src/components/panel.rs:
--------------------------------------------------------------------------------
1 | use ash::vk::{self};
2 | use egui::Pos2;
3 | use glam::Vec2;
4 |
5 | use crate::components::Mesh;
6 | use crate::hotham_error::HothamError;
7 | use crate::rendering::material::{pack2x16, Material, MaterialFlags};
8 | use crate::rendering::mesh_data::MeshData;
9 | use crate::rendering::primitive::Primitive;
10 | use crate::rendering::vertex::Vertex;
11 | use crate::{
12 | contexts::{RenderContext, VulkanContext},
13 | rendering::texture::Texture,
14 | };
15 |
16 | pub struct Panel {
17 | /// The resolution of the Panel
18 | pub resolution: vk::Extent2D,
19 | /// The size of the panel in the world
20 | pub world_size: Vec2,
21 | /// Texture backing the Panel
22 | pub texture: Texture,
23 | /// Input received this frame
24 | pub input: Option,
25 | }
26 |
27 | impl Panel {
28 | pub fn create(
29 | vulkan_context: &VulkanContext,
30 | render_context: &mut RenderContext,
31 | resolution: vk::Extent2D,
32 | world_size: Vec2,
33 | ) -> Result<(Panel, Mesh), HothamError> {
34 | let texture = Texture::empty(vulkan_context, render_context, resolution);
35 | let mesh = create_panel_mesh(&texture, render_context, world_size);
36 |
37 | Ok((
38 | Panel {
39 | resolution,
40 | world_size,
41 | texture,
42 | input: Default::default(),
43 | },
44 | mesh,
45 | ))
46 | }
47 | }
48 |
49 | fn create_panel_mesh(
50 | output_texture: &Texture,
51 | render_context: &mut RenderContext,
52 | world_size: Vec2,
53 | ) -> Mesh {
54 | let material_id = add_material(output_texture, render_context);
55 | let (half_width, half_height) = (world_size.x / 2., world_size.y / 2.);
56 |
57 | let positions = [
58 | [-half_width, half_height, 0.].into(), // v0
59 | [half_width, -half_height, 0.].into(), // v1
60 | [half_width, half_height, 0.].into(), // v2
61 | [-half_width, -half_height, 0.].into(), // v3
62 | ];
63 | let tex_coords_0: [glam::Vec2; 4] = [
64 | [0., 0.].into(), // v0
65 | [1., 1.].into(), // v1
66 | [1., 0.].into(), // v2
67 | [0., 1.].into(), // v3
68 | ];
69 | let vertices: Vec = tex_coords_0
70 | .iter()
71 | .map(|t| Vertex {
72 | texture_coords: *t,
73 | ..Default::default()
74 | })
75 | .collect();
76 |
77 | let indices = [0, 1, 2, 0, 3, 1];
78 | let primitive = Primitive::new(&positions, &vertices, &indices, material_id, render_context);
79 | Mesh::new(MeshData::new(vec![primitive]), render_context)
80 | }
81 |
82 | fn add_material(output_texture: &Texture, render_context: &mut RenderContext) -> u32 {
83 | let mut material = Material::unlit_white();
84 | material.packed_flags_and_base_texture_id = pack2x16(
85 | (MaterialFlags::HAS_BASE_COLOR_TEXTURE | MaterialFlags::UNLIT_WORKFLOW).bits(),
86 | output_texture.index,
87 | );
88 | unsafe { render_context.resources.materials_buffer.push(&material) }
89 | }
90 |
91 | /// Input to a panel
92 | #[derive(Debug, Clone)]
93 | pub struct PanelInput {
94 | /// Location of the cursor, in panel space
95 | pub cursor_location: Pos2,
96 | /// Value of the controller trigger
97 | pub trigger_value: f32,
98 | }
99 |
--------------------------------------------------------------------------------
/hotham/src/components/parent.rs:
--------------------------------------------------------------------------------
1 | use hecs::Entity;
2 |
3 | /// Component added to indicate that an entity has a parent
4 | /// Used by `update_global_transform_with_parent_system`
5 | #[derive(Debug, Clone, Copy, PartialEq, Eq)]
6 | pub struct Parent(pub Entity);
7 |
--------------------------------------------------------------------------------
/hotham/src/components/physics/additional_mass.rs:
--------------------------------------------------------------------------------
1 | #[derive(Debug, Clone, Default)]
2 | pub struct AdditionalMass {
3 | pub value: f32,
4 | }
5 |
6 | impl AdditionalMass {
7 | pub fn new(value: f32) -> Self {
8 | Self { value }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/hotham/src/components/physics/collider.rs:
--------------------------------------------------------------------------------
1 | use std::fmt::Debug;
2 |
3 | use hecs::Entity;
4 | pub use rapier3d::prelude::{ActiveCollisionTypes, Group, SharedShape};
5 |
6 | use crate::contexts::physics_context::{DEFAULT_COLLISION_GROUP, HAND_COLLISION_GROUP};
7 |
8 | /// A component that enables collision detection - essentially a thin wrapper around [`rapier3d::prelude::Collider`].
9 | #[derive(Clone)]
10 | pub struct Collider {
11 | /// A list of entities that may have collided with this one this frame
12 | pub collisions_this_frame: Vec,
13 | /// The shape of this collider
14 | pub shape: SharedShape,
15 | /// Is this a sensor collider?
16 | pub sensor: bool,
17 | /// What collision groups is this a member of?
18 | pub collision_groups: Group,
19 | /// What groups can this collider interact with?
20 | pub collision_filter: Group,
21 | /// What kinds of colliders can this collider interact with?
22 | pub active_collision_types: ActiveCollisionTypes,
23 | /// Should this collider be offset from its parent (if it has one)?
24 | pub offset_from_parent: glam::Vec3,
25 | /// How "bouncy" is this collider?
26 | pub restitution: f32,
27 | /// What is the mass of this collider?
28 | pub mass: f32,
29 | }
30 |
31 | impl Debug for Collider {
32 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 | f.debug_struct("Collider")
34 | .field("collisions_this_frame", &self.collisions_this_frame)
35 | .field("shape", &self.shape.shape_type())
36 | .field("sensor", &self.sensor)
37 | .field("collision_groups", &self.collision_groups)
38 | .field("collision_filter", &self.collision_filter)
39 | .field("active_collision_types", &self.active_collision_types)
40 | .field("offset_from_parent", &self.offset_from_parent)
41 | .field("restitution", &self.restitution)
42 | .field("mass", &self.mass)
43 | .finish()
44 | }
45 | }
46 |
47 | impl Collider {
48 | /// Create a new collider
49 | pub fn new(shape: SharedShape) -> Collider {
50 | Collider {
51 | shape,
52 | ..Default::default()
53 | }
54 | }
55 | }
56 |
57 | impl Default for Collider {
58 | fn default() -> Self {
59 | Self {
60 | collisions_this_frame: Default::default(),
61 | shape: SharedShape::ball(1.0),
62 | sensor: false,
63 | collision_groups: DEFAULT_COLLISION_GROUP,
64 | collision_filter: DEFAULT_COLLISION_GROUP | HAND_COLLISION_GROUP,
65 | active_collision_types: ActiveCollisionTypes::default(),
66 | offset_from_parent: Default::default(),
67 | restitution: 0.,
68 | mass: 0.,
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/hotham/src/components/physics/impulse.rs:
--------------------------------------------------------------------------------
1 | #[derive(Debug, Clone)]
2 | pub struct Impulse {
3 | pub value: glam::Vec3,
4 | }
5 |
6 | impl Impulse {
7 | pub fn new(value: glam::Vec3) -> Self {
8 | Self { value }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/hotham/src/components/physics/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod additional_mass;
2 | pub mod collider;
3 | pub mod impulse;
4 | pub mod rigid_body;
5 | pub mod teleport;
6 |
7 | pub use additional_mass::AdditionalMass;
8 | pub use collider::ActiveCollisionTypes;
9 | pub use collider::Collider;
10 | pub use collider::SharedShape;
11 | pub use impulse::Impulse;
12 | pub use rigid_body::BodyType;
13 | pub use rigid_body::RigidBody;
14 | pub use teleport::Teleport;
15 |
--------------------------------------------------------------------------------
/hotham/src/components/physics/rigid_body.rs:
--------------------------------------------------------------------------------
1 | use rapier3d::prelude::RigidBodyType as RapierBodyType;
2 |
3 | /// A component used to synchronise this entity's position in the game simulation with the physics simulation.
4 | ///
5 | /// You can indicate to [`crate::systems::physics_system`] how you'd like this entity to be treated by changing the `body_type` field
6 | /// . Setting the `body_type` to [`BodyType::Dynamic`] will result in the entity having its [`crate::components::GlobalTransform`]
7 | /// overwritten by its position in the physics simulation - any updates to [`crate::components::LocalTransform`] or [`crate::components::GlobalTransform`] will be overwritten.
8 | ///
9 | /// Any other kind of body is treated as *game controlled* - that is, updating the entity's [`crate::components::LocalTransform`] will not be overwritten
10 | /// and the position of the entity in the physics simulation will be updated based on its [`crate::components::GlobalTransform`] (all transforms in the
11 | /// physics simulation are in global space).
12 | ///
13 | /// ## Panics
14 | ///
15 | /// Trying to create a [`RigidBody`] with a `body_type` of [`BodyType::Dynamic`], or change an existing [`RigidBody`]'s `body_type` to
16 | /// be [`BodyType::Dynamic`] on a [`hecs::Entity`] that has a [`Parent`] component will cause a panic. Don't do it.
17 | #[derive(Debug, Clone)]
18 | pub struct RigidBody {
19 | pub body_type: BodyType,
20 | pub linear_velocity: glam::Vec3,
21 | pub angular_velocity: glam::Vec3,
22 | pub mass: f32,
23 | pub lock_rotations: bool,
24 | }
25 |
26 | #[derive(Debug, Clone, Copy, PartialEq, Eq)]
27 | pub enum BodyType {
28 | KinematicPositionBased,
29 | KinematicVelocityBased,
30 | Dynamic,
31 | Fixed,
32 | }
33 |
34 | impl From for RapierBodyType {
35 | fn from(r: BodyType) -> Self {
36 | match r {
37 | BodyType::KinematicPositionBased => RapierBodyType::KinematicPositionBased,
38 | BodyType::KinematicVelocityBased => RapierBodyType::KinematicVelocityBased,
39 | BodyType::Dynamic => RapierBodyType::Dynamic,
40 | BodyType::Fixed => RapierBodyType::Fixed,
41 | }
42 | }
43 | }
44 |
45 | impl Default for RigidBody {
46 | fn default() -> Self {
47 | Self {
48 | body_type: BodyType::Dynamic,
49 | linear_velocity: Default::default(),
50 | angular_velocity: Default::default(),
51 | mass: 0.,
52 | lock_rotations: false,
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/hotham/src/components/physics/teleport.rs:
--------------------------------------------------------------------------------
1 | /// A "one-shot" tag component that indicates to the physics system that you would like
2 | /// this entity to be teleported - ie. moved in a non-physical way. The mechanism for this is simple:
3 | ///
4 | /// 1. Insert this component to the entity you'd like to teleport
5 | /// 2. Set the entity's LocalTransform to where you'd like the entity teleported
6 | ///
7 | /// In the next tick, this component will be removed.
8 | pub struct Teleport {}
9 |
--------------------------------------------------------------------------------
/hotham/src/components/pointer.rs:
--------------------------------------------------------------------------------
1 | use super::hand::Handedness;
2 |
3 | /// A component added to an entity to allow users to interact with `UIPanels` using their
4 | /// controllers.
5 | pub struct Pointer {
6 | /// Which hand is the pointer in?
7 | pub handedness: Handedness,
8 | /// How much has the trigger been pulled down?
9 | pub trigger_value: f32,
10 | }
11 |
--------------------------------------------------------------------------------
/hotham/src/components/root.rs:
--------------------------------------------------------------------------------
1 | /// Component to represent that this is the root entity of a glTF model
2 | /// Automatically added by `gltf_loader`
3 | #[derive(Debug, Clone, Copy, Default)]
4 | pub struct Root {}
5 |
--------------------------------------------------------------------------------
/hotham/src/components/skin.rs:
--------------------------------------------------------------------------------
1 | use crate::{asset_importer::ImportContext, rendering::resources::MAX_JOINTS};
2 | use glam::{Affine3A, Mat4};
3 | use hecs::Entity;
4 |
5 | pub static NO_SKIN: u32 = std::u32::MAX;
6 |
7 | /// Component added to an entity to point to the joints in the node
8 | /// Automatically added by `gltf_loader`
9 | #[derive(Debug, Clone, PartialEq)]
10 | pub struct Skin {
11 | /// List of joints
12 | pub joints: Vec,
13 | /// Inverse bind matrices, used to build the final joint matrices for this skin
14 | pub inverse_bind_matrices: Vec,
15 | /// Index into skin buffer
16 | pub id: u32,
17 | }
18 |
19 | impl Skin {
20 | pub(crate) fn load(skin: gltf::Skin, import_context: &mut ImportContext) -> Skin {
21 | let reader = skin.reader(|_| Some(&import_context.buffer));
22 | let inverse_bind_matrices = reader
23 | .read_inverse_bind_matrices()
24 | .unwrap()
25 | .map(|m| Affine3A::from_mat4(Mat4::from_cols_array_2d(&m)))
26 | .collect();
27 |
28 | let joints = skin
29 | .joints()
30 | .map(|j| {
31 | import_context
32 | .node_entity_map
33 | .get(&j.index())
34 | .cloned()
35 | .unwrap()
36 | })
37 | .collect();
38 |
39 | let empty_matrices = [Mat4::IDENTITY; MAX_JOINTS];
40 | let id = unsafe {
41 | import_context
42 | .render_context
43 | .resources
44 | .skins_buffer
45 | .push(&empty_matrices)
46 | };
47 |
48 | Skin {
49 | joints,
50 | id,
51 | inverse_bind_matrices,
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/hotham/src/components/sound_emitter.rs:
--------------------------------------------------------------------------------
1 | use std::sync::Arc;
2 |
3 | use oddio::{Frames, Stop};
4 |
5 | type AudioHandle = oddio::Handle>>>;
6 |
7 | /// A component added to an entity to allow it to emit a sound, usually a sound effect
8 | /// Used by `audio_system`
9 | pub struct SoundEmitter {
10 | /// The actual sound data
11 | pub frames: Arc>,
12 | /// Handle into the `oddio` spatializer
13 | pub handle: Option,
14 | /// Used to indicate that the emitter wants to change its state
15 | pub next_state: Option,
16 | }
17 |
18 | impl Clone for SoundEmitter {
19 | fn clone(&self) -> Self {
20 | Self {
21 | frames: self.frames.clone(),
22 | handle: None,
23 | next_state: None,
24 | }
25 | }
26 | }
27 |
28 | /// State of a sound
29 | #[derive(Debug, Clone, Copy, PartialEq, Eq)]
30 | pub enum SoundState {
31 | /// The sound has stopped permanently
32 | Stopped,
33 | /// The sound is playing
34 | Playing,
35 | /// The sound is temporarily paused
36 | Paused,
37 | }
38 |
39 | impl SoundEmitter {
40 | /// Convenience function to create a new `SoundEmitter`
41 | pub fn new(frames: Arc>) -> Self {
42 | Self {
43 | frames,
44 | handle: None,
45 | next_state: None,
46 | }
47 | }
48 |
49 | /// Convenience function to get the `SoundState` of this `SoundEmitter`
50 | pub fn current_state(&mut self) -> SoundState {
51 | if let Some(handle) = self.handle.as_mut() {
52 | let control = handle.control::, _>();
53 | if control.is_paused() {
54 | return SoundState::Paused;
55 | }
56 | if control.is_stopped() {
57 | return SoundState::Stopped;
58 | }
59 | SoundState::Playing
60 | } else {
61 | SoundState::Stopped
62 | }
63 | }
64 |
65 | /// Play the sound
66 | pub fn play(&mut self) {
67 | self.next_state = Some(SoundState::Playing);
68 | }
69 |
70 | /// Pause the sound
71 | pub fn pause(&mut self) {
72 | self.next_state = Some(SoundState::Paused);
73 | }
74 |
75 | /// Stop the sound
76 | pub fn stop(&mut self) {
77 | self.next_state = Some(SoundState::Stopped);
78 | }
79 |
80 | /// Resume the sound
81 | pub fn resume(&mut self) {
82 | self.next_state = Some(SoundState::Playing);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/hotham/src/components/stage.rs:
--------------------------------------------------------------------------------
1 | /// A component to assist with artificial locomotion.
2 | ///
3 | /// On startup, [`crate::Engine`] will create an entity with the [`Stage`] component, and an entity with the
4 | /// [`super::HMD`] component (representing the user's headset), parented to the stage entity.
5 | ///
6 | /// This makes moving the player around in the virtual world *relatively* straightforward, as all is required is
7 | /// to move the [`Stage`]. The engine will ensure that the player's position in the game simulation AND the
8 | /// virtual cameras are correctly updated to account for this.
9 | ///
10 | /// In short, the final position of the player in the game simulation (ie. global space) is:
11 | ///
12 | /// `stage.position * hmd.position`
13 | ///
14 | /// *You* are responsible for controlling the [`Stage`], and the *engine* will update the [`super::HMD`].
15 | ///
16 | /// For more information on how this works, check out [`super::HMD`] and [`crate::contexts::InputContext`].
17 | #[derive(Debug)]
18 | pub struct Stage;
19 |
20 | use glam::Affine3A;
21 | use hecs::With;
22 |
23 | use crate::{components::GlobalTransform, hecs::World};
24 |
25 | /// Get the transform of the stage in global space.
26 | pub fn get_global_from_stage(world: &World) -> Affine3A {
27 | // Get the stage transform
28 | world
29 | .query::>()
30 | .into_iter()
31 | .next()
32 | .map(|(_, global_transform)| global_transform.0)
33 | .unwrap_or(Affine3A::IDENTITY)
34 | }
35 |
--------------------------------------------------------------------------------
/hotham/src/components/visible.rs:
--------------------------------------------------------------------------------
1 | /// The Visibility component determines whether a given entity is shown or hidden within the world.
2 | ///
3 | /// During each tick of the Hotham engine, entities can have Visibility assigned or removed.
4 | ///
5 | /// Basic usage:
6 | /// ```ignore
7 | /// use hotham::components::Visible;
8 | /// world.insert_one(entity, Visible {});
9 | /// world.remove_one::(entity);
10 | /// ```
11 |
12 | #[derive(Debug, Clone, Copy)]
13 | pub struct Visible {}
14 |
--------------------------------------------------------------------------------
/hotham/src/contexts/haptic_context.rs:
--------------------------------------------------------------------------------
1 | use crate::components::hand::Handedness;
2 |
3 | /// Wrapper around XR Haptics
4 | #[derive(Clone, Debug, Default)]
5 | pub struct HapticContext {
6 | /// Haptics that should be applied to the left hand
7 | pub left_hand_amplitude_this_frame: f32,
8 | /// Haptics that should be applied to the right hand
9 | pub right_hand_amplitude_this_frame: f32,
10 | }
11 |
12 | impl HapticContext {
13 | /// Request haptics be applied this frame
14 | pub fn request_haptic_feedback(&mut self, amplitude: f32, handedness: Handedness) {
15 | match handedness {
16 | Handedness::Left => {
17 | if amplitude > self.left_hand_amplitude_this_frame {
18 | self.left_hand_amplitude_this_frame = amplitude;
19 | }
20 | }
21 | Handedness::Right => {
22 | if amplitude > self.right_hand_amplitude_this_frame {
23 | self.right_hand_amplitude_this_frame = amplitude;
24 | }
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/hotham/src/contexts/mod.rs:
--------------------------------------------------------------------------------
1 | #![allow(missing_docs)]
2 | pub mod audio_context;
3 | pub mod gui_context;
4 | pub mod haptic_context;
5 | pub mod input_context;
6 | pub mod physics_context;
7 | pub mod render_context;
8 | pub mod vulkan_context;
9 | pub mod xr_context;
10 |
11 | pub use audio_context::AudioContext;
12 | pub use gui_context::GuiContext;
13 | pub use haptic_context::HapticContext;
14 | pub use input_context::InputContext;
15 | pub use physics_context::PhysicsContext;
16 | pub use render_context::RenderContext;
17 | pub use vulkan_context::VulkanContext;
18 | pub use xr_context::{XrContext, XrContextBuilder};
19 |
--------------------------------------------------------------------------------
/hotham/src/contexts/physics_context.rs:
--------------------------------------------------------------------------------
1 | use rapier3d::{
2 | crossbeam::{self, channel::Receiver},
3 | na::Matrix3x1,
4 | prelude::*,
5 | };
6 |
7 | pub const DEFAULT_COLLISION_GROUP: Group = Group::GROUP_1;
8 | pub const PANEL_COLLISION_GROUP: Group = Group::GROUP_2;
9 | pub const HAND_COLLISION_GROUP: Group = Group::GROUP_3;
10 | pub const WALL_COLLISION_GROUP: Group = Group::GROUP_4;
11 | pub const SENSOR_COLLISION_GROUP: Group = Group::GROUP_5;
12 |
13 | /// TODO: This is *usually* 72fps on the Quest 2, but we may support higher resolutions later.
14 | pub const DELTA_TIME: f32 = 1. / 72.;
15 |
16 | pub struct PhysicsContext {
17 | pub physics_pipeline: PhysicsPipeline,
18 | pub gravity: Matrix3x1,
19 | pub query_pipeline: QueryPipeline,
20 | pub colliders: ColliderSet,
21 | pub broad_phase: BroadPhase,
22 | pub narrow_phase: NarrowPhase,
23 | pub rigid_bodies: RigidBodySet,
24 | pub island_manager: IslandManager,
25 | pub collision_recv: Receiver,
26 | pub contact_force_recv: Receiver,
27 | pub event_handler: ChannelEventCollector,
28 | pub integration_parameters: IntegrationParameters,
29 | pub impulse_joints: ImpulseJointSet,
30 | pub multibody_joints: MultibodyJointSet,
31 | pub ccd_solver: CCDSolver,
32 | }
33 |
34 | impl Default for PhysicsContext {
35 | fn default() -> Self {
36 | let (collision_send, collision_recv) = crossbeam::channel::unbounded();
37 | let (contact_force_send, contact_force_recv) = crossbeam::channel::unbounded();
38 | let event_handler = ChannelEventCollector::new(collision_send, contact_force_send);
39 | let integration_parameters = IntegrationParameters {
40 | dt: DELTA_TIME,
41 | ..Default::default()
42 | };
43 |
44 | let physics_pipeline = PhysicsPipeline::new();
45 | let impulse_joints = ImpulseJointSet::new();
46 | let multibody_joints = MultibodyJointSet::new();
47 | let ccd_solver = CCDSolver::new();
48 |
49 | PhysicsContext {
50 | physics_pipeline,
51 | gravity: [0., 0., 0.].into(),
52 | query_pipeline: QueryPipeline::new(),
53 | colliders: ColliderSet::new(),
54 | broad_phase: BroadPhase::new(),
55 | narrow_phase: NarrowPhase::new(),
56 | rigid_bodies: RigidBodySet::new(),
57 | island_manager: IslandManager::new(),
58 | collision_recv,
59 | contact_force_recv,
60 | event_handler,
61 | integration_parameters,
62 | impulse_joints,
63 | multibody_joints,
64 | ccd_solver,
65 | }
66 | }
67 | }
68 |
69 | impl PhysicsContext {
70 | pub fn update(&mut self) {
71 | self.physics_pipeline.step(
72 | &self.gravity,
73 | &self.integration_parameters,
74 | &mut self.island_manager,
75 | &mut self.broad_phase,
76 | &mut self.narrow_phase,
77 | &mut self.rigid_bodies,
78 | &mut self.colliders,
79 | &mut self.impulse_joints,
80 | &mut self.multibody_joints,
81 | &mut self.ccd_solver,
82 | Some(&mut self.query_pipeline),
83 | &(),
84 | &(),
85 | );
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/hotham/src/contexts/xr_context/time.rs:
--------------------------------------------------------------------------------
1 | use super::XrContext;
2 | use crate::HothamResult;
3 |
4 | #[cfg(target_os = "windows")]
5 | impl XrContext {
6 | pub fn now(&self) -> HothamResult {
7 | if let Some(ext) = &self
8 | .instance
9 | .exts()
10 | .khr_win32_convert_performance_counter_time
11 | {
12 | let mut xr_time = openxr::Time::from_nanos(0);
13 | let performance_counter = get_performance_counter().unwrap();
14 | match unsafe {
15 | (ext.convert_win32_performance_counter_to_time)(
16 | self.instance.as_raw(),
17 | &performance_counter,
18 | &mut xr_time,
19 | )
20 | } {
21 | openxr::sys::Result::SUCCESS => Ok(xr_time),
22 | _ => Err(anyhow::anyhow!(
23 | "OpenXR convert_win32_performance_counter_to_time failed."
24 | )
25 | .into()),
26 | }
27 | } else {
28 | Err(anyhow::anyhow!(
29 | "OpenXR extension khr_win32_convert_performance_counter_time needs to be enabled. \
30 | Enable it via XrContextBuilder::required_extensions()."
31 | )
32 | .into())
33 | }
34 | }
35 | }
36 |
37 | #[cfg(target_os = "windows")]
38 | fn get_performance_counter() -> Result {
39 | unsafe {
40 | let mut time = 0;
41 | windows::Win32::System::Performance::QueryPerformanceCounter(&mut time).ok()?;
42 | Ok(time)
43 | }
44 | }
45 |
46 | #[cfg(not(target_os = "windows"))]
47 | impl XrContext {
48 | pub fn now(&self) -> HothamResult {
49 | if let Some(ext) = &self.instance.exts().khr_convert_timespec_time {
50 | let mut xr_time = openxr::Time::from_nanos(0);
51 | let timespec_time = now_monotonic();
52 | match unsafe {
53 | (ext.convert_timespec_time_to_time)(
54 | self.instance.as_raw(),
55 | ×pec_time,
56 | &mut xr_time,
57 | )
58 | } {
59 | openxr::sys::Result::SUCCESS => Ok(xr_time),
60 | _ => Err(anyhow::anyhow!("OpenXR convert_timespec_time_to_time failed.").into()),
61 | }
62 | } else {
63 | Err(anyhow::anyhow!(
64 | "OpenXR extension khr_convert_timespec_time needs to be enabled. \
65 | Enable it via XrContextBuilder::required_extensions()."
66 | )
67 | .into())
68 | }
69 | }
70 | }
71 |
72 | #[cfg(not(target_os = "windows"))]
73 | fn now_monotonic() -> libc::timespec {
74 | let mut time = libc::timespec {
75 | tv_sec: 0,
76 | tv_nsec: 0,
77 | };
78 | let ret = unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut time) };
79 | assert!(ret == 0);
80 | time
81 | }
82 |
--------------------------------------------------------------------------------
/hotham/src/hotham_error.rs:
--------------------------------------------------------------------------------
1 | use ash::vk::Result as VulkanResult;
2 | use openxr::sys::Result as OpenXRResult;
3 | use thiserror::Error;
4 |
5 | /// Hotham Error type
6 | #[derive(Error, Debug)]
7 | pub enum HothamError {
8 | /// OpenXR error
9 | #[error("There was a problem with an OpenXR operation")]
10 | OpenXRError(#[from] OpenXRResult),
11 | /// Vulkan error
12 | #[error("There was a problem with a Vulkan operation")]
13 | VulkanError(#[from] VulkanResult),
14 | /// An empty list // TODO: useless
15 | #[error("The list was empty")]
16 | EmptyListError,
17 | /// Unsupported version
18 | #[error("the version of vulkan or openxr is not supported")]
19 | UnsupportedVersionError,
20 | /// Invalid format
21 | #[error("The format provided - {format:?} - is not supported for this operation")]
22 | InvalidFormatError {
23 | /// The format that was invalid
24 | format: String,
25 | },
26 | /// Engine shutting down
27 | #[error("The engine is shutting down")]
28 | ShuttingDown,
29 | /// IO error
30 | #[error(transparent)]
31 | IO(#[from] std::io::Error),
32 | /// Not rendering yet
33 | #[error("this session is not rendering yet")]
34 | NotRendering,
35 | /// Some other error
36 | #[error(transparent)]
37 | Other(#[from] anyhow::Error),
38 | }
39 |
--------------------------------------------------------------------------------
/hotham/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![deny(missing_docs)]
2 | // TODO Safety doc would be nice
3 | #![allow(clippy::missing_safety_doc)]
4 |
5 | //! G'day, and welcome to Hotham! 👋
6 | //!
7 | //! Hotham is an attempt to create a lightweight, high performance game engine for mobile VR headsets. It's primarily aimed at small (1-5 person) teams of mostly technical folk who are looking to create games for devices like the Oculus Quest, but find existing tools cumbersome to work with. You can learn more about the project [in the FAQ](https://github.com/leetvr/hotham/wiki/FAQ).
8 | //!
9 | //! # Getting started
10 | //! Hotham is a complex project with many moving parts! Have no fear - we've written an easy to follow [Getting Started guide](https://github.com/leetvr/hotham/wiki/Getting-started) that will have you running our example application in no time. Head on over to [getting started](https://github.com/leetvr/hotham/wiki/Getting-started) to.. get.. started.
11 | //!
12 | //! # Sponsoring
13 | //! Hotham's development is only possible thanks to the support of the community. It's currently being developed on full time by [@kanerogers](https://github.com/kanerogers) If you'd like to help make VR development in Rust possible, please [consider becoming a donor](https://github.com/sponsors/leetvr). 💗
14 |
15 | pub use anyhow;
16 | pub use ash;
17 | pub use ash::vk;
18 | pub use openxr as xr;
19 | pub use vk_shader_macros;
20 |
21 | pub use engine::{Engine, EngineBuilder, TickData};
22 | pub use glam;
23 | pub use hecs;
24 | pub use hotham_error::HothamError;
25 | pub use id_arena;
26 | pub use rapier3d::na;
27 |
28 | /// Components are data that are used to update the simulation and interact with the external world
29 | pub mod components;
30 | mod engine;
31 |
32 | /// A tool to import models from glTF files into Hotham
33 | pub mod asset_importer;
34 | /// Contexts are wrappers around some external state that the engine will interact with
35 | pub mod contexts;
36 | mod hotham_error;
37 | /// Systems are functions called each frame to update either the external state or the current simulation
38 | pub mod systems;
39 |
40 | /// Kitchen sink utility functions
41 | pub mod util;
42 |
43 | /// Functionality used by the rendering engine
44 | pub mod rendering;
45 | mod workers;
46 |
47 | /// Hotham result type
48 | pub type HothamResult = std::result::Result;
49 |
50 | /// Format used for color textures
51 | pub const COLOR_FORMAT: vk::Format = vk::Format::R8G8B8A8_SRGB;
52 | /// Format used for depth textures
53 | pub const DEPTH_FORMAT: vk::Format = vk::Format::D32_SFLOAT;
54 |
55 | /// Number of views
56 | pub const VIEW_COUNT: u32 = 2;
57 |
58 | /// Swapchain length
59 | pub const SWAPCHAIN_LENGTH: usize = 3;
60 |
61 | /// OpenXR view type
62 | pub const VIEW_TYPE: xr::ViewConfigurationType = xr::ViewConfigurationType::PRIMARY_STEREO;
63 |
64 | /// OpenXR blend mode
65 | pub const BLEND_MODE: xr::EnvironmentBlendMode = xr::EnvironmentBlendMode::OPAQUE;
66 |
--------------------------------------------------------------------------------
/hotham/src/rendering/camera.rs:
--------------------------------------------------------------------------------
1 | use glam::{Affine3A, Mat4, Vec4};
2 | use openxr as xr;
3 |
4 | use crate::util::affine_from_posef;
5 |
6 | #[derive(Debug, Clone)]
7 | /// The Camera, or View, in a scene.
8 | pub struct Camera {
9 | /// The camera's pose in globally oriented stage space
10 | pub gos_from_view: Affine3A,
11 | /// The view matrix
12 | pub view_from_gos: Mat4,
13 | }
14 |
15 | impl Default for Camera {
16 | fn default() -> Self {
17 | Self {
18 | gos_from_view: Affine3A::IDENTITY,
19 | view_from_gos: Mat4::IDENTITY,
20 | }
21 | }
22 | }
23 |
24 | impl Camera {
25 | /// Update the camera's position from an OpenXR view
26 | pub fn update(&mut self, view: &xr::View, gos_from_stage: &Affine3A) -> Mat4 {
27 | // Convert values from OpenXR format and use globally oriented stage space instead of stage space
28 | let stage_from_view = affine_from_posef(view.pose);
29 | self.gos_from_view = *gos_from_stage * stage_from_view;
30 |
31 | self.view_from_gos = self.build_matrix();
32 | self.view_from_gos
33 | }
34 |
35 | /// Get the camera's position in homogenous coordinates
36 | pub fn position_in_gos(&self) -> Vec4 {
37 | let p = self.gos_from_view.translation;
38 | [p[0], p[1], p[2], 1.].into()
39 | }
40 |
41 | /// Build the camera's view matrix
42 | pub fn build_matrix(&self) -> Mat4 {
43 | self.gos_from_view.inverse().into()
44 | }
45 | }
46 |
47 | #[derive(Debug, Copy, Clone)]
48 | /// A frustrum for the virtual camera.
49 | pub struct Frustum {
50 | /// The left angle
51 | pub left: f32,
52 | /// The right angle
53 | pub right: f32,
54 | /// The top angle
55 | pub up: f32,
56 | /// The bottom angle
57 | pub down: f32,
58 | }
59 |
60 | impl Frustum {
61 | #[rustfmt::skip]
62 | /// Compute right-handed y-up inverse Z perspective projection matrix
63 | pub fn projection(&self, znear: f32) -> Mat4 {
64 | // Based on http://dev.theomader.com/depth-precision/ + OpenVR docs
65 | let left = self.left.tan();
66 | let right = self.right.tan();
67 | let down = self.down.tan();
68 | let up = self.up.tan();
69 | let idx = 1.0 / (right - left);
70 | let idy = 1.0 / (down - up);
71 | let sx = right + left;
72 | let sy = down + up;
73 |
74 | // TODO: This was originally written using nalgebra's row-order format, so we just
75 | // transpose the resulting matrix. We should probably just.. you know, rewrite this.
76 | Mat4::from_cols_array(&[
77 | 2.0 * idx, 0.0, sx * idx, 0.0,
78 | 0.0, 2.0 * idy, sy * idy, 0.0,
79 | 0.0, 0.0, 0.0, znear,
80 | 0.0, 0.0, -1.0, 0.0]).transpose()
81 | }
82 | }
83 |
84 | impl From for Frustum {
85 | fn from(x: xr::Fovf) -> Self {
86 | Self {
87 | left: x.angle_left,
88 | right: x.angle_right,
89 | up: x.angle_up,
90 | down: x.angle_down,
91 | }
92 | }
93 | }
94 |
95 | /// Normals of the clipping planes are pointing towards the inside of the frustum.
96 | /// We are only using four planes per camera. The near and far planes are not used.
97 | /// This link points to a paper describing the math behind these expressions:
98 | /// https://www.gamedevs.org/uploads/fast-extraction-viewing-frustum-planes-from-world-view-projection-matrix.pdf
99 | pub(crate) fn extract_planes_from_frustum(frustum: &Mat4) -> Mat4 {
100 | // Glam doesn't support creating matrices from rows, so we'll just use columns and transpose.
101 | Mat4::from_cols(
102 | normalize_plane(frustum.row(3) + frustum.row(0)),
103 | normalize_plane(frustum.row(3) - frustum.row(0)),
104 | normalize_plane(frustum.row(3) + frustum.row(1)),
105 | normalize_plane(frustum.row(3) - frustum.row(1)),
106 | )
107 | .transpose()
108 | }
109 |
110 | pub(crate) fn normalize_plane(p: Vec4) -> Vec4 {
111 | p / p.truncate().length()
112 | }
113 |
--------------------------------------------------------------------------------
/hotham/src/rendering/frame.rs:
--------------------------------------------------------------------------------
1 | use ash::vk;
2 |
3 | use crate::contexts::{render_context::CullParams, VulkanContext};
4 | use anyhow::Result;
5 |
6 | use super::{
7 | buffer::Buffer,
8 | descriptors::{
9 | Descriptors, CULL_PARAMS_BINDING, DRAW_DATA_BINDING, PRIMITIVE_CULL_DATA_BINDING,
10 | SCENE_DATA_BINDING,
11 | },
12 | resources::{DrawData, PrimitiveCullData},
13 | scene_data::SceneData,
14 | };
15 |
16 | // We *can* draw this many objects, but.. seriously?
17 | static DRAW_DATA_BUFFER_SIZE: usize = 5000;
18 |
19 | // We *can* draw this many objects, but.. seriously?
20 | static PRIMITIVE_CULL_DATA_BUFFER_SIZE: usize = 100_000;
21 |
22 | /// A container for all the resources necessary to render a single frame.
23 | #[derive(Debug, Clone)]
24 | pub struct Frame {
25 | /// The fence used to signal when the frame has completed rendering
26 | pub fence: vk::Fence,
27 | /// A command buffer used to record commands
28 | pub command_buffer: vk::CommandBuffer,
29 | /// The fence used to signal when the frame has completed rendering
30 | pub compute_fence: vk::Fence,
31 | /// A command buffer used to record commands
32 | pub compute_command_buffer: vk::CommandBuffer,
33 | /// Data for the primitives that will be drawn this frame, indexed by gl_InstanceId
34 | pub draw_data_buffer: Buffer,
35 | /// The actual draw calls for this frame.
36 | pub primitive_cull_data_buffer: Buffer,
37 | /// Shared data used in a scene
38 | pub scene_data_buffer: Buffer,
39 | /// Shared data used in a scene
40 | pub cull_params_buffer: Buffer,
41 | }
42 |
43 | impl Frame {
44 | pub(crate) fn new(
45 | vulkan_context: &VulkanContext,
46 | index: usize,
47 | descriptors: &Descriptors,
48 | ) -> Result {
49 | let device = &vulkan_context.device;
50 | let command_pool = vulkan_context.command_pool;
51 |
52 | let fence = unsafe {
53 | device.create_fence(
54 | &vk::FenceCreateInfo::builder().flags(vk::FenceCreateFlags::SIGNALED),
55 | None,
56 | )
57 | }?;
58 | let compute_fence = unsafe { device.create_fence(&vk::FenceCreateInfo::builder(), None) }?;
59 |
60 | let command_buffers = unsafe {
61 | device.allocate_command_buffers(
62 | &vk::CommandBufferAllocateInfo::builder()
63 | .command_buffer_count(2)
64 | .level(vk::CommandBufferLevel::PRIMARY)
65 | .command_pool(command_pool),
66 | )
67 | }?;
68 |
69 | let command_buffer = command_buffers[0];
70 | let compute_command_buffer = command_buffers[1];
71 |
72 | let draw_data_buffer = unsafe {
73 | Buffer::new(
74 | vulkan_context,
75 | vk::BufferUsageFlags::STORAGE_BUFFER,
76 | DRAW_DATA_BUFFER_SIZE,
77 | )
78 | };
79 | let primitive_cull_data_buffer = unsafe {
80 | Buffer::new(
81 | vulkan_context,
82 | vk::BufferUsageFlags::STORAGE_BUFFER,
83 | PRIMITIVE_CULL_DATA_BUFFER_SIZE,
84 | )
85 | };
86 | let mut scene_data_buffer =
87 | unsafe { Buffer::new(vulkan_context, vk::BufferUsageFlags::UNIFORM_BUFFER, 1) };
88 | let cull_params_buffer =
89 | unsafe { Buffer::new(vulkan_context, vk::BufferUsageFlags::UNIFORM_BUFFER, 1) };
90 |
91 | // Update the descriptor sets for this frame.
92 | unsafe {
93 | // Graphics
94 | draw_data_buffer.update_descriptor_set(
95 | &vulkan_context.device,
96 | descriptors.sets[index],
97 | DRAW_DATA_BINDING,
98 | );
99 | scene_data_buffer.update_descriptor_set(
100 | &vulkan_context.device,
101 | descriptors.sets[index],
102 | SCENE_DATA_BINDING,
103 | );
104 |
105 | // Compute
106 | primitive_cull_data_buffer.update_descriptor_set(
107 | &vulkan_context.device,
108 | descriptors.compute_sets[index],
109 | PRIMITIVE_CULL_DATA_BINDING,
110 | );
111 | cull_params_buffer.update_descriptor_set(
112 | &vulkan_context.device,
113 | descriptors.compute_sets[index],
114 | CULL_PARAMS_BINDING,
115 | );
116 |
117 | // Add some default data to the scene buffer.
118 | scene_data_buffer.push(&Default::default());
119 | }
120 |
121 | Ok(Self {
122 | fence,
123 | compute_fence,
124 | command_buffer,
125 | compute_command_buffer,
126 | draw_data_buffer,
127 | primitive_cull_data_buffer,
128 | scene_data_buffer,
129 | cull_params_buffer,
130 | })
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/hotham/src/rendering/image.rs:
--------------------------------------------------------------------------------
1 | use ash::vk;
2 |
3 | /// Thin wrapper around a locally created Vulkan image.
4 | #[derive(Debug, Clone)]
5 | pub struct Image {
6 | /// Handle to the underlying Vulkan image
7 | pub handle: vk::Image,
8 | /// Handle to an ImageView for this image
9 | pub view: vk::ImageView,
10 | /// Handle to the underlying image
11 | pub device_memory: vk::DeviceMemory,
12 | /// The extent (size) of this image
13 | pub extent: vk::Extent2D,
14 | /// Flags to indicate to the renderer how this image will be used
15 | pub usage: vk::ImageUsageFlags,
16 | /// The format of this image
17 | pub format: vk::Format,
18 | /// The type of ImageView
19 | pub view_type: vk::ImageViewType,
20 | /// The number of layers in the image
21 | pub layer_count: u32,
22 | }
23 |
24 | impl Image {
25 | /// Create a new image
26 | /// TODO: At the moment the "logic" for this function is handled by `vulkan_context`. We should change that.
27 | #[cfg_attr(feature = "cargo-clippy", allow(clippy::too_many_arguments))]
28 | pub fn new(
29 | handle: vk::Image,
30 | view: vk::ImageView,
31 | device_memory: vk::DeviceMemory,
32 | extent: vk::Extent2D,
33 | usage: vk::ImageUsageFlags,
34 | format: vk::Format,
35 | view_type: vk::ImageViewType,
36 | layer_count: u32,
37 | ) -> Self {
38 | Self {
39 | handle,
40 | view,
41 | device_memory,
42 | extent,
43 | usage,
44 | format,
45 | view_type,
46 | layer_count,
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/hotham/src/rendering/legacy_buffer.rs:
--------------------------------------------------------------------------------
1 | #![allow(deprecated)]
2 |
3 | use anyhow::Result;
4 | use ash::vk;
5 | use std::marker::PhantomData;
6 |
7 | use crate::contexts::VulkanContext;
8 |
9 | /// A thin wrapper around `vk::Buffer`
10 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
11 | #[deprecated]
12 | pub struct Buffer {
13 | /// Handle to the underlying buffer
14 | pub handle: vk::Buffer,
15 | /// Handle to the underlying memory
16 | pub device_memory: vk::DeviceMemory,
17 | /// Marker for the type
18 | pub _phantom: PhantomData,
19 | /// Size of the data
20 | pub size: vk::DeviceSize,
21 | /// Size of the underlying buffer
22 | pub device_memory_size: vk::DeviceSize,
23 | /// Usage flags for the buffer
24 | pub usage: vk::BufferUsageFlags,
25 | }
26 |
27 | impl Buffer
28 | where
29 | T: Sized + Copy,
30 | {
31 | /// Create a new buffer from a slice of data.
32 | pub fn new(
33 | vulkan_context: &VulkanContext,
34 | data: &[T],
35 | usage: vk::BufferUsageFlags,
36 | ) -> Result {
37 | let size = std::mem::size_of_val(data) as vk::DeviceSize;
38 | let (handle, device_memory, device_memory_size) =
39 | vulkan_context.create_buffer_with_data(data, usage, size)?;
40 |
41 | Ok(Self {
42 | handle,
43 | device_memory,
44 | size,
45 | device_memory_size,
46 | usage,
47 | _phantom: PhantomData,
48 | })
49 | }
50 |
51 | /// **NOTE**: If passing in a Vec, you MUST use vec.as_ptr(), passing in
52 | /// a reference will result in A Very Bad Time.
53 | pub fn update(&self, vulkan_context: &VulkanContext, data: &[T]) -> Result<()> {
54 | vulkan_context.update_buffer(
55 | data,
56 | self.device_memory,
57 | self.device_memory_size,
58 | self.usage,
59 | )
60 | }
61 | }
62 |
63 | // TODO: Need to be able to drop Buffers
64 | // impl Buffer {
65 | // pub(crate) fn destroy(&self, vulkan_context: &VulkanContext) -> () {
66 | // let device = &vulkan_context.device;
67 | // unsafe {
68 | // device.destroy_buffer(self.handle, None);
69 | // device.free_memory(self.device_memory, None);
70 | // };
71 | // }
72 | // }
73 |
--------------------------------------------------------------------------------
/hotham/src/rendering/memory.rs:
--------------------------------------------------------------------------------
1 | use crate::contexts::VulkanContext;
2 | use ash::vk;
3 |
4 | pub(crate) unsafe fn allocate_memory(
5 | vulkan_context: &VulkanContext,
6 | memory_requirements: vk::MemoryRequirements,
7 | memory_property_flags: vk::MemoryPropertyFlags,
8 | ) -> vk::DeviceMemory {
9 | let instance = &vulkan_context.instance;
10 | let device = &vulkan_context.device;
11 | let physical_device = vulkan_context.physical_device;
12 |
13 | let memory_type_bits_requirement = memory_requirements.memory_type_bits;
14 | let memory_properties = instance.get_physical_device_memory_properties(physical_device);
15 | let memory_type_index = find_memory_type_index(
16 | memory_properties,
17 | memory_type_bits_requirement,
18 | memory_property_flags,
19 | );
20 | println!("[HOTHAM_VULKAN] Using memory type {memory_type_index}");
21 | device
22 | .allocate_memory(
23 | &vk::MemoryAllocateInfo::builder()
24 | .allocation_size(memory_requirements.size)
25 | .memory_type_index(memory_type_index as _),
26 | None,
27 | )
28 | .unwrap()
29 | }
30 |
31 | fn find_memory_type_index(
32 | memory_properties: vk::PhysicalDeviceMemoryProperties,
33 | memory_type_bits_requirement: u32,
34 | memory_property_flags: vk::MemoryPropertyFlags,
35 | ) -> usize {
36 | let mut memory_type_index = !0;
37 | for memory_index in 0..memory_properties.memory_type_count as usize {
38 | let memory_type_bits: u32 = 1 << memory_index;
39 | let is_required_memory_type = (memory_type_bits_requirement & memory_type_bits) != 0;
40 | let properties = memory_properties.memory_types[memory_index].property_flags;
41 |
42 | if is_required_memory_type && properties.contains(memory_property_flags) {
43 | memory_type_index = memory_index;
44 | break;
45 | }
46 | }
47 | if memory_type_index == !0 {
48 | panic!("Unable to find suitable memory!")
49 | }
50 | memory_type_index
51 | }
52 |
--------------------------------------------------------------------------------
/hotham/src/rendering/mesh_data.rs:
--------------------------------------------------------------------------------
1 | use crate::{asset_importer::ImportContext, rendering::primitive::Primitive};
2 | /// Wrapper that encapsulates geometry. Maps closely to the [glTF spec](https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#meshes)
3 | /// Usually automatically added by `gltf_loader`.
4 | #[derive(Debug, Clone, PartialEq)]
5 | pub struct MeshData {
6 | /// The primitives in this mesh (eg. actual geometry)
7 | pub primitives: Vec,
8 | }
9 |
10 | impl MeshData {
11 | /// Create new MeshData from a list of Primitives
12 | pub fn new(primitives: Vec) -> Self {
13 | Self { primitives }
14 | }
15 |
16 | /// Takes mesh data from a glTF file, uploads it to the GPU and inserts it into mesh_map
17 | pub(crate) fn load(mesh: gltf::Mesh, import_context: &mut ImportContext) -> Self {
18 | let mesh_name = mesh
19 | .name()
20 | .map(|s| s.to_string())
21 | .unwrap_or(format!("Mesh {}", mesh.index()));
22 |
23 | let primitives = mesh
24 | .primitives()
25 | .map(|p| Primitive::load(p, import_context, &mesh_name))
26 | .collect::>();
27 |
28 | MeshData { primitives }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/hotham/src/rendering/mod.rs:
--------------------------------------------------------------------------------
1 | /// Legacy wrapper around GPU buffers
2 | pub mod legacy_buffer;
3 |
4 | /// Buffers are used to transfer data to the GPU
5 | pub mod buffer;
6 |
7 | /// Functionality for interacting with GPU memory
8 | pub mod memory;
9 |
10 | /// The virtual camera
11 | pub mod camera;
12 |
13 | /// A wrapper around the frame-dependent resources
14 | pub mod frame;
15 |
16 | /// A wrapper around an image
17 | pub mod image;
18 |
19 | /// Shared data for a scene
20 | pub mod scene_data;
21 |
22 | /// Helper wrapper for interacting with the swapchain
23 | pub mod swapchain;
24 |
25 | /// Geometry
26 | pub mod primitive;
27 |
28 | /// Functionality for adding textures (images) to meshes
29 | pub mod texture;
30 |
31 | /// Vertex representation
32 | pub mod vertex;
33 |
34 | /// A wrapper for all Descriptor related functionality
35 | pub(crate) mod descriptors;
36 |
37 | /// Container for all the resources used to render objects
38 | pub mod resources;
39 |
40 | /// Data to instruct the renderer how a primitive should look
41 | pub mod material;
42 |
43 | /// Lights and related functionality
44 | pub mod light;
45 | /// Wrapper around geometry data.
46 | pub mod mesh_data;
47 |
--------------------------------------------------------------------------------
/hotham/src/rendering/scene_data.rs:
--------------------------------------------------------------------------------
1 | use glam::{Mat4, Vec4};
2 | use serde::{Deserialize, Serialize};
3 |
4 | use super::light::{Light, MAX_LIGHTS};
5 |
6 | /// The amount of Image Based Lighting (IBL) to show in the scene
7 | pub const DEFAULT_IBL_INTENSITY: f32 = 1.0;
8 |
9 | /// Data about the current scene. Sent to the vertex and fragment shaders
10 | #[derive(Deserialize, Serialize, Clone, Debug)]
11 | #[repr(C)]
12 | pub struct SceneData {
13 | /// View-Projection matrices (one per eye)
14 | pub view_projection: [Mat4; 2],
15 | /// Position of the cameras (one per eye)
16 | pub camera_position: [Vec4; 2],
17 | /// Scene Parameters - x = IBL intensity, y = unused, z = debug render inputs, w = debug render algorithm
18 | pub params: Vec4,
19 | /// Dynamic punctual lights
20 | pub lights: [Light; MAX_LIGHTS],
21 | }
22 |
23 | impl Default for SceneData {
24 | fn default() -> Self {
25 | Self {
26 | view_projection: [Mat4::IDENTITY, Mat4::IDENTITY],
27 | camera_position: [Vec4::ZERO, Vec4::ZERO],
28 | params: [DEFAULT_IBL_INTENSITY, 0., 0., 0.].into(),
29 | lights: [Light::none(), Light::none(), Light::none(), Light::none()],
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/hotham/src/rendering/vertex.rs:
--------------------------------------------------------------------------------
1 | use ash::vk;
2 | use glam::{Vec2, Vec3, Vec4};
3 | // const VERTEX_FORMAT: vk::Format = vk::Format::R16G16B16_SFLOAT;
4 | const VERTEX_FORMAT: vk::Format = vk::Format::R32G32B32_SFLOAT;
5 |
6 | /// Representation of a single vertex, usually imported from a glTF file.
7 | #[repr(C)]
8 | #[derive(Clone, Debug, Copy, PartialEq, Default)]
9 | pub struct Vertex {
10 | // /// Position in model space
11 | // pub position: Vec3,
12 | /// Normal in model space
13 | pub normal: Vec3,
14 | /// First set of texture coordinates
15 | pub texture_coords: Vec2,
16 | /// Joint indices (for skinning), one byte per index.
17 | pub joint_indices: u32,
18 | /// Joint weights (for skinning), one byte per weight.
19 | pub joint_weights: u32,
20 | }
21 |
22 | impl Vertex {
23 | /// Create a new vertex
24 | pub fn new(normal: Vec3, texture_coords: Vec2, joint_indices: u32, joint_weights: u32) -> Self {
25 | Self {
26 | // position,
27 | normal,
28 | texture_coords,
29 | joint_indices,
30 | joint_weights,
31 | }
32 | }
33 |
34 | /// Create a new vertex from a zip - useful when importing from glTF
35 | // Clippy warning suppressed for adjudication separately
36 | #[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))]
37 | pub fn from_zip(t: (Vec3, Vec2, [u8; 4], Vec4)) -> Self {
38 | // Normalize weights to 0 <= w <= 255 while avoiding division with zero.
39 | let max_weight = t.3.max_element().max(f32::EPSILON);
40 | let weight_normalization = 255.0 / max_weight;
41 | Vertex::new(
42 | t.0,
43 | t.1,
44 | // Pack indices into one u32 with one byte per index.
45 | (t.2[0] as u32)
46 | + (t.2[1] as u32) * 256
47 | + (t.2[2] as u32) * 256 * 256
48 | + (t.2[3] as u32) * 256 * 256 * 256,
49 | // Pack weights into one u32 with one byte per weight.
50 | ((t.3[0] * weight_normalization).round() as u32)
51 | + ((t.3[1] * weight_normalization).round() as u32) * 256
52 | + ((t.3[2] * weight_normalization).round() as u32) * 256 * 256
53 | + ((t.3[3] * weight_normalization).round() as u32) * 256 * 256 * 256,
54 | )
55 | }
56 | }
57 |
58 | impl Vertex {
59 | /// Get the vertex attributes to be used in the Vertex Shader
60 | pub fn attribute_descriptions() -> Vec {
61 | let position = vk::VertexInputAttributeDescription::builder()
62 | .binding(0)
63 | .location(0)
64 | .format(VERTEX_FORMAT)
65 | .offset(0)
66 | .build();
67 |
68 | let normal = vk::VertexInputAttributeDescription::builder()
69 | .binding(1)
70 | .location(1)
71 | .format(vk::Format::R32G32B32_SFLOAT)
72 | .offset(memoffset::offset_of!(Vertex, normal) as _)
73 | .build();
74 |
75 | let texture_coords = vk::VertexInputAttributeDescription::builder()
76 | .binding(1)
77 | .location(2)
78 | .format(vk::Format::R32G32_SFLOAT)
79 | .offset(memoffset::offset_of!(Vertex, texture_coords) as _)
80 | .build();
81 |
82 | let joint_indices = vk::VertexInputAttributeDescription::builder()
83 | .binding(1)
84 | .location(3)
85 | .format(vk::Format::R32_UINT)
86 | .offset(memoffset::offset_of!(Vertex, joint_indices) as _)
87 | .build();
88 |
89 | let joint_weights = vk::VertexInputAttributeDescription::builder()
90 | .binding(1)
91 | .location(4)
92 | .format(vk::Format::R32_UINT)
93 | .offset(memoffset::offset_of!(Vertex, joint_weights) as _)
94 | .build();
95 |
96 | vec![
97 | position,
98 | normal,
99 | texture_coords,
100 | joint_indices,
101 | joint_weights,
102 | ]
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/hotham/src/shaders/brdf.glsl:
--------------------------------------------------------------------------------
1 | // Filament's lambertian term is 1.0 / PI, so we just pre-compute that
2 | #define BRDF_LAMBERTIAN F16(0.3183098862)
3 |
4 | // The following equation models the Fresnel reflectance term of the spec equation (aka F())
5 | // Implementation of fresnel from [4], Equation 15
6 |
7 | // Anything less than 2% is physically impossible and is instead considered to be shadowing. Compare to "Real-Time-Rendering" 4th edition on page 325.
8 | const f16vec3 f90 = V16(1.0);
9 |
10 | // f16vec3 F_Schlick(f16vec3 f0, float16_tVdotH) {
11 | // return f0 + (f90 - f0) * pow(clamp(1.0 - VdotH, 0.0, 1.0), 5.0);
12 | // }
13 |
14 | // Smith Joint GGX
15 | // Note: Vis = G / (4 * NdotL * NdotV)
16 | // see Eric Heitz. 2014. Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs. Journal of Computer Graphics Techniques, 3
17 | // see Real-Time Rendering. Page 331 to 336.
18 | // see https://google.github.io/filament/Filament.md.html#materialsystem/specularbrdf/geometricshadowing(specularg)
19 | // float16_t V_GGX(float16_t NdotL, float16_t NdotV, float16_t alphaRoughness) {
20 | // float16_t alphaRoughnessSq = alphaRoughness * alphaRoughness;
21 |
22 | // float16_t GGXV = NdotL * sqrt(NdotV * NdotV * (1.0 - alphaRoughnessSq) + alphaRoughnessSq);
23 | // float16_t GGXL = NdotV * sqrt(NdotL * NdotL * (1.0 - alphaRoughnessSq) + alphaRoughnessSq);
24 |
25 | // float16_t GGX = GGXV + GGXL;
26 | // if (GGX > 0.0) {
27 | // return 0.5 / GGX;
28 | // }
29 | // return 0.0;
30 | // }
31 |
32 | float16_t D_GGX(float16_t roughness, f16vec3 n, float16_t NdotH, f16vec3 h) {
33 | f16vec3 NxH = cross(n, h);
34 | float16_t oneMinusNoHSquared = dot(NxH, NxH);
35 |
36 | float16_t a = NdotH * roughness;
37 | float16_t k = roughness / (oneMinusNoHSquared + a * a);
38 | float16_t d = k * k * BRDF_LAMBERTIAN;
39 | return saturateMediump(d);
40 | }
41 |
42 | float16_t V_SmithGGXCorrelated_Fast(float16_t roughness, float16_t NdotV, float16_t NdotL) {
43 | // Hammon 2017, "PBR Diffuse Lighting for GGX+Smith Microsurfaces"
44 | float16_t v = F16(0.5) / mix(F16(2.0) * NdotL * NdotV, NdotL + NdotV, roughness);
45 | return saturateMediump(v);
46 | }
47 |
48 | // KR: why is this VdotH instead of LdotH?
49 | f16vec3 F_Schlick(const f16vec3 f0, float16_t VdotH) {
50 | float16_t f = pow(F16(1.0) - VdotH, F16(5.0));
51 | return f + f0 * (F16(1.0) - f);
52 | }
53 |
54 | // // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#acknowledgments AppendixB
55 | // f16vec3 BRDF_specularGGX(f16vec3 f0, float16_talphaRoughness, float16_tVdotH, float16_tNdotL, float16_tNdotV, float16_tNdotH) {
56 | // f16vec3 F = F_Schlick(f0, VdotH);
57 | // float16_tVis = V_GGX(NdotL, NdotV, alphaRoughness);
58 | // float16_tD = D_GGX(NdotH, alphaRoughness);
59 |
60 | // return F * Vis * D;
61 | // }
62 |
63 | f16vec3 BRDF_specular(const f16vec3 f0, const float16_t roughness, f16vec3 h, const f16vec3 n, float16_t NdotV, float16_t NdotL, float16_t NdotH, float16_t LdotH) {
64 | float16_t D = D_GGX(roughness, n, NdotH, h);
65 | float16_t V = V_SmithGGXCorrelated_Fast(roughness, NdotV, NdotL);
66 | f16vec3 F = F_Schlick(f0, LdotH);
67 |
68 | return (D * V) * F;
69 | }
70 |
--------------------------------------------------------------------------------
/hotham/src/shaders/common.glsl:
--------------------------------------------------------------------------------
1 | #extension GL_ARB_separate_shader_objects : enable
2 | #extension GL_EXT_multiview : enable
3 |
4 | #define NOT_PRESENT 4294967295
5 | #define MAX_JOINTS 64
6 |
7 | #define PI F16(3.14159265359)
8 | #define HALF_PI F16(1.570796327)
9 | #define MEDIUMP_FLT_MAX F16(65504.0)
10 | #define MEDIUMP_FLT_MIN F16(0.00006103515625)
11 | #define saturateMediump(x) min(x, MEDIUMP_FLT_MAX)
12 | #define F16(x) float16_t(x)
13 | #define V16(x) f16vec3(x)
14 | #define saturate(x) clamp(x, F16(0), F16(1))
15 |
16 | // Representation of a light in a scene, based on the KHR_lights_punctual extension:
17 | // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
18 | // TODO: make these f16
19 | struct Light {
20 | vec3 direction;
21 | float falloff;
22 |
23 | vec3 color;
24 | float intensity;
25 |
26 | vec3 position;
27 | float lightAngleScale;
28 |
29 | float lightAngleOffset;
30 | uint type;
31 | };
32 |
33 | const uint LightType_Directional = 0;
34 | const uint LightType_Point = 1;
35 | const uint LightType_Spot = 2;
36 |
37 | layout (set = 0, binding = 2) readonly uniform SceneData {
38 | mat4 viewProjection[2];
39 | vec4 cameraPosition[2];
40 | vec4 params;
41 | Light lights[4];
42 | } sceneData;
43 |
--------------------------------------------------------------------------------
/hotham/src/shaders/culling.comp:
--------------------------------------------------------------------------------
1 | #version 460
2 |
3 | layout (local_size_x = 1024) in;
4 | struct VkDrawIndexedIndirectCommand {
5 | uint indexCount;
6 | uint instanceCount;
7 | uint firstIndex;
8 | int vertexOffset;
9 | uint firstInstance;
10 | };
11 |
12 | struct PrimitiveCullData {
13 | vec4 boundingSphere;
14 | uint indexInstance;
15 | uint indexOffset;
16 | bool visible;
17 | };
18 |
19 | layout(std430, set = 0, binding = 0) buffer block {
20 | PrimitiveCullData data[];
21 | } primitiveCullDataBuffer;
22 |
23 | layout(set = 0, binding = 1) uniform CullData {
24 | mat4 leftClipPlanes;
25 | mat4 rightClipPlanes;
26 | uint drawCalls;
27 | } cullData;
28 |
29 | void main() {
30 | uint id = gl_GlobalInvocationID.x;
31 |
32 | if (id >= cullData.drawCalls) { return; }
33 |
34 | PrimitiveCullData d = primitiveCullDataBuffer.data[id];
35 | vec4 center4 = vec4(d.boundingSphere.xyz, 1);
36 | vec4 negRadius4 = -d.boundingSphere.wwww;
37 |
38 | // Perform a plane intersection check against each eye's clip plane.
39 | // If the primitive is visible in either eye, we consider it visible.
40 | primitiveCullDataBuffer.data[id].visible = any(bvec2(
41 | all(greaterThan(cullData.leftClipPlanes * center4, negRadius4)),
42 | all(greaterThan(cullData.rightClipPlanes * center4, negRadius4))
43 | ));
44 | }
45 |
--------------------------------------------------------------------------------
/hotham/src/shaders/gui.frag:
--------------------------------------------------------------------------------
1 | #version 450
2 |
3 | layout (location = 0) in vec4 inColor;
4 | layout (location = 1) in vec2 inUV;
5 |
6 | layout (location = 0) out vec4 outColor;
7 |
8 | layout (binding = 0, set = 0) uniform sampler2D font_texture;
9 |
10 | void main() {
11 | outColor = inColor * texture(font_texture, inUV);
12 | }
13 |
--------------------------------------------------------------------------------
/hotham/src/shaders/gui.vert:
--------------------------------------------------------------------------------
1 | // Adapted from https://github.com/MatchaChoco010/egui-winit-ash-integration/blob/main/src/shaders/src/vert.vert
2 | #version 450
3 |
4 | layout (location = 0) in vec2 inPos;
5 | layout (location = 1) in vec2 inUV;
6 | layout (location = 2) in vec4 inColor;
7 |
8 | layout (location = 0) out vec4 outColor;
9 | layout (location = 1) out vec2 outUV;
10 |
11 | layout (push_constant) uniform PushConstants {
12 | vec2 screen_size;
13 | } pushConstants;
14 |
15 | vec3 srgb_to_linear(vec3 srgb) {
16 | bvec3 cutoff = lessThan(srgb, vec3(0.04045));
17 | vec3 lower = srgb / vec3(12.92);
18 | vec3 higher = pow((srgb + vec3(0.055)) / vec3(1.055), vec3(2.4));
19 | return mix(higher, lower, cutoff);
20 | }
21 |
22 | void main() {
23 | gl_Position = vec4(
24 | 2.0 * inPos.x / pushConstants.screen_size.x - 1.0,
25 | 2.0 * inPos.y / pushConstants.screen_size.y - 1.0,
26 | 0.0,
27 | 1.0);
28 | outColor = vec4(srgb_to_linear(inColor.rgb), inColor.a);
29 | outUV = inUV;
30 | }
31 |
--------------------------------------------------------------------------------
/hotham/src/shaders/lights.glsl:
--------------------------------------------------------------------------------
1 | float16_t getSquareFalloffAttenuation(float16_t distanceSquare, float16_t falloff) {
2 | float16_t factor = distanceSquare * falloff;
3 | float16_t smoothFactor = saturate(F16(1.0) - factor * factor);
4 | // // We would normally divide by the square distance here
5 | // // but we do it at the call site
6 | return smoothFactor * smoothFactor;
7 | }
8 |
9 | float16_t getRangeAttenuation(const vec3 pointToLight, float16_t falloff) {
10 | float16_t distanceSquare = F16(dot(pointToLight, pointToLight));
11 | float16_t attenuation = getSquareFalloffAttenuation(distanceSquare, falloff);
12 | // Assume a punctual light occupies a volume of 1cm to avoid a division by 0
13 | return attenuation / max(distanceSquare, F16(1e-4));
14 | }
15 |
16 | // https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_lights_punctual/README.md#inner-and-outer-cone-angles
17 | float16_t getSpotAttenuation(vec3 l, vec3 spotDirection, float16_t spotScale, float16_t spotOffset) {
18 | float16_t cd = F16(dot(spotDirection, l));
19 | float16_t attenuation = saturate(cd * spotScale + spotOffset);
20 | return attenuation * attenuation;
21 |
22 | }
23 |
24 | float16_t getLightAttenuation(Light light, vec3 pointToLight, vec3 l) {
25 | float16_t rangeAttenuation = F16(1.0);
26 | float16_t spotAttenuation = F16(1.0);
27 |
28 | if (light.type != LightType_Directional) {
29 | rangeAttenuation = getRangeAttenuation(pointToLight, F16(light.falloff));
30 | }
31 |
32 | if (light.type == LightType_Spot) {
33 | spotAttenuation = getSpotAttenuation(l, -light.direction, F16(light.lightAngleScale), F16(light.lightAngleOffset));
34 | }
35 |
36 | return rangeAttenuation * spotAttenuation;
37 | }
38 |
--------------------------------------------------------------------------------
/hotham/src/shaders/pbr.vert:
--------------------------------------------------------------------------------
1 | // PBR shader based on the Khronos glTF-Sample Viewer:
2 | // https://github.com/KhronosGroup/glTF-WebGL-PBR
3 | #version 460
4 | #extension GL_EXT_multiview : enable
5 |
6 | #include "common.glsl"
7 |
8 | layout (location = 0) in vec3 inPos;
9 | layout (location = 1) in vec3 inNormal;
10 | layout (location = 2) in vec2 inUV;
11 | layout (location = 3) in uint inJoint;
12 | layout (location = 4) in uint inWeight;
13 |
14 | layout (location = 0) out vec4 outGosPos;
15 | layout (location = 1) out vec2 outUV;
16 | layout (location = 2) out vec3 outNormal;
17 |
18 | struct DrawData {
19 | mat4 gosFromLocal;
20 | mat4 localFromGos;
21 | uint skinID;
22 | };
23 |
24 | layout (set = 0, binding = 0) readonly buffer DrawDataBuffer {
25 | DrawData data[5000];
26 | } drawDataBuffer;
27 |
28 | layout (std430, set = 0, binding = 1) readonly buffer SkinsBuffer {
29 | mat4 jointMatrices[100][64];
30 | } skinsBuffer;
31 |
32 | out gl_PerVertex {
33 | vec4 gl_Position;
34 | };
35 |
36 | void main() {
37 | uint skinID = drawDataBuffer.data[gl_InstanceIndex].skinID;
38 | mat4 gosFromLocal = drawDataBuffer.data[gl_InstanceIndex].gosFromLocal;
39 | mat4 localFromGos = drawDataBuffer.data[gl_InstanceIndex].localFromGos;
40 |
41 | if (skinID == NOT_PRESENT) {
42 | // Mesh has no skin
43 | outGosPos = gosFromLocal * vec4(inPos, 1.0);
44 | outNormal = normalize(inNormal * mat3(localFromGos));
45 | } else {
46 | // Mesh is skinned
47 | // Shift and mask to unpack the individual indices and weights.
48 | // There is no need to divide with the sum of weights because we are using homogenous coordinates.
49 | mat4 skinMatrix =
50 | ((inWeight) & 255) * skinsBuffer.jointMatrices[skinID][(inJoint) & 255] +
51 | ((inWeight >> 8) & 255) * skinsBuffer.jointMatrices[skinID][(inJoint >> 8) & 255] +
52 | ((inWeight >> 16) & 255) * skinsBuffer.jointMatrices[skinID][(inJoint >> 16) & 255] +
53 | ((inWeight >> 24) & 255) * skinsBuffer.jointMatrices[skinID][(inJoint >> 24) & 255];
54 |
55 | outGosPos = gosFromLocal * skinMatrix * vec4(inPos, 1.0);
56 | outNormal = normalize(mat3(skinMatrix) * inNormal * mat3(localFromGos));
57 | }
58 |
59 | outUV = inUV;
60 | gl_Position = sceneData.viewProjection[gl_ViewIndex] * outGosPos;
61 | }
62 |
--------------------------------------------------------------------------------
/hotham/src/systems/animation.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | components::{animation_controller::AnimationController, LocalTransform},
3 | Engine,
4 | };
5 |
6 | /// Animation system
7 | /// Walks through each AnimationController and applies the appropriate animation to its targets.
8 | pub fn animation_system(engine: &mut Engine) {
9 | animation_system_inner(&mut engine.world);
10 | }
11 |
12 | fn animation_system_inner(world: &mut hecs::World) {
13 | for (_, controller) in world.query::<&AnimationController>().iter() {
14 | let blend_from = controller.blend_from;
15 | let blend_to = controller.blend_to;
16 | let blend_amount = controller.blend_amount;
17 |
18 | for target in &controller.targets {
19 | let mut local_transform = world.get::<&mut LocalTransform>(target.target).unwrap();
20 | local_transform.translation =
21 | target.translations[blend_from].lerp(target.translations[blend_to], blend_amount);
22 | local_transform.rotation =
23 | target.rotations[blend_from].slerp(target.rotations[blend_to], blend_amount);
24 | local_transform.scale =
25 | target.scales[blend_from].lerp(target.scales[blend_to], blend_amount);
26 | }
27 | }
28 | }
29 |
30 | #[cfg(target_os = "windows")]
31 | #[cfg(test)]
32 | mod tests {
33 | use crate::{
34 | asset_importer::{add_model_to_world, load_models_from_glb},
35 | contexts::RenderContext,
36 | };
37 |
38 | use super::*;
39 | #[test]
40 | pub fn animation_test() {
41 | let (mut render_context, vulkan_context) = RenderContext::testing();
42 |
43 | let data: Vec<&[u8]> = vec![include_bytes!("../../../test_assets/left_hand.glb")];
44 | let models = load_models_from_glb(&data, &vulkan_context, &mut render_context).unwrap();
45 | let mut world = hecs::World::new();
46 |
47 | // Add the left hand
48 | let left_hand = add_model_to_world("Left Hand", &models, &mut world, None).unwrap();
49 | {
50 | let mut left_hand_controller =
51 | world.get::<&mut AnimationController>(left_hand).unwrap();
52 | left_hand_controller.blend_from = 0;
53 | left_hand_controller.blend_to = 1;
54 | left_hand_controller.blend_amount = 0.0;
55 | }
56 |
57 | // Collect all the transforms in the world so we can compare them later.
58 | let transforms_before = world
59 | .query_mut::<&LocalTransform>()
60 | .into_iter()
61 | .map(|r| *r.1)
62 | .collect::>();
63 |
64 | // Run the animation system
65 | animation_system_inner(&mut world);
66 |
67 | // Collect all the transforms after the system has been run.
68 | let transforms_after = world
69 | .query_mut::<&LocalTransform>()
70 | .into_iter()
71 | .map(|r| *r.1)
72 | .collect::>();
73 |
74 | // Make sure our transforms have been modified!
75 | assert_ne!(transforms_before, transforms_after);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/hotham/src/systems/debug.rs:
--------------------------------------------------------------------------------
1 | use crate::Engine;
2 |
3 | /// A simple system used to assist with debugging the fragment shader.
4 | pub fn debug_system(engine: &mut Engine) {
5 | let input_context = &mut engine.input_context;
6 | let render_context = &mut engine.render_context;
7 |
8 | if input_context.left.x_button_just_pressed() {
9 | let params = &mut render_context.scene_data.params;
10 | params.w = 0.;
11 | params.z = (params.z + 1.) % 7.;
12 | println!("[HOTHAM_DEBUG] params.z is now {}", params.z);
13 | }
14 |
15 | if input_context.left.y_button_just_pressed() {
16 | let params = &mut render_context.scene_data.params;
17 | params.z = 0.;
18 | params.w = (params.w + 1.) % 6.;
19 | println!("[HOTHAM_DEBUG] params.w is now {}", params.w);
20 | }
21 |
22 | if input_context.right.b_button_just_pressed() {
23 | let params = &mut render_context.scene_data.params;
24 | params.x = (params.x + 0.1) % 5.;
25 | println!("[HOTHAM_DEBUG] params.x is now {}", params.x);
26 | }
27 |
28 | if input_context.right.a_button_just_pressed() {
29 | let params = &mut render_context.scene_data.params;
30 | params.x = (params.x + 5. - 0.1) % 5.;
31 | println!("[HOTHAM_DEBUG] params.x is now {}", params.x);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/hotham/src/systems/haptics.rs:
--------------------------------------------------------------------------------
1 | use openxr::{Duration, HapticVibration};
2 |
3 | use crate::{
4 | contexts::{HapticContext, XrContext},
5 | Engine,
6 | };
7 | static HAPTIC_FREQUENCY: f32 = 400.;
8 | static HAPTIC_DURATION: i64 = 1e+8 as _; // 100ms
9 |
10 | /// Triggers the application of vibrations to the appropriate user input device at prescribed amplitude, frequency, and duration given a Hotham::resources::XrContent and Hotham::resources::HapticContext.
11 | ///
12 | /// During each tick of the Hotham engine, haptic feedback is applied to generate a HapticVibration
13 | /// event which propagates to the appropriate user input device.
14 | ///
15 | /// Basic usage:
16 | /// ```ignore
17 | /// fn tick (...) {
18 | /// apply_haptic_feedback(xr_context, haptic_context)
19 | /// }
20 | /// ```
21 | pub fn haptics_system(engine: &mut Engine) {
22 | haptics_system_inner(&mut engine.xr_context, &mut engine.haptic_context)
23 | }
24 |
25 | fn haptics_system_inner(xr_context: &mut XrContext, haptic_context: &mut HapticContext) {
26 | let input = &xr_context.input;
27 |
28 | let haptic_duration = Duration::from_nanos(HAPTIC_DURATION);
29 | if haptic_context.left_hand_amplitude_this_frame != 0. {
30 | let event = HapticVibration::new()
31 | .amplitude(haptic_context.left_hand_amplitude_this_frame)
32 | .frequency(HAPTIC_FREQUENCY)
33 | .duration(haptic_duration);
34 |
35 | input
36 | .haptic_feedback_action
37 | .apply_feedback(&xr_context.session, input.left_hand_subaction_path, &event)
38 | .expect("Unable to apply haptic feedback!");
39 |
40 | // Reset the value
41 | haptic_context.left_hand_amplitude_this_frame = 0.;
42 | }
43 |
44 | if haptic_context.right_hand_amplitude_this_frame != 0. {
45 | let event = HapticVibration::new()
46 | .amplitude(haptic_context.right_hand_amplitude_this_frame)
47 | .frequency(HAPTIC_FREQUENCY)
48 | .duration(haptic_duration);
49 |
50 | input
51 | .haptic_feedback_action
52 | .apply_feedback(&xr_context.session, input.right_hand_subaction_path, &event)
53 | .expect("Unable to apply haptic feedback!");
54 |
55 | // Reset the value
56 | haptic_context.right_hand_amplitude_this_frame = 0.;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/hotham/src/systems/mod.rs:
--------------------------------------------------------------------------------
1 | #![allow(missing_docs)]
2 | pub mod animation;
3 | pub mod audio;
4 | pub mod debug;
5 | pub mod draw_gui;
6 | pub mod grabbing;
7 | pub mod hands;
8 | pub mod haptics;
9 | pub mod physics;
10 | pub mod pointers;
11 | pub mod rendering;
12 | pub mod skinning;
13 | pub mod update_global_transform;
14 |
15 | pub use animation::animation_system;
16 | pub use audio::audio_system;
17 | pub use draw_gui::draw_gui_system;
18 | pub use grabbing::grabbing_system;
19 | pub use hands::hands_system;
20 | pub use haptics::haptics_system;
21 | pub use physics::physics_system;
22 | pub use pointers::pointers_system;
23 | pub use rendering::rendering_system;
24 | pub use skinning::skinning_system;
25 | pub use update_global_transform::update_global_transform_system;
26 |
--------------------------------------------------------------------------------
/hotham/src/systems/skinning.rs:
--------------------------------------------------------------------------------
1 | use hecs::World;
2 | use render_context::RenderContext;
3 |
4 | use crate::{
5 | components::{GlobalTransform, Skin},
6 | contexts::render_context,
7 | Engine,
8 | };
9 |
10 | /// Skinning system
11 | /// Walks through each joint in the system and builds up the `joint_matrices` that will be sent to the vertex shader
12 | pub fn skinning_system(engine: &mut Engine) {
13 | let world = &mut engine.world;
14 | let render_context = &mut engine.render_context;
15 | skinning_system_inner(world, render_context);
16 | }
17 |
18 | fn skinning_system_inner(world: &mut World, render_context: &mut RenderContext) {
19 | for (_, (skin, global_transform)) in world.query::<(&Skin, &GlobalTransform)>().iter() {
20 | let buffer = unsafe { render_context.resources.skins_buffer.as_slice_mut() };
21 | let joint_matrices = &mut buffer[skin.id as usize];
22 | let local_from_global = global_transform.0.inverse();
23 |
24 | for (n, (joint, joint_from_mesh)) in skin
25 | .joints
26 | .iter()
27 | .zip(skin.inverse_bind_matrices.iter())
28 | .enumerate()
29 | {
30 | let global_from_joint = world.get::<&GlobalTransform>(*joint).unwrap().0;
31 | let local_from_mesh = local_from_global * global_from_joint * *joint_from_mesh;
32 | joint_matrices[n] = local_from_mesh.into();
33 | }
34 | }
35 | }
36 |
37 | #[cfg(target_os = "windows")]
38 | #[cfg(test)]
39 | mod tests {
40 |
41 | use std::io::Write;
42 |
43 | use crate::{
44 | components::{Info, Skin},
45 | util::get_world_with_hands,
46 | };
47 |
48 | use super::*;
49 | use approx::relative_eq;
50 | use glam::{Affine3A, Mat4};
51 |
52 | #[test]
53 | pub fn test_hand_skinning() {
54 | let (mut render_context, vulkan_context) = RenderContext::testing();
55 | let mut world = get_world_with_hands(&vulkan_context, &mut render_context);
56 |
57 | skinning_system_inner(&mut world, &mut render_context);
58 |
59 | assert!(verify_matrices(&world, &render_context));
60 |
61 | // Muck all the joints up
62 | for (_, skin) in world.query::<&Skin>().iter() {
63 | for joint in &skin.joints {
64 | let mut global_transform = world.get::<&mut GlobalTransform>(*joint).unwrap();
65 | global_transform.0 = Affine3A::ZERO;
66 | }
67 | }
68 | skinning_system_inner(&mut world, &mut render_context);
69 |
70 | // TODO: This test is broken: https://github.com/leetvr/hotham/issues/370
71 | // assert!(verify_matrices(&world, &render_context));
72 | }
73 |
74 | fn verify_matrices(world: &World, render_context: &RenderContext) -> bool {
75 | let mut called = 0;
76 | for (_, (skin, info)) in world.query::<(&Skin, &Info)>().iter() {
77 | let correct_matrices: Vec = if info.name == "hands:Lhand" {
78 | serde_json::from_slice(include_bytes!(
79 | "../../../test_assets/left_hand_skinned_matrices.json"
80 | ))
81 | .unwrap()
82 | } else {
83 | serde_json::from_slice(include_bytes!(
84 | "../../../test_assets/right_hand_skinned_matrices.json"
85 | ))
86 | .unwrap()
87 | };
88 | let buffer = unsafe { render_context.resources.skins_buffer.as_slice() };
89 | let joint_matrices = &buffer[skin.id as usize];
90 |
91 | for i in 0..correct_matrices.len() {
92 | let expected = correct_matrices[i];
93 | let actual = joint_matrices[i];
94 | if !relative_eq!(expected, actual) {
95 | println!("Matrix {i} is incorrect");
96 | println!("Actual:");
97 | println!("{}", serde_json::to_string_pretty(&actual).unwrap());
98 | println!("Expected:");
99 | println!("{}", serde_json::to_string_pretty(&expected).unwrap());
100 | std::fs::File::create("matrix_failed.json")
101 | .unwrap()
102 | .write_all(&serde_json::to_vec_pretty(&joint_matrices[..]).unwrap())
103 | .unwrap();
104 | return false;
105 | }
106 | }
107 | called += 1;
108 | }
109 | assert_ne!(called, 0);
110 |
111 | true
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/hotham/src/workers/mod.rs:
--------------------------------------------------------------------------------
1 | use hotham_asset_client::{watch, AssetUpdatedMessage};
2 |
3 | use std::sync::mpsc;
4 |
5 | #[derive(Debug, Clone)]
6 | pub(crate) enum WorkerMessage {
7 | AssetUpdated(AssetUpdatedMessage),
8 | Error(WorkerError),
9 | }
10 |
11 | #[derive(Debug, Clone)]
12 | pub(crate) enum WorkerError {
13 | TaskFailed(String),
14 | }
15 |
16 | pub(crate) struct Workers {
17 | pub(crate) receiver: mpsc::Receiver,
18 | }
19 |
20 | impl Workers {
21 | pub fn new(asset_list: Vec) -> Self {
22 | let (to_engine, from_worker) = mpsc::channel();
23 | if asset_list.is_empty() {
24 | return Self {
25 | receiver: from_worker,
26 | };
27 | }
28 |
29 | std::thread::spawn(|| {
30 | let local_set = tokio::task::LocalSet::new();
31 | let (to_workers, mut from_asset_watcher) = tokio::sync::mpsc::channel(100);
32 | let to_engine_1 = to_engine.clone();
33 | local_set.spawn_local(async move {
34 | watch(asset_list, to_workers).await.map_err(|e| {
35 | to_engine_1.send(WorkerMessage::Error(WorkerError::TaskFailed(format!(
36 | "{e:?}"
37 | ))))
38 | })
39 | });
40 | local_set.spawn_local(async move {
41 | loop {
42 | if let Some(message) = from_asset_watcher.recv().await {
43 | to_engine
44 | .send(WorkerMessage::AssetUpdated(message))
45 | .unwrap();
46 | }
47 | }
48 | });
49 |
50 | let runtime = tokio::runtime::Builder::new_current_thread()
51 | .enable_all()
52 | .build()
53 | .unwrap();
54 |
55 | println!("[HOTHAM_WORKER] Runtime starting..");
56 | runtime.block_on(local_set);
57 | panic!("[HOTHAM_WORKER] RUNTIME FINISHED - THIS SHOULD NOT HAPPEN");
58 | });
59 |
60 | Self {
61 | receiver: from_worker,
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leetvr/hotham/cab13bfcc2dcce1828d0520c03950a28668f5330/logo.jpg
--------------------------------------------------------------------------------
/openxr_loader.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leetvr/hotham/cab13bfcc2dcce1828d0520c03950a28668f5330/openxr_loader.dll
--------------------------------------------------------------------------------
/shell.nix:
--------------------------------------------------------------------------------
1 | { pkgs ? import {} }:
2 |
3 | # based on https://nixos.org/manual/nixpkgs/stable/#android
4 | # note: if you get any weird errors of "required extensions not found" or the like, somehow hotham
5 | # doesn't search /run/opengl-driver
6 | # you can set VK_ICD_FILENAMES manually instead, see https://nixos.org/manual/nixos/stable/index.html#sec-gpu-accel-vulkan
7 | let
8 | cmakeVersion = "3.22.1";
9 |
10 | androidComposition = pkgs.androidenv.composeAndroidPackages {
11 | includeNDK = true;
12 | ndkVersion = "22.1.7171670";
13 | platformVersions = ["28"];
14 | cmakeVersions = [cmakeVersion];
15 | };
16 |
17 | openXrDropin = {
18 | file_format_version = "1.0.0";
19 | runtime = {
20 | api_version = "1.0";
21 | name = "Hotham Simulator";
22 | # this won't work with pure flakes but flakes are unstable anyway
23 | library_path = "${toString ./.}/target/debug/libhotham_simulator.so";
24 | };
25 | };
26 | in
27 | pkgs.mkShell rec {
28 | buildInputs = with pkgs; [
29 | rustup ninja cmake
30 | openssl pkg-config
31 |
32 | cargo-apk adoptopenjdk-bin
33 |
34 | shaderc python3
35 | vulkan-headers vulkan-loader
36 | vulkan-tools vulkan-tools-lunarg
37 | vulkan-validation-layers vulkan-extension-layer
38 | monado openxr-loader openxr-loader.dev
39 |
40 | libxkbcommon
41 | wayland xorg.libX11 xorg.libXcursor xorg.libXrandr xorg.libXi
42 | fontconfig freetype
43 | alsa-lib
44 |
45 | renderdoc
46 | ];
47 |
48 | # the nixpkgs manual lists that ANDROID_HOME is outdated and ANDROID_SDK_ROOT should be used instead
49 | # when actually running the android tools, they say the opposite, ANDROID_SDK_ROOT being outdated
50 | # so I just set both
51 | ANDROID_SDK_ROOT = "${androidComposition.androidsdk}/libexec/android-sdk";
52 | ANDROID_HOME = ANDROID_SDK_ROOT;
53 | ANDROID_NDK_ROOT = "${ANDROID_SDK_ROOT}/ndk-bundle";
54 |
55 | shellHook = ''
56 | export PATH="$(echo "$ANDROID_SDK_ROOT/cmake/${cmakeVersion}".*/bin):$PATH"
57 | export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${builtins.toString (pkgs.lib.makeLibraryPath buildInputs)}";
58 | export XR_RUNTIME_JSON="${builtins.toFile "hotham-openxr-runtime.json" (builtins.toJSON openXrDropin)}"
59 |
60 | rustup default 1.67 # required due to libunwind having fun
61 | rustup target add aarch64-linux-android
62 | '';
63 | }
64 |
--------------------------------------------------------------------------------
/test_assets/Quartet 14 - Clip.mp3:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:e4866b9e24ea6bba6c764f434460bfd5a19718742384e276249d6f0305470bef
3 | size 2368036
4 |
--------------------------------------------------------------------------------
/test_assets/asteroid.glb:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:a29081bb2bed055a062ac64b41df14948908738cda6f2d3abb9bbde148d96baf
3 | size 23616344
4 |
--------------------------------------------------------------------------------
/test_assets/box_no_material.glb:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:664d4544f9c287f76abe0408fc456d043f41c613fbacae795882ec7388dbe928
3 | size 1740
4 |
--------------------------------------------------------------------------------
/test_assets/box_with_colliders.glb:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:2cd373968e7cf532923f1be43851c7b01b41925d5ed96183a6b6e140168595de
3 | size 31240
4 |
--------------------------------------------------------------------------------
/test_assets/cube.glb:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:aaabc33da12631937d1fa586dbfb9a3fa0cdfc256160e52384733c80d43215f3
3 | size 2044
4 |
--------------------------------------------------------------------------------
/test_assets/culling_stress_test.glb:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:9a154196db8e1ad47a50d45fc6cc53f9d1a91b3a70ad682e37bd264a49465719
3 | size 15290044
4 |
--------------------------------------------------------------------------------
/test_assets/damaged_helmet.glb:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:5922d73673fa4a2e14e3622a543ccdadef464ab261f17416455d5209db6d7839
3 | size 16598244
4 |
--------------------------------------------------------------------------------
/test_assets/damaged_helmet_squished.glb:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:76d6c5f831088a132b01426b83e177569feb2c7a000d34d689c538b96cc5dc81
3 | size 16117496
4 |
--------------------------------------------------------------------------------
/test_assets/ferris-the-crab/source/ferris.glb:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:ba3a61f50ac3d5c17a63b817f7ea65d687f45445e3dd492f3d0bb75a846534bf
3 | size 221636
4 |
--------------------------------------------------------------------------------
/test_assets/ferris-the-crab/textures/ferris_eyes_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leetvr/hotham/cab13bfcc2dcce1828d0520c03950a28668f5330/test_assets/ferris-the-crab/textures/ferris_eyes_0.png
--------------------------------------------------------------------------------
/test_assets/floor.glb:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:98eb2cde11da5a7a4490cde5a004f5d1d8fddc30b4e5a2cbff52249eda5a1529
3 | size 42276
4 |
--------------------------------------------------------------------------------
/test_assets/gymnopedie.mp3:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:857dde693fae9e4e50a51fbe4fa4b202ae1468d39b054d2f2e58ab4660b7c793
3 | size 6929028
4 |
--------------------------------------------------------------------------------
/test_assets/hologram_templates.glb:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:7b1766b1019fc57d9da019328b8ea9929b03082c4aae6dc216dbed4fe6cf2f12
3 | size 14236
4 |
--------------------------------------------------------------------------------
/test_assets/ibl_test.glb:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:d00cc3d91bc5c3b1afef9fbbe62574c73036facf50ad72c1cd9085d92e863095
3 | size 361720
4 |
--------------------------------------------------------------------------------
/test_assets/ibl_test_squished.glb:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:9e88e7e256978f873a193e787e48a8cf5512a6efe3f7e240c56bd56fbb7f1a7c
3 | size 476624
4 |
--------------------------------------------------------------------------------
/test_assets/ice_crash.mp3:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:ff4e9f9c351c1ef48e92863e111b690d8c8ad8a47ba3b13171116a41161fa8bf
3 | size 39775
4 |
--------------------------------------------------------------------------------
/test_assets/left_hand.glb:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:33e57bcf816fe8b70250ca54b603f390e36b630ee0afe543ca66ca60f1344b3b
3 | size 95132
4 |
--------------------------------------------------------------------------------
/test_assets/normal_tangent_mirror_test.glb:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:705465c5045c113c680be8631e9295db8fe8e6342b0e5f9b094b89d33d9bb12a
3 | size 2019856
4 |
--------------------------------------------------------------------------------
/test_assets/normal_tangent_test.glb:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:7b8f8bf00c5fa43fc97a3e36aadb9eed949f08622785695aaf9c674090d9f1ae
3 | size 1921368
4 |
--------------------------------------------------------------------------------
/test_assets/render_Diffuse.jpg:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:283cb92296bc937a429b808b16746cce4012460824aa3842579e0cbf5b26dc2f
3 | size 45682
4 |
--------------------------------------------------------------------------------
/test_assets/render_Diffuse_known_good.jpg:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:283cb92296bc937a429b808b16746cce4012460824aa3842579e0cbf5b26dc2f
3 | size 45682
4 |
--------------------------------------------------------------------------------
/test_assets/render_Full.jpg:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:5266b2a6775edb3c79ca9067a81eb58288a08cf6ead6862292f3e2b7634204d6
3 | size 50094
4 |
--------------------------------------------------------------------------------
/test_assets/render_Full_known_good.jpg:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:5266b2a6775edb3c79ca9067a81eb58288a08cf6ead6862292f3e2b7634204d6
3 | size 50094
4 |
--------------------------------------------------------------------------------
/test_assets/render_No_IBL.jpg:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:537f33e06705dbfa2159c66d171ad75a10aaeec5e0b072f55e3cb9d04eab5264
3 | size 24097
4 |
--------------------------------------------------------------------------------
/test_assets/render_No_IBL_known_good.jpg:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:537f33e06705dbfa2159c66d171ad75a10aaeec5e0b072f55e3cb9d04eab5264
3 | size 24097
4 |
--------------------------------------------------------------------------------
/test_assets/render_Normals.jpg:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:926e260e819609d1b23ff591b8c5e65007a8f30d2020064ee1e0f9c181845040
3 | size 41878
4 |
--------------------------------------------------------------------------------
/test_assets/render_Normals_known_good.jpg:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:926e260e819609d1b23ff591b8c5e65007a8f30d2020064ee1e0f9c181845040
3 | size 41878
4 |
--------------------------------------------------------------------------------
/test_assets/render_Spotlight.jpg:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:4f866483aa0d13a13d3cd2afbda55a5c81d8c67a3b3f46996284fef2aad0b9d8
3 | size 31373
4 |
--------------------------------------------------------------------------------
/test_assets/render_Spotlight_known_good.jpg:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:4f866483aa0d13a13d3cd2afbda55a5c81d8c67a3b3f46996284fef2aad0b9d8
3 | size 31373
4 |
--------------------------------------------------------------------------------
/test_assets/render_draw_gui.jpg:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:de5cde4144b368492f0e3fd78f0570329a885adacf1a074c919c403c0f1538db
3 | size 23771
4 |
--------------------------------------------------------------------------------
/test_assets/render_draw_gui_known_good.jpg:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:de5cde4144b368492f0e3fd78f0570329a885adacf1a074c919c403c0f1538db
3 | size 23771
4 |
--------------------------------------------------------------------------------
/test_assets/render_normal_tangent_test_IBL.jpg:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:5fca86cf9d80d007c05bbd1a1bd0689296b170e6c467c6aa4680b28af88323da
3 | size 72085
4 |
--------------------------------------------------------------------------------
/test_assets/render_normal_tangent_test_IBL_and_spotlight.jpg:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:523cc42bcf0f5ddebf6527b6f9734e2365027ff66a4c7ccf732a5077468fceca
3 | size 74460
4 |
--------------------------------------------------------------------------------
/test_assets/render_normal_tangent_test_IBL_and_spotlight_known_good.jpg:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:523cc42bcf0f5ddebf6527b6f9734e2365027ff66a4c7ccf732a5077468fceca
3 | size 74460
4 |
--------------------------------------------------------------------------------
/test_assets/render_normal_tangent_test_IBL_known_good.jpg:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:5fca86cf9d80d007c05bbd1a1bd0689296b170e6c467c6aa4680b28af88323da
3 | size 72085
4 |
--------------------------------------------------------------------------------
/test_assets/render_normal_tangent_test_Normals.jpg:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:6b5e45ccc5794d18e73942bbdd6a668e8a0b32f4bc3424e9c60e184dd1a088f7
3 | size 71646
4 |
--------------------------------------------------------------------------------
/test_assets/render_normal_tangent_test_Normals_known_good.jpg:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:6b5e45ccc5794d18e73942bbdd6a668e8a0b32f4bc3424e9c60e184dd1a088f7
3 | size 71646
4 |
--------------------------------------------------------------------------------
/test_assets/render_normal_tangent_test_Spotlight1.jpg:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:a17ac6e324cd260e2917470feb0c1b5ac0aaf3ce3123d2af5e0b5be6d488ffa5
3 | size 48865
4 |
--------------------------------------------------------------------------------
/test_assets/render_normal_tangent_test_Spotlight1_known_good.jpg:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:a17ac6e324cd260e2917470feb0c1b5ac0aaf3ce3123d2af5e0b5be6d488ffa5
3 | size 48865
4 |
--------------------------------------------------------------------------------
/test_assets/render_normal_tangent_test_Spotlight2.jpg:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:382a73d6ea34b99868883360243435b27501ace0ddf531a9d81a3e428d690cbe
3 | size 53614
4 |
--------------------------------------------------------------------------------
/test_assets/render_normal_tangent_test_Spotlight2_known_good.jpg:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:382a73d6ea34b99868883360243435b27501ace0ddf531a9d81a3e428d690cbe
3 | size 53614
4 |
--------------------------------------------------------------------------------
/test_assets/renderdoc_performance_counters.json:
--------------------------------------------------------------------------------
1 | {
2 | "counters": [
3 | [
4 | 1,
5 | 0,
6 | 0,
7 | 0
8 | ],
9 | [
10 | 3,
11 | 0,
12 | 0,
13 | 0
14 | ],
15 | [
16 | 4,
17 | 0,
18 | 0,
19 | 0
20 | ],
21 | [
22 | 5,
23 | 0,
24 | 0,
25 | 0
26 | ],
27 | [
28 | 6,
29 | 0,
30 | 0,
31 | 0
32 | ],
33 | [
34 | 7,
35 | 0,
36 | 0,
37 | 0
38 | ],
39 | [
40 | 15,
41 | 0,
42 | 0,
43 | 0
44 | ],
45 | [
46 | 16,
47 | 0,
48 | 0,
49 | 0
50 | ],
51 | [
52 | 21,
53 | 0,
54 | 0,
55 | 0
56 | ],
57 | [
58 | 22,
59 | 0,
60 | 0,
61 | 0
62 | ],
63 | [
64 | 23,
65 | 0,
66 | 0,
67 | 0
68 | ],
69 | [
70 | 27,
71 | 0,
72 | 0,
73 | 0
74 | ],
75 | [
76 | 28,
77 | 0,
78 | 0,
79 | 0
80 | ],
81 | [
82 | 30,
83 | 0,
84 | 0,
85 | 0
86 | ],
87 | [
88 | 31,
89 | 0,
90 | 0,
91 | 0
92 | ],
93 | [
94 | 33,
95 | 0,
96 | 0,
97 | 0
98 | ],
99 | [
100 | 34,
101 | 0,
102 | 0,
103 | 0
104 | ],
105 | [
106 | 35,
107 | 0,
108 | 0,
109 | 0
110 | ],
111 | [
112 | 40,
113 | 0,
114 | 0,
115 | 0
116 | ],
117 | [
118 | 41,
119 | 0,
120 | 0,
121 | 0
122 | ],
123 | [
124 | 42,
125 | 0,
126 | 0,
127 | 0
128 | ],
129 | [
130 | 43,
131 | 0,
132 | 0,
133 | 0
134 | ],
135 | [
136 | 45,
137 | 0,
138 | 0,
139 | 0
140 | ],
141 | [
142 | 46,
143 | 0,
144 | 0,
145 | 0
146 | ]
147 | ],
148 | "rdocPerformanceCounterSettings": 1
149 | }
150 |
--------------------------------------------------------------------------------
/test_assets/right_hand.glb:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:92f636788d02bf908f96b04182aa9072f2d7f7718410dcc5b8928fd6e2b4e49b
3 | size 95332
4 |
--------------------------------------------------------------------------------
/test_assets/right_here_beside_you_clip.mp3:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:6a888bcbcc5ab0d2d82932697b35243199327ec7721e71591ba8a0660e806bea
3 | size 6978908
4 |
--------------------------------------------------------------------------------
/test_assets/sphere.glb:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:f25ae974673d67512913c98b0d81095c6a1cf9237d4a00662b3adb3e20c0a3a2
3 | size 1960
4 |
--------------------------------------------------------------------------------
/test_assets/tell_me_that_i_cant_clip.mp3:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:ce668126834a4b9e9d790b5418e8f2534769df404559eb23dd2feafea965cd53
3 | size 439937
4 |
--------------------------------------------------------------------------------