18 |
19 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help the project improve
4 | title: "(Summary of the bug)"
5 | labels: bug
6 | assignees: Uralstech
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Development environment:**
24 | - Unity version: [e.g. 2022.3, 2020.2]
25 | - Build target: [e.g. Android, Editor]
26 | - Package version: [e.g. 1.0.0]
27 |
28 | **Additional context**
29 | Add any other context about the problem here.
30 |
--------------------------------------------------------------------------------
/UXR.QuestCamera/Packages/com.uralstech.uxr.questcamera/Runtime/UXR.QuestCamera.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "UXR.QuestCamera",
3 | "rootNamespace": "",
4 | "references": [
5 | "GUID:2281fe9ed9abb88469162fc481fcc7a1",
6 | "GUID:a6609af893242c7438d701ddd4cce46a",
7 | "GUID:f64c9ebcd7899c3448a08dc9f9ddbe30"
8 | ],
9 | "includePlatforms": [],
10 | "excludePlatforms": [],
11 | "allowUnsafeCode": true,
12 | "overrideReferences": true,
13 | "precompiledReferences": [],
14 | "autoReferenced": true,
15 | "defineConstraints": [],
16 | "versionDefines": [
17 | {
18 | "name": "com.meta.xr.sdk.core",
19 | "expression": "1.0.0",
20 | "define": "META_XR_SDK_CORE"
21 | }
22 | ],
23 | "noEngineReferences": false
24 | }
--------------------------------------------------------------------------------
/UCamera/UCamera/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/UCamera/UCamera/src/main/cpp/JNIExtensions.h:
--------------------------------------------------------------------------------
1 | // Copyright 2025 URAV ADVANCED LEARNING SYSTEMS PRIVATE LIMITED
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | #ifndef UCAMERA_JNIEXTENSIONS_H
16 | #define UCAMERA_JNIEXTENSIONS_H
17 |
18 | #include
19 |
20 | bool HasJNIException(JNIEnv* env);
21 |
22 | JNIEnv* AttachEnv(JavaVM* javaVm, bool* shouldDetach);
23 | void DetachJNIEnv(JavaVM* javaVm);
24 |
25 | #endif //UCAMERA_JNIEXTENSIONS_H
26 |
--------------------------------------------------------------------------------
/UCamera/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/UXR.QuestCamera/ProjectSettings/UnityConnectSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!310 &1
4 | UnityConnectSettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 1
7 | m_Enabled: 0
8 | m_TestMode: 0
9 | m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events
10 | m_EventUrl: https://cdp.cloud.unity3d.com/v1/events
11 | m_ConfigUrl: https://config.uca.cloud.unity3d.com
12 | m_DashboardUrl: https://dashboard.unity3d.com
13 | m_TestInitMode: 0
14 | CrashReportingSettings:
15 | m_EventUrl: https://perf-events.cloud.unity3d.com
16 | m_Enabled: 0
17 | m_LogBufferSize: 10
18 | m_CaptureEditorExceptions: 1
19 | UnityPurchasingSettings:
20 | m_Enabled: 0
21 | m_TestMode: 0
22 | UnityAnalyticsSettings:
23 | m_Enabled: 0
24 | m_TestMode: 0
25 | m_InitializeOnStartup: 1
26 | m_PackageRequiringCoreStatsPresent: 0
27 | UnityAdsSettings:
28 | m_Enabled: 0
29 | m_InitializeOnStartup: 1
30 | m_TestMode: 0
31 | m_IosGameId:
32 | m_AndroidGameId:
33 | m_GameIds: {}
34 | m_GameId:
35 | PerformanceReportingSettings:
36 | m_Enabled: 0
37 |
--------------------------------------------------------------------------------
/UXR.QuestCamera/ProjectSettings/AndroidResolverDependencies.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | androidx.camera:camera-camera2:1.5.2
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/.github/workflows/pages-upload-action.yml:
--------------------------------------------------------------------------------
1 | # Trigger the action on push to master
2 | on:
3 | push:
4 | branches:
5 | - master
6 |
7 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
8 | permissions:
9 | actions: read
10 | pages: write
11 | id-token: write
12 |
13 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
14 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
15 | concurrency:
16 | group: "pages"
17 | cancel-in-progress: false
18 |
19 | jobs:
20 | publish-docs:
21 | environment:
22 | name: github-pages
23 | url: ${{ steps.deployment.outputs.page_url }}
24 | runs-on: ubuntu-latest
25 | steps:
26 | - name: Checkout
27 | uses: actions/checkout@v4
28 | - name: Dotnet Setup
29 | uses: actions/setup-dotnet@v4
30 | with:
31 | dotnet-version: 8.x
32 |
33 | - run: dotnet tool update -g docfx
34 | - run: docfx Documentation/docfx.json
35 |
36 | - name: Upload artifact
37 | uses: actions/upload-pages-artifact@v3
38 | with:
39 | # Upload entire repository
40 | path: 'Documentation/_site'
41 | - name: Deploy to GitHub Pages
42 | id: deployment
43 | uses: actions/deploy-pages@v4
44 |
--------------------------------------------------------------------------------
/UXR.QuestCamera/Packages/com.uralstech.uxr.questcamera/Runtime/Scripts/NativeWrapperState.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2025 URAV ADVANCED LEARNING SYSTEMS PRIVATE LIMITED
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | #nullable enable
16 | namespace Uralstech.UXR.QuestCamera
17 | {
18 | ///
19 | /// The current assumed state of a native wrapper.
20 | ///
21 | public enum NativeWrapperState
22 | {
23 | /// The native wrapper is still initializing.
24 | Initializing,
25 |
26 | /// The native wrapper is open and ready.
27 | Opened,
28 |
29 | /// The native wrapper failed with an error, was disconnected or is being/was closed normally.
30 | Closed,
31 | }
32 | }
--------------------------------------------------------------------------------
/UXR.QuestCamera/Packages/com.uralstech.uxr.questcamera/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "com.uralstech.uxr.questcamera",
3 | "displayName": "UXR.QuestCamera",
4 | "description": "A Unity package to use the new Meta Quest Passthrough Camera API.",
5 | "keywords": [],
6 | "version": "3.1.3",
7 | "unity": "2022.3",
8 | "hideInEditor": false,
9 | "documentationUrl": "https://uralstech.github.io/UXR.QuestCamera/",
10 | "changelogUrl": "https://github.com/Uralstech/UXR.QuestCamera/releases",
11 | "licensesUrl": "https://github.com/Uralstech/UXR.QuestCamera/blob/master/LICENSE",
12 | "license": "Apache-2.0",
13 | "repository": {
14 | "type": "git",
15 | "repository": "https://github.com/Uralstech/UXR.QuestCamera.git"
16 | },
17 | "samples": [
18 | {
19 | "displayName": "Digit Recognition with Unity Inference Engine",
20 | "description": "A sample showcasing a Unity Inference Engine (formerly known as Unity Sentis) digit recognition model being used with the Meta Quest Passthrough Camera API.",
21 | "path": "Samples~/DigitRecognitionSample"
22 | }
23 | ],
24 | "author": {
25 | "name": "Udayshankar Ravikumar",
26 | "url": "https://github.com/Uralstech"
27 | },
28 | "dependencies": {
29 | "com.uralstech.utils.singleton": "1.2.1",
30 | "com.unity.modules.androidjni": "1.0.0"
31 | }
32 | }
--------------------------------------------------------------------------------
/UXR.QuestCamera/ProjectSettings/DynamicsManager.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!55 &1
4 | PhysicsManager:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 11
7 | m_Gravity: {x: 0, y: -9.81, z: 0}
8 | m_DefaultMaterial: {fileID: 0}
9 | m_BounceThreshold: 2
10 | m_SleepThreshold: 0.005
11 | m_DefaultContactOffset: 0.01
12 | m_DefaultSolverIterations: 6
13 | m_DefaultSolverVelocityIterations: 1
14 | m_QueriesHitBackfaces: 0
15 | m_QueriesHitTriggers: 1
16 | m_EnableAdaptiveForce: 0
17 | m_ClothInterCollisionDistance: 0
18 | m_ClothInterCollisionStiffness: 0
19 | m_ContactsGeneration: 1
20 | m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
21 | m_AutoSimulation: 1
22 | m_AutoSyncTransforms: 0
23 | m_ReuseCollisionCallbacks: 1
24 | m_ClothInterCollisionSettingsToggle: 0
25 | m_ContactPairsMode: 0
26 | m_BroadphaseType: 0
27 | m_WorldBounds:
28 | m_Center: {x: 0, y: 0, z: 0}
29 | m_Extent: {x: 250, y: 250, z: 250}
30 | m_WorldSubdivisions: 8
31 | m_FrictionType: 0
32 | m_EnableEnhancedDeterminism: 0
33 | m_EnableUnifiedHeightmaps: 1
34 | m_DefaultMaxAngluarSpeed: 7
35 |
--------------------------------------------------------------------------------
/UXR.QuestCamera/ProjectSettings/MemorySettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!387306366 &1
4 | MemorySettings:
5 | m_ObjectHideFlags: 0
6 | m_EditorMemorySettings:
7 | m_MainAllocatorBlockSize: -1
8 | m_ThreadAllocatorBlockSize: -1
9 | m_MainGfxBlockSize: -1
10 | m_ThreadGfxBlockSize: -1
11 | m_CacheBlockSize: -1
12 | m_TypetreeBlockSize: -1
13 | m_ProfilerBlockSize: -1
14 | m_ProfilerEditorBlockSize: -1
15 | m_BucketAllocatorGranularity: -1
16 | m_BucketAllocatorBucketsCount: -1
17 | m_BucketAllocatorBlockSize: -1
18 | m_BucketAllocatorBlockCount: -1
19 | m_ProfilerBucketAllocatorGranularity: -1
20 | m_ProfilerBucketAllocatorBucketsCount: -1
21 | m_ProfilerBucketAllocatorBlockSize: -1
22 | m_ProfilerBucketAllocatorBlockCount: -1
23 | m_TempAllocatorSizeMain: -1
24 | m_JobTempAllocatorBlockSize: -1
25 | m_BackgroundJobTempAllocatorBlockSize: -1
26 | m_JobTempAllocatorReducedBlockSize: -1
27 | m_TempAllocatorSizeGIBakingWorker: -1
28 | m_TempAllocatorSizeNavMeshWorker: -1
29 | m_TempAllocatorSizeAudioWorker: -1
30 | m_TempAllocatorSizeCloudWorker: -1
31 | m_TempAllocatorSizeGfx: -1
32 | m_TempAllocatorSizeJobWorker: -1
33 | m_TempAllocatorSizeBackgroundWorker: -1
34 | m_TempAllocatorSizePreloadManager: -1
35 | m_PlatformMemorySettings: {}
36 |
--------------------------------------------------------------------------------
/UCamera/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/UXR.QuestCamera/ProjectSettings/PackageManagerSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!114 &1
4 | MonoBehaviour:
5 | m_ObjectHideFlags: 61
6 | m_CorrespondingSourceObject: {fileID: 0}
7 | m_PrefabInstance: {fileID: 0}
8 | m_PrefabAsset: {fileID: 0}
9 | m_GameObject: {fileID: 0}
10 | m_Enabled: 1
11 | m_EditorHideFlags: 0
12 | m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0}
13 | m_Name:
14 | m_EditorClassIdentifier:
15 | m_EnablePreReleasePackages: 0
16 | m_AdvancedSettingsExpanded: 1
17 | m_ScopedRegistriesSettingsExpanded: 1
18 | m_SeeAllPackageVersions: 0
19 | m_DismissPreviewPackagesInUse: 0
20 | oneTimeWarningShown: 0
21 | oneTimeDeprecatedPopUpShown: 0
22 | m_Registries:
23 | - m_Id: main
24 | m_Name:
25 | m_Url: https://packages.unity.com
26 | m_Scopes: []
27 | m_IsDefault: 1
28 | m_Capabilities: 7
29 | m_ConfigSource: 0
30 | - m_Id: scoped:project:OpenUPM
31 | m_Name: OpenUPM
32 | m_Url: https://package.openupm.com
33 | m_Scopes:
34 | - com.google.external-dependency-manager
35 | - com.uralstech.utils.singleton
36 | - com.utilities.async
37 | m_IsDefault: 0
38 | m_Capabilities: 0
39 | m_ConfigSource: 4
40 | m_UserSelectedRegistryName: OpenUPM
41 | m_UserAddingNewScopedRegistry: 0
42 | m_RegistryInfoDraft:
43 | m_Modified: 0
44 | m_ErrorMessage:
45 | m_UserModificationsInstanceId: -868
46 | m_OriginalInstanceId: -870
47 | m_LoadAssets: 0
48 |
--------------------------------------------------------------------------------
/UCamera/UCamera/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2 |
3 | plugins {
4 | alias(libs.plugins.android.library)
5 | alias(libs.plugins.kotlin.android)
6 | }
7 |
8 | android {
9 | namespace = "com.uralstech.ucamera"
10 | compileSdk = 36
11 |
12 | defaultConfig {
13 | minSdk = 29
14 |
15 | setProperty("archivesBaseName", "$namespace")
16 |
17 | ndk {
18 | //noinspection ChromeOsAbiSupport
19 | abiFilters += listOf("arm64-v8a")
20 | }
21 | }
22 |
23 | buildTypes {
24 | release {
25 | isMinifyEnabled = false
26 | proguardFiles(
27 | getDefaultProguardFile("proguard-android-optimize.txt"),
28 | "proguard-rules.pro"
29 | )
30 | }
31 | }
32 | compileOptions {
33 | sourceCompatibility = JavaVersion.VERSION_11
34 | targetCompatibility = JavaVersion.VERSION_11
35 | }
36 |
37 | kotlin {
38 | compilerOptions {
39 | jvmTarget.set(JvmTarget.JVM_11)
40 | }
41 | }
42 |
43 | externalNativeBuild {
44 | cmake {
45 | path = file("src/main/cpp/CMakeLists.txt")
46 | }
47 | }
48 |
49 | packaging {
50 | resources {
51 | excludes.add("com/unity3d/**")
52 | }
53 | }
54 | }
55 |
56 | dependencies {
57 | compileOnly(files("libs/classes.jar"))
58 | implementation(libs.androidx.core.ktx)
59 | implementation(libs.androidx.camera.camera2)
60 | }
--------------------------------------------------------------------------------
/UCamera/UCamera/src/main/java/com/uralstech/ucamera/RepeatingCaptureSessionWrapper.kt:
--------------------------------------------------------------------------------
1 | // Copyright 2025 URAV ADVANCED LEARNING SYSTEMS PRIVATE LIMITED
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package com.uralstech.ucamera
16 |
17 | import android.hardware.camera2.CameraCaptureSession
18 | import android.hardware.camera2.CameraDevice
19 | import android.hardware.camera2.params.OutputConfiguration
20 |
21 | /**
22 | * Wrapper class for [CameraCaptureSession] with a repeating capture request.
23 | */
24 | class RepeatingCaptureSessionWrapper(
25 | cameraDevice: CameraDevice, captureTemplate: Int,
26 | callbacks: Callbacks, width: Int, height: Int) : CaptureSessionWrapper(cameraDevice, captureTemplate, callbacks, width, height) {
27 |
28 | /**
29 | * Creates a new capture session and sets the repeating capture request.
30 | */
31 | override fun startCaptureSession(camera: CameraDevice, captureTemplate: Int) {
32 | super.startRepeatingCaptureSession(camera, captureTemplate,
33 | listOf(OutputConfiguration(imageReader.surface)), imageReader.surface)
34 | }
35 | }
--------------------------------------------------------------------------------
/UXR.QuestCamera/.gitignore:
--------------------------------------------------------------------------------
1 | # This .gitignore file should be placed at the root of your Unity project directory
2 | #
3 | # Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore
4 | #
5 | /[Aa]ssets/
6 | /[Ll]ibrary/
7 | /[Tt]emp/
8 | /[Oo]bj/
9 | /[Bb]uild/
10 | /[Bb]uilds/
11 | /[Ll]ogs/
12 | /[Uu]ser[Ss]ettings/
13 |
14 | # MemoryCaptures can get excessive in size.
15 | # They also could contain extremely sensitive data
16 | /[Mm]emoryCaptures/
17 |
18 | # Recordings can get excessive in size
19 | /[Rr]ecordings/
20 |
21 | # Uncomment this line if you wish to ignore the asset store tools plugin
22 | # /[Aa]ssets/AssetStoreTools*
23 |
24 | # Autogenerated Jetbrains Rider plugin
25 | /[Aa]ssets/Plugins/Editor/JetBrains*
26 |
27 | # Visual Studio cache directory
28 | .vs/
29 |
30 | # Gradle cache directory
31 | .gradle/
32 |
33 | # Autogenerated VS/MD/Consulo solution and project files
34 | ExportedObj/
35 | .consulo/
36 | *.csproj
37 | *.unityproj
38 | *.sln
39 | *.suo
40 | *.tmp
41 | *.user
42 | *.userprefs
43 | *.pidb
44 | *.booproj
45 | *.svd
46 | *.pdb
47 | *.mdb
48 | *.opendb
49 | *.VC.db
50 |
51 | # Unity3D generated meta files
52 | *.pidb.meta
53 | *.pdb.meta
54 | *.mdb.meta
55 |
56 | # Unity3D generated file on crash reports
57 | sysinfo.txt
58 |
59 | # Builds
60 | *.apk
61 | *.aab
62 | *.unitypackage
63 | *.app
64 |
65 | # Crashlytics generated file
66 | crashlytics-build.properties
67 |
68 | # Packed Addressables
69 | /[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin*
70 |
71 | # Temporary auto-generated Android Assets
72 | /[Aa]ssets/[Ss]treamingAssets/aa.meta
73 | /[Aa]ssets/[Ss]treamingAssets/aa/*
74 |
75 | /[Aa]ssets
76 | /.utmp
77 | /.vscode
--------------------------------------------------------------------------------
/UXR.QuestCamera/Packages/com.uralstech.uxr.questcamera/Runtime/Prefabs/QuestCameraManager.prefab:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!1 &439309611987774447
4 | GameObject:
5 | m_ObjectHideFlags: 0
6 | m_CorrespondingSourceObject: {fileID: 0}
7 | m_PrefabInstance: {fileID: 0}
8 | m_PrefabAsset: {fileID: 0}
9 | serializedVersion: 6
10 | m_Component:
11 | - component: {fileID: 1528171967605105492}
12 | - component: {fileID: 1727881059514603240}
13 | m_Layer: 0
14 | m_Name: QuestCameraManager
15 | m_TagString: Untagged
16 | m_Icon: {fileID: 0}
17 | m_NavMeshLayer: 0
18 | m_StaticEditorFlags: 0
19 | m_IsActive: 1
20 | --- !u!4 &1528171967605105492
21 | Transform:
22 | m_ObjectHideFlags: 0
23 | m_CorrespondingSourceObject: {fileID: 0}
24 | m_PrefabInstance: {fileID: 0}
25 | m_PrefabAsset: {fileID: 0}
26 | m_GameObject: {fileID: 439309611987774447}
27 | serializedVersion: 2
28 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
29 | m_LocalPosition: {x: 0, y: 0, z: 0}
30 | m_LocalScale: {x: 1, y: 1, z: 1}
31 | m_ConstrainProportionsScale: 0
32 | m_Children: []
33 | m_Father: {fileID: 0}
34 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
35 | --- !u!114 &1727881059514603240
36 | MonoBehaviour:
37 | m_ObjectHideFlags: 0
38 | m_CorrespondingSourceObject: {fileID: 0}
39 | m_PrefabInstance: {fileID: 0}
40 | m_PrefabAsset: {fileID: 0}
41 | m_GameObject: {fileID: 439309611987774447}
42 | m_Enabled: 1
43 | m_EditorHideFlags: 0
44 | m_Script: {fileID: 11500000, guid: cfe1b046d243a524aa171f4d1d110abc, type: 3}
45 | m_Name:
46 | m_EditorClassIdentifier:
47 | YUVToRGBAComputeShader: {fileID: 7200000, guid: 33a2078621338e548bada15b083db82e, type: 3}
48 |
--------------------------------------------------------------------------------
/UXR.QuestCamera/ProjectSettings/GvhProjectSettings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/UXR.QuestCamera/ProjectSettings/EditorSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!159 &1
4 | EditorSettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 13
7 | m_SerializationMode: 2
8 | m_LineEndingsForNewScripts: 0
9 | m_DefaultBehaviorMode: 0
10 | m_PrefabRegularEnvironment: {fileID: 0}
11 | m_PrefabUIEnvironment: {fileID: 0}
12 | m_SpritePackerMode: 0
13 | m_SpritePackerCacheSize: 10
14 | m_SpritePackerPaddingPower: 1
15 | m_Bc7TextureCompressor: 0
16 | m_EtcTextureCompressorBehavior: 1
17 | m_EtcTextureFastCompressor: 1
18 | m_EtcTextureNormalCompressor: 2
19 | m_EtcTextureBestCompressor: 4
20 | m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd;asmdef;rsp;asmref
21 | m_ProjectGenerationRootNamespace:
22 | m_EnableTextureStreamingInEditMode: 1
23 | m_EnableTextureStreamingInPlayMode: 1
24 | m_EnableEditorAsyncCPUTextureLoading: 0
25 | m_AsyncShaderCompilation: 1
26 | m_PrefabModeAllowAutoSave: 1
27 | m_EnterPlayModeOptionsEnabled: 1
28 | m_EnterPlayModeOptions: 0
29 | m_GameObjectNamingDigits: 1
30 | m_GameObjectNamingScheme: 0
31 | m_AssetNamingUsesSpace: 1
32 | m_InspectorUseIMGUIDefaultInspector: 0
33 | m_UseLegacyProbeSampleCount: 0
34 | m_SerializeInlineMappingsOnOneLine: 1
35 | m_DisableCookiesInLightmapper: 0
36 | m_AssetPipelineMode: 1
37 | m_RefreshImportMode: 0
38 | m_CacheServerMode: 0
39 | m_CacheServerEndpoint:
40 | m_CacheServerNamespacePrefix: default
41 | m_CacheServerEnableDownload: 1
42 | m_CacheServerEnableUpload: 1
43 | m_CacheServerEnableAuth: 0
44 | m_CacheServerEnableTls: 0
45 | m_CacheServerValidationMode: 2
46 | m_CacheServerDownloadBatchSize: 128
47 | m_EnableEnlightenBakedGI: 0
48 | m_ReferencedClipsExactNaming: 1
49 |
--------------------------------------------------------------------------------
/UXR.QuestCamera/ProjectSettings/NavMeshAreas.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!126 &1
4 | NavMeshProjectSettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 2
7 | areas:
8 | - name: Walkable
9 | cost: 1
10 | - name: Not Walkable
11 | cost: 1
12 | - name: Jump
13 | cost: 2
14 | - name:
15 | cost: 1
16 | - name:
17 | cost: 1
18 | - name:
19 | cost: 1
20 | - name:
21 | cost: 1
22 | - name:
23 | cost: 1
24 | - name:
25 | cost: 1
26 | - name:
27 | cost: 1
28 | - name:
29 | cost: 1
30 | - name:
31 | cost: 1
32 | - name:
33 | cost: 1
34 | - name:
35 | cost: 1
36 | - name:
37 | cost: 1
38 | - name:
39 | cost: 1
40 | - name:
41 | cost: 1
42 | - name:
43 | cost: 1
44 | - name:
45 | cost: 1
46 | - name:
47 | cost: 1
48 | - name:
49 | cost: 1
50 | - name:
51 | cost: 1
52 | - name:
53 | cost: 1
54 | - name:
55 | cost: 1
56 | - name:
57 | cost: 1
58 | - name:
59 | cost: 1
60 | - name:
61 | cost: 1
62 | - name:
63 | cost: 1
64 | - name:
65 | cost: 1
66 | - name:
67 | cost: 1
68 | - name:
69 | cost: 1
70 | - name:
71 | cost: 1
72 | m_LastAgentTypeID: -887442657
73 | m_Settings:
74 | - serializedVersion: 2
75 | agentTypeID: 0
76 | agentRadius: 0.5
77 | agentHeight: 2
78 | agentSlope: 45
79 | agentClimb: 0.75
80 | ledgeDropHeight: 0
81 | maxJumpAcrossDistance: 0
82 | minRegionArea: 2
83 | manualCellSize: 0
84 | cellSize: 0.16666667
85 | manualTileSize: 0
86 | tileSize: 256
87 | accuratePlacement: 0
88 | debug:
89 | m_Flags: 0
90 | m_SettingNames:
91 | - Humanoid
92 |
--------------------------------------------------------------------------------
/UCamera/UCamera/src/main/cpp/Renderer.h:
--------------------------------------------------------------------------------
1 | // Copyright 2025 URAV ADVANCED LEARNING SYSTEMS PRIVATE LIMITED
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | #ifndef UCAMERA_RENDERER_H
16 | #define UCAMERA_RENDERER_H
17 |
18 | #include
19 | #include
20 |
21 | class Renderer {
22 | public:
23 | Renderer(GLuint unityTexture, GLint width, GLint height);
24 | bool initialize(GLuint* texture);
25 | bool render(ASurfaceTexture* surfaceTexture) const;
26 | void dispose();
27 |
28 | private:
29 | GLuint _unityTexture;
30 | GLuint _sourceTexture;
31 | GLuint _frameBufferObject;
32 |
33 | GLint _width; GLint _height;
34 | bool _disposed;
35 |
36 | static uint8_t _staticReferenceHolders;
37 |
38 | static GLuint _shaderProgram;
39 | static GLint _transformMatrixHandle;
40 | static GLint _textureSamplerHandle;
41 |
42 | static GLuint _vertexBufferObject;
43 | static GLuint _vertexArrayObject;
44 |
45 | static bool hasGlErrors(const char* methodName);
46 | static bool linkShaderProgram(GLuint vertexShader, GLuint fragmentShader);
47 | static bool compileShader(GLenum type, const char* source, GLuint* shader);
48 | static bool setupGeometry();
49 | };
50 |
51 |
52 | #endif //UCAMERA_RENDERER_H
53 |
--------------------------------------------------------------------------------
/Documentation/docfx.json:
--------------------------------------------------------------------------------
1 | {
2 | "metadata": [
3 | {
4 | "src": [
5 | {
6 | "src": "../UXR.QuestCamera",
7 | "files": [
8 | "Packages/com.uralstech.uxr.questcamera/Runtime/**/*.cs"
9 | ]
10 | }
11 | ],
12 | "properties": {
13 | "DefineConstants": "UNITY_6000_0_OR_NEWER;META_XR_SDK_CORE"
14 | },
15 | "dest": "api",
16 | "filter": "filterConfig.yml",
17 | "includePrivateMembers": false,
18 | "allowCompilationErrors": true
19 | }
20 | ],
21 | "build": {
22 | "content": [
23 | {
24 | "files": [
25 | "**/*.{md,yml}"
26 | ],
27 | "exclude": [
28 | "_site/**"
29 | ]
30 | }
31 | ],
32 | "resource": [
33 | {
34 | "files": [
35 | "images/**"
36 | ]
37 | }
38 | ],
39 | "output": "_site",
40 | "template": [
41 | "default",
42 | "modern"
43 | ],
44 | "globalMetadata": {
45 | "_appName": "UXR.QuestCamera",
46 | "_appTitle": "UXR.QuestCamera",
47 | "_appFooter": "(C) 2025 URAV ADVANCED LEARNING SYSTEMS PRIVATE LIMITED, licensed under the Apache License, Version 2.0.",
48 | "_enableSearch": true
49 | },
50 | "fileMetadata": {
51 | "pdf": {
52 | "api/toc.yml": true
53 | },
54 | "pdfPrintBackground": {
55 | "api/toc.yml": true
56 | },
57 | "pdfTocPage": {
58 | "api/toc.yml": true
59 | },
60 | "pdfFileName": {
61 | "api/toc.yml": "APIReferenceManual.pdf"
62 | }
63 | },
64 | "sitemap": {
65 | "baseUrl": "https://uralstech.github.io/UXR.QuestCamera",
66 | "priority": 1.0,
67 | "changefreq": "monthly"
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/THIRD-PARTY-LICENSES.md:
--------------------------------------------------------------------------------
1 | THIRD-PARTY SOFTWARE NOTICES AND INFORMATION
2 | ==============================================
3 |
4 | This project (UXR.QuestCamera) incorporates or utilizes portions of the following third-party software, which is subject to the terms and conditions of its respective license.
5 |
6 | --------------------------------------------------
7 |
8 | **Component:** Unity-PassthroughCameraApiSamples (portions)
9 |
10 | **Source:** https://github.com/oculus-samples/Unity-PassthroughCameraApiSamples
11 |
12 | **Copyright:** Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.
13 |
14 | **License:** Oculus SDK License Agreement
15 |
16 | **License Text:**
17 |
18 | Copyright (c) Meta Platforms, Inc. and affiliates.
19 | All rights reserved.
20 |
21 | Licensed under the Oculus SDK License Agreement (the "License");
22 | you may not use the Oculus SDK except in compliance with the License,
23 | which is provided at the time of installation or download, or which
24 | otherwise accompanies this software in either electronic or hard copy form.
25 |
26 | You may obtain a copy of the License at
27 |
28 | https://developer.oculus.com/licenses/oculussdk/
29 |
30 | Unless required by applicable law or agreed to in writing, the Oculus SDK
31 | distributed under the License is distributed on an "AS IS" BASIS,
32 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
33 | See the License for the specific language governing permissions and
34 | limitations under the License.
35 |
36 | --------------------------------------------------
37 |
38 | **Note**: This file provides a summary of the third-party licenses applicable to *this* project (UXR.QuestCamera). The full Oculus SDK License Agreement (linked above) governs the use of the incorporated portions from Meta's sample code. Please review the full license agreement for complete details. This file does *not* include licenses for *your* code, only for the external dependencies you've indicated. You should separately license your own original contributions.
--------------------------------------------------------------------------------
/UXR.QuestCamera/Packages/com.uralstech.uxr.questcamera/Runtime/Scripts/CaptureSession/CaptureTemplate.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2025 URAV ADVANCED LEARNING SYSTEMS PRIVATE LIMITED
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | #nullable enable
16 | namespace Uralstech.UXR.QuestCamera
17 | {
18 | ///
19 | /// Capture template to use when recording.
20 | ///
21 | public enum CaptureTemplate
22 | {
23 | /// Default value, do not use.
24 | Default = 0,
25 |
26 | /// Creates a request suitable for a camera preview window.
27 | ///
28 | Preview = 1,
29 |
30 | /// Creates a request suitable for still image capture.
31 | ///
32 | StillCapture = 2,
33 |
34 | /// Creates a request suitable for video recording.
35 | ///
36 | Record = 3,
37 |
38 | /// Creates a request suitable for still image capture while recording video.
39 | ///
40 | VideoSnapshot = 4,
41 | }
42 | }
--------------------------------------------------------------------------------
/UXR.QuestCamera/Packages/com.uralstech.uxr.questcamera/Runtime/Scripts/CaptureSession/CapturePipeline.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2025 URAV ADVANCED LEARNING SYSTEMS PRIVATE LIMITED
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | using System;
16 | using System.Threading.Tasks;
17 |
18 | #nullable enable
19 | namespace Uralstech.UXR.QuestCamera
20 | {
21 | ///
22 | /// Simple class for grouping a capture session and its texture converter.
23 | ///
24 | public class CapturePipeline : IAsyncDisposable
25 | where T : ContinuousCaptureSession
26 | {
27 | ///
28 | /// The capture session wrapper.
29 | ///
30 | public readonly T CaptureSession;
31 |
32 | ///
33 | /// The YUV to RGBA texture converter.
34 | ///
35 | public readonly YUVToRGBAConverter TextureConverter;
36 |
37 | public CapturePipeline(T captureSession, YUVToRGBAConverter textureConverter)
38 | {
39 | CaptureSession = captureSession;
40 | TextureConverter = textureConverter;
41 | }
42 |
43 | private bool _disposed = false;
44 |
45 | ///
46 | /// Closes and disposes the capture session and texture converter.
47 | ///
48 | public async ValueTask DisposeAsync()
49 | {
50 | if (_disposed)
51 | return;
52 |
53 | _disposed = true;
54 | await CaptureSession.DisposeAsync();
55 | TextureConverter.Dispose();
56 | GC.SuppressFinalize(this);
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/UXR.QuestCamera/ProjectSettings/Physics2DSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!19 &1
4 | Physics2DSettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 4
7 | m_Gravity: {x: 0, y: -9.81}
8 | m_DefaultMaterial: {fileID: 0}
9 | m_VelocityIterations: 8
10 | m_PositionIterations: 3
11 | m_VelocityThreshold: 1
12 | m_MaxLinearCorrection: 0.2
13 | m_MaxAngularCorrection: 8
14 | m_MaxTranslationSpeed: 100
15 | m_MaxRotationSpeed: 360
16 | m_BaumgarteScale: 0.2
17 | m_BaumgarteTimeOfImpactScale: 0.75
18 | m_TimeToSleep: 0.5
19 | m_LinearSleepTolerance: 0.01
20 | m_AngularSleepTolerance: 2
21 | m_DefaultContactOffset: 0.01
22 | m_JobOptions:
23 | serializedVersion: 2
24 | useMultithreading: 0
25 | useConsistencySorting: 0
26 | m_InterpolationPosesPerJob: 100
27 | m_NewContactsPerJob: 30
28 | m_CollideContactsPerJob: 100
29 | m_ClearFlagsPerJob: 200
30 | m_ClearBodyForcesPerJob: 200
31 | m_SyncDiscreteFixturesPerJob: 50
32 | m_SyncContinuousFixturesPerJob: 50
33 | m_FindNearestContactsPerJob: 100
34 | m_UpdateTriggerContactsPerJob: 100
35 | m_IslandSolverCostThreshold: 100
36 | m_IslandSolverBodyCostScale: 1
37 | m_IslandSolverContactCostScale: 10
38 | m_IslandSolverJointCostScale: 10
39 | m_IslandSolverBodiesPerJob: 50
40 | m_IslandSolverContactsPerJob: 50
41 | m_AutoSimulation: 1
42 | m_QueriesHitTriggers: 1
43 | m_QueriesStartInColliders: 1
44 | m_CallbacksOnDisable: 1
45 | m_ReuseCollisionCallbacks: 1
46 | m_AutoSyncTransforms: 0
47 | m_AlwaysShowColliders: 0
48 | m_ShowColliderSleep: 1
49 | m_ShowColliderContacts: 0
50 | m_ShowColliderAABB: 0
51 | m_ContactArrowScale: 0.2
52 | m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412}
53 | m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432}
54 | m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745}
55 | m_ColliderAABBColor: {r: 1, g: 1, b: 0, a: 0.2509804}
56 | m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
57 |
--------------------------------------------------------------------------------
/UCamera/UCamera/src/main/cpp/JNIExtensions.cpp:
--------------------------------------------------------------------------------
1 | // Copyright 2025 URAV ADVANCED LEARNING SYSTEMS PRIVATE LIMITED
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | #include
16 | #include "JNIExtensions.h"
17 |
18 | #define LOG_TAG "UCameraJNIExt"
19 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
20 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
21 |
22 | bool HasJNIException(JNIEnv* env) {
23 | if (env->ExceptionCheck()) {
24 | env->ExceptionDescribe();
25 | env->ExceptionClear();
26 | return true;
27 | }
28 |
29 | return false;
30 | }
31 |
32 | JNIEnv* AttachEnv(JavaVM* javaVm, bool* shouldDetach) {
33 | if (javaVm == nullptr) {
34 | LOGE("javaVM is a nullptr, can't get JNIEnv.");
35 | return nullptr;
36 | }
37 |
38 | *shouldDetach = false;
39 |
40 | JNIEnv* env;
41 | jint result = javaVm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6);
42 |
43 | if (result == JNI_EDETACHED) {
44 | LOGI("Attaching to JNI thread.");
45 |
46 | result = javaVm->AttachCurrentThread(&env, nullptr);
47 | if (result != JNI_OK) {
48 | LOGE("Failed to attach to JNI thread, result: %i", result);
49 | return nullptr;
50 | }
51 |
52 | *shouldDetach = true;
53 | } else if (result != JNI_OK) {
54 | LOGE("Failed to get JNIEnv, result: %i", result);
55 | return nullptr;
56 | }
57 |
58 | LOGI("Got JNIEnv.");
59 | return env;
60 | }
61 |
62 |
63 | void DetachJNIEnv(JavaVM* javaVm) {
64 | if (javaVm == nullptr) {
65 | LOGE("javaVM is a nullptr, can't detach JNI thread.");
66 | return;
67 | }
68 |
69 | jint result = javaVm->DetachCurrentThread();
70 | if (result != JNI_OK) {
71 | LOGE("Failed to detach from JNI thread, result: %i", result);
72 | }
73 | }
--------------------------------------------------------------------------------
/UXR.QuestCamera/ProjectSettings/GraphicsSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!30 &1
4 | GraphicsSettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 16
7 | m_Deferred:
8 | m_Mode: 1
9 | m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0}
10 | m_DeferredReflections:
11 | m_Mode: 1
12 | m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0}
13 | m_ScreenSpaceShadows:
14 | m_Mode: 1
15 | m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0}
16 | m_DepthNormals:
17 | m_Mode: 1
18 | m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0}
19 | m_MotionVectors:
20 | m_Mode: 1
21 | m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0}
22 | m_LightHalo:
23 | m_Mode: 1
24 | m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0}
25 | m_LensFlare:
26 | m_Mode: 1
27 | m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0}
28 | m_VideoShadersIncludeMode: 2
29 | m_AlwaysIncludedShaders:
30 | - {fileID: 7, guid: 0000000000000000f000000000000000, type: 0}
31 | - {fileID: 15104, guid: 0000000000000000f000000000000000, type: 0}
32 | - {fileID: 15105, guid: 0000000000000000f000000000000000, type: 0}
33 | - {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0}
34 | - {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
35 | - {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0}
36 | m_PreloadedShaders: []
37 | m_PreloadShadersBatchTimeLimit: -1
38 | m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
39 | m_CustomRenderPipeline: {fileID: 0}
40 | m_TransparencySortMode: 0
41 | m_TransparencySortAxis: {x: 0, y: 0, z: 1}
42 | m_DefaultRenderingPath: 1
43 | m_DefaultMobileRenderingPath: 1
44 | m_TierSettings: []
45 | m_LightmapStripping: 0
46 | m_FogStripping: 0
47 | m_InstancingStripping: 0
48 | m_BrgStripping: 0
49 | m_LightmapKeepPlain: 1
50 | m_LightmapKeepDirCombined: 1
51 | m_LightmapKeepDynamicPlain: 1
52 | m_LightmapKeepDynamicDirCombined: 1
53 | m_LightmapKeepShadowMask: 1
54 | m_LightmapKeepSubtractive: 1
55 | m_FogKeepLinear: 1
56 | m_FogKeepExp: 1
57 | m_FogKeepExp2: 1
58 | m_AlbedoSwatchInfos: []
59 | m_RenderPipelineGlobalSettingsMap: {}
60 | m_LightsUseLinearIntensity: 0
61 | m_LightsUseColorTemperature: 0
62 | m_LogWhenShaderIsCompiled: 0
63 | m_LightProbeOutsideHullStrategy: 0
64 | m_CameraRelativeLightCulling: 0
65 | m_CameraRelativeShadowCulling: 0
66 |
--------------------------------------------------------------------------------
/UXR.QuestCamera/Packages/com.uralstech.uxr.questcamera/Samples~/DigitRecognitionSample/Shaders/YUVToLuminanceConverter.compute:
--------------------------------------------------------------------------------
1 | // Copyright 2025 URAV ADVANCED LEARNING SYSTEMS PRIVATE LIMITED
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | #pragma kernel CSMain
16 |
17 | // Input buffers (read-only)
18 | ByteAddressBuffer YBuffer;
19 | ByteAddressBuffer UBuffer;
20 | ByteAddressBuffer VBuffer;
21 |
22 | // Row strides
23 | uint YRowStride;
24 | uint UVRowStride;
25 |
26 | // Pixel strides
27 | uint UVPixelStride;
28 |
29 | // Image dimensions
30 | uint TargetWidth;
31 | uint TargetHeight;
32 |
33 | // Output texture (read-write)
34 | RWTexture2D OutputTexture;
35 |
36 | // Helper function to get a byte from a ByteAddressBuffer.
37 | // buffer: The ByteAddressBuffer.
38 | // byteIndex: The *byte* index (offset) into the buffer.
39 | uint GetByteFromBuffer(ByteAddressBuffer buffer, uint byteIndex)
40 | {
41 | // Calculate the 32-bit word offset (each word is 4 bytes).
42 | uint wordOffset = byteIndex / 4;
43 |
44 | // Load the 32-bit word containing the byte.
45 | uint word = buffer.Load(wordOffset * 4); // MUST multiply by 4 for ByteAddressBuffer.Load()
46 |
47 | // Calculate the byte position *within* the word (0, 1, 2, or 3).
48 | uint byteInWord = byteIndex % 4;
49 |
50 | // Extract the correct byte using bit shifts and masking.
51 | return (word >> (byteInWord * 8)) & 0xFF;
52 | }
53 |
54 | [numthreads(8, 8, 1)]
55 | void CSMain(uint3 id : SV_DispatchThreadID)
56 | {
57 | if (id.x >= TargetWidth || id.y >= TargetHeight)
58 | return;
59 |
60 | // The YUV stream is flipped, so we have to un-flip it.
61 | uint flippedY = TargetHeight - 1 - id.y;
62 |
63 | // Index of Y value in buffer.
64 | uint yIndex = flippedY * YRowStride + id.x;
65 |
66 | // Get the Y (luminance) value.
67 | uint yValue = GetByteFromBuffer(YBuffer, yIndex);
68 |
69 | // Convert the values from 0-255 to 0-1 and set the output texture.
70 | float3 luminance = float3(yValue, yValue, yValue) / 255.0;
71 | OutputTexture[id.xy] = float4(luminance.rgb, 1.0);
72 | }
--------------------------------------------------------------------------------
/UXR.QuestCamera/Packages/com.uralstech.uxr.questcamera/Runtime/Scripts/CaptureSession/OnDemandCaptureSession.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2025 URAV ADVANCED LEARNING SYSTEMS PRIVATE LIMITED
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | using System;
16 |
17 | #nullable enable
18 | namespace Uralstech.UXR.QuestCamera
19 | {
20 | ///
21 | /// A wrapper for a native Camera2 CaptureSession and ImageReader.
22 | ///
23 | ///
24 | /// This is different from as it only returns a frame
25 | /// from the native plugin when required. This is recommended for single-image
26 | /// capturing or on-demand capturing where you don't need a continuous stream
27 | /// of images.
28 | ///
29 | /// Why does inherit from ?
30 | /// Because under the hood, both do the same thing - a repeating capture session. A true on-demand
31 | /// capture results in a black image, so runs a repeating capture
32 | /// request running on an dummy texture natively, and reads the actual image through an ImageReader only
33 | /// when requested to do so. This means that while the processes
34 | /// each and every frame sent to it, converting it to RGBA, only
35 | /// does it when required.
36 | ///
37 | public class OnDemandCaptureSession : ContinuousCaptureSession
38 | {
39 | ///
40 | /// Requests a new capture from the session.
41 | ///
42 | /// The capture template to use for the capture
43 | /// If the capture request was set successfully, , otherwise, .
44 | public bool RequestCapture(CaptureTemplate captureTemplate = CaptureTemplate.StillCapture)
45 | {
46 | return _captureSession?.Call("setSingleCaptureRequest", (int)captureTemplate) ?? throw new ObjectDisposedException(nameof(OnDemandCaptureSession));
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/UXR.QuestCamera/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # UXR.QuestCamera
2 |
3 | A Unity package to use the new Meta Quest Passthrough Camera API.
4 |
5 | [](https://openupm.com/packages/com.uralstech.uxr.questcamera/)
6 | [](https://openupm.com/packages/com.uralstech.uxr.questcamera/)
7 |
8 | ## Installation
9 |
10 | This *should* work on any reasonably modern Unity version. Built and tested in Unity 6.3.
11 | Versions older than Unity 6.0 **REQUIRE** [com.utilities.async](https://github.com/RageAgainstThePixel/com.utilities.async/).
12 |
13 | Since this package contains a native AAR plugin, it depends on the [External Dependency Manager for Unity (EDM4U)](https://github.com/googlesamples/unity-jar-resolver) to resolve native dependencies.
14 | You may already have EDM4U if you use Google or Firebase SDKs in your Unity project. If you do not, the installation steps are available
15 | here: [EDM4U - Getting Started](https://github.com/googlesamples/unity-jar-resolver?tab=readme-ov-file#getting-started)
16 |
17 | ### OpenUPM
18 |
19 | 1. Open project settings
20 | 2. Select `Package Manager`
21 | 3. Add the OpenUPM package registry:
22 | - Name: `OpenUPM`
23 | - URL: `https://package.openupm.com`
24 | - Scope(s)
25 | - `com.uralstech`
26 | 4. Open the Unity Package Manager window (`Window` -> `Package Manager`)
27 | 5. Change the registry from `Unity` to `My Registries`
28 | 6. Add the `UXR.QuestCamera` package
29 |
30 | ### Unity Package Manager
31 |
32 | 1. Open the Unity Package Manager window (`Window` -> `Package Manager`)
33 | 2. Select the `+` icon and `Add package from git URL...`
34 | 3. Paste the UPM branch URL and press enter:
35 | - `https://github.com/Uralstech/UXR.QuestCamera.git#upm`
36 | 4. Check the instructions for [`Utils.Singleton`](https://uralstech.github.io/Utils.Singleton) to install the dependency
37 |
38 | ### GitHub Clone
39 |
40 | 1. Clone or download the repository from the desired branch (master, preview/unstable)
41 | 2. Drag the package folder `UXR.QuestCamera/UXR.QuestCamera/Packages/com.uralstech.uxr.questcamera` into your Unity project's `Packages` folder
42 | 3. Check the instructions for [`Utils.Singleton`](https://uralstech.github.io/Utils.Singleton) to install the dependency
43 |
44 | ## Preview Versions
45 |
46 | Do not use preview versions (i.e. versions that end with "-preview") for production use as they are unstable and untested.
47 |
48 | ## Documentation
49 |
50 | See or `APIReferenceManual.pdf` and `Documentation.pdf` in the package documentation for the reference manual and tutorial.
51 |
--------------------------------------------------------------------------------
/Documentation/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | _layout: landing
3 | ---
4 |
5 | # UXR.QuestCamera
6 |
7 | A Unity package to use the new Meta Quest Passthrough Camera API.
8 |
9 | [](https://openupm.com/packages/com.uralstech.uxr.questcamera/)
10 | [](https://openupm.com/packages/com.uralstech.uxr.questcamera/)
11 |
12 | ## Installation
13 |
14 | This *should* work on any reasonably modern Unity version. Built and tested in Unity 6.3.
15 | Versions older than Unity 6.0 **REQUIRE** [com.utilities.async](https://github.com/RageAgainstThePixel/com.utilities.async/).
16 |
17 | Since this package contains a native AAR plugin, it depends on the [External Dependency Manager for Unity (EDM4U)](https://github.com/googlesamples/unity-jar-resolver) to resolve native dependencies.
18 | You may already have EDM4U if you use Google or Firebase SDKs in your Unity project. If you do not, the installation steps are available
19 | here: [EDM4U - Getting Started](https://github.com/googlesamples/unity-jar-resolver?tab=readme-ov-file#getting-started)
20 |
21 | # [OpenUPM](#tab/openupm)
22 |
23 | 1. Open project settings
24 | 2. Select `Package Manager`
25 | 3. Add the OpenUPM package registry:
26 | - Name: `OpenUPM`
27 | - URL: `https://package.openupm.com`
28 | - Scope(s)
29 | - `com.uralstech`
30 | 4. Open the Unity Package Manager window (`Window` -> `Package Manager`)
31 | 5. Change the registry from `Unity` to `My Registries`
32 | 6. Add the `UXR.QuestCamera` package
33 |
34 | # [Unity Package Manager](#tab/upm)
35 |
36 | 1. Open the Unity Package Manager window (`Window` -> `Package Manager`)
37 | 2. Select the `+` icon and `Add package from git URL...`
38 | 3. Paste the UPM branch URL and press enter:
39 | - `https://github.com/Uralstech/UXR.QuestCamera.git#upm`
40 | 4. Check the instructions for [`Utils.Singleton`](https://uralstech.github.io/Utils.Singleton) to install the dependency
41 |
42 | # [GitHub Clone](#tab/github)
43 |
44 | 1. Clone or download the repository from the desired branch (master, preview/unstable)
45 | 2. Drag the package folder `UXR.QuestCamera/UXR.QuestCamera/Packages/com.uralstech.uxr.questcamera` into your Unity project's `Packages` folder
46 | 3. Check the instructions for [`Utils.Singleton`](https://uralstech.github.io/Utils.Singleton) to install the dependency
47 |
48 | ---
49 |
50 | ## Preview Versions
51 |
52 | Do not use preview versions (i.e. versions that end with "-preview") for production use as they are unstable and untested.
53 |
54 | ## Documentation
55 |
56 | See or `APIReferenceManual.pdf` and `Documentation.pdf` in the package documentation for the reference manual and tutorial.
57 |
--------------------------------------------------------------------------------
/UCamera/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/UXR.QuestCamera/Packages/com.uralstech.uxr.questcamera/Runtime/Shaders/YUVConverter.compute:
--------------------------------------------------------------------------------
1 | // Copyright 2025 URAV ADVANCED LEARNING SYSTEMS PRIVATE LIMITED
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | #pragma kernel CSMain
16 |
17 | // Input buffers (read-only)
18 | ByteAddressBuffer YBuffer;
19 | ByteAddressBuffer UBuffer;
20 | ByteAddressBuffer VBuffer;
21 |
22 | // Row strides
23 | uint YRowStride;
24 | uint UVRowStride;
25 |
26 | // Pixel strides
27 | uint UVPixelStride;
28 |
29 | // Image dimensions
30 | uint TargetWidth;
31 | uint TargetHeight;
32 |
33 | // Output texture (read-write)
34 | RWTexture2D OutputTexture;
35 |
36 | // Helper function to get a byte from a ByteAddressBuffer.
37 | // buffer: The ByteAddressBuffer.
38 | // byteIndex: The *byte* index (offset) into the buffer.
39 | uint GetByteFromBuffer(ByteAddressBuffer buffer, uint byteIndex)
40 | {
41 | // Calculate the 32-bit word offset (each word is 4 bytes).
42 | uint wordOffset = byteIndex / 4;
43 |
44 | // Load the 32-bit word containing the byte.
45 | uint word = buffer.Load(wordOffset * 4); // MUST multiply by 4 for ByteAddressBuffer.Load()
46 |
47 | // Calculate the byte position *within* the word (0, 1, 2, or 3).
48 | uint byteInWord = byteIndex % 4;
49 |
50 | // Extract the correct byte using bit shifts and masking.
51 | return (word >> (byteInWord * 8)) & 0xFF;
52 | }
53 |
54 | // Helper function to convert YUV to RGB.
55 | float3 YUVtoRGB(uint y, uint u, uint v)
56 | {
57 | // ITU-R BT.601 standard convertion
58 |
59 | float yf = float(y) + 16.0;
60 | float uf = float(u) - 128.0;
61 | float vf = float(v) - 128.0;
62 |
63 | float3 rgb = float3(
64 | yf + 1.402 * vf,
65 | yf - 0.344136 * uf - 0.714136 * vf,
66 | yf + 1.772 * uf
67 | );
68 |
69 | return saturate(rgb / 255.0);
70 | }
71 |
72 | [numthreads(8, 8, 1)]
73 | void CSMain(uint3 id : SV_DispatchThreadID)
74 | {
75 | if (id.x >= TargetWidth || id.y >= TargetHeight)
76 | return;
77 |
78 | // The YUV stream is flipped, so we have to un-flip it.
79 | uint flippedY = TargetHeight - 1 - id.y;
80 |
81 | // Index of Y value in buffer.
82 | uint yIndex = flippedY * YRowStride + id.x;
83 |
84 | // Index of the U and V values in the buffer. They are the same for YUV_420_888:
85 | // https://developer.android.com/reference/android/graphics/ImageFormat#YUV_420_888
86 | uint uvIndex = (flippedY / 2) * UVRowStride + (id.x / 2) * UVPixelStride;
87 |
88 | // Get Y, U, and V values.
89 | uint yValue = GetByteFromBuffer(YBuffer, yIndex);
90 | uint uValue = GetByteFromBuffer(UBuffer, uvIndex);
91 | uint vValue = GetByteFromBuffer(VBuffer, uvIndex);
92 |
93 | // Convert them and set the output texture.
94 | float3 converted = YUVtoRGB(yValue, uValue, vValue);
95 | OutputTexture[id.xy] = float4(converted.rgb, 1.0);
96 | }
--------------------------------------------------------------------------------
/UCamera/UCamera/src/main/java/com/uralstech/ucamera/Camera2Wrapper.kt:
--------------------------------------------------------------------------------
1 | // Copyright 2025 URAV ADVANCED LEARNING SYSTEMS PRIVATE LIMITED
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package com.uralstech.ucamera
16 |
17 | import android.Manifest
18 | import android.content.Context
19 | import android.hardware.camera2.CameraAccessException
20 | import android.hardware.camera2.CameraManager
21 | import android.util.Log
22 | import androidx.annotation.RequiresPermission
23 | import com.unity3d.player.UnityPlayer
24 |
25 | /**
26 | * Script to manage Camera2 resources.
27 | */
28 | class Camera2Wrapper {
29 |
30 | companion object {
31 | /** Logcat tag. */
32 | private const val TAG = "Camera2Wrapper"
33 |
34 | @JvmStatic
35 | val instance: Camera2Wrapper by lazy {
36 | Camera2Wrapper()
37 | }
38 | }
39 |
40 | /** The application context. */
41 | private val appContext = UnityPlayer.currentContext.applicationContext
42 |
43 | /** Detects, characterizes, and connects to a CameraDevice (used for all camera operations). */
44 | private val cameraManager: CameraManager by lazy {
45 | appContext.getSystemService(Context.CAMERA_SERVICE) as CameraManager
46 | }
47 |
48 | /**
49 | * Gets all available camera devices and their characteristics.
50 | */
51 | fun getCameraDevices(): Array? {
52 | return try {
53 | Log.i(TAG, "Getting camera intrinsics.")
54 |
55 | val cameraIdList = cameraManager.cameraIdList
56 | val wrappers = mutableListOf()
57 |
58 | for (cameraId in cameraIdList) {
59 | try {
60 | val characteristics = cameraManager.getCameraCharacteristics(cameraId)
61 | wrappers.add(CameraCharacteristicsWrapper(cameraId, characteristics))
62 | } catch (_: CameraAccessException) { }
63 | }
64 |
65 | wrappers.toTypedArray()
66 | } catch (exp: CameraAccessException) {
67 | Log.e(TAG, "Could not get camera characteristics due to a camera access exception.", exp)
68 | null
69 | } catch (exp: SecurityException) {
70 | Log.e(TAG, "Could not get camera characteristics due to a security exception.", exp)
71 | null
72 | }
73 | }
74 |
75 | /**
76 | * Creates a wrapper for a camera device, and tries to open it.
77 | * The returned wrapper may or may not have an open device, the
78 | * exact status of the device will be sent through the wrapper's
79 | * callbacks.
80 | */
81 | @RequiresPermission(Manifest.permission.CAMERA)
82 | fun openCameraDevice(camera: String, callbacks: CameraDeviceWrapper.Callbacks): CameraDeviceWrapper {
83 | Log.i(TAG, "Opening camera device with ID \"$camera\"")
84 | return CameraDeviceWrapper(camera, callbacks, cameraManager)
85 | }
86 | }
--------------------------------------------------------------------------------
/UCamera/UCamera/src/main/java/com/uralstech/ucamera/CameraCharacteristicsWrapper.kt:
--------------------------------------------------------------------------------
1 | // Copyright 2025 URAV ADVANCED LEARNING SYSTEMS PRIVATE LIMITED
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package com.uralstech.ucamera
16 |
17 | import android.graphics.ImageFormat
18 | import android.hardware.camera2.CameraCharacteristics
19 |
20 | /**
21 | * Wrapper for [CameraCharacteristics].
22 | */
23 | class CameraCharacteristicsWrapper(val cameraId: String, val characteristics: CameraCharacteristics) {
24 | companion object {
25 | private const val META_CAMERA_SOURCE_METADATA = "com.meta.extra_metadata.camera_source"
26 | private const val META_CAMERA_POSITION_METADATA = "com.meta.extra_metadata.position"
27 |
28 | private val metaCameraSourceMetadata = CameraCharacteristics.Key(META_CAMERA_SOURCE_METADATA, Int::class.java)
29 | private val metaCameraPositionMetadata = CameraCharacteristics.Key(META_CAMERA_POSITION_METADATA, Int::class.java)
30 | }
31 |
32 | /**
33 | * (Meta Quest) The source of the camera feed.
34 | */
35 | val metaQuestCameraSource = characteristics.get(metaCameraSourceMetadata)
36 |
37 | /**
38 | * (Meta Quest) The eye which the camera is closest to.
39 | */
40 | val metaQuestCameraEye = characteristics.get(metaCameraPositionMetadata)
41 |
42 | /**
43 | * The position of the camera device's lens optical center.
44 | */
45 | val lensPoseTranslation = characteristics.get(CameraCharacteristics.LENS_POSE_TRANSLATION)
46 |
47 | /**
48 | * The orientation of the camera relative to the sensor coordinate system.
49 | */
50 | val lensPoseRotation = characteristics.get(CameraCharacteristics.LENS_POSE_ROTATION)
51 |
52 | /**
53 | * The resolutions supported by this device.
54 | */
55 | val supportedResolutions = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)?.getOutputSizes(ImageFormat.YUV_420_888) ?: emptyArray()
56 |
57 | /**
58 | * The area of the image sensor which corresponds to active pixels prior to the application of any geometric distortion correction.
59 | */
60 | val intrinsicsResolution = characteristics.get(CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE)?.let { sensorSize ->
61 | intArrayOf(sensorSize.right, sensorSize.bottom)
62 | }
63 |
64 | /**
65 | * The horizontal and vertical focal lengths, in pixels.
66 | */
67 | val intrinsicsFocalLength: FloatArray?
68 |
69 | /**
70 | * Principal point in pixels from the image's top-left corner.
71 | */
72 | val intrinsicsPrincipalPoint: FloatArray?
73 |
74 | /**
75 | * Skew coefficient for axis misalignment.
76 | */
77 | val intrinsicsSkew: Float?
78 |
79 | init {
80 | val lensCalibration = characteristics.get(CameraCharacteristics.LENS_INTRINSIC_CALIBRATION)
81 | intrinsicsFocalLength = lensCalibration?.let { floatArrayOf(it[0], it[1]) }
82 | intrinsicsPrincipalPoint = lensCalibration?.let { floatArrayOf(it[2], it[3]) }
83 | intrinsicsSkew = lensCalibration?.get(4)
84 | }
85 | }
--------------------------------------------------------------------------------
/UXR.QuestCamera/Packages/com.uralstech.uxr.questcamera/Runtime/Scripts/External/CameraSupport.cs:
--------------------------------------------------------------------------------
1 | // Copyright(c) Meta Platforms, Inc. and affiliates.
2 | // All rights reserved.
3 | //
4 | // Licensed under the Oculus SDK License Agreement (the "License");
5 | // you may not use the Oculus SDK except in compliance with the License,
6 | // which is provided at the time of installation or download, or which
7 | // otherwise accompanies this software in either electronic or hard copy form.
8 | //
9 | // You may obtain a copy of the License at
10 | //
11 | // https://developer.oculus.com/licenses/oculussdk/
12 | //
13 | // Unless required by applicable law or agreed to in writing, the Oculus SDK
14 | // distributed under the License is distributed on an "AS IS" BASIS,
15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | // See the License for the specific language governing permissions and
17 | // limitations under the License.
18 |
19 | #if META_XR_SDK_CORE
20 | using UnityEngine;
21 |
22 | #nullable enable
23 | namespace Uralstech.UXR.QuestCamera
24 | {
25 | ///
26 | /// Utility to check if the current Meta Quest device supports the Passthrough Camera API.
27 | ///
28 | ///
29 | /// Requires the Meta XR Core SDK.
30 | ///
31 | public static class CameraSupport
32 | {
33 |
34 | // The Horizon OS starts supporting PCA with v74.
35 | public const int MINSUPPORTOSVERSION = 74;
36 |
37 | private static bool? s_isSupported;
38 | private static int? s_horizonOsVersion;
39 |
40 | ///
41 | /// Get the Horizon OS version number on the headset
42 | ///
43 | ///
44 | /// Requires the Meta XR Core SDK.
45 | ///
46 | public static int? HorizonOSVersion
47 | {
48 | get
49 | {
50 | if (!s_horizonOsVersion.HasValue)
51 | {
52 | AndroidJavaClass vrosClass = new("vros.os.VrosBuild");
53 | s_horizonOsVersion = vrosClass.CallStatic("getSdkVersion");
54 | #if OVR_INTERNAL_CODE
55 | // 10000 means that the build doesn't have a proper release version, and it is still in Mainline,
56 | // not in a release branch.
57 | #endif // OVR_INTERNAL_CODE
58 | if (s_horizonOsVersion == 10000)
59 | {
60 | s_horizonOsVersion = -1;
61 | }
62 | }
63 |
64 | return s_horizonOsVersion.Value != -1 ? s_horizonOsVersion.Value : null;
65 | }
66 | }
67 |
68 | ///
69 | /// Returns true if the current headset supports Passthrough Camera API
70 | ///
71 | ///
72 | /// Requires the Meta XR Core SDK.
73 | ///
74 | public static bool IsSupported
75 | {
76 | get
77 | {
78 | if (!s_isSupported.HasValue)
79 | {
80 | OVRPlugin.SystemHeadset headset = OVRPlugin.GetSystemHeadsetType();
81 | bool isSupported = (headset == OVRPlugin.SystemHeadset.Meta_Quest_3 ||
82 | headset == OVRPlugin.SystemHeadset.Meta_Quest_3S) &&
83 | (!HorizonOSVersion.HasValue || HorizonOSVersion >= MINSUPPORTOSVERSION);
84 |
85 | s_isSupported = isSupported;
86 | }
87 |
88 | return s_isSupported.Value;
89 | }
90 | }
91 | }
92 | }
93 |
94 | #endif
--------------------------------------------------------------------------------
/UXR.QuestCamera/ProjectSettings/SceneTemplateSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "templatePinStates": [],
3 | "dependencyTypeInfos": [
4 | {
5 | "userAdded": false,
6 | "type": "UnityEngine.AnimationClip",
7 | "defaultInstantiationMode": 0
8 | },
9 | {
10 | "userAdded": false,
11 | "type": "UnityEditor.Animations.AnimatorController",
12 | "defaultInstantiationMode": 0
13 | },
14 | {
15 | "userAdded": false,
16 | "type": "UnityEngine.AnimatorOverrideController",
17 | "defaultInstantiationMode": 0
18 | },
19 | {
20 | "userAdded": false,
21 | "type": "UnityEditor.Audio.AudioMixerController",
22 | "defaultInstantiationMode": 0
23 | },
24 | {
25 | "userAdded": false,
26 | "type": "UnityEngine.ComputeShader",
27 | "defaultInstantiationMode": 1
28 | },
29 | {
30 | "userAdded": false,
31 | "type": "UnityEngine.Cubemap",
32 | "defaultInstantiationMode": 0
33 | },
34 | {
35 | "userAdded": false,
36 | "type": "UnityEngine.GameObject",
37 | "defaultInstantiationMode": 0
38 | },
39 | {
40 | "userAdded": false,
41 | "type": "UnityEditor.LightingDataAsset",
42 | "defaultInstantiationMode": 0
43 | },
44 | {
45 | "userAdded": false,
46 | "type": "UnityEngine.LightingSettings",
47 | "defaultInstantiationMode": 0
48 | },
49 | {
50 | "userAdded": false,
51 | "type": "UnityEngine.Material",
52 | "defaultInstantiationMode": 0
53 | },
54 | {
55 | "userAdded": false,
56 | "type": "UnityEditor.MonoScript",
57 | "defaultInstantiationMode": 1
58 | },
59 | {
60 | "userAdded": false,
61 | "type": "UnityEngine.PhysicsMaterial",
62 | "defaultInstantiationMode": 0
63 | },
64 | {
65 | "userAdded": false,
66 | "type": "UnityEngine.PhysicsMaterial2D",
67 | "defaultInstantiationMode": 0
68 | },
69 | {
70 | "userAdded": false,
71 | "type": "UnityEngine.Rendering.PostProcessing.PostProcessProfile",
72 | "defaultInstantiationMode": 0
73 | },
74 | {
75 | "userAdded": false,
76 | "type": "UnityEngine.Rendering.PostProcessing.PostProcessResources",
77 | "defaultInstantiationMode": 0
78 | },
79 | {
80 | "userAdded": false,
81 | "type": "UnityEngine.Rendering.VolumeProfile",
82 | "defaultInstantiationMode": 0
83 | },
84 | {
85 | "userAdded": false,
86 | "type": "UnityEditor.SceneAsset",
87 | "defaultInstantiationMode": 1
88 | },
89 | {
90 | "userAdded": false,
91 | "type": "UnityEngine.Shader",
92 | "defaultInstantiationMode": 1
93 | },
94 | {
95 | "userAdded": false,
96 | "type": "UnityEngine.ShaderVariantCollection",
97 | "defaultInstantiationMode": 1
98 | },
99 | {
100 | "userAdded": false,
101 | "type": "UnityEngine.Texture",
102 | "defaultInstantiationMode": 0
103 | },
104 | {
105 | "userAdded": false,
106 | "type": "UnityEngine.Texture2D",
107 | "defaultInstantiationMode": 0
108 | },
109 | {
110 | "userAdded": false,
111 | "type": "UnityEngine.Timeline.TimelineAsset",
112 | "defaultInstantiationMode": 0
113 | }
114 | ],
115 | "defaultDependencyTypeInfo": {
116 | "userAdded": false,
117 | "type": "",
118 | "defaultInstantiationMode": 1
119 | },
120 | "newSceneOverride": 0
121 | }
--------------------------------------------------------------------------------
/UXR.QuestCamera/Packages/com.uralstech.uxr.questcamera/Runtime/Scripts/Extensions/TaskExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2025 URAV ADVANCED LEARNING SYSTEMS PRIVATE LIMITED
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | using System;
16 | using System.Threading.Tasks;
17 | using UnityEngine;
18 |
19 | #if !UNITY_6000_0_OR_NEWER
20 | using Utilities.Async;
21 | #endif
22 |
23 | #nullable enable
24 | namespace Uralstech.UXR.QuestCamera
25 | {
26 | ///
27 | /// Extensions for .
28 | ///
29 | public static class TaskExtensions
30 | {
31 | ///
32 | /// Adds a continuation to a task to log exceptions.
33 | ///
34 | public static void HandleAnyException(this Task current)
35 | {
36 | current.ContinueWith(static t =>
37 | {
38 | foreach (Exception ex in t.Exception.Flatten().InnerExceptions)
39 | Debug.LogException(ex);
40 | }, TaskContinuationOptions.OnlyOnFaulted);
41 | }
42 |
43 | ///
44 | /// Invokes the current action on the main thread.
45 | ///
46 | public static async Task InvokeOnMainThread(this Action? current)
47 | {
48 | #if UNITY_6000_0_OR_NEWER
49 | await Awaitable.MainThreadAsync();
50 | #else
51 | await Awaiters.UnityMainThread;
52 | #endif
53 |
54 | current?.Invoke();
55 | }
56 |
57 | ///
58 | /// Invokes the current action on the main thread.
59 | ///
60 | public static async Task InvokeOnMainThread(this Action? current, T arg0)
61 | {
62 | #if UNITY_6000_0_OR_NEWER
63 | await Awaitable.MainThreadAsync();
64 | #else
65 | await Awaiters.UnityMainThread;
66 | #endif
67 |
68 | current?.Invoke(arg0);
69 | }
70 |
71 | ///
72 | /// Invokes the current action on the main thread.
73 | ///
74 | public static async Task InvokeOnMainThread(this Action? current, T0 arg0, T1 arg1)
75 | {
76 | #if UNITY_6000_0_OR_NEWER
77 | await Awaitable.MainThreadAsync();
78 | #else
79 | await Awaiters.UnityMainThread;
80 | #endif
81 |
82 | current?.Invoke(arg0, arg1);
83 | }
84 |
85 | ///
86 | /// Allows for "yielding" a using a object.
87 | ///
88 | public static WaitUntil Yield(this ValueTask current) => new(() => current.IsCompleted);
89 |
90 | ///
91 | /// Allows for "yielding" a using a object.
92 | ///
93 | public static WaitUntil Yield(this Task current) => new(() => current.IsCompleted);
94 |
95 | #if UNITY_6000_0_OR_NEWER
96 | ///
97 | ///
98 | public static WaitUntil Yield(this ValueTask current, TimeSpan timeout, Action onTimeout, WaitTimeoutMode timeoutMode = WaitTimeoutMode.Realtime) =>
99 | new(() => current.IsCompleted, timeout, onTimeout, timeoutMode);
100 |
101 | ///
102 | ///
103 | public static WaitUntil Yield(this Task current, TimeSpan timeout, Action onTimeout, WaitTimeoutMode timeoutMode = WaitTimeoutMode.Realtime) =>
104 | new(() => current.IsCompleted, timeout, onTimeout, timeoutMode);
105 | #endif
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/UCamera/UCamera/src/main/java/com/uralstech/ucamera/OnDemandCaptureSessionWrapper.kt:
--------------------------------------------------------------------------------
1 | // Copyright 2025 URAV ADVANCED LEARNING SYSTEMS PRIVATE LIMITED
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package com.uralstech.ucamera
16 |
17 | import android.graphics.SurfaceTexture
18 | import android.hardware.camera2.CameraAccessException
19 | import android.hardware.camera2.CameraCaptureSession
20 | import android.hardware.camera2.CameraDevice
21 | import android.hardware.camera2.params.OutputConfiguration
22 | import android.util.Log
23 | import android.view.Surface
24 |
25 | /**
26 | * Wrapper class for [CameraCaptureSession] meant for taking on-demand captures.
27 | *
28 | * It still runs a repeating preview stream in the background, so that when
29 | * a capture is requested, it is not pitch black due to it being the first
30 | * frame being captured.
31 | */
32 | class OnDemandCaptureSessionWrapper(
33 | cameraDevice: CameraDevice, captureTemplate: Int,
34 | callbacks: Callbacks, width: Int, height: Int) : CaptureSessionWrapper(cameraDevice, captureTemplate, callbacks, width, height) {
35 | companion object {
36 | const val TAG = "ODCaptureSessionWrapper"
37 | }
38 |
39 | /** Dummy surface texture for preview capture request. */
40 | private var dummySurfaceTexture: SurfaceTexture? = null
41 |
42 | /** Dummy surface for preview capture request. */
43 | private var dummySurface: Surface? = null
44 |
45 | /**
46 | * Creates a new capture session and sets the repeating capture request.
47 | */
48 | override fun startCaptureSession(camera: CameraDevice, captureTemplate: Int) {
49 | Log.i(TAG, "Setting up capture session for single-capture request.")
50 |
51 | val dummySurfaceTexture = SurfaceTexture(0)
52 | val dummySurface = Surface(dummySurfaceTexture)
53 | this.dummySurfaceTexture = dummySurfaceTexture
54 | this.dummySurface = dummySurface
55 |
56 | try {
57 | super.startRepeatingCaptureSession(camera, captureTemplate,
58 | listOf(OutputConfiguration(dummySurface), OutputConfiguration(imageReader.surface)), dummySurface)
59 | } catch (exp: Exception) {
60 | dummySurface.release()
61 | this.dummySurface = null
62 |
63 | dummySurfaceTexture.release()
64 | this.dummySurfaceTexture = null
65 |
66 | Log.e(TAG, "Failed to start repeating capture session, cleaned up dummy surfaces.", exp)
67 | throw exp
68 | }
69 | }
70 |
71 | /**
72 | * Sets a new non-repeating capture request.
73 | */
74 | fun setSingleCaptureRequest(captureTemplate: Int): Boolean {
75 | val captureSession = this.captureSession
76 | if (isDisposed || captureSession == null) {
77 | Log.e(TAG, "Tried to set non-repeating capture request for unusable capture session.")
78 | return false
79 | }
80 |
81 | Log.i(TAG, "Setting non-repeating capture request.")
82 |
83 | try {
84 | val captureRequest = captureSession.device.createCaptureRequest(captureTemplate).apply {
85 | addTarget(imageReader.surface)
86 | }.build()
87 |
88 | captureSession.captureSingleRequest(captureRequest, captureSessionExecutor, object : CameraCaptureSession.CaptureCallback() { })
89 |
90 | Log.i(TAG, "Non-repeating capture request set for camera session of camera with ID \"${captureSession.device.id}\".")
91 | return true
92 | } catch (exp: CameraAccessException) {
93 | Log.e(TAG, "Camera device with ID \"${captureSession.device.id}\" erred out with a camera access exception.", exp)
94 | return false
95 | } catch (exp: SecurityException) {
96 | Log.e(TAG, "Camera device with ID \"${captureSession.device.id}\" erred out with a security exception.", exp)
97 | return false
98 | }
99 | }
100 |
101 | /**
102 | * Same as [CaptureSessionWrapper.closeWork], but also releases [dummySurfaceTexture].
103 | */
104 | override fun closeWork() {
105 | super.closeWork()
106 |
107 | dummySurface?.release()
108 | dummySurface = null
109 |
110 | dummySurfaceTexture?.release()
111 | dummySurfaceTexture = null
112 |
113 | Log.i(TAG, "Dummy texture released.")
114 | }
115 | }
--------------------------------------------------------------------------------
/UXR.QuestCamera/Packages/com.uralstech.uxr.questcamera/Runtime/Scripts/CaptureSession/SurfaceTextureCapture/OnDemandSurfaceTextureCaptureSession.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2025 URAV ADVANCED LEARNING SYSTEMS PRIVATE LIMITED
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | using System;
16 | using System.Threading;
17 | using System.Threading.Tasks;
18 | using UnityEngine;
19 | using static Uralstech.UXR.QuestCamera.SurfaceTextureCapture.STCaptureSessionNative;
20 |
21 | #if !UNITY_6000_0_OR_NEWER
22 | using Utilities.Async;
23 | #endif
24 |
25 | #nullable enable
26 | namespace Uralstech.UXR.QuestCamera.SurfaceTextureCapture
27 | {
28 | ///
29 | /// On-demand version of .
30 | ///
31 | ///
32 | /// This experimental capture session uses a native OpenGL texture to capture images for better performance and
33 | /// requires OpenGL ES 3.0 as the project's graphics API. Works with single and multi-threaded rendering.
34 | ///
35 | public class OnDemandSurfaceTextureCaptureSession : SurfaceTextureCaptureSession
36 | {
37 | public OnDemandSurfaceTextureCaptureSession(Resolution resolution) : base(resolution) { }
38 |
39 | ///
40 | public override IntPtr Invoke(string methodName, IntPtr javaArgs)
41 | {
42 | if (methodName == "onCaptureCompleted")
43 | {
44 | CaptureTimestamp = JNIExtensions.UnboxLongElement(javaArgs, 0);
45 | return IntPtr.Zero;
46 | }
47 |
48 | return base.Invoke(methodName, javaArgs);
49 | }
50 |
51 | ///
52 | /// Has the capture session received its first frame?
53 | ///
54 | public bool HasFrame => _nativeTextureId != null && CaptureTimestamp != 0;
55 |
56 | ///
57 | /// Updates the unity texture with the latest capture from the camera.
58 | ///
59 | /// Called when the capture has been rendered in unity, with its timestamp.
60 | /// if the renderer was invoked, otherwise.
61 | public bool RequestCapture(Action onDone)
62 | {
63 | ThrowIfDisposed();
64 | if (!HasFrame)
65 | return false;
66 |
67 | SendNativeUpdate(NativeEventId.RenderTextures, (_, result, timestamp) =>
68 | {
69 | if (result)
70 | {
71 | GL.InvalidateState();
72 | OnFrameReadyInvk(Texture, timestamp);
73 | onDone.InvokeOnMainThread(Texture, timestamp).HandleAnyException();
74 | }
75 | }, CaptureTimestamp).HandleAnyException();
76 | return true;
77 | }
78 |
79 | ///
80 | /// Updates the unity texture with the latest capture from the camera.
81 | ///
82 | /// Returns a WaitUntil operation if the renderer was invoked, otherwise.
83 | public WaitUntil? RequestCapture()
84 | {
85 | ThrowIfDisposed();
86 |
87 | bool isDone = false;
88 | return RequestCapture((_, _) => isDone = true)
89 | ? new WaitUntil(() => isDone) : null;
90 | }
91 |
92 | #if UNITY_6000_0_OR_NEWER
93 | ///
94 | ///
95 | public WaitUntil? RequestCapture(TimeSpan timeout, Action onTimeout, WaitTimeoutMode timeoutMode = WaitTimeoutMode.Realtime)
96 | {
97 | ThrowIfDisposed();
98 |
99 | bool isDone = false;
100 | return RequestCapture((_, _) => isDone = true)
101 | ? new WaitUntil(() => isDone, timeout, onTimeout, timeoutMode) : null;
102 | }
103 | #endif
104 |
105 | ///
106 | /// Updates the unity texture with the latest capture from the camera.
107 | ///
108 | /// The rendered texture and timestamp, or default values if the renderer could not be invoked.
109 | public async Task<(Texture2D?, long)> RequestCaptureAsync(CancellationToken token = default)
110 | {
111 | ThrowIfDisposed();
112 |
113 | TaskCompletionSource<(Texture2D?, long)> tcs = new();
114 | using (token.Register(tcs.SetCanceled))
115 | {
116 | return RequestCapture((texture, timestamp) => tcs.SetResult((texture, timestamp)))
117 | ? await tcs.Task : (null, 0);
118 | }
119 | }
120 |
121 | private void ThrowIfDisposed()
122 | {
123 | if (Disposed)
124 | throw new ObjectDisposedException(nameof(OnDemandSurfaceTextureCaptureSession));
125 | }
126 | }
127 | }
--------------------------------------------------------------------------------
/UXR.QuestCamera/Packages/com.uralstech.uxr.questcamera/Runtime/Scripts/Extensions/JNIExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2025 URAV ADVANCED LEARNING SYSTEMS PRIVATE LIMITED
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | using System;
16 | using UnityEngine;
17 |
18 | #nullable enable
19 | namespace Uralstech.UXR.QuestCamera
20 | {
21 | ///
22 | /// QOL extensions for the JNI.
23 | ///
24 | public static class JNIExtensions
25 | {
26 | ///
27 | /// Unboxes and creates a global ref of a native ByteBuffer from a native Object array, and returns its direct buffer address.
28 | ///
29 | /// The native array to take the buffer from.
30 | /// The index of the buffer object in the native array.
31 | /// The global reference and the direct buffer address.
32 | public static unsafe (IntPtr obj, IntPtr ptr) UnboxAndCreateGlobalRefForByteBufferElement(IntPtr args, int index)
33 | {
34 | IntPtr localRef = AndroidJNI.GetObjectArrayElement(args, index);
35 | IntPtr globalRef = AndroidJNI.NewGlobalRef(localRef);
36 | AndroidJNI.DeleteLocalRef(localRef);
37 |
38 | return (globalRef, (IntPtr)AndroidJNI.GetDirectBufferAddress(globalRef));
39 | }
40 |
41 | ///
42 | /// Unboxes an integer from a native Object array.
43 | ///
44 | /// The native array to take the integer from.
45 | /// The index of the integer object in the native array.
46 | /// The unboxed integer.
47 | public static int UnboxIntElement(IntPtr args, int index)
48 | {
49 | IntPtr ptr = AndroidJNI.GetObjectArrayElement(args, index);
50 | AndroidJNIHelper.Unbox(ptr, out int value);
51 |
52 | AndroidJNI.DeleteLocalRef(ptr);
53 | return value;
54 | }
55 |
56 | ///
57 | /// Unboxes a long from a native Object array.
58 | ///
59 | /// The native array to take the long from.
60 | /// The index of the long object in the native array.
61 | /// The unboxed long.
62 | public static long UnboxLongElement(IntPtr args, int index)
63 | {
64 | IntPtr ptr = AndroidJNI.GetObjectArrayElement(args, index);
65 | AndroidJNIHelper.Unbox(ptr, out long value);
66 |
67 | AndroidJNI.DeleteLocalRef(ptr);
68 | return value;
69 | }
70 |
71 | ///
72 | /// Unboxes a string from a native Object array.
73 | ///
74 | /// The native array to take the string from.
75 | /// The index of the string object in the native array.
76 | /// The unboxed string.
77 | public static string? UnboxStringElement(IntPtr args, int index)
78 | {
79 | IntPtr ptr = AndroidJNI.GetObjectArrayElement(args, index);
80 | string? value = AndroidJNI.GetStringUTFChars(ptr);
81 |
82 | AndroidJNI.DeleteLocalRef(ptr);
83 | return value;
84 | }
85 |
86 | ///
87 | /// Unboxes a boolean from a native Object array.
88 | ///
89 | /// The native array to take the boolean from.
90 | /// The index of the boolean object in the native array.
91 | /// The unboxed boolean.
92 | public static bool UnboxBoolElement(IntPtr args, int index)
93 | {
94 | IntPtr ptr = AndroidJNI.GetObjectArrayElement(args, index);
95 | AndroidJNIHelper.Unbox(ptr, out bool value);
96 |
97 | AndroidJNI.DeleteLocalRef(ptr);
98 | return value;
99 | }
100 |
101 | ///
102 | /// Unboxes a native nullable integer field into an int?.
103 | ///
104 | /// The field to unbox.
105 | /// The unboxed value.
106 | public static int? GetNullableInt(this AndroidJavaObject current, string fieldName)
107 | {
108 | using AndroidJavaObject? nullable = current.Get(fieldName);
109 | return nullable?.Call("intValue");
110 | }
111 |
112 | ///
113 | /// Unboxes a native nullable float field into an float?.
114 | ///
115 | ///
116 | public static float? GetNullableFloat(this AndroidJavaObject current, string fieldName)
117 | {
118 | using AndroidJavaObject? nullable = current.Get(fieldName);
119 | return nullable?.Call("floatValue");
120 | }
121 | }
122 | }
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | uralstech@gmail.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/Documentation/DocSource/AdvancedSamples.md:
--------------------------------------------------------------------------------
1 | # Advanced Samples
2 |
3 | This page contains some samples for advanced use-cases, like custom texture converters or multi-camera streaming.
4 |
5 | ## Custom Texture Converters
6 |
7 | The texture converter in `CapturePipeline.TextureConverter` allows you to easily change the conversion compute shader to custom
8 | ones. All you have to do is set `CapturePipeline.TextureConverter.Shader` to your shader. You can also change the compute shader
9 | for all new capture sessions by changing `UCameraManager.YUVToRGBAComputeShader`.
10 |
11 | For example, the following compute shader ignores the U and V values of the YUV stream to provide a Luminance-only image:
12 |
13 | ```hlsl
14 | #pragma kernel CSMain
15 |
16 | // Input buffers (read-only)
17 | ByteAddressBuffer YBuffer;
18 | ByteAddressBuffer UBuffer;
19 | ByteAddressBuffer VBuffer;
20 |
21 | // Row strides
22 | uint YRowStride;
23 | uint UVRowStride;
24 |
25 | // Pixel strides
26 | uint UVPixelStride;
27 |
28 | // Image dimensions
29 | uint TargetWidth;
30 | uint TargetHeight;
31 |
32 | // Output texture (read-write)
33 | RWTexture2D OutputTexture;
34 |
35 | // Helper function to get a byte from a ByteAddressBuffer.
36 | // buffer: The ByteAddressBuffer.
37 | // byteIndex: The *byte* index (offset) into the buffer.
38 | uint GetByteFromBuffer(ByteAddressBuffer buffer, uint byteIndex)
39 | {
40 | // Calculate the 32-bit word offset (each word is 4 bytes).
41 | uint wordOffset = byteIndex / 4;
42 |
43 | // Load the 32-bit word containing the byte.
44 | uint word = buffer.Load(wordOffset * 4); // MUST multiply by 4 for ByteAddressBuffer.Load()
45 |
46 | // Calculate the byte position *within* the word (0, 1, 2, or 3).
47 | uint byteInWord = byteIndex % 4;
48 |
49 | // Extract the correct byte using bit shifts and masking.
50 | return (word >> (byteInWord * 8)) & 0xFF;
51 | }
52 |
53 | [numthreads(8, 8, 1)]
54 | void CSMain(uint3 id : SV_DispatchThreadID)
55 | {
56 | if (id.x >= TargetWidth || id.y >= TargetHeight)
57 | return;
58 |
59 | // The YUV stream is flipped, so we have to un-flip it.
60 | uint flippedY = TargetHeight - 1 - id.y;
61 |
62 | // Index of Y value in buffer.
63 | uint yIndex = flippedY * YRowStride + id.x;
64 | uint yValue = GetByteFromBuffer(YBuffer, yIndex);
65 |
66 | float3 luminance = float3(yValue, yValue, yValue) / 255.0;
67 | OutputTexture[id.xy] = float4(luminance.rgb, 1.0);
68 | }
69 | ```
70 |
71 | ## Multiple Streams From One Camera
72 |
73 | By adding multiple texture converters to the same request, you can emulate the effect of having more than one image stream from a
74 | single camera. For example, you can have one converter stream the camera image as-is, and another streaming with a simple Sepia
75 | post-processing effect:
76 |
77 | ```csharp
78 | // Create a capture session with the camera, at the chosen resolution.
79 | CapturePipeline capturePipeline = camera.CreateContinuousCaptureSession(highestResolution);
80 | if (capturePipeline == null...
81 |
82 | yield return capturePipeline.CaptureSession.WaitForInitialization();
83 |
84 | // Check if it opened successfully.
85 | if (capturePipeline.CaptureSession.CurrentState...
86 |
87 | // Set the image texture.
88 | _rawImage.texture = capturePipeline.TextureConverter.FrameRenderTexture;
89 |
90 | // Create a new YUVToRGBAConverter.
91 | YUVToRGBAConverter secondary = new YUVToRGBAConverter(highestResolution);
92 |
93 | // Assign it a different shader.
94 | secondary.Shader = _postProcessShader;
95 |
96 | // Link the capture session and the converter.
97 | capturePipeline.CaptureSession.OnFrameReady += secondary.OnFrameReady;
98 |
99 | // Set the second image to the post processed RenderTexture.
100 | _rawImagePostProcessed.texture = secondary.FrameRenderTexture;
101 | ```
102 |
103 | ### YUV To RGBA Converter With Sepia Effect
104 |
105 | ```hlsl
106 | #pragma kernel CSMain
107 |
108 | // Input buffers (read-only)
109 | ByteAddressBuffer YBuffer;
110 | ByteAddressBuffer UBuffer;
111 | ByteAddressBuffer VBuffer;
112 |
113 | // Row strides
114 | uint YRowStride;
115 | uint UVRowStride;
116 |
117 | // Pixel strides
118 | uint UVPixelStride;
119 |
120 | // Image dimensions
121 | uint TargetWidth;
122 | uint TargetHeight;
123 |
124 | // Output texture (read-write)
125 | RWTexture2D OutputTexture;
126 |
127 | // Helper function to get a byte from a ByteAddressBuffer.
128 | // buffer: The ByteAddressBuffer.
129 | // byteIndex: The *byte* index (offset) into the buffer.
130 | uint GetByteFromBuffer(ByteAddressBuffer buffer, uint byteIndex)
131 | {
132 | // Calculate the 32-bit word offset (each word is 4 bytes).
133 | uint wordOffset = byteIndex / 4;
134 |
135 | // Load the 32-bit word containing the byte.
136 | uint word = buffer.Load(wordOffset * 4); // MUST multiply by 4 for ByteAddressBuffer.Load()
137 |
138 | // Calculate the byte position *within* the word (0, 1, 2, or 3).
139 | uint byteInWord = byteIndex % 4;
140 |
141 | // Extract the correct byte using bit shifts and masking.
142 | return (word >> (byteInWord * 8)) & 0xFF;
143 | }
144 |
145 | [numthreads(8, 8, 1)]
146 | void CSMain(uint3 id : SV_DispatchThreadID)
147 | {
148 | if (id.x >= TargetWidth || id.y >= TargetHeight)
149 | return;
150 |
151 | // The YUV stream is flipped, so we have to un-flip it.
152 | uint flippedY = TargetHeight - 1 - id.y;
153 |
154 | // Index of Y value in buffer.
155 | uint yIndex = flippedY * YRowStride + id.x;
156 | uint yValue = GetByteFromBuffer(YBuffer, yIndex);
157 |
158 | float3 luminance = float3(yValue, yValue, yValue) / 255.0;
159 |
160 | // --- Post-processing (Sepia Tone) ---
161 | float4 color = float4(luminance.rgb, 1.0);
162 |
163 | //Simple Sepia. Could also do a vignette, bloom, etc. here.
164 | float4 sepiaColor;
165 | sepiaColor.r = dot(color.rgb, float3(0.393, 0.769, 0.189));
166 | sepiaColor.g = dot(color.rgb, float3(0.349, 0.686, 0.168));
167 | sepiaColor.b = dot(color.rgb, float3(0.272, 0.534, 0.131));
168 | sepiaColor.a = 1.0;
169 |
170 | OutputTexture[id.xy] = sepiaColor;
171 | }
172 | ```
173 |
--------------------------------------------------------------------------------
/UXR.QuestCamera/Packages/packages-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "com.google.external-dependency-manager": {
4 | "version": "1.2.186",
5 | "depth": 0,
6 | "source": "registry",
7 | "dependencies": {},
8 | "url": "https://package.openupm.com"
9 | },
10 | "com.unity.ai.inference": {
11 | "version": "2.3.0",
12 | "depth": 0,
13 | "source": "registry",
14 | "dependencies": {
15 | "com.unity.burst": "1.8.17",
16 | "com.unity.dt.app-ui": "1.3.1",
17 | "com.unity.collections": "2.4.3",
18 | "com.unity.modules.imageconversion": "1.0.0"
19 | },
20 | "url": "https://packages.unity.com"
21 | },
22 | "com.unity.burst": {
23 | "version": "1.8.26",
24 | "depth": 1,
25 | "source": "registry",
26 | "dependencies": {
27 | "com.unity.mathematics": "1.2.1",
28 | "com.unity.modules.jsonserialize": "1.0.0"
29 | },
30 | "url": "https://packages.unity.com"
31 | },
32 | "com.unity.collections": {
33 | "version": "2.6.2",
34 | "depth": 1,
35 | "source": "registry",
36 | "dependencies": {
37 | "com.unity.burst": "1.8.23",
38 | "com.unity.mathematics": "1.3.2",
39 | "com.unity.test-framework": "1.4.6",
40 | "com.unity.nuget.mono-cecil": "1.11.5",
41 | "com.unity.test-framework.performance": "3.0.3"
42 | },
43 | "url": "https://packages.unity.com"
44 | },
45 | "com.unity.dt.app-ui": {
46 | "version": "2.1.1",
47 | "depth": 1,
48 | "source": "registry",
49 | "dependencies": {
50 | "com.unity.modules.physics": "1.0.0",
51 | "com.unity.modules.androidjni": "1.0.0",
52 | "com.unity.modules.uielements": "1.0.0",
53 | "com.unity.modules.screencapture": "1.0.0"
54 | },
55 | "url": "https://packages.unity.com"
56 | },
57 | "com.unity.ext.nunit": {
58 | "version": "2.0.5",
59 | "depth": 2,
60 | "source": "builtin",
61 | "dependencies": {}
62 | },
63 | "com.unity.ide.visualstudio": {
64 | "version": "2.0.25",
65 | "depth": 0,
66 | "source": "registry",
67 | "dependencies": {
68 | "com.unity.test-framework": "1.1.31"
69 | },
70 | "url": "https://packages.unity.com"
71 | },
72 | "com.unity.mathematics": {
73 | "version": "1.3.3",
74 | "depth": 2,
75 | "source": "registry",
76 | "dependencies": {},
77 | "url": "https://packages.unity.com"
78 | },
79 | "com.unity.mobile.android-logcat": {
80 | "version": "1.4.6",
81 | "depth": 0,
82 | "source": "registry",
83 | "dependencies": {},
84 | "url": "https://packages.unity.com"
85 | },
86 | "com.unity.nuget.mono-cecil": {
87 | "version": "1.11.6",
88 | "depth": 2,
89 | "source": "registry",
90 | "dependencies": {},
91 | "url": "https://packages.unity.com"
92 | },
93 | "com.unity.test-framework": {
94 | "version": "1.6.0",
95 | "depth": 1,
96 | "source": "builtin",
97 | "dependencies": {
98 | "com.unity.ext.nunit": "2.0.3",
99 | "com.unity.modules.imgui": "1.0.0",
100 | "com.unity.modules.jsonserialize": "1.0.0"
101 | }
102 | },
103 | "com.unity.test-framework.performance": {
104 | "version": "3.2.0",
105 | "depth": 2,
106 | "source": "registry",
107 | "dependencies": {
108 | "com.unity.test-framework": "1.1.33",
109 | "com.unity.modules.jsonserialize": "1.0.0"
110 | },
111 | "url": "https://packages.unity.com"
112 | },
113 | "com.unity.ugui": {
114 | "version": "2.0.0",
115 | "depth": 0,
116 | "source": "builtin",
117 | "dependencies": {
118 | "com.unity.modules.ui": "1.0.0",
119 | "com.unity.modules.imgui": "1.0.0"
120 | }
121 | },
122 | "com.uralstech.utils.singleton": {
123 | "version": "1.2.1",
124 | "depth": 1,
125 | "source": "registry",
126 | "dependencies": {},
127 | "url": "https://package.openupm.com"
128 | },
129 | "com.uralstech.uxr.questcamera": {
130 | "version": "file:com.uralstech.uxr.questcamera",
131 | "depth": 0,
132 | "source": "embedded",
133 | "dependencies": {
134 | "com.uralstech.utils.singleton": "1.2.1",
135 | "com.unity.modules.androidjni": "1.0.0"
136 | }
137 | },
138 | "com.unity.modules.androidjni": {
139 | "version": "1.0.0",
140 | "depth": 1,
141 | "source": "builtin",
142 | "dependencies": {}
143 | },
144 | "com.unity.modules.hierarchycore": {
145 | "version": "1.0.0",
146 | "depth": 3,
147 | "source": "builtin",
148 | "dependencies": {}
149 | },
150 | "com.unity.modules.imageconversion": {
151 | "version": "1.0.0",
152 | "depth": 1,
153 | "source": "builtin",
154 | "dependencies": {}
155 | },
156 | "com.unity.modules.imgui": {
157 | "version": "1.0.0",
158 | "depth": 1,
159 | "source": "builtin",
160 | "dependencies": {}
161 | },
162 | "com.unity.modules.jsonserialize": {
163 | "version": "1.0.0",
164 | "depth": 2,
165 | "source": "builtin",
166 | "dependencies": {}
167 | },
168 | "com.unity.modules.physics": {
169 | "version": "1.0.0",
170 | "depth": 2,
171 | "source": "builtin",
172 | "dependencies": {}
173 | },
174 | "com.unity.modules.screencapture": {
175 | "version": "1.0.0",
176 | "depth": 2,
177 | "source": "builtin",
178 | "dependencies": {
179 | "com.unity.modules.imageconversion": "1.0.0"
180 | }
181 | },
182 | "com.unity.modules.ui": {
183 | "version": "1.0.0",
184 | "depth": 1,
185 | "source": "builtin",
186 | "dependencies": {}
187 | },
188 | "com.unity.modules.uielements": {
189 | "version": "1.0.0",
190 | "depth": 2,
191 | "source": "builtin",
192 | "dependencies": {
193 | "com.unity.modules.ui": "1.0.0",
194 | "com.unity.modules.imgui": "1.0.0",
195 | "com.unity.modules.jsonserialize": "1.0.0",
196 | "com.unity.modules.hierarchycore": "1.0.0",
197 | "com.unity.modules.physics": "1.0.0"
198 | }
199 | },
200 | "com.unity.modules.unitywebrequest": {
201 | "version": "1.0.0",
202 | "depth": 0,
203 | "source": "builtin",
204 | "dependencies": {}
205 | }
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/UCamera/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/UXR.QuestCamera/Packages/com.uralstech.uxr.questcamera/Runtime/Scripts/CameraInfo.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2025 URAV ADVANCED LEARNING SYSTEMS PRIVATE LIMITED
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | using System;
16 | using UnityEngine;
17 |
18 | #nullable enable
19 | namespace Uralstech.UXR.QuestCamera
20 | {
21 | ///
22 | /// Wrapper for Camera2's CameraCharacteristics.
23 | ///
24 | public record CameraInfo : IDisposable
25 | {
26 | #region Enums
27 | ///
28 | /// The camera eye.
29 | ///
30 | public enum CameraEye
31 | {
32 | /// Unknown.
33 | Unknown = -1,
34 |
35 | /// The leftmost camera.
36 | Left = 0,
37 |
38 | /// The rightmost camera.
39 | Right = 1,
40 | }
41 |
42 | ///
43 | /// The source of the camera feed.
44 | ///
45 | public enum CameraSource
46 | {
47 | /// Unknown.
48 | Unknown = -1,
49 |
50 | /// Meta Quest Passthrough RGB cameras.
51 | PassthroughRGB = 0,
52 | }
53 | #endregion
54 |
55 | ///
56 | /// Defines the camera's intrinsic properties. All values are in pixels.
57 | ///
58 | public record CameraIntrinsics
59 | {
60 | /// Resolution in pixels.
61 | public readonly Vector2 Resolution;
62 |
63 | /// Focal length in pixels.
64 | public readonly Vector2 FocalLength;
65 |
66 | /// Principal point in pixels from the image's top-left corner.
67 | public readonly Vector2 PrincipalPoint;
68 |
69 | /// Skew coefficient for axis misalignment.
70 | public readonly float Skew;
71 |
72 | public CameraIntrinsics(Vector2 resolution, Vector2 focalLength, Vector2 principalPoint, float skew)
73 | {
74 | Resolution = resolution;
75 | FocalLength = focalLength;
76 | PrincipalPoint = principalPoint;
77 | Skew = skew;
78 | }
79 | }
80 |
81 | ///
82 | /// The actual device ID of this camera.
83 | ///
84 | public readonly string CameraId;
85 |
86 | ///
87 | /// (Meta Quest) The source of the camera feed.
88 | ///
89 | public readonly CameraSource Source;
90 |
91 | ///
92 | /// (Meta Quest) The eye which the camera is closest to.
93 | ///
94 | public readonly CameraEye Eye;
95 |
96 | ///
97 | /// The position of the camera's optical center.
98 | ///
99 | public readonly Vector3? LensPoseTranslation;
100 |
101 | ///
102 | /// The orientation of the camera relative to the sensor coordinate system.
103 | ///
104 | public readonly Quaternion? LensPoseRotation;
105 |
106 | ///
107 | /// The resolutions supported by this camera.
108 | ///
109 | public readonly Resolution[] SupportedResolutions;
110 |
111 | ///
112 | /// The intrinsic data for this camera.
113 | ///
114 | public readonly CameraIntrinsics? Intrinsics;
115 |
116 | ///
117 | /// The native CameraCharacteristics object.
118 | ///
119 | public readonly AndroidJavaObject NativeCameraCharacteristics;
120 |
121 | public CameraInfo(AndroidJavaObject cameraInfo)
122 | {
123 | CameraId = cameraInfo.Get("cameraId");
124 | Source = cameraInfo.GetNullableInt("metaQuestCameraSource") is int source ? (CameraSource)source : CameraSource.Unknown;
125 | Eye = cameraInfo.GetNullableInt("metaQuestCameraEye") is int eye ? (CameraEye)eye : CameraEye.Unknown;
126 | LensPoseTranslation = cameraInfo.Get("lensPoseTranslation") is float[] translation
127 | ? new Vector3(translation[0], translation[1], -translation[2]) : null;
128 | LensPoseRotation = cameraInfo.Get("lensPoseRotation") is float[] rotation
129 | ? new Quaternion(-rotation[0], -rotation[1], rotation[2], rotation[3]) : null;
130 | SupportedResolutions = Array.ConvertAll(cameraInfo.Get("supportedResolutions"), size =>
131 | {
132 | int width = size.Call("getWidth");
133 | int height = size.Call("getHeight");
134 | size.Dispose();
135 |
136 | return new Resolution()
137 | {
138 | width = width,
139 | height = height
140 | };
141 | });
142 |
143 | if (cameraInfo.Call("getIntrinsicsResolution") is int[] intrinsicsResolution
144 | && cameraInfo.Get("intrinsicsFocalLength") is float[] intrinsicsFocalLength
145 | && cameraInfo.Get("intrinsicsPrincipalPoint") is float[] intrinsicsPrincipalPoint
146 | && cameraInfo.GetNullableFloat("intrinsicsSkew") is float intrinsicsSkew)
147 | {
148 | Intrinsics = new CameraIntrinsics(
149 | new Vector2(intrinsicsResolution[0], intrinsicsResolution[1]),
150 | new Vector2(intrinsicsFocalLength[0], intrinsicsFocalLength[1]),
151 | new Vector2(intrinsicsPrincipalPoint[0], intrinsicsPrincipalPoint[1]),
152 | intrinsicsSkew
153 | );
154 | }
155 |
156 | NativeCameraCharacteristics = cameraInfo.Get("characteristics");
157 | }
158 |
159 | public static implicit operator string(CameraInfo camera) => camera.CameraId;
160 |
161 | private bool _disposed = false;
162 |
163 | ///
164 | /// Releases native plugin resources.
165 | ///
166 | public void Dispose()
167 | {
168 | if (_disposed)
169 | return;
170 |
171 | _disposed = true;
172 | NativeCameraCharacteristics.Dispose();
173 | GC.SuppressFinalize(this);
174 | }
175 | }
176 | }
--------------------------------------------------------------------------------
/UXR.QuestCamera/ProjectSettings/InputManager.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!13 &1
4 | InputManager:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 2
7 | m_Axes:
8 | - serializedVersion: 3
9 | m_Name: Horizontal
10 | descriptiveName:
11 | descriptiveNegativeName:
12 | negativeButton: left
13 | positiveButton: right
14 | altNegativeButton: a
15 | altPositiveButton: d
16 | gravity: 3
17 | dead: 0.001
18 | sensitivity: 3
19 | snap: 1
20 | invert: 0
21 | type: 0
22 | axis: 0
23 | joyNum: 0
24 | - serializedVersion: 3
25 | m_Name: Vertical
26 | descriptiveName:
27 | descriptiveNegativeName:
28 | negativeButton: down
29 | positiveButton: up
30 | altNegativeButton: s
31 | altPositiveButton: w
32 | gravity: 3
33 | dead: 0.001
34 | sensitivity: 3
35 | snap: 1
36 | invert: 0
37 | type: 0
38 | axis: 0
39 | joyNum: 0
40 | - serializedVersion: 3
41 | m_Name: Fire1
42 | descriptiveName:
43 | descriptiveNegativeName:
44 | negativeButton:
45 | positiveButton: left ctrl
46 | altNegativeButton:
47 | altPositiveButton: mouse 0
48 | gravity: 1000
49 | dead: 0.001
50 | sensitivity: 1000
51 | snap: 0
52 | invert: 0
53 | type: 0
54 | axis: 0
55 | joyNum: 0
56 | - serializedVersion: 3
57 | m_Name: Fire2
58 | descriptiveName:
59 | descriptiveNegativeName:
60 | negativeButton:
61 | positiveButton: left alt
62 | altNegativeButton:
63 | altPositiveButton: mouse 1
64 | gravity: 1000
65 | dead: 0.001
66 | sensitivity: 1000
67 | snap: 0
68 | invert: 0
69 | type: 0
70 | axis: 0
71 | joyNum: 0
72 | - serializedVersion: 3
73 | m_Name: Fire3
74 | descriptiveName:
75 | descriptiveNegativeName:
76 | negativeButton:
77 | positiveButton: left shift
78 | altNegativeButton:
79 | altPositiveButton: mouse 2
80 | gravity: 1000
81 | dead: 0.001
82 | sensitivity: 1000
83 | snap: 0
84 | invert: 0
85 | type: 0
86 | axis: 0
87 | joyNum: 0
88 | - serializedVersion: 3
89 | m_Name: Jump
90 | descriptiveName:
91 | descriptiveNegativeName:
92 | negativeButton:
93 | positiveButton: space
94 | altNegativeButton:
95 | altPositiveButton:
96 | gravity: 1000
97 | dead: 0.001
98 | sensitivity: 1000
99 | snap: 0
100 | invert: 0
101 | type: 0
102 | axis: 0
103 | joyNum: 0
104 | - serializedVersion: 3
105 | m_Name: Mouse X
106 | descriptiveName:
107 | descriptiveNegativeName:
108 | negativeButton:
109 | positiveButton:
110 | altNegativeButton:
111 | altPositiveButton:
112 | gravity: 0
113 | dead: 0
114 | sensitivity: 0.1
115 | snap: 0
116 | invert: 0
117 | type: 1
118 | axis: 0
119 | joyNum: 0
120 | - serializedVersion: 3
121 | m_Name: Mouse Y
122 | descriptiveName:
123 | descriptiveNegativeName:
124 | negativeButton:
125 | positiveButton:
126 | altNegativeButton:
127 | altPositiveButton:
128 | gravity: 0
129 | dead: 0
130 | sensitivity: 0.1
131 | snap: 0
132 | invert: 0
133 | type: 1
134 | axis: 1
135 | joyNum: 0
136 | - serializedVersion: 3
137 | m_Name: Mouse ScrollWheel
138 | descriptiveName:
139 | descriptiveNegativeName:
140 | negativeButton:
141 | positiveButton:
142 | altNegativeButton:
143 | altPositiveButton:
144 | gravity: 0
145 | dead: 0
146 | sensitivity: 0.1
147 | snap: 0
148 | invert: 0
149 | type: 1
150 | axis: 2
151 | joyNum: 0
152 | - serializedVersion: 3
153 | m_Name: Horizontal
154 | descriptiveName:
155 | descriptiveNegativeName:
156 | negativeButton:
157 | positiveButton:
158 | altNegativeButton:
159 | altPositiveButton:
160 | gravity: 0
161 | dead: 0.19
162 | sensitivity: 1
163 | snap: 0
164 | invert: 0
165 | type: 2
166 | axis: 0
167 | joyNum: 0
168 | - serializedVersion: 3
169 | m_Name: Vertical
170 | descriptiveName:
171 | descriptiveNegativeName:
172 | negativeButton:
173 | positiveButton:
174 | altNegativeButton:
175 | altPositiveButton:
176 | gravity: 0
177 | dead: 0.19
178 | sensitivity: 1
179 | snap: 0
180 | invert: 1
181 | type: 2
182 | axis: 1
183 | joyNum: 0
184 | - serializedVersion: 3
185 | m_Name: Fire1
186 | descriptiveName:
187 | descriptiveNegativeName:
188 | negativeButton:
189 | positiveButton: joystick button 0
190 | altNegativeButton:
191 | altPositiveButton:
192 | gravity: 1000
193 | dead: 0.001
194 | sensitivity: 1000
195 | snap: 0
196 | invert: 0
197 | type: 0
198 | axis: 0
199 | joyNum: 0
200 | - serializedVersion: 3
201 | m_Name: Fire2
202 | descriptiveName:
203 | descriptiveNegativeName:
204 | negativeButton:
205 | positiveButton: joystick button 1
206 | altNegativeButton:
207 | altPositiveButton:
208 | gravity: 1000
209 | dead: 0.001
210 | sensitivity: 1000
211 | snap: 0
212 | invert: 0
213 | type: 0
214 | axis: 0
215 | joyNum: 0
216 | - serializedVersion: 3
217 | m_Name: Fire3
218 | descriptiveName:
219 | descriptiveNegativeName:
220 | negativeButton:
221 | positiveButton: joystick button 2
222 | altNegativeButton:
223 | altPositiveButton:
224 | gravity: 1000
225 | dead: 0.001
226 | sensitivity: 1000
227 | snap: 0
228 | invert: 0
229 | type: 0
230 | axis: 0
231 | joyNum: 0
232 | - serializedVersion: 3
233 | m_Name: Jump
234 | descriptiveName:
235 | descriptiveNegativeName:
236 | negativeButton:
237 | positiveButton: joystick button 3
238 | altNegativeButton:
239 | altPositiveButton:
240 | gravity: 1000
241 | dead: 0.001
242 | sensitivity: 1000
243 | snap: 0
244 | invert: 0
245 | type: 0
246 | axis: 0
247 | joyNum: 0
248 | - serializedVersion: 3
249 | m_Name: Submit
250 | descriptiveName:
251 | descriptiveNegativeName:
252 | negativeButton:
253 | positiveButton: return
254 | altNegativeButton:
255 | altPositiveButton: joystick button 0
256 | gravity: 1000
257 | dead: 0.001
258 | sensitivity: 1000
259 | snap: 0
260 | invert: 0
261 | type: 0
262 | axis: 0
263 | joyNum: 0
264 | - serializedVersion: 3
265 | m_Name: Submit
266 | descriptiveName:
267 | descriptiveNegativeName:
268 | negativeButton:
269 | positiveButton: enter
270 | altNegativeButton:
271 | altPositiveButton: space
272 | gravity: 1000
273 | dead: 0.001
274 | sensitivity: 1000
275 | snap: 0
276 | invert: 0
277 | type: 0
278 | axis: 0
279 | joyNum: 0
280 | - serializedVersion: 3
281 | m_Name: Cancel
282 | descriptiveName:
283 | descriptiveNegativeName:
284 | negativeButton:
285 | positiveButton: escape
286 | altNegativeButton:
287 | altPositiveButton: joystick button 1
288 | gravity: 1000
289 | dead: 0.001
290 | sensitivity: 1000
291 | snap: 0
292 | invert: 0
293 | type: 0
294 | axis: 0
295 | joyNum: 0
296 |
--------------------------------------------------------------------------------
/UCamera/UCamera/src/main/java/com/uralstech/ucamera/CameraDeviceWrapper.kt:
--------------------------------------------------------------------------------
1 | // Copyright 2025 URAV ADVANCED LEARNING SYSTEMS PRIVATE LIMITED
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package com.uralstech.ucamera
16 |
17 | import android.Manifest
18 | import android.hardware.camera2.CameraAccessException
19 | import android.hardware.camera2.CameraDevice
20 | import android.hardware.camera2.CameraManager
21 | import android.util.Log
22 | import androidx.annotation.RequiresPermission
23 | import java.util.concurrent.Executors
24 |
25 | /**
26 | * Wrapper class for [CameraDevice].
27 | */
28 | class CameraDeviceWrapper private constructor(private val id: String, private val callbacks: Callbacks) {
29 | interface Callbacks {
30 | fun onDeviceOpened(id: String)
31 | fun onDeviceClosed(id: String?)
32 | fun onDeviceErred(id: String?, errorCode: Int)
33 | fun onDeviceDisconnected(id: String)
34 | }
35 |
36 | companion object {
37 | /** Logcat tag. */
38 | private const val TAG = "CameraDeviceWrapper"
39 |
40 | private const val ERROR_CODE_CAMERA_ACCESS_EXCEPTION = 1000
41 | private const val ERROR_CODE_SECURITY_EXCEPTION = 1001
42 | }
43 |
44 | /** Is this object active and usable? */
45 | @Volatile
46 | private var isDisposed = false
47 |
48 | /** [java.util.concurrent.ExecutorService] for [cameraDevice]. */
49 | private val cameraExecutor = Executors.newSingleThreadExecutor()
50 |
51 | /** The camera device being wrapped by this object. */
52 | private var cameraDevice: CameraDevice? = null
53 |
54 | @RequiresPermission(Manifest.permission.CAMERA)
55 | constructor(id: String, callbacks: Callbacks, cameraManager: CameraManager) : this(id, callbacks) {
56 | cameraExecutor.submit {
57 | try {
58 | cameraManager.openCamera(id, cameraExecutor, object : CameraDevice.StateCallback() {
59 | override fun onOpened(camera: CameraDevice) {
60 | Log.i(TAG, "Camera device with ID \"$id\" opened.")
61 | cameraDevice = camera
62 |
63 | callbacks.onDeviceOpened(camera.id)
64 | }
65 |
66 | override fun onClosed(camera: CameraDevice) {
67 | Log.i(TAG, "Camera device with ID \"$id\" closed.")
68 | callbacks.onDeviceClosed(camera.id)
69 | }
70 |
71 | override fun onDisconnected(camera: CameraDevice) {
72 | Log.i(TAG, "Camera device with ID \"$id\" disconnected.")
73 | close()
74 |
75 | callbacks.onDeviceDisconnected(camera.id)
76 | }
77 |
78 | override fun onError(camera: CameraDevice, error: Int) {
79 | Log.i(TAG, "Camera device with ID \"$id\" erred out, code: $error")
80 | close()
81 |
82 | callbacks.onDeviceErred(camera.id, error)
83 | }
84 | })
85 | } catch (exp: CameraAccessException) {
86 | Log.e(TAG, "Camera could not be opened due to a camera access exception.", exp)
87 | close()
88 |
89 | callbacks.onDeviceErred(null, ERROR_CODE_CAMERA_ACCESS_EXCEPTION)
90 | } catch (exp: SecurityException) {
91 | Log.e(TAG, "Camera could not be opened due to a security exception.", exp)
92 | close()
93 |
94 | callbacks.onDeviceErred(null, ERROR_CODE_SECURITY_EXCEPTION)
95 | }
96 | }
97 | }
98 |
99 | /**
100 | * Gets the [CameraDevice] this object is wrapping, or logs an error and returns null if it was not found.
101 | */
102 | private fun getActiveDevice(): CameraDevice? {
103 | if (isDisposed || cameraDevice == null) {
104 | Log.e(TAG, "Tried to use an unusable CameraDeviceWrapper for camera ID \"$id\"!")
105 | return null
106 | }
107 | return cameraDevice
108 | }
109 |
110 | /**
111 | * Creates a new capture session with a repeating capture request and a wrapper for it.
112 | */
113 | fun createContinuousCaptureSession(
114 | callbacks: CaptureSessionWrapper.Callbacks,
115 | width: Int, height: Int,
116 | captureTemplate: Int): RepeatingCaptureSessionWrapper? {
117 |
118 | val cameraDevice = getActiveDevice() ?: return null
119 | Log.i(TAG, "Creating new repeating camera session for camera with ID \"$id\".")
120 | return RepeatingCaptureSessionWrapper(cameraDevice, captureTemplate, callbacks, width, height)
121 | }
122 |
123 | /**
124 | * Creates a new on-demand capture session and a wrapper for it.
125 | */
126 | fun createOnDemandCaptureSession(
127 | callbacks: CaptureSessionWrapper.Callbacks,
128 | width: Int, height: Int): OnDemandCaptureSessionWrapper? {
129 |
130 | val cameraDevice = getActiveDevice() ?: return null
131 | Log.i(TAG, "Creating new on-demand camera session for camera with ID \"$id\".")
132 | return OnDemandCaptureSessionWrapper(cameraDevice, CameraDevice.TEMPLATE_PREVIEW, callbacks, width, height)
133 | }
134 |
135 | /**
136 | * Creates a new SurfaceTexture-based capture session and a wrapper for it.
137 | */
138 | fun createSurfaceTextureCaptureSession(
139 | timeStamp: Long, callbacks: STCaptureSessionWrapper.Callbacks,
140 | width: Int, height: Int,
141 | captureTemplate: Int): STCaptureSessionWrapper? {
142 |
143 | val cameraDevice = getActiveDevice() ?: return null
144 | Log.i(TAG, "Creating new SurfaceTexture-based camera session for camera with ID \"$id\".")
145 |
146 | val session = STCaptureSessionWrapper(timeStamp, callbacks, cameraDevice, width, height, captureTemplate)
147 | if (!session.tryRegister()) {
148 | session.close()
149 | return null
150 | }
151 |
152 | return session
153 | }
154 |
155 | /**
156 | * Releases associated resources and closes the camera device.
157 | */
158 | fun close(): Boolean {
159 | if (isDisposed) {
160 | return false
161 | }
162 |
163 | Log.i(TAG, "Closing camera device wrapper for camera with ID \"$id\".")
164 | isDisposed = true
165 |
166 | cameraExecutor.submit {
167 | try {
168 | if (cameraDevice != null) {
169 | cameraDevice?.close()
170 | cameraDevice = null
171 | } else {
172 | callbacks.onDeviceClosed(null)
173 | }
174 |
175 | Log.i(TAG, "Camera device closed.")
176 | } finally {
177 | cameraExecutor.shutdown()
178 | }
179 | }
180 |
181 | return true
182 | }
183 | }
--------------------------------------------------------------------------------
/UXR.QuestCamera/Packages/com.uralstech.uxr.questcamera/Runtime/Scripts/Managers/UCameraManager.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2025 URAV ADVANCED LEARNING SYSTEMS PRIVATE LIMITED
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | using UnityEngine;
16 | using Uralstech.Utils.Singleton;
17 |
18 | #nullable enable
19 | namespace Uralstech.UXR.QuestCamera
20 | {
21 | ///
22 | /// Class for interfacing with the native Camera2 API on Android.
23 | ///
24 | [AddComponentMenu("Uralstech/UXR/Quest Camera/Quest Camera Manager")]
25 | public class UCameraManager : DontCreateNewSingleton
26 | {
27 | ///
28 | /// The permission required to access the Meta Quest's Passthrough cameras.
29 | ///
30 | public const string HeadsetCameraPermission = "horizonos.permission.HEADSET_CAMERA";
31 |
32 | ///
33 | /// The permission required to access the Meta Quest's avatar camera.
34 | ///
35 | public const string AvatarCameraPermission = "android.permission.CAMERA";
36 |
37 | ///
38 | /// The compute shader to use to convert the camera's YUV 4:2:0 images to RGBA.
39 | ///
40 | [Tooltip("The compute shader to use to convert the camera's YUV 4:2:0 images to RGBA.")]
41 | public ComputeShader YUVToRGBAComputeShader;
42 |
43 | ///
44 | /// Gets a cached array of the available cameras and their characteristics.
45 | ///
46 | ///
47 | /// The disposal of the objects generated by this property
48 | /// are managed by the instance. If you require control
49 | /// of the objects, use instead.
50 | ///
51 | public CameraInfo[]? Cameras => _cameraInfosCached is not null ? _cameraInfosCached : (_cameraInfosCached = GetCameraInfos());
52 |
53 | private CameraInfo[]? _cameraInfosCached = null;
54 | private AndroidJavaObject? _camera2Wrapper;
55 |
56 | protected override void Awake()
57 | {
58 | base.Awake();
59 | DontDestroyOnLoad(gameObject);
60 |
61 | using AndroidJavaClass camera2WrapperClass = new("com.uralstech.ucamera.Camera2Wrapper");
62 | _camera2Wrapper = camera2WrapperClass.CallStatic("getInstance");
63 | }
64 |
65 | protected void OnDestroy()
66 | {
67 | ClearAndDisposeCameraInfos();
68 | _camera2Wrapper?.Dispose();
69 | _camera2Wrapper = null;
70 | }
71 |
72 | ///
73 | /// Refreshes the cached array of camera devices.
74 | ///
75 | /// if the refresh was successful; otherwise.
76 | public bool RefreshCachedCameraInfos()
77 | {
78 | ClearAndDisposeCameraInfos();
79 | return (_cameraInfosCached = GetCameraInfos()) != null;
80 | }
81 |
82 | private void ClearAndDisposeCameraInfos()
83 | {
84 | if (_cameraInfosCached is not null)
85 | {
86 | int cameraInfoCount = _cameraInfosCached.Length;
87 | for (int i = 0; i < cameraInfoCount; i++)
88 | _cameraInfosCached[i].Dispose();
89 |
90 | _cameraInfosCached = null;
91 | }
92 | }
93 |
94 | ///
95 | /// Gets all available cameras and their characteristics. This is not cached.
96 | ///
97 | ///
98 | /// You will have to manage the disposal of the objects
99 | /// returned by this method. Use if you don't want to handle
100 | /// the objects yourself.
101 | ///
102 | /// An array of objects or if any errors occurred.
103 | public CameraInfo[]? GetCameraInfos()
104 | {
105 | AndroidJavaObject[]? nativeObjects = _camera2Wrapper?.Call("getCameraDevices");
106 | if (nativeObjects is null)
107 | {
108 | Debug.LogError("Could not get camera device information.");
109 | return null;
110 | }
111 |
112 | int count = nativeObjects.Length;
113 | CameraInfo[] wrappers = new CameraInfo[count];
114 |
115 | for (int i = 0; i < count; i++)
116 | {
117 | AndroidJavaObject nativeObj = nativeObjects[i];
118 | wrappers[i] = new CameraInfo(nativeObj);
119 | nativeObj.Dispose();
120 | }
121 |
122 | return wrappers;
123 | }
124 |
125 | ///
126 | /// Gets a camera device by the eye it is closest to.
127 | ///
128 | ///
129 | /// The object returned by this method is managed by the
130 | /// instance, so do not dispose it manually.
131 | ///
132 | /// The eye.
133 | /// A object or if none were found.
134 | public CameraInfo? GetCamera(CameraInfo.CameraEye eye)
135 | {
136 | if (Cameras is CameraInfo[] cameras)
137 | {
138 | foreach (CameraInfo cameraInfo in cameras)
139 | {
140 | if (cameraInfo.Eye == eye)
141 | return cameraInfo;
142 | }
143 | }
144 |
145 | return null;
146 | }
147 |
148 | ///
149 | /// Opens a camera device for use.
150 | ///
151 | ///
152 | /// Once you have finished using the camera, close and dispose of it using .
153 | ///
154 | /// The ID of the camera to open; accepts objects through an implicit cast.
155 | /// A object or if any errors occurred.
156 | public CameraDevice? OpenCamera(string camera)
157 | {
158 | CameraDevice cameraDevice = new(camera);
159 | AndroidJavaObject? nativeObject = _camera2Wrapper?.Call("openCameraDevice", camera, cameraDevice);
160 | if (nativeObject is null)
161 | {
162 | StartCoroutine(cameraDevice.DisposeAsync().Yield());
163 | return null;
164 | }
165 |
166 | cameraDevice._cameraDevice = nativeObject;
167 | return cameraDevice;
168 | }
169 | }
170 | }
--------------------------------------------------------------------------------
/UXR.QuestCamera/Packages/com.uralstech.uxr.questcamera/Runtime/Scripts/CaptureSession/SurfaceTextureCapture/STCaptureSessionNative.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2025 URAV ADVANCED LEARNING SYSTEMS PRIVATE LIMITED
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | using AOT;
16 | using System;
17 | using System.Collections.Concurrent;
18 | using System.Runtime.InteropServices;
19 |
20 | #nullable enable
21 | namespace Uralstech.UXR.QuestCamera.SurfaceTextureCapture
22 | {
23 | ///
24 | /// Class to interact with the native graphics plugin for SurfaceTexture rendering.
25 | ///
26 | public static class STCaptureSessionNative
27 | {
28 | ///
29 | /// Data for setting up a native renderer.
30 | ///
31 | [StructLayout(LayoutKind.Sequential)]
32 | public struct NativeSetupData
33 | {
34 | ///
35 | /// The unity texture to render to.
36 | ///
37 | public uint UnityTexture;
38 |
39 | ///
40 | /// The width of the texture;
41 | ///
42 | public int Width;
43 |
44 | ///
45 | /// The height of the texture.
46 | ///
47 | public int Height;
48 |
49 | ///
50 | /// Timestamp associated with the STCaptureSessionWrapper which will be the source for rendering.
51 | ///
52 | public long Timestamp;
53 |
54 | ///
55 | /// Callback for when the operation is done, type: .
56 | ///
57 | public IntPtr OnDoneCallback;
58 | }
59 |
60 | ///
61 | /// Data for updating a native renderer.
62 | ///
63 | [StructLayout(LayoutKind.Sequential)]
64 | public struct NativeUpdateData
65 | {
66 | ///
67 | /// The native texture to update.
68 | ///
69 | public uint NativeTexture;
70 |
71 | ///
72 | /// Callback for when the operation is done, type: .
73 | ///
74 | public IntPtr OnDoneCallback;
75 | }
76 |
77 | ///
78 | /// Gets the pointer to the native render event handler.
79 | ///
80 | [DllImport("NativeTextureHelper")]
81 | public static extern IntPtr GetRenderEventFunction();
82 |
83 | ///
84 | /// Event ID for native rendering events.
85 | ///
86 | public enum NativeEventId
87 | {
88 | ///
89 | /// Sets up a native renderer.
90 | ///
91 | SetupNativeTexture = 1,
92 |
93 | ///
94 | /// Disposes a native renderer.
95 | ///
96 | CleanupNativeTexture = 2,
97 |
98 | ///
99 | /// Renders the textures.
100 | ///
101 | RenderTextures = 3,
102 | }
103 |
104 | ///
105 | /// Callback type for and events.
106 | ///
107 | /// The ID of the native texture which was updated.
108 | /// If the operation was successful.
109 | public delegate void NativeUpdateCallbackType(uint textureId, bool success);
110 |
111 | ///
112 | /// Same as , but can include a timestamp tracked from C#.
113 | ///
114 | /// The timestamp tracked from C#.
115 | ///
116 | public delegate void NativeUpdateCallbackWithTimestampType(uint textureId, bool success, long timestamp);
117 |
118 | ///
119 | /// Additional data tracked in C# related to a native renderer update event.
120 | ///
121 | public readonly struct AdditionalUpdateCallbackData
122 | {
123 | ///
124 | /// Optional callback that should be called after processing for the current native callback is done.
125 | ///
126 | public readonly NativeUpdateCallbackWithTimestampType? NextCall;
127 |
128 | ///
129 | /// Native data that should be disposed as part of this callback.
130 | ///
131 | public readonly IntPtr NativeData;
132 |
133 | ///
134 | /// Timestamp value which will be provided in .
135 | ///
136 | public readonly long Timestamp;
137 |
138 | public AdditionalUpdateCallbackData(NativeUpdateCallbackWithTimestampType? nextCall, IntPtr nativeData, long timestamp)
139 | {
140 | NextCall = nextCall;
141 | NativeData = nativeData;
142 | Timestamp = timestamp;
143 | }
144 | }
145 |
146 | ///
147 | /// Event queues for update events, mapped to the IDs of the native textures they are for.
148 | ///
149 | public static readonly ConcurrentDictionary> NativeUpdateCallbacksQueue = new();
150 |
151 | ///
152 | /// The actual callback for native rendering updates.
153 | ///
154 | ///
155 | /// This will dequeue from and process the callback data.
156 | ///
157 | ///
158 | [MonoPInvokeCallback(typeof(NativeUpdateCallbackType))]
159 | public static void NativeUpdateCallback(uint textureId, bool success)
160 | {
161 | if (NativeUpdateCallbacksQueue.TryGetValue(textureId, out ConcurrentQueue queue)
162 | && queue.TryDequeue(out AdditionalUpdateCallbackData data))
163 | {
164 | if (data.NativeData != IntPtr.Zero)
165 | Marshal.FreeHGlobal(data.NativeData);
166 |
167 | data.NextCall?.Invoke(textureId, success, data.Timestamp);
168 | }
169 | }
170 |
171 | ///
172 | /// Deregisters a native update queue for a texture and disposes allocated data.
173 | ///
174 | /// The ID of the native texture to deregister updates of.
175 | public static void DeregisterNativeUpdateCallbacks(uint textureId)
176 | {
177 | if (NativeUpdateCallbacksQueue.TryRemove(textureId, out ConcurrentQueue queue))
178 | {
179 | while (queue.TryDequeue(out AdditionalUpdateCallbackData data))
180 | {
181 | if (data.NativeData != IntPtr.Zero)
182 | Marshal.FreeHGlobal(data.NativeData);
183 | }
184 | }
185 | }
186 |
187 | ///
188 | /// Callback type for events.
189 | ///
190 | /// Was the GL context successfully cleaned up in this call?
191 | /// Was the call to start the capture session sent to the Kotlin class?
192 | /// The unity texture associated with the event.
193 | /// The native texture created by the call, may be invalid.
194 | /// Is a valid texture?
195 | public delegate void NativeSetupCallbackType(bool glIsClean, bool sessionCallSent, uint unityTextureId, uint textureId, bool idIsValid);
196 |
197 | ///
198 | /// Event queues for renderer setup events, mapped to the IDs of the unity textures they are for.
199 | ///
200 | public static readonly ConcurrentDictionary NativeSetupCallbacksQueue = new();
201 |
202 | ///
203 | /// The actual callback for native renderer setup events.
204 | ///
205 | ///
206 | /// This will dequeue from and process the callbacks.
207 | ///
208 | ///
209 | [MonoPInvokeCallback(typeof(NativeSetupCallbackType))]
210 | public static void NativeSetupCallback(bool glIsClean, bool sessionCallSent, uint unityTextureId, uint textureId, bool idIsValid)
211 | {
212 | if (NativeSetupCallbacksQueue.TryRemove(unityTextureId, out NativeSetupCallbackType callback))
213 | callback.Invoke(glIsClean, sessionCallSent, unityTextureId, textureId, idIsValid);
214 | }
215 | }
216 | }
--------------------------------------------------------------------------------
/UXR.QuestCamera/ProjectSettings/QualitySettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!47 &1
4 | QualitySettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 5
7 | m_CurrentQuality: 2
8 | m_QualitySettings:
9 | - serializedVersion: 5
10 | name: Very Low
11 | pixelLightCount: 0
12 | shadows: 0
13 | shadowResolution: 0
14 | shadowProjection: 1
15 | shadowCascades: 1
16 | shadowDistance: 15
17 | shadowNearPlaneOffset: 3
18 | shadowCascade2Split: 0.33333334
19 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
20 | shadowmaskMode: 0
21 | skinWeights: 1
22 | globalTextureMipmapLimit: 1
23 | textureMipmapLimitSettings: []
24 | anisotropicTextures: 0
25 | antiAliasing: 0
26 | softParticles: 0
27 | softVegetation: 0
28 | realtimeReflectionProbes: 0
29 | billboardsFaceCameraPosition: 0
30 | useLegacyDetailDistribution: 1
31 | adaptiveVsync: 0
32 | vSyncCount: 0
33 | realtimeGICPUUsage: 25
34 | adaptiveVsyncExtraA: 0
35 | adaptiveVsyncExtraB: 0
36 | lodBias: 0.3
37 | meshLodThreshold: 1
38 | maximumLODLevel: 0
39 | enableLODCrossFade: 1
40 | streamingMipmapsActive: 0
41 | streamingMipmapsAddAllCameras: 1
42 | streamingMipmapsMemoryBudget: 512
43 | streamingMipmapsRenderersPerFrame: 512
44 | streamingMipmapsMaxLevelReduction: 2
45 | streamingMipmapsMaxFileIORequests: 1024
46 | particleRaycastBudget: 4
47 | asyncUploadTimeSlice: 2
48 | asyncUploadBufferSize: 16
49 | asyncUploadPersistentBuffer: 1
50 | resolutionScalingFixedDPIFactor: 1
51 | customRenderPipeline: {fileID: 0}
52 | terrainQualityOverrides: 0
53 | terrainPixelError: 1
54 | terrainDetailDensityScale: 1
55 | terrainBasemapDistance: 1000
56 | terrainDetailDistance: 80
57 | terrainTreeDistance: 5000
58 | terrainBillboardStart: 50
59 | terrainFadeLength: 5
60 | terrainMaxTrees: 50
61 | excludedTargetPlatforms: []
62 | - serializedVersion: 5
63 | name: Low
64 | pixelLightCount: 0
65 | shadows: 0
66 | shadowResolution: 0
67 | shadowProjection: 1
68 | shadowCascades: 1
69 | shadowDistance: 20
70 | shadowNearPlaneOffset: 3
71 | shadowCascade2Split: 0.33333334
72 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
73 | shadowmaskMode: 0
74 | skinWeights: 2
75 | globalTextureMipmapLimit: 0
76 | textureMipmapLimitSettings: []
77 | anisotropicTextures: 0
78 | antiAliasing: 0
79 | softParticles: 0
80 | softVegetation: 0
81 | realtimeReflectionProbes: 0
82 | billboardsFaceCameraPosition: 0
83 | useLegacyDetailDistribution: 1
84 | adaptiveVsync: 0
85 | vSyncCount: 0
86 | realtimeGICPUUsage: 25
87 | adaptiveVsyncExtraA: 0
88 | adaptiveVsyncExtraB: 0
89 | lodBias: 0.4
90 | meshLodThreshold: 1
91 | maximumLODLevel: 0
92 | enableLODCrossFade: 1
93 | streamingMipmapsActive: 0
94 | streamingMipmapsAddAllCameras: 1
95 | streamingMipmapsMemoryBudget: 512
96 | streamingMipmapsRenderersPerFrame: 512
97 | streamingMipmapsMaxLevelReduction: 2
98 | streamingMipmapsMaxFileIORequests: 1024
99 | particleRaycastBudget: 16
100 | asyncUploadTimeSlice: 2
101 | asyncUploadBufferSize: 16
102 | asyncUploadPersistentBuffer: 1
103 | resolutionScalingFixedDPIFactor: 1
104 | customRenderPipeline: {fileID: 0}
105 | terrainQualityOverrides: 0
106 | terrainPixelError: 1
107 | terrainDetailDensityScale: 1
108 | terrainBasemapDistance: 1000
109 | terrainDetailDistance: 80
110 | terrainTreeDistance: 5000
111 | terrainBillboardStart: 50
112 | terrainFadeLength: 5
113 | terrainMaxTrees: 50
114 | excludedTargetPlatforms: []
115 | - serializedVersion: 5
116 | name: Medium
117 | pixelLightCount: 1
118 | shadows: 1
119 | shadowResolution: 0
120 | shadowProjection: 1
121 | shadowCascades: 1
122 | shadowDistance: 20
123 | shadowNearPlaneOffset: 3
124 | shadowCascade2Split: 0.33333334
125 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
126 | shadowmaskMode: 0
127 | skinWeights: 2
128 | globalTextureMipmapLimit: 0
129 | textureMipmapLimitSettings: []
130 | anisotropicTextures: 1
131 | antiAliasing: 0
132 | softParticles: 0
133 | softVegetation: 0
134 | realtimeReflectionProbes: 0
135 | billboardsFaceCameraPosition: 0
136 | useLegacyDetailDistribution: 1
137 | adaptiveVsync: 0
138 | vSyncCount: 0
139 | realtimeGICPUUsage: 25
140 | adaptiveVsyncExtraA: 0
141 | adaptiveVsyncExtraB: 0
142 | lodBias: 0.7
143 | meshLodThreshold: 1
144 | maximumLODLevel: 0
145 | enableLODCrossFade: 1
146 | streamingMipmapsActive: 0
147 | streamingMipmapsAddAllCameras: 1
148 | streamingMipmapsMemoryBudget: 512
149 | streamingMipmapsRenderersPerFrame: 512
150 | streamingMipmapsMaxLevelReduction: 2
151 | streamingMipmapsMaxFileIORequests: 1024
152 | particleRaycastBudget: 64
153 | asyncUploadTimeSlice: 2
154 | asyncUploadBufferSize: 16
155 | asyncUploadPersistentBuffer: 1
156 | resolutionScalingFixedDPIFactor: 1
157 | customRenderPipeline: {fileID: 0}
158 | terrainQualityOverrides: 0
159 | terrainPixelError: 1
160 | terrainDetailDensityScale: 1
161 | terrainBasemapDistance: 1000
162 | terrainDetailDistance: 80
163 | terrainTreeDistance: 5000
164 | terrainBillboardStart: 50
165 | terrainFadeLength: 5
166 | terrainMaxTrees: 50
167 | excludedTargetPlatforms: []
168 | - serializedVersion: 5
169 | name: High
170 | pixelLightCount: 2
171 | shadows: 2
172 | shadowResolution: 1
173 | shadowProjection: 1
174 | shadowCascades: 2
175 | shadowDistance: 40
176 | shadowNearPlaneOffset: 3
177 | shadowCascade2Split: 0.33333334
178 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
179 | shadowmaskMode: 1
180 | skinWeights: 2
181 | globalTextureMipmapLimit: 0
182 | textureMipmapLimitSettings: []
183 | anisotropicTextures: 1
184 | antiAliasing: 0
185 | softParticles: 0
186 | softVegetation: 1
187 | realtimeReflectionProbes: 1
188 | billboardsFaceCameraPosition: 1
189 | useLegacyDetailDistribution: 1
190 | adaptiveVsync: 0
191 | vSyncCount: 1
192 | realtimeGICPUUsage: 50
193 | adaptiveVsyncExtraA: 0
194 | adaptiveVsyncExtraB: 0
195 | lodBias: 1
196 | meshLodThreshold: 1
197 | maximumLODLevel: 0
198 | enableLODCrossFade: 1
199 | streamingMipmapsActive: 0
200 | streamingMipmapsAddAllCameras: 1
201 | streamingMipmapsMemoryBudget: 512
202 | streamingMipmapsRenderersPerFrame: 512
203 | streamingMipmapsMaxLevelReduction: 2
204 | streamingMipmapsMaxFileIORequests: 1024
205 | particleRaycastBudget: 256
206 | asyncUploadTimeSlice: 2
207 | asyncUploadBufferSize: 16
208 | asyncUploadPersistentBuffer: 1
209 | resolutionScalingFixedDPIFactor: 1
210 | customRenderPipeline: {fileID: 0}
211 | terrainQualityOverrides: 0
212 | terrainPixelError: 1
213 | terrainDetailDensityScale: 1
214 | terrainBasemapDistance: 1000
215 | terrainDetailDistance: 80
216 | terrainTreeDistance: 5000
217 | terrainBillboardStart: 50
218 | terrainFadeLength: 5
219 | terrainMaxTrees: 50
220 | excludedTargetPlatforms: []
221 | - serializedVersion: 5
222 | name: Very High
223 | pixelLightCount: 3
224 | shadows: 2
225 | shadowResolution: 2
226 | shadowProjection: 1
227 | shadowCascades: 2
228 | shadowDistance: 70
229 | shadowNearPlaneOffset: 3
230 | shadowCascade2Split: 0.33333334
231 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
232 | shadowmaskMode: 1
233 | skinWeights: 4
234 | globalTextureMipmapLimit: 0
235 | textureMipmapLimitSettings: []
236 | anisotropicTextures: 2
237 | antiAliasing: 2
238 | softParticles: 1
239 | softVegetation: 1
240 | realtimeReflectionProbes: 1
241 | billboardsFaceCameraPosition: 1
242 | useLegacyDetailDistribution: 1
243 | adaptiveVsync: 0
244 | vSyncCount: 1
245 | realtimeGICPUUsage: 50
246 | adaptiveVsyncExtraA: 0
247 | adaptiveVsyncExtraB: 0
248 | lodBias: 1.5
249 | meshLodThreshold: 1
250 | maximumLODLevel: 0
251 | enableLODCrossFade: 1
252 | streamingMipmapsActive: 0
253 | streamingMipmapsAddAllCameras: 1
254 | streamingMipmapsMemoryBudget: 512
255 | streamingMipmapsRenderersPerFrame: 512
256 | streamingMipmapsMaxLevelReduction: 2
257 | streamingMipmapsMaxFileIORequests: 1024
258 | particleRaycastBudget: 1024
259 | asyncUploadTimeSlice: 2
260 | asyncUploadBufferSize: 16
261 | asyncUploadPersistentBuffer: 1
262 | resolutionScalingFixedDPIFactor: 1
263 | customRenderPipeline: {fileID: 0}
264 | terrainQualityOverrides: 0
265 | terrainPixelError: 1
266 | terrainDetailDensityScale: 1
267 | terrainBasemapDistance: 1000
268 | terrainDetailDistance: 80
269 | terrainTreeDistance: 5000
270 | terrainBillboardStart: 50
271 | terrainFadeLength: 5
272 | terrainMaxTrees: 50
273 | excludedTargetPlatforms: []
274 | - serializedVersion: 5
275 | name: Ultra
276 | pixelLightCount: 4
277 | shadows: 2
278 | shadowResolution: 2
279 | shadowProjection: 1
280 | shadowCascades: 4
281 | shadowDistance: 150
282 | shadowNearPlaneOffset: 3
283 | shadowCascade2Split: 0.33333334
284 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
285 | shadowmaskMode: 1
286 | skinWeights: 4
287 | globalTextureMipmapLimit: 0
288 | textureMipmapLimitSettings: []
289 | anisotropicTextures: 2
290 | antiAliasing: 2
291 | softParticles: 1
292 | softVegetation: 1
293 | realtimeReflectionProbes: 1
294 | billboardsFaceCameraPosition: 1
295 | useLegacyDetailDistribution: 1
296 | adaptiveVsync: 0
297 | vSyncCount: 0
298 | realtimeGICPUUsage: 100
299 | adaptiveVsyncExtraA: 0
300 | adaptiveVsyncExtraB: 0
301 | lodBias: 2
302 | meshLodThreshold: 1
303 | maximumLODLevel: 0
304 | enableLODCrossFade: 1
305 | streamingMipmapsActive: 0
306 | streamingMipmapsAddAllCameras: 1
307 | streamingMipmapsMemoryBudget: 512
308 | streamingMipmapsRenderersPerFrame: 512
309 | streamingMipmapsMaxLevelReduction: 2
310 | streamingMipmapsMaxFileIORequests: 1024
311 | particleRaycastBudget: 4096
312 | asyncUploadTimeSlice: 2
313 | asyncUploadBufferSize: 16
314 | asyncUploadPersistentBuffer: 1
315 | resolutionScalingFixedDPIFactor: 1
316 | customRenderPipeline: {fileID: 0}
317 | terrainQualityOverrides: 0
318 | terrainPixelError: 1
319 | terrainDetailDensityScale: 1
320 | terrainBasemapDistance: 1000
321 | terrainDetailDistance: 80
322 | terrainTreeDistance: 5000
323 | terrainBillboardStart: 50
324 | terrainFadeLength: 5
325 | terrainMaxTrees: 50
326 | excludedTargetPlatforms: []
327 | m_TextureMipmapLimitGroupNames: []
328 | m_PerPlatformDefaultQuality:
329 | Android: 2
330 | GameCoreScarlett: 5
331 | GameCoreXboxOne: 5
332 | Lumin: 5
333 | Nintendo 3DS: 5
334 | Nintendo Switch: 5
335 | PS4: 5
336 | PS5: 5
337 | Stadia: 5
338 | Standalone: 5
339 | WebGL: 3
340 | Windows Store Apps: 5
341 | XboxOne: 5
342 | iPhone: 2
343 | tvOS: 2
344 |
--------------------------------------------------------------------------------